Hi,

I have made some changes to the structure of the HBAC time rules extension, namely the code that validates the time rules' strings was moved from the ipalib/parameters to the hbacrule module itself, and a more "fresh" approach was used in code for methods of adding/removing time policies to HBAC rules.


A slight change was made to understanding a week in a month. The change follows the Java implementation of a week in a month as suggested by Petr V., given a week starts on Monday (=1; iso 8601). More on that on the previously mentioned link

https://docs.oracle.com/javase/8/docs/api/java/time/temporal/WeekFields.html

What this change means is that a first week in a month is a week that contains at least 4 days. If it has less days, it's 0-th week (probably better than having it belong to the previous month as some sources also suggest - iso 8601 does not have a definition for a week in month but it has a definition for a week in a year).

I had Jan C. check the current implementation of the FreeIPA side for the time-based policies and it seems to work as is. He created official number identifiers for the 2 new LDAP attributeTypes, too. \o/

I was also going through some old mockups for the WebUI Petr V. sent me earlier last month. It brought some questions worth sharing here.

1. Do we need time rules based on the day and week of the year? Currently, there is no such option as dayofyear or weekofyear in the rules language, although adding it should not be that much of a problem. I did not include them as it seemed more convenient to set the data as combinations of dayofmonth and monthofyear values.

2. Should we add dayofyear and weekofyear, a possible need for "intervals" might be required. An "interval" is a behavior from the iCalendar format. It basically functions as range() in Python, although with possible 'infinite' end. Example: should you have a recurrence rule on daily basis with interval=2, a rule would apply on every other day. This is kind of a question of keeping it easy and light or heading a way of robust implementation during which dragons may appear, although with a tiny tiny possibility of a golden treasure in the end.

3. The mockups for HBAC time policies show quite a wizard-like UI. While I might be very wrong here, I was thinking of rather a simple UI where user would be able to set the values for each of the rule keywords (timeofday, dayofweek, ...) directly in some text input fields with possible help of JavaScript, that would add text description to the user input (e.g. "Monday to Friday" with user input "1-5" at the dayofweek input field).

4. Do we want some special settings for "absolute" time policies (policies that start and end at certain time in year)? The issue now would be that some of such rules would have to be broken down in more than one time rule (e.g. rule starting at a certain time of a day in a month in one year and ending at a certain time, day and month of a different year might get broken down to up to 6 rules if I count right). This could actually be solved by a UI wizard-like setting where the user gets to pick the dates and times of the rule, a conversion method would need to be created and such a thing would then work for the CLI, too. Still, usually more than one time rule would be created for such cases.

Thanks for keeping up with me and my long emails. I am a terrible person for that and I hope I will be able to cut them short in the future.

Cheers,
Standa
From 4f25d320d9a31707bf7d0b55ea28b7e223e09f70 Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Tue, 7 Jul 2015 09:44:23 +0200
Subject: [PATCH 1/3] Added time-based policies types to LDAP schema.

https://fedorahosted.org/freeipa/ticket/547
https://fedorahosted.org/freeipa/ticket/548
---
 install/share/60basev2.ldif | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/install/share/60basev2.ldif b/install/share/60basev2.ldif
index 00712ddda2c548b7f7924a012f3f68499f2f01da..c3251a4331005ade1333f9e64b57a62a89706ce9 100644
--- a/install/share/60basev2.ldif
+++ b/install/share/60basev2.ldif
@@ -37,7 +37,9 @@ attributeTypes: (2.16.840.1.113730.3.8.3.11 NAME 'externalHost' DESC 'Multivalue
 attributeTypes: (2.16.840.1.113730.3.8.3.12 NAME 'sourceHostCategory' DESC 'Additional classification for hosts' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
 attributeTypes: (2.16.840.1.113730.3.8.3.13 NAME 'accessRuleType' DESC 'The flag to represent if it is allow or deny rule.' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
 attributeTypes: (2.16.840.1.113730.3.8.3.14 NAME 'accessTime' DESC 'Access time' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
-objectClasses: (2.16.840.1.113730.3.8.4.7 NAME 'ipaHBACRule' SUP ipaAssociation STRUCTURAL MUST accessRuleType MAY ( sourceHost $ sourceHostCategory $ serviceCategory $ memberService $ externalHost $ accessTime ) X-ORIGIN 'IPA v2' )
+attributeTypes: (2.16.840.1.113730.3.8.11.72 NAME 'accessTimeExclude' DESC 'Access time - exclude these values' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4' )
+attributeTypes: (2.16.840.1.113730.3.8.11.73 NAME 'ipaTimeZone' DESC 'Olson database timezone name' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA v4' )
+objectClasses: (2.16.840.1.113730.3.8.4.7 NAME 'ipaHBACRule' SUP ipaAssociation STRUCTURAL MUST accessRuleType MAY ( sourceHost $ sourceHostCategory $ serviceCategory $ memberService $ externalHost $ ipaTimeZone $ accessTime $ accessTimeExclude ) X-ORIGIN 'IPA v2' )
 attributeTypes: (2.16.840.1.113730.3.8.3.15 NAME 'nisDomainName' DESC 'NIS domain name.' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
 objectClasses: (2.16.840.1.113730.3.8.4.8 NAME 'ipaNISNetgroup' DESC 'IPA version of NIS netgroup' SUP ipaAssociation STRUCTURAL MAY ( externalHost $ nisDomainName $ member $ memberOf ) X-ORIGIN 'IPA v2' )
 attributeTypes: (1.3.6.1.1.1.1.31 NAME 'automountMapName' DESC 'automount Map Name' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN 'RFC 2307bis' )
-- 
2.4.3

From e564ef2c03c31fe6de17bb43800276b0b27647ee Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Tue, 7 Jul 2015 09:47:39 +0200
Subject: [PATCH 2/3] Added methods for setting time-based policies.

Methods for time-based policies in HBAC rules.

https://fedorahosted.org/freeipa/ticket/547
https://fedorahosted.org/freeipa/ticket/548
---
 ACI.txt                    |   4 +-
 API.txt                    |  50 ++++++++-
 freeipa.spec.in            |   2 +
 ipalib/__init__.py         |   2 +-
 ipalib/parameters.py       | 180 ------------------------------
 ipalib/plugins/hbacrule.py | 268 +++++++++++++++++++++++++++++++--------------
 6 files changed, 238 insertions(+), 268 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 60607b98deb74d0b7f45d24ee9359b0cf8162b0d..bdf8f1e5ca64abb86ade4396edc4ec2936df9acf 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -91,9 +91,9 @@ aci: (targetfilter = "(objectclass=ipahbacrule)")(version 3.0;acl "permission:Sy
 dn: cn=hbac,dc=ipa,dc=example
 aci: (targetattr = "externalhost || memberhost || memberservice || memberuser")(targetfilter = "(objectclass=ipahbacrule)")(version 3.0;acl "permission:System: Manage HBAC Rule Membership";allow (write) groupdn = "ldap:///cn=System: Manage HBAC Rule Membership,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hbac,dc=ipa,dc=example
-aci: (targetattr = "accessruletype || accesstime || cn || description || hostcategory || ipaenabledflag || servicecategory || sourcehost || sourcehostcategory || usercategory")(targetfilter = "(objectclass=ipahbacrule)")(version 3.0;acl "permission:System: Modify HBAC Rule";allow (write) groupdn = "ldap:///cn=System: Modify HBAC Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+aci: (targetattr = "accessruletype || accesstime || accesstimeexclude || cn || description || hostcategory || ipaenabledflag || ipatimezone || servicecategory || sourcehost || sourcehostcategory || usercategory")(targetfilter = "(objectclass=ipahbacrule)")(version 3.0;acl "permission:System: Modify HBAC Rule";allow (write) groupdn = "ldap:///cn=System: Modify HBAC Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hbac,dc=ipa,dc=example
-aci: (targetattr = "accessruletype || accesstime || cn || createtimestamp || description || entryusn || externalhost || hostcategory || ipaenabledflag || ipauniqueid || member || memberhost || memberservice || memberuser || modifytimestamp || objectclass || servicecategory || sourcehost || sourcehostcategory || usercategory")(targetfilter = "(objectclass=ipahbacrule)")(version 3.0;acl "permission:System: Read HBAC Rules";allow (compare,read,search) userdn = "ldap:///all";;)
+aci: (targetattr = "accessruletype || accesstime || accesstimeexclude || cn || createtimestamp || description || entryusn || externalhost || hostcategory || ipaenabledflag || ipatimezone || ipauniqueid || member || memberhost || memberservice || memberuser || modifytimestamp || objectclass || servicecategory || sourcehost || sourcehostcategory || usercategory")(targetfilter = "(objectclass=ipahbacrule)")(version 3.0;acl "permission:System: Read HBAC Rules";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=hbacservices,cn=hbac,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=ipahbacservice)")(version 3.0;acl "permission:System: Add HBAC Services";allow (add) groupdn = "ldap:///cn=System: Add HBAC Services,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hbacservices,cn=hbac,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index 04f2f894f7667239d94a2a7278d0cc80704afeb5..bfe72a60057ff836a5e58d6490e38b13601c4a59 100644
--- a/API.txt
+++ b/API.txt
@@ -1654,7 +1654,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: hbacrule_add
-args: 1,16,3
+args: 1,17,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, required=True)
 option: StrEnum('accessruletype', attribute=True, autofill=True, cli_name='type', default=u'allow', exclude='webui', multivalue=False, required=True, values=(u'allow', u'deny'))
 option: Str('addattr*', cli_name='addattr', exclude='webui')
@@ -1663,6 +1663,7 @@ option: Str('description', attribute=True, cli_name='desc', multivalue=False, re
 option: Str('externalhost', attribute=True, cli_name='externalhost', multivalue=True, required=False)
 option: StrEnum('hostcategory', attribute=True, cli_name='hostcat', multivalue=False, required=False, values=(u'all',))
 option: Bool('ipaenabledflag', attribute=True, cli_name='ipaenabledflag', multivalue=False, required=False)
+option: Str('ipatimezone', attribute=True, cli_name='timezone', multivalue=False, required=False)
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: StrEnum('servicecategory', attribute=True, cli_name='servicecat', multivalue=False, required=False, values=(u'all',))
@@ -1675,6 +1676,28 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: hbacrule_add_accesstime
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('accesstime', alwaysask=True, attribute=True, cli_name='time', multivalue=True, required=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: hbacrule_add_accesstimeexclude
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('accesstimeexclude', alwaysask=True, attribute=True, cli_name='excltime', multivalue=True, required=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
 command: hbacrule_add_host
 args: 1,6,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
@@ -1771,7 +1794,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: hbacrule_mod
-args: 1,18,3
+args: 1,19,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
 option: StrEnum('accessruletype', attribute=True, autofill=False, cli_name='type', default=u'allow', exclude='webui', multivalue=False, required=False, values=(u'allow', u'deny'))
 option: Str('addattr*', cli_name='addattr', exclude='webui')
@@ -1781,6 +1804,7 @@ option: Str('description', attribute=True, autofill=False, cli_name='desc', mult
 option: Str('externalhost', attribute=True, autofill=False, cli_name='externalhost', multivalue=True, required=False)
 option: StrEnum('hostcategory', attribute=True, autofill=False, cli_name='hostcat', multivalue=False, required=False, values=(u'all',))
 option: Bool('ipaenabledflag', attribute=True, autofill=False, cli_name='ipaenabledflag', multivalue=False, required=False)
+option: Str('ipatimezone', attribute=True, autofill=False, cli_name='timezone', multivalue=False, required=False)
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: Flag('rights', autofill=True, default=False)
@@ -1794,6 +1818,28 @@ option: Str('version?', exclude='webui')
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
+command: hbacrule_remove_accesstime
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('accesstime', alwaysask=True, attribute=True, cli_name='time', multivalue=True, required=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
+command: hbacrule_remove_accesstimeexclude
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: Str('accesstimeexclude', alwaysask=True, attribute=True, cli_name='excltime', multivalue=True, required=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('no_members', autofill=True, default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('version?', exclude='webui')
+output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
 command: hbacrule_remove_host
 args: 1,6,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
diff --git a/freeipa.spec.in b/freeipa.spec.in
index 0351952c692eb0cee2148053462c50b6d9073b5d..a4d8d1dfa8db72b18ac9c891cb4eed178c17eb90 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -101,6 +101,7 @@ BuildRequires:  pki-base >= 10.2.6
 BuildRequires:  python-pytest-multihost >= 0.5
 BuildRequires:  python-pytest-sourceorder
 BuildRequires:  python-kdcproxy >= 0.3
+BuildRequires:  pytz
 
 %description
 IPA is an integrated solution to provide centrally managed Identity (machine,
@@ -321,6 +322,7 @@ Requires: python-sss-murmur
 Requires: wget
 Requires: dbus-python
 Requires: python-setuptools
+Requires: pytz
 
 Conflicts: %{alt_name}-python
 Obsoletes: %{alt_name}-python < %{version}
diff --git a/ipalib/__init__.py b/ipalib/__init__.py
index 4ab152243221a9a629ecbc79e058fbdf4f1afec9..a6a4601cd1bbb95a4e88fb290d1ebe0c79e1a8d1 100644
--- a/ipalib/__init__.py
+++ b/ipalib/__init__.py
@@ -886,7 +886,7 @@ from frontend import Command, LocalOrRemote, Updater
 from frontend import Object, Method
 from crud import Create, Retrieve, Update, Delete, Search
 from parameters import DefaultFrom, Bool, Flag, Int, Decimal, Bytes, Str, IA5Str, Password, DNParam, DeprecatedParam
-from parameters import (BytesEnum, StrEnum, IntEnum, AccessTime, File,
+from parameters import (BytesEnum, StrEnum, IntEnum, File,
                         DateTime, DNSNameParam)
 from errors import SkipPluginModule
 from text import _, ngettext, GettextFactory, NGettextFactory
diff --git a/ipalib/parameters.py b/ipalib/parameters.py
index 6cc6f8c9244abb9e895782f40cbdde63b2144d22..359d89b6277b35ed9bdf324370a937f62f3a2f58 100644
--- a/ipalib/parameters.py
+++ b/ipalib/parameters.py
@@ -1655,186 +1655,6 @@ class DateTime(Param):
         return super(DateTime, self)._convert_scalar(value, index)
 
 
-class AccessTime(Str):
-    """
-    Access time parameter type.
-
-    Accepts values conforming to generalizedTime as defined in RFC 4517
-    section 3.3.13 without time zone information.
-    """
-    def _check_HHMM(self, t):
-        if len(t) != 4:
-            raise ValueError('HHMM must be exactly 4 characters long')
-        if not t.isnumeric():
-            raise ValueError('HHMM non-numeric')
-        hh = int(t[0:2])
-        if hh < 0 or hh > 23:
-            raise ValueError('HH out of range')
-        mm = int(t[2:4])
-        if mm < 0 or mm > 59:
-            raise ValueError('MM out of range')
-
-    def _check_dotw(self, t):
-        if t.isnumeric():
-            value = int(t)
-            if value < 1 or value > 7:
-                raise ValueError('day of the week out of range')
-        elif t not in ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'):
-            raise ValueError('invalid day of the week')
-
-    def _check_dotm(self, t, month_num=1, year=4):
-        if not t.isnumeric():
-            raise ValueError('day of the month non-numeric')
-        value = int(t)
-        if month_num in (1, 3, 5, 7, 8, 10, 12):
-            if value < 1 or value > 31:
-                raise ValueError('day of the month out of range')
-        elif month_num in (4, 6, 9, 11):
-            if value < 1 or value > 30:
-                raise ValueError('day of the month out of range')
-        elif month_num == 2:
-            if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
-                if value < 1 or value > 29:
-                    raise ValueError('day of the month out of range')
-            else:
-                if value < 1 or value > 28:
-                    raise ValueError('day of the month out of range')
-
-    def _check_wotm(self, t):
-        if not t.isnumeric():
-            raise ValueError('week of the month non-numeric')
-        value = int(t)
-        if value < 1 or value > 6:
-            raise ValueError('week of the month out of range')
-
-    def _check_woty(self, t):
-        if not t.isnumeric():
-            raise ValueError('week of the year non-numeric')
-        value = int(t)
-        if value < 1 or value > 52:
-            raise ValueError('week of the year out of range')
-
-    def _check_doty(self, t):
-        if not t.isnumeric():
-            raise ValueError('day of the year non-numeric')
-        value = int(t)
-        if value < 1 or value > 365:
-            raise ValueError('day of the year out of range')
-
-    def _check_month_num(self, t):
-        if not t.isnumeric():
-            raise ValueError('month number non-numeric')
-        value = int(t)
-        if value < 1 or value > 12:
-            raise ValueError('month number out of range')
-
-    def _check_interval(self, t, check_func):
-        intervals = t.split(',')
-        for i in intervals:
-            if not i:
-                raise ValueError('invalid time range')
-            values = i.split('-')
-            if len(values) > 2:
-                raise ValueError('invalid time range')
-            for v in values:
-                check_func(v)
-            if len(values) == 2:
-                if int(values[0]) > int(values[1]):
-                    raise ValueError('invalid time range')
-
-    def _check_W_spec(self, ts, index):
-        if ts[index] != 'day':
-            raise ValueError('invalid week specifier')
-        index += 1
-        self._check_interval(ts[index], self._check_dotw)
-        return index
-
-    def _check_M_spec(self, ts, index):
-        if ts[index] == 'week':
-            self._check_interval(ts[index + 1], self._check_wotm)
-            index = self._check_W_spec(ts, index + 2)
-        elif ts[index] == 'day':
-            index += 1
-            self._check_interval(ts[index], self._check_dotm)
-        else:
-            raise ValueError('invalid month specifier')
-        return index
-
-    def _check_Y_spec(self, ts, index):
-        if ts[index] == 'month':
-            index += 1
-            self._check_interval(ts[index], self._check_month_num)
-            month_num = int(ts[index])
-            index = self._check_M_spec(ts, index + 1)
-        elif ts[index] == 'week':
-            self._check_interval(ts[index + 1], self._check_woty)
-            index = self._check_W_spec(ts, index + 2)
-        elif ts[index] == 'day':
-            index += 1
-            self._check_interval(ts[index], self._check_doty)
-        else:
-            raise ValueError('invalid year specifier')
-        return index
-
-    def _check_generalized(self, t):
-        assert type(t) is unicode
-        if len(t) not in (10, 12, 14):
-            raise ValueError('incomplete generalized time')
-        if not t.isnumeric():
-            raise ValueError('time non-numeric')
-        # don't check year value, with time travel and all :)
-        self._check_month_num(t[4:6])
-        year_num = int(t[0:4])
-        month_num = int(t[4:6])
-        self._check_dotm(t[6:8], month_num, year_num)
-        if len(t) >= 12:
-            self._check_HHMM(t[8:12])
-        else:
-            self._check_HHMM('%s00' % t[8:10])
-        if len(t) == 14:
-            s = int(t[12:14])
-            if s < 0 or s > 60:
-                raise ValueError('seconds out of range')
-
-    def _check(self, time):
-        ts = time.split()
-        if ts[0] == 'absolute':
-            if len(ts) != 4:
-                raise ValueError('invalid format, must be \'absolute generalizedTime ~ generalizedTime\'')
-            self._check_generalized(ts[1])
-            if ts[2] != '~':
-                raise ValueError('invalid time range separator')
-            self._check_generalized(ts[3])
-            if int(ts[1]) >= int(ts[3]):
-                raise ValueError('invalid time range')
-        elif ts[0] == 'periodic':
-            index = None
-            if ts[1] == 'yearly':
-                index = self._check_Y_spec(ts, 2)
-            elif ts[1] == 'monthly':
-                index = self._check_M_spec(ts, 2)
-            elif ts[1] == 'weekly':
-                index = self._check_W_spec(ts, 2)
-            elif ts[1] == 'daily':
-                index = 1
-            if index is None:
-                raise ValueError('period must be yearly, monthy or daily, got \'%s\'' % ts[1])
-            self._check_interval(ts[index + 1], self._check_HHMM)
-        else:
-            raise ValueError('time neither absolute or periodic')
-
-    def _rule_required(self, _, value):
-        try:
-            self._check(value)
-        except ValueError, e:
-            raise ValidationError(name=self.get_param_name(), error=e.args[0])
-        except IndexError:
-            raise ValidationError(
-                name=self.get_param_name(), error=ugettext('incomplete time value')
-            )
-        return None
-
-
 class DNParam(Param):
     type = DN
 
diff --git a/ipalib/plugins/hbacrule.py b/ipalib/plugins/hbacrule.py
index 82a52bd80f58ede43249264db69acd193233448d..df2700a327769a89445adcd2358fd51ef44561f8 100644
--- a/ipalib/plugins/hbacrule.py
+++ b/ipalib/plugins/hbacrule.py
@@ -17,8 +17,10 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from pytz import timezone, UnknownTimeZoneError
+
 from ipalib import api, errors
-from ipalib import AccessTime, Password, Str, StrEnum, Bool, DeprecatedParam
+from ipalib import Password, Str, StrEnum, Bool, DeprecatedParam
 from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import *
 from ipalib import _, ngettext
@@ -109,6 +111,145 @@ def is_all(options, attribute):
         return False
 
 
+# The following functions serve for validation of access time strings
+def _timearg_to_list(time):
+    '''
+    Parses the argument of either of the access time language keywords
+
+    Example: '1-5,12' into [[1,5], 12]
+    '''
+    return (t.split('-') for t in time.split(','))
+
+
+def _validate_range(keyword, lst, r1, r2, unit, unitlen):
+    '''
+    Checks the range of each keyword
+
+    keyword is the keyword string
+    lst is a list from _timearg_to_list,
+    r1 and r2 are the range borders for each keyword
+    unit is the unit of each keyword (e.g. day, year, ...)
+    unitlen is the maximum lenght of the argument (e.g. 2 for monthofyear
+        because max. month number is 12)
+    '''
+    for elempair in lst:
+        elold = 0
+        for i, elem in enumerate(elempair):
+            if len(elem) > unitlen:
+                return _('{kw} requires {unit} number {lb}-{rb}'
+                         .format(kw=keyword, unit=unit, lb=r1, rb=r2))
+            if not(elem.isdigit()):
+                return _('{unit} in {kw} is not a number'
+                         .format(unit=unit, kw=keyword))
+            elnum = int(elem)
+            if elnum < r1 or elnum > r2:
+                return _('{unit} in {kw} out of range. It needs '
+                         'number {lb}-{rb}'.format(unit=unit,
+                          kw=keyword, lb=r1, rb=r2))
+            if not(i):
+                elold = elnum
+            elif elold > elnum:
+                return _('first part of {kw} range is greater than '
+                         'the second'.format(kw=keyword))
+
+
+def _validate_timeofday(t):
+    t = _timearg_to_list(t)
+
+    for time in t:
+        hhmmval = 0
+        for i, hhmm in enumerate(time):
+            if len(hhmm) != 4:
+                return _('timeofday requires time in HHMM format')
+            if not(hhmm.isdigit()):
+                return _('time in timeofday is not a number')
+            hours = int(hhmm[0:2])
+            minutes = int(hhmm[2:4])
+            if hours < 0 or hours > 23:
+                return _('hours value out of range')
+            if minutes < 0 or minutes > 59:
+                return _('minutes value out of range')
+            if not(i % 2):
+                hhmmval = hours*100 + minutes
+            elif hhmmval > (hours*100 + minutes):
+                return _('first part of timeofday range is greater than the second')
+
+
+def _validate_dayofweek(t):
+    t = _timearg_to_list(t)
+    return _validate_range('dayofweek', t, 1, 7, 'day', 1)
+
+
+def _validate_dayofmonth(t):
+    t = _timearg_to_list(t)
+    return _validate_range('dayofmonth', t, 1, 31, 'day', 2)
+
+
+def _validate_weekofmonth(t):
+    t = _timearg_to_list(t)
+    return _validate_range('weekofmonth', t, 0, 5, 'week', 1)
+
+
+def _validate_monthofyear(t):
+    t = _timearg_to_list(t)
+    return _validate_range('monthofyear', t, 1, 12, 'month', 2)
+
+
+def _validate_year(t):
+    t = _timearg_to_list(t)
+    return _validate_range('year', t, 1970, 9999, 'year', 4)
+
+
+def normalize_accesstime(t):
+    # multiple whitespaces to one
+    t = ' '.join(t.split())
+    t = t.lower()
+    # no spaces between intervals
+    t = re.sub(r'\s?,\s?', r',', t)
+    # no spaces around = sign
+    t = re.sub(r'\s?=\s?', r'=', t)
+    # no spaces around - sign
+    t = re.sub(r'\s?-\s?', r'-', t)
+    # if keyword=1234keyword make a space after 1234
+    t = re.sub(r'(\d)([a-zA-Z])', r'\1 \2', t)
+
+    return t
+
+
+def validate_accesstime(ugettext, value):
+    value = normalize_accesstime(value)
+    ts = value.split()
+
+    res = None
+    for i, el in enumerate(ts):
+        if(el.startswith('timeofday=')):
+            res = _validate_timeofday(el[10:])
+        elif(el.startswith('dayofweek=')):
+            res = _validate_dayofweek(el[10:])
+        elif(el.startswith('dayofmonth=')):
+            res = _validate_dayofmonth(el[11:])
+        elif(el.startswith('weekofmonth=')):
+            res = _validate_weekofmonth(el[12:])
+        elif(el.startswith('monthofyear=')):
+            res = _validate_monthofyear(el[12:])
+        elif(el.startswith('year=')):
+            res = _validate_year(el[5:])
+        else:
+            res = _('Unknown expression {exp} at position {pos}'
+                    .format(exp=el, pos=i))
+        if res:
+            return res
+
+
+def validate_timezone(ugettext, value):
+    if value.lower() == 'host':
+        return
+    try:
+        timezone(value)
+    except UnknownTimeZoneError, e:
+        return _('Time zone unknown: {tz}'.format(tz=e.args[0]))
+
+
 @register()
 class hbacrule(LDAPObject):
     """
@@ -124,7 +265,8 @@ class hbacrule(LDAPObject):
         'description', 'usercategory', 'hostcategory',
         'servicecategory', 'ipaenabledflag',
         'memberuser', 'sourcehost', 'memberhost', 'memberservice',
-        'externalhost',
+        'memberhostgroup', 'externalhost', 'ipatimezone',
+        'accesstime', 'accesstimeexclude'
     ]
     uuid_attribute = 'ipauniqueid'
     rdn_attribute = 'ipauniqueid'
@@ -140,7 +282,8 @@ class hbacrule(LDAPObject):
             'ipapermbindruletype': 'all',
             'ipapermright': {'read', 'search', 'compare'},
             'ipapermdefaultattr': {
-                'accessruletype', 'accesstime', 'cn', 'description',
+                'accessruletype', 'ipatimezone', 'accesstime',
+                'accesstimeexclude', 'cn', 'description',
                 'externalhost', 'hostcategory', 'ipaenabledflag',
                 'ipauniqueid', 'memberhost', 'memberservice', 'memberuser',
                 'servicecategory', 'sourcehost', 'sourcehostcategory',
@@ -174,7 +317,8 @@ class hbacrule(LDAPObject):
         'System: Modify HBAC Rule': {
             'ipapermright': {'write'},
             'ipapermdefaultattr': {
-                'accessruletype', 'accesstime', 'cn', 'description',
+                'accessruletype', 'ipatimezone', 'accesstime',
+                'accesstimeexclude', 'cn', 'description',
                 'hostcategory', 'ipaenabledflag', 'servicecategory',
                 'sourcehost', 'sourcehostcategory', 'usercategory'
             },
@@ -224,10 +368,23 @@ class hbacrule(LDAPObject):
             doc=_('Service category the rule applies to'),
             values=(u'all', ),
         ),
-#        AccessTime('accesstime?',
-#            cli_name='time',
-#            label=_('Access time'),
-#        ),
+        Str('ipatimezone?', validate_timezone,
+            cli_name = 'timezone',
+            label = _('Time zone'),
+            flags = {'no_search'},
+        ),
+        Str('accesstime*', validate_accesstime,
+            cli_name='time',
+            label=_('Access time'),
+            flags = {'no_search', 'no_create', 'no_update'},
+            normalizer=normalize_accesstime,
+        ),
+        Str('accesstimeexclude*', validate_accesstime,
+            cli_name='excltime',
+            label=_('Access time exceptions'),
+            flags = {'no_search', 'no_create', 'no_update'},
+            normalizer=normalize_accesstime,
+        ),
         Str('description?',
             cli_name='desc',
             label=_('Description'),
@@ -397,86 +554,32 @@ class hbacrule_disable(LDAPQuery):
         )
 
 
+@register()
+class hbacrule_add_accesstime(LDAPAddAttribute):
+    __doc__ = _('Add allowed access times to an HBAC Rule')
+    msg_summary = _('Added allowed access times to the rule "%(value)s"')
+    attribute = 'accesstime'
 
-class hbacrule_add_accesstime(LDAPQuery):
-    """
-    Add an access time to an HBAC rule.
-    """
 
-    takes_options = (
-        AccessTime('accesstime',
-            cli_name='time',
-            label=_('Access time'),
-        ),
-    )
+@register()
+class hbacrule_remove_accesstime(LDAPRemoveAttribute):
+    __doc__ = _('Remove access times from an HBAC Rule')
+    msg_summary = _('Removed access times from rule "%(value)s"')
+    attribute = 'accesstime'
 
-    def execute(self, cn, **options):
-        ldap = self.obj.backend
 
-        dn = self.obj.get_dn(cn)
+@register()
+class hbacrule_add_accesstimeexclude(LDAPAddAttribute):
+    __doc__ = _('Add exceptions in access time to an HBAC Rule')
+    msg_summary = _('Added exceptions in access times to the rule "%(value)s"')
+    attribute = 'accesstimeexclude'
 
-        entry_attrs = ldap.get_entry(dn, ['accesstime'])
-        entry_attrs.setdefault('accesstime', []).append(
-            options['accesstime']
-        )
-        try:
-            ldap.update_entry(entry_attrs)
-        except errors.EmptyModlist:
-            pass
-        except errors.NotFound:
-            self.obj.handle_not_found(cn)
 
-        return dict(result=True)
-
-    def output_for_cli(self, textui, result, cn, **options):
-        textui.print_name(self.name)
-        textui.print_dashed(
-            'Added access time "%s" to HBAC rule "%s"' % (
-                options['accesstime'], cn
-            )
-        )
-
-#api.register(hbacrule_add_accesstime)
-
-
-class hbacrule_remove_accesstime(LDAPQuery):
-    """
-    Remove access time to HBAC rule.
-    """
-    takes_options = (
-        AccessTime('accesstime?',
-            cli_name='time',
-            label=_('Access time'),
-        ),
-    )
-
-    def execute(self, cn, **options):
-        ldap = self.obj.backend
-
-        dn = self.obj.get_dn(cn)
-
-        entry_attrs = ldap.get_entry(dn, ['accesstime'])
-        try:
-            entry_attrs.setdefault('accesstime', []).remove(
-                options['accesstime']
-            )
-            ldap.update_entry(entry_attrs)
-        except (ValueError, errors.EmptyModlist):
-            pass
-        except errors.NotFound:
-            self.obj.handle_not_found(cn)
-
-        return dict(result=True)
-
-    def output_for_cli(self, textui, result, cn, **options):
-        textui.print_name(self.name)
-        textui.print_dashed(
-            'Removed access time "%s" from HBAC rule "%s"' % (
-                options['accesstime'], cn
-            )
-        )
-
-#api.register(hbacrule_remove_accesstime)
+@register()
+class hbacrule_remove_accesstimeexclude(LDAPRemoveAttribute):
+    __doc__ = _('Remove exceptions in access times from an HBAC Rule')
+    msg_summary = _('Removed exceptions in access times from rule "%(value)s"')
+    attribute = 'accesstimeexclude'
 
 
 @register()
@@ -593,4 +696,3 @@ class hbacrule_remove_service(LDAPRemoveMember):
 
     member_attributes = ['memberservice']
     member_count_out = ('%i object removed.', '%i objects removed.')
-
-- 
2.4.3

From 63f7727452ab3ac79d325b22d715d27f77f7bb68 Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Fri, 10 Jul 2015 14:23:03 +0200
Subject: [PATCH 3/3] Created basic UI for setting time policies at HBAC rules

https://fedorahosted.org/freeipa/ticket/547
https://fedorahosted.org/freeipa/ticket/548
---
 install/ui/less/widgets.less   |   7 +-
 install/ui/src/freeipa/hbac.js | 182 ++++++++++++++++++++++++++++++++++++++++-
 install/ui/src/freeipa/rule.js |  85 ++++++++++++++++++-
 3 files changed, 270 insertions(+), 4 deletions(-)

diff --git a/install/ui/less/widgets.less b/install/ui/less/widgets.less
index 99b22068d7b721d93e7b6901e262ad11781ffae7..be3ef16e0ffc1dde100d1f6375023ed12555f378 100644
--- a/install/ui/less/widgets.less
+++ b/install/ui/less/widgets.less
@@ -143,5 +143,10 @@
     }
 }
 
+.tz-select {
+    width:220px;
+    margin-bottom:2px
+}
+
 // workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=409254
-tbody:empty { display: none; }
\ No newline at end of file
+tbody:empty { display: none; }
diff --git a/install/ui/src/freeipa/hbac.js b/install/ui/src/freeipa/hbac.js
index 6161942b93fce654830330fdbdf6853ce9e428ff..929e3e88204bfc12c1cb425f398efa0026dbb505 100644
--- a/install/ui/src/freeipa/hbac.js
+++ b/install/ui/src/freeipa/hbac.js
@@ -472,6 +472,186 @@ var add_hbacrule_details_facet_widgets = function (spec) {
             ]
         }
     );
+
+    //
+    // AccessTime
+    //
+
+    spec.fields.push(
+        {
+            $type: 'select',
+            name: 'ipatimezone',
+            widget: 'time_policies.ipatimezone'
+        },
+        {
+            $type: 'time_rules_table',
+            name: 'accesstime',
+            widget: 'time_policies.accesstime',
+            priority: IPA.hbac.remove_method_priority
+        },
+        {
+            $type: 'time_rules_table',
+            name: 'accesstimeexclude',
+            widget: 'time_policies.accesstimeexclude',
+            priority: IPA.hbac.remove_method_priority
+        }
+    );
+
+    spec.widgets.push(
+        {
+            $factory: IPA.section,
+            name: 'time_policies',
+            label: 'When', // TODO: add text to i18n
+            widgets: [
+                    {
+                        $type: 'select',
+                        name: 'ipatimezone',
+                        css_class: 'tz-select',
+                        options: IPA.create_options([
+                            '', 'UTC', 'Host', 'Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa',
+                            'Africa/Algiers', 'Africa/Asmera', 'Africa/Bamako', 'Africa/Bangui',
+                            'Africa/Banjul', 'Africa/Bissau', 'Africa/Blantyre', 'Africa/Brazzaville',
+                            'Africa/Bujumbura', 'Africa/Cairo', 'Africa/Casablanca', 'Africa/Ceuta',
+                            'Africa/Conakry', 'Africa/Dakar', 'Africa/Dar_es_Salaam', 'Africa/Djibouti',
+                            'Africa/Douala', 'Africa/El_Aaiun', 'Africa/Freetown', 'Africa/Gaborone',
+                            'Africa/Harare', 'Africa/Johannesburg', 'Africa/Kampala', 'Africa/Khartoum',
+                            'Africa/Kigali', 'Africa/Kinshasa', 'Africa/Lagos', 'Africa/Libreville',
+                            'Africa/Lome', 'Africa/Luanda', 'Africa/Lubumbashi', 'Africa/Lusaka',
+                            'Africa/Malabo', 'Africa/Maputo', 'Africa/Maseru', 'Africa/Mbabane',
+                            'Africa/Mogadishu', 'Africa/Monrovia', 'Africa/Nairobi', 'Africa/Ndjamena',
+                            'Africa/Niamey', 'Africa/Nouakchott', 'Africa/Ouagadougou', 'Africa/Porto-Novo',
+                            'Africa/Sao_Tome', 'Africa/Timbuktu', 'Africa/Tripoli', 'Africa/Tunis', 'Africa/Windhoek',
+                            'America/Adak', 'America/Anchorage', 'America/Anguilla', 'America/Antigua',
+                            'America/Araguaina', 'America/Aruba', 'America/Asuncion', 'America/Barbados',
+                            'America/Belem', 'America/Belize', 'America/Boa_Vista', 'America/Bogota',
+                            'America/Boise', 'America/Buenos_Aires', 'America/Cambridge_Bay', 'America/Cancun',
+                            'America/Caracas', 'America/Catamarca', 'America/Cayenne', 'America/Cayman',
+                            'America/Chicago', 'America/Chihuahua', 'America/Cordoba', 'America/Costa_Rica',
+                            'America/Cuiaba', 'America/Curacao', 'America/Danmarkshavn', 'America/Dawson',
+                            'America/Dawson_Creek', 'America/Denver', 'America/Detroit', 'America/Dominica',
+                            'America/Edmonton', 'America/Eirunepe', 'America/El_Salvador', 'America/Fortaleza',
+                            'America/Glace_Bay', 'America/Godthab', 'America/Goose_Bay', 'America/Grand_Turk',
+                            'America/Grenada', 'America/Guadeloupe', 'America/Guatemala', 'America/Guayaquil',
+                            'America/Guyana', 'America/Halifax', 'America/Havana', 'America/Hermosillo',
+                            'America/Indiana/Indianapolis', 'America/Indiana/Knox', 'America/Indiana/Marengo',
+                            'America/Indiana/Vevay', 'America/Indianapolis', 'America/Inuvik', 'America/Iqaluit',
+                            'America/Jamaica', 'America/Jujuy', 'America/Juneau', 'America/Kentucky/Louisville',
+                            'America/Kentucky/Monticello', 'America/La_Paz', 'America/Lima', 'America/Los_Angeles',
+                            'America/Louisville', 'America/Maceio', 'America/Managua', 'America/Manaus',
+                            'America/Martinique', 'America/Mazatlan', 'America/Mendoza', 'America/Menominee',
+                            'America/Merida', 'America/Mexico_City', 'America/Miquelon', 'America/Monterrey',
+                            'America/Montevideo', 'America/Montreal', 'America/Montserrat', 'America/Nassau',
+                            'America/New_York', 'America/Nipigon', 'America/Nome', 'America/Noronha',
+                            'America/North_Dakota/Center', 'America/Panama', 'America/Pangnirtung',
+                            'America/Paramaribo', 'America/Phoenix', 'America/Port-au-Prince',
+                            'America/Port_of_Spain', 'America/Porto_Velho', 'America/Puerto_Rico',
+                            'America/Rainy_River', 'America/Rankin_Inlet', 'America/Recife',
+                            'America/Regina', 'America/Rio_Branco', 'America/Rosario', 'America/Santiago',
+                            'America/Santo_Domingo', 'America/Sao_Paulo', 'America/Scoresbysund',
+                            'America/Shiprock', 'America/St_Johns', 'America/St_Kitts', 'America/St_Lucia',
+                            'America/St_Thomas', 'America/St_Vincent', 'America/Swift_Current', 'America/Tegucigalpa',
+                            'America/Thule', 'America/Thunder_Bay', 'America/Tijuana', 'America/Tortola',
+                            'America/Vancouver', 'America/Whitehorse', 'America/Winnipeg', 'America/Yakutat',
+                            'America/Yellowknife', 'Antarctica/Casey', 'Antarctica/Davis', 'Antarctica/DumontDUrville',
+                            'Antarctica/Mawson', 'Antarctica/McMurdo', 'Antarctica/Palmer', 'Antarctica/South_Pole',
+                            'Antarctica/Syowa', 'Antarctica/Vostok', 'Arctic/Longyearbyen', 'Asia/Aden',
+                            'Asia/Almaty', 'Asia/Amman', 'Asia/Anadyr', 'Asia/Aqtau', 'Asia/Aqtobe',
+                            'Asia/Ashgabat', 'Asia/Baghdad', 'Asia/Bahrain', 'Asia/Baku', 'Asia/Bangkok',
+                            'Asia/Beirut', 'Asia/Bishkek', 'Asia/Brunei', 'Asia/Calcutta', 'Asia/Choibalsan',
+                            'Asia/Chongqing', 'Asia/Colombo', 'Asia/Damascus', 'Asia/Dhaka', 'Asia/Dili',
+                            'Asia/Dubai', 'Asia/Dushanbe', 'Asia/Gaza', 'Asia/Harbin', 'Asia/Hong_Kong',
+                            'Asia/Hovd', 'Asia/Irkutsk', 'Asia/Istanbul', 'Asia/Jakarta', 'Asia/Jayapura',
+                            'Asia/Jerusalem', 'Asia/Kabul', 'Asia/Kamchatka', 'Asia/Karachi', 'Asia/Kashgar',
+                            'Asia/Katmandu', 'Asia/Krasnoyarsk', 'Asia/Kuala_Lumpur', 'Asia/Kuching',
+                            'Asia/Kuwait', 'Asia/Macao', 'Asia/Macau', 'Asia/Magadan', 'Asia/Makassar',
+                            'Asia/Manila', 'Asia/Muscat', 'Asia/Nicosia', 'Asia/Novosibirsk', 'Asia/Omsk',
+                            'Asia/Oral', 'Asia/Phnom_Penh', 'Asia/Pontianak', 'Asia/Pyongyang', 'Asia/Qyzylorda',
+                            'Asia/Qatar', 'Asia/Rangoon', 'Asia/Riyadh', 'Asia/Saigon', 'Asia/Sakhalin',
+                            'Asia/Samarkand', 'Asia/Seoul', 'Asia/Shanghai', 'Asia/Singapore', 'Asia/Taipei',
+                            'Asia/Tashkent', 'Asia/Tbilisi', 'Asia/Tehran', 'Asia/Thimphu', 'Asia/Tokyo',
+                            'Asia/Ujung_Pandang', 'Asia/Ulaanbaatar', 'Asia/Urumqi', 'Asia/Vientiane',
+                            'Asia/Vladivostok', 'Asia/Yakutsk', 'Asia/Yekaterinburg', 'Asia/Yerevan',
+                            'Atlantic/Azores', 'Atlantic/Bermuda', 'Atlantic/Canary', 'Atlantic/Cape_Verde',
+                            'Atlantic/Faeroe', 'Atlantic/Jan_Mayen', 'Atlantic/Madeira', 'Atlantic/Reykjavik',
+                            'Atlantic/South_Georgia', 'Atlantic/St_Helena', 'Atlantic/Stanley', 'Australia/Adelaide',
+                            'Australia/Brisbane', 'Australia/Broken_Hill', 'Australia/Darwin', 'Australia/Hobart',
+                            'Australia/Lindeman', 'Australia/Lord_Howe', 'Australia/Melbourne', 'Australia/Perth',
+                            'Australia/Sydney', 'Europe/Amsterdam', 'Europe/Andorra', 'Europe/Athens',
+                            'Europe/Belfast', 'Europe/Belgrade', 'Europe/Berlin', 'Europe/Bratislava',
+                            'Europe/Brussels', 'Europe/Bucharest', 'Europe/Budapest', 'Europe/Chisinau',
+                            'Europe/Copenhagen', 'Europe/Dublin', 'Europe/Gibraltar', 'Europe/Helsinki',
+                            'Europe/Istanbul', 'Europe/Kaliningrad', 'Europe/Kiev', 'Europe/Lisbon',
+                            'Europe/Ljubljana', 'Europe/London', 'Europe/Luxembourg', 'Europe/Madrid',
+                            'Europe/Malta', 'Europe/Minsk', 'Europe/Monaco', 'Europe/Moscow', 'Europe/Nicosia',
+                            'Europe/Oslo', 'Europe/Paris', 'Europe/Prague', 'Europe/Riga', 'Europe/Rome',
+                            'Europe/Samara', 'Europe/San_Marino', 'Europe/Sarajevo', 'Europe/Simferopol',
+                            'Europe/Skopje', 'Europe/Sofia', 'Europe/Stockholm', 'Europe/Tallinn',
+                            'Europe/Tirane', 'Europe/Uzhgorod', 'Europe/Vaduz', 'Europe/Vatican',
+                            'Europe/Vienna', 'Europe/Vilnius', 'Europe/Warsaw', 'Europe/Zagreb',
+                            'Europe/Zaporozhye', 'Europe/Zurich', 'Indian/Antananarivo', 'Indian/Chagos',
+                            'Indian/Christmas', 'Indian/Cocos', 'Indian/Comoro', 'Indian/Kerguelen', 'Indian/Mahe',
+                            'Indian/Maldives', 'Indian/Mauritius', 'Indian/Mayotte', 'Indian/Reunion',
+                            'Pacific/Apia', 'Pacific/Auckland', 'Pacific/Chatham', 'Pacific/Easter',
+                            'Pacific/Efate', 'Pacific/Enderbury', 'Pacific/Fakaofo', 'Pacific/Fiji',
+                            'Pacific/Funafuti', 'Pacific/Galapagos', 'Pacific/Gambier', 'Pacific/Guadalcanal',
+                            'Pacific/Guam', 'Pacific/Honolulu', 'Pacific/Johnston', 'Pacific/Kiritimati',
+                            'Pacific/Kosrae', 'Pacific/Kwajalein', 'Pacific/Majuro', 'Pacific/Marquesas',
+                            'Pacific/Midway', 'Pacific/Nauru', 'Pacific/Niue', 'Pacific/Norfolk',
+                            'Pacific/Noumea', 'Pacific/Pago_Pago', 'Pacific/Palau', 'Pacific/Pitcairn',
+                            'Pacific/Ponape', 'Pacific/Port_Moresby', 'Pacific/Rarotonga', 'Pacific/Saipan',
+                            'Pacific/Tahiti', 'Pacific/Tarawa', 'Pacific/Tongatapu', 'Pacific/Truk',
+                            'Pacific/Wake', 'Pacific/Wallis', 'Pacific/Yap'
+                        ])
+                    },
+
+                    {
+                        $type: 'time_rules_table',
+                        name: 'accesstime',
+                        columns: [
+                            {
+                            name: 'time',
+                            label: 'Access Time'
+                        }],
+                        add_command: 'add_accesstime',
+                        remove_command: 'remove_accesstime',
+                        add_title: '@i18n:association.add.member',
+                        remove_title: '@i18n:association.remove.member',
+                        adder_dialog: {
+                            title: 'Add access time',
+                            fields: [
+                                {
+                                    name: 'accesstime',
+                                    title: 'Access Time',
+                                }
+                            ]
+                        }
+                    },
+
+                    {
+                        $type: 'time_rules_table',
+                        name: 'accesstimeexclude',
+                        columns: [
+                            {
+                            name: 'time',
+                            label: 'Access Time Exception'
+                        }],
+                        add_command: 'add_accesstimeexclude',
+                        remove_command: 'remove_accesstimeexclude',
+                        add_title: '@i18n:association.add.member',
+                        remove_title: '@i18n:association.remove.member',
+                        adder_dialog: {
+                            title: 'Add access time exception',
+                            fields: [
+                                {
+                                    name: 'accesstimeexclude',
+                                    title: 'Access Time Exception',
+                                }
+                            ]
+                        }
+                    }
+            ]
+        }
+    );
 };
 
 IPA.hbacrule_details_facet = function(spec) {
@@ -503,4 +683,4 @@ exp.register = function() {
 phases.on('registration', exp.register);
 
 return exp;
-});
\ No newline at end of file
+});
diff --git a/install/ui/src/freeipa/rule.js b/install/ui/src/freeipa/rule.js
index 706827190261efda136f6d1489bdb13543c00f7a..c5709a37a9d777644d3b753626b73333b5ecc47e 100644
--- a/install/ui/src/freeipa/rule.js
+++ b/install/ui/src/freeipa/rule.js
@@ -19,6 +19,8 @@
  */
 
 define([
+    'dojo/_base/declare',
+    './field',
     './ipa',
     './jquery',
     './phases',
@@ -28,7 +30,7 @@ define([
     './search',
     './association',
     './entity'],
-        function(IPA, $, phases, reg, rpc) {
+        function(declare, field_mod, IPA, $, phases, reg, rpc) {
 
 IPA.rule_details_widget = function(spec) {
 
@@ -265,13 +267,92 @@ IPA.rule_association_adder_dialog = function(spec) {
     return that;
 };
 
+IPA.time_rules_field = function(spec) {
+
+    spec = spec || {};
+    spec.adapter = spec.adapter || IPA.time_rules_adapter;
+    var that = IPA.field(spec);
+    return that;
+};
+
+IPA.time_rules_adapter = declare([field_mod.Adapter], {
+
+    load: function(data) {
+        var accesstimes = this.inherited(arguments);
+        var values = [];
+        if (accesstimes) {
+            for (var i=0, j=0; i<accesstimes.length; i++) {
+                if (accesstimes[i] === '') continue;
+                values.push({time: accesstimes[i]});
+            }
+        }
+
+        return values;
+    }
+});
+
+IPA.time_rules_table = function(spec) {
+
+    spec = spec || {};
+    spec.footer = spec.footer === undefined ? false : spec.footer;
+
+    spec.value_attribute = 'time';
+
+    var that = IPA.attribute_table_widget(spec);
+
+    that.on_add = function(data) {
+        that.refresh_facet();
+    };
+
+    that.on_remove = function(data) {
+
+        var results = data.result.results;
+
+        var i = results.length - 1;
+        while (i >= 0) {
+            if (results[i].completed === 1){
+                that.reload_facet({ result: results[i] });
+                return;
+            }
+            i--;
+        }
+
+        that.refresh_facet();
+    };
+
+    that.create_remove_command = function(values, on_success, on_error) {
+
+        var batch = rpc.batch_command({
+            name: 'hbacrule_remove_accesstime',
+            on_success: on_success,
+            on_error: on_error
+        });
+
+        var pkeys = that.get_pkeys();
+
+        for (var i=0; i<values.length; i++) {
+
+            var command = that.attribute_table_create_remove_command([]);
+            command.set_option(spec.name, values[i]);
+
+            batch.add_command(command);
+        }
+
+        return batch;
+    };
+
+    return that;
+};
+
 phases.on('registration', function() {
     var w = reg.widget;
     var f = reg.field;
 
     w.register('rule_association_table', IPA.rule_association_table_widget);
     f.register('rule_association_table', IPA.rule_association_table_field);
+    w.register('time_rules_table', IPA.time_rules_table);
+    f.register('time_rules_table', IPA.time_rules_field);
 });
 
 return {};
-});
\ No newline at end of file
+});
-- 
2.4.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to