On 07/10/2015 04:17 PM, Martin Basti wrote:
On 10/07/15 12:08, Stanislav Laznicka wrote:
Hi,

Long time no post from me, time to make it up to you.

I have been working on the the implementation of the design of time policies for HBAC rules on FreeIPA and SSSD sides. Attached is the current state of the FreeIPA solution. My comments and notes to the solution follow.

The FreeIPA side backend base for time policies in HBAC seems working to me but still needs formal testing. Also, there is no conversion from the iCal format as previously requested and I personally would postpone this feature until the time policies functionality is rock solid.

There were some uncertainties in the design as well. I ran into 2 of these but more may come.

The first thing is how to deal with weeks in a month. There are two possibilities. A week in month (as specified by the weekofmonth keyword in the time policies) may be understood as a period of time between two Sundays, so when a month starts on, say, Friday the 1st, weekofmonth=1 would specify days Friday, Saturday, Sunday and anything from that Sunday on would be a weekofmonth=2 and on. However, I think a week in a month may also be considered a period of time that equals 7 days of a month. In the previous example, a weekofmonth=1 would therefore also apply to the following days up until Friday the 8th, excluding this last day. Although I implemented the first case in the SSSD, I actually started thinking the second case scenario might be the right or "better" one.

The other thing is which years should be allowed to be the input of the "year" keyword. Currently, I set the range for these values to 1970-2038 according to the Unix timestamp. I'm not sure if anyone would want to set it less than 1970, setting it for a higher value than 2038 might probably make sense in some very special cases, although I really can't think of a one.

As for the WebUI, I am not really satisfied with the current state - the time zone select button requires saving the rule before any further setting on the page and the tables for setting the time rules don't allow editing the rules, which gets annoying fast. The WebUI for the time policies in HBAC was created for my Master's thesis purposes in a hurry and I will probably need to discuss it some more with Petr V. It works well for basic display and add/remove of the time rules, though.

So, that is what I do now, aside from SSSD functionality. Please, let me know what your ideas are, especially about those weekofmonth and year issues.

Cheers,
Stanislav Laznicka


Please revert this change, 'replaces' keyword is used only for legacy permission. Changes in new permissions are handled automatically by update plugin.

              'replaces': [
-                '(targetattr = "servicecategory || sourcehostcategory || cn || description || ipaenabledflag || 
accesstime || usercategory || hostcategory || accessruletype || sourcehost")(target 
="ldap:///ipauniqueid=*,cn=hbac,$SUFFIX";)(version 3.0;acl "permission:Modify HBAC rule";allow 
(write) groupdn ="ldap:///cn=Modify HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)',
+                '(targetattr = "servicecategory || sourcehostcategory || cn || description || ipaenabledflag || 
timezone || accesstime || accesstimeexclude || usercategory || hostcategory || accessruletype || 
sourcehost")(target ="ldap:///ipauniqueid=*,cn=hbac,$SUFFIX";)(version 3.0;acl "permission:Modify 
HBAC rule";allow (write) groupdn ="ldap:///cn=Modify HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)',
              ],

Martin
--
Martin Basti
Attaching the sequence of fixed patches.
From 3bd1b08e00417d32138dbe7e92536b474f62fc8b 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/4] 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..846c304264e3d9af9eeb293e4a8178282dc4958c 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.3.28 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.3.29 NAME 'timezone' 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 $ timezone $ 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 7e1877342eb40915825d889fcaa5266c4a02be3e 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/4] Prepared parameters for HBAC Rule plugin time policies
 extension.

Two new parameter types were added to parameters.py - AccessTime
for setting of allowed access times and exceptions in them; and
Timezone for setting the timezone for a time policy.

https://fedorahosted.org/freeipa/ticket/547
https://fedorahosted.org/freeipa/ticket/548
---
 ipalib/__init__.py   |   2 +-
 ipalib/parameters.py | 256 +++++++++++++++++++++------------------------------
 2 files changed, 105 insertions(+), 153 deletions(-)

diff --git a/ipalib/__init__.py b/ipalib/__init__.py
index 44aacd0916454e03c6611f106088b98ea0169979..10d84d2d0698fedda2b4cd18d5bdb6ff3363bf21 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, TimeZone, AccessTime, 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..67480c30a4cd7c8275faf83ff7b9a26dec067f98 100644
--- a/ipalib/parameters.py
+++ b/ipalib/parameters.py
@@ -104,6 +104,7 @@ import decimal
 import base64
 import datetime
 from xmlrpclib import MAXINT, MININT
+from pytz import timezone, UnknownTimeZoneError
 
 from types import NoneType
 from text import _ as ugettext
@@ -1659,181 +1660,132 @@ 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 _timearg_to_list(self, time):
+        '''
+        Parses the argument of either of the accessTime language keywords
+        '''
+        return (t.split('-') for t in time.split(','))
 
-    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_range(self, keyword, lst, r1, r2, unit, unitlen):
+        for elempair in lst:
+            elold = 0
+            for i, elem in enumerate(elempair):
+                if len(elem) > unitlen:
+                    raise ValueError(keyword + ' requires ' + unit + 'number ' + r1 + '-' + r2)
+                if not(elem.isdigit()):
+                    raise ValueError(unit + ' in ' + keyword + ' is not a number')
+                elnum = int(elem)
+                if elnum < r1 or elnum > r2:
+                    raise ValueError(unit + ' in ' + keyword + ' out of range')
+                if not(i):
+                    elold = elnum
+                elif elold > elnum:
+                    raise ValidationError(
+                            name = self.get_param_name(),
+                            error = _('first part of ' + keyword + ' range is greater than the second')
+                        )
 
-    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_timeofday(self, t):
+        t = self._timearg_to_list(t)
 
-    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')
+        for time in t:
+            hhmmval = 0
+            for i, hhmm in enumerate(time):
+                if len(hhmm) != 4:
+                    raise ValueError('timeofday requires time in HHMM format')
+                if not(hhmm.isdigit()):
+                    raise ValueError('time in timeofday is not a number')
+                hours = int(hhmm[0:2])
+                minutes = int(hhmm[2:4])
+                if hours < 0 or hours > 23:
+                    raise ValueError('hours value out of range')
+                if minutes < 0 or minutes > 59:
+                    raise ValueError('minutes value out of range')
+                if not(i % 2):
+                    hhmmval = hours*100 + minutes
+                elif hhmmval > (hours*100 + minutes):
+                    raise ValidationError(
+                        name=self.get_param_name(),
+                        error = _('first part of timeofday range is greater than the second')
+                    )
 
-    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_dayofweek(self, t):
+        t = self._timearg_to_list(t)
+        self._check_range('dayofweek', t, 1, 7, 'day', 1)
 
-    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_dayofmonth(self, t):
+        t = self._timearg_to_list(t)
+        self._check_range('dayofmonth', t, 1, 31, 'day', 2)
 
-    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_weekofmonth(self, t):
+        t = self._timearg_to_list(t)
+        self._check_range('weekofmont', t, 1, 6, 'week', 1)
 
-    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_monthofyear(self, t):
+        t = self._timearg_to_list(t)
+        self._check_range('monthofyear', t, 1, 12, 'month', 2)
 
-    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_year(self, t):
+        t = self._timearg_to_list(t)
+        self._check_range('year', t, 1970, 2038, 'year', 4)
 
-    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 normal_form(self, 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)
 
-    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')
+        return t
 
     def _check(self, time):
+        time = self.normal_form(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')
+
+        for i, el in enumerate(ts):
+            if(el.startswith('timeofday=')):
+                self._check_timeofday(el[10:])
+            elif(el.startswith('dayofweek=')):
+                self._check_dayofweek(el[10:])
+            elif(el.startswith('dayofmonth=')):
+                self._check_dayofmonth(el[11:])
+            elif(el.startswith('weekofmonth=')):
+                self._check_weekofmonth(el[12:])
+            elif(el.startswith('monthofyear=')):
+                self._check_monthofyear(el[12:])
+            elif(el.startswith('year=')):
+                self._check_year(el[5:])
+            else:
+                raise ValueError('Unknown keyword at position ' + str(i))
 
     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')
+                name = self.get_param_name(),
+                error = e.args[0],
             )
-        return None
 
+class TimeZone(Str):
+    def _rule_required(self, _, value):
+        if value.lower() == 'host':
+            return
+        try:
+            timezone(value)
+        except UnknownTimeZoneError, e:
+            raise ValidationError(
+                name = self.get_param_name(),
+                error = 'Time zone unknown: ' + e.args[0],
+            )
 
 class DNParam(Param):
     type = DN
-- 
2.4.3

From e601c8a8b3e8b3c6a9c1da1f37452e3c1ff1e9b4 Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Tue, 7 Jul 2015 09:51:24 +0200
Subject: [PATCH 3/4] Added methods for setting time-based policies in hbacrule
 object.

https://fedorahosted.org/freeipa/ticket/547
https://fedorahosted.org/freeipa/ticket/548
---
 ACI.txt                    |   4 +-
 API.txt                    |  45 ++++++++++-
 ipalib/plugins/hbacrule.py | 183 ++++++++++++++++++++++++++++++++++++++-------
 3 files changed, 200 insertions(+), 32 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 76a7ff70e27c032bdd8fa26e076271e02b23d3b3..c0bd3fe4d851281944a0153e1ebf199731552267 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 || servicecategory || sourcehost || sourcehostcategory || timezone || 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 || ipauniqueid || member || memberhost || memberservice || memberuser || modifytimestamp || objectclass || servicecategory || sourcehost || sourcehostcategory || timezone || 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 c68bee94e3a9ed6182f6bd2152070222e32c7532..6c4b4ce32bd21f62c0a324961aae96f39f3c09dd 100644
--- a/API.txt
+++ b/API.txt
@@ -1674,9 +1674,11 @@ 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,19,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: AccessTime('accesstime', attribute=True, cli_name='time', multivalue=False, required=False)
+option: AccessTime('accesstimeexclude', attribute=True, cli_name='excltime', multivalue=False, required=False)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False)
@@ -1690,11 +1692,24 @@ option: Str('setattr*', cli_name='setattr', exclude='webui')
 option: DeprecatedParam('sourcehost_host', attribute=True, cli_name='sourcehost_host', multivalue=False, required=False)
 option: DeprecatedParam('sourcehost_hostgroup', attribute=True, cli_name='sourcehost_hostgroup', multivalue=False, required=False)
 option: DeprecatedParam('sourcehostcategory', attribute=True, cli_name='sourcehostcategory', multivalue=False, required=False)
+option: TimeZone('timezone', attribute=True, cli_name='timezone', multivalue=False, required=False)
 option: StrEnum('usercategory', attribute=True, cli_name='usercat', multivalue=False, required=False, values=(u'all',))
 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,2,1
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: AccessTime('accesstime', cli_name='time')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
+command: hbacrule_add_exclude_accesstime
+args: 1,2,1
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: AccessTime('accesstimeexclude', cli_name='excltime')
+option: Str('version?', exclude='webui')
+output: Output('result', 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)
@@ -1766,9 +1781,11 @@ output: Output('result', <type 'bool'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: hbacrule_find
-args: 1,18,4
+args: 1,21,4
 arg: Str('criteria?', noextrawhitespace=False)
 option: StrEnum('accessruletype', attribute=True, autofill=False, cli_name='type', default=u'allow', exclude='webui', multivalue=False, query=True, required=False, values=(u'allow', u'deny'))
+option: AccessTime('accesstime', attribute=True, autofill=False, cli_name='time', multivalue=False, query=True, required=False)
+option: AccessTime('accesstimeexclude', attribute=True, autofill=False, cli_name='excltime', multivalue=False, query=True, required=False)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('cn', attribute=True, autofill=False, cli_name='name', multivalue=False, primary_key=True, query=True, required=False)
 option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
@@ -1784,6 +1801,7 @@ option: DeprecatedParam('sourcehost_host', attribute=True, autofill=False, cli_n
 option: DeprecatedParam('sourcehost_hostgroup', attribute=True, autofill=False, cli_name='sourcehost_hostgroup', multivalue=False, query=True, required=False)
 option: DeprecatedParam('sourcehostcategory', attribute=True, autofill=False, cli_name='sourcehostcategory', multivalue=False, query=True, required=False)
 option: Int('timelimit?', autofill=False, minvalue=0)
+option: TimeZone('timezone', attribute=True, autofill=False, cli_name='timezone', multivalue=False, query=True, required=False)
 option: StrEnum('usercategory', attribute=True, autofill=False, cli_name='usercat', multivalue=False, query=True, required=False, values=(u'all',))
 option: Str('version?', exclude='webui')
 output: Output('count', <type 'int'>, None)
@@ -1791,9 +1809,11 @@ 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,21,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: AccessTime('accesstime', attribute=True, autofill=False, cli_name='time', multivalue=False, required=False)
+option: AccessTime('accesstimeexclude', attribute=True, autofill=False, cli_name='excltime', multivalue=False, required=False)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: Str('delattr*', cli_name='delattr', exclude='webui')
@@ -1809,11 +1829,24 @@ option: Str('setattr*', cli_name='setattr', exclude='webui')
 option: DeprecatedParam('sourcehost_host', attribute=True, autofill=False, cli_name='sourcehost_host', multivalue=False, required=False)
 option: DeprecatedParam('sourcehost_hostgroup', attribute=True, autofill=False, cli_name='sourcehost_hostgroup', multivalue=False, required=False)
 option: DeprecatedParam('sourcehostcategory', attribute=True, autofill=False, cli_name='sourcehostcategory', multivalue=False, required=False)
+option: TimeZone('timezone', attribute=True, autofill=False, cli_name='timezone', multivalue=False, required=False)
 option: StrEnum('usercategory', attribute=True, autofill=False, cli_name='usercat', multivalue=False, required=False, values=(u'all',))
 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,2,1
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: AccessTime('accesstime', cli_name='time')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
+command: hbacrule_remove_exclude_accesstime
+args: 1,2,1
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: AccessTime('accesstimeexclude', cli_name='excltime')
+option: Str('version?', exclude='webui')
+output: Output('result', 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)
@@ -1862,6 +1895,12 @@ option: Str('version?', exclude='webui')
 output: Output('completed', <type 'int'>, None)
 output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
+command: hbacrule_set_timezone
+args: 1,2,1
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+option: TimeZone('timezone', cli_name='timezone')
+option: Str('version?', exclude='webui')
+output: Output('result', None, None)
 command: hbacrule_show
 args: 1,5,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
diff --git a/ipalib/plugins/hbacrule.py b/ipalib/plugins/hbacrule.py
index 34bdc9bdfe03f01662851bd5aea9daf9e28823d0..58d505602c1e1431648e113bd7279055e1423e46 100644
--- a/ipalib/plugins/hbacrule.py
+++ b/ipalib/plugins/hbacrule.py
@@ -18,7 +18,7 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from ipalib import api, errors
-from ipalib import AccessTime, Password, Str, StrEnum, Bool, DeprecatedParam
+from ipalib import AccessTime, TimeZone, Password, Str, StrEnum, Bool, DeprecatedParam
 from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import *
 from ipalib import _, ngettext
@@ -124,7 +124,8 @@ class hbacrule(LDAPObject):
         'description', 'usercategory', 'hostcategory',
         'servicecategory', 'ipaenabledflag',
         'memberuser', 'sourcehost', 'memberhost', 'memberservice',
-        'memberhostgroup', 'externalhost',
+        'memberhostgroup', 'externalhost', 'timezone',
+        'accesstime', 'accesstimeexclude'
     ]
     uuid_attribute = 'ipauniqueid'
     rdn_attribute = 'ipauniqueid'
@@ -140,7 +141,8 @@ class hbacrule(LDAPObject):
             'ipapermbindruletype': 'all',
             'ipapermright': {'read', 'search', 'compare'},
             'ipapermdefaultattr': {
-                'accessruletype', 'accesstime', 'cn', 'description',
+                'accessruletype', 'timezone', 'accesstime',
+                'accesstimeexclude', 'cn', 'description',
                 'externalhost', 'hostcategory', 'ipaenabledflag',
                 'ipauniqueid', 'memberhost', 'memberservice', 'memberuser',
                 'servicecategory', 'sourcehost', 'sourcehostcategory',
@@ -174,7 +176,8 @@ class hbacrule(LDAPObject):
         'System: Modify HBAC Rule': {
             'ipapermright': {'write'},
             'ipapermdefaultattr': {
-                'accessruletype', 'accesstime', 'cn', 'description',
+                'accessruletype', 'timezone', 'accesstime',
+                'accesstimeexclude', 'cn', 'description',
                 'hostcategory', 'ipaenabledflag', 'servicecategory',
                 'sourcehost', 'sourcehostcategory', 'usercategory'
             },
@@ -196,7 +199,7 @@ class hbacrule(LDAPObject):
         ),
         StrEnum('accessruletype', validate_type,
             cli_name='type',
-            doc=_('Rule type (allow)'),
+            doc=_('Rule type (allow)'), 
             label=_('Rule type'),
             values=(u'allow', u'deny'),
             default=u'allow',
@@ -224,10 +227,18 @@ class hbacrule(LDAPObject):
             doc=_('Service category the rule applies to'),
             values=(u'all', ),
         ),
-#        AccessTime('accesstime?',
-#            cli_name='time',
-#            label=_('Access time'),
-#        ),
+        TimeZone('timezone?',
+            cli_name='timezone',
+            label=_('Time zone'),
+        ),
+        AccessTime('accesstime?',
+            cli_name='time',
+            label=_('Access time'),
+        ),
+        AccessTime('accesstimeexclude?',
+            cli_name='excltime',
+            label=_('Access time exceptions'),
+        ),
         Str('description?',
             cli_name='desc',
             label=_('Description'),
@@ -397,11 +408,46 @@ class hbacrule_disable(LDAPQuery):
         )
 
 
+@register()
+class hbacrule_set_timezone(LDAPQuery):
+    __doc__ = _('Change time zone of an HBAC rule')
 
+    takes_options = (
+        TimeZone('timezone',
+            cli_name='timezone',
+            label=_('Time zone'),
+        ),
+    )
+
+    def execute(self, cn, **options):
+        ldap = self.obj.backend
+
+        dn = self.obj.get_dn(cn)
+        try:
+            entry_attrs = ldap.get_entry(dn, ['timezone'])
+        except errors.NotFound:
+            self.obj.handle_not_found(cn)
+
+        entry_attrs['timezone'] = options['timezone']
+
+        try:
+            ldap.update_entry(entry_attrs)
+        except errors.EmptyModlist:
+            pass
+
+        return dict(result=True)
+
+    def output_for_cli(self, textui, result, cn, **options):
+        textui.print_name(self.name)
+        textui.print_dashed(
+        'Changed timezone to "%s" at HBAC rule "%s"' % (
+            options['timezone'], cn
+            )
+        )
+
+@register()
 class hbacrule_add_accesstime(LDAPQuery):
-    """
-    Add an access time to an HBAC rule.
-    """
+    __doc__ = _('Add access time to an HBAC rule')
 
     takes_options = (
         AccessTime('accesstime',
@@ -414,8 +460,13 @@ class hbacrule_add_accesstime(LDAPQuery):
         ldap = self.obj.backend
 
         dn = self.obj.get_dn(cn)
+        at = AccessTime('accesstime')
+        options['accesstime'] = at.normal_form(options['accesstime'])
+        try:
+            entry_attrs = ldap.get_entry(dn, ['accesstime'])
+        except errors.NotFound:
+            self.obj.handle_not_found(cn)
 
-        entry_attrs = ldap.get_entry(dn, ['accesstime'])
         entry_attrs.setdefault('accesstime', []).append(
             options['accesstime']
         )
@@ -423,12 +474,13 @@ class hbacrule_add_accesstime(LDAPQuery):
             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):
+        at = AccessTime('accesstime')
+        options['accesstime'] = at.normal_form(options['accesstime'])
+
         textui.print_name(self.name)
         textui.print_dashed(
             'Added access time "%s" to HBAC rule "%s"' % (
@@ -436,15 +488,12 @@ class hbacrule_add_accesstime(LDAPQuery):
             )
         )
 
-#api.register(hbacrule_add_accesstime)
-
-
+@register()
 class hbacrule_remove_accesstime(LDAPQuery):
-    """
-    Remove access time to HBAC rule.
-    """
+    __doc__ = _('Remove access time from an HBAC rule')
+
     takes_options = (
-        AccessTime('accesstime?',
+        AccessTime('accesstime',
             cli_name='time',
             label=_('Access time'),
         ),
@@ -454,8 +503,11 @@ class hbacrule_remove_accesstime(LDAPQuery):
         ldap = self.obj.backend
 
         dn = self.obj.get_dn(cn)
+        try:
+            entry_attrs = ldap.get_entry(dn, ['accesstime'])
+        except errors.NotFound:
+            self.obj.handle_not_found(cn)
 
-        entry_attrs = ldap.get_entry(dn, ['accesstime'])
         try:
             entry_attrs.setdefault('accesstime', []).remove(
                 options['accesstime']
@@ -463,8 +515,6 @@ class hbacrule_remove_accesstime(LDAPQuery):
             ldap.update_entry(entry_attrs)
         except (ValueError, errors.EmptyModlist):
             pass
-        except errors.NotFound:
-            self.obj.handle_not_found(cn)
 
         return dict(result=True)
 
@@ -476,7 +526,87 @@ class hbacrule_remove_accesstime(LDAPQuery):
             )
         )
 
-#api.register(hbacrule_remove_accesstime)
+
+@register()
+class hbacrule_add_exclude_accesstime(LDAPQuery):
+    __doc__ = _('Add exception to access times set at an HBAC rule')
+
+    takes_options = (
+        AccessTime('accesstimeexclude',
+            cli_name='excltime',
+            label=_('Access time exception'),
+        ),
+    )
+
+    def execute(self, cn, **options):
+        ldap = self.obj.backend
+
+        dn = self.obj.get_dn(cn)
+        at = AccessTime('accesstimeexclude')
+        options['accesstimeexclude'] = at.normal_form(options['accesstimeexclude'])
+        try:
+            entry_attrs = ldap.get_entry(dn, ['accesstimeexclude'])
+        except errors.NotFound:
+            self.obj.handle_not_found(cn)
+
+        entry_attrs.setdefault('accesstimeexclude', []).append(
+            options['accesstimeexclude']
+        )
+        try:
+            ldap.update_entry(entry_attrs)
+        except errors.EmptyModlist:
+            pass
+
+        return dict(result=True)
+
+    def output_for_cli(self, textui, result, cn, **options):
+        at = AccessTime('accesstimeexclude')
+        options['accesstimeexclude'] = at.normal_form(options['accesstimeexclude'])
+
+        textui.print_name(self.name)
+        textui.print_dashed(
+            'Added access time exception "%s" to HBAC rule "%s"' % (
+                options['accesstimeexclude'], cn
+            )
+        )
+
+@register()
+class hbacrule_remove_exclude_accesstime(LDAPQuery):
+    __doc__ = _('Remove access time exception from an HBAC rule')
+
+    takes_options = (
+        AccessTime('accesstimeexclude',
+            cli_name='excltime',
+            label=_('Access time exception'),
+        ),
+    )
+
+    def execute(self, cn, **options):
+        ldap = self.obj.backend
+
+        dn = self.obj.get_dn(cn)
+        try:
+            entry_attrs = ldap.get_entry(dn, ['accesstimeexclude'])
+        except errors.NotFound:
+            self.obj.handle_not_found(cn)
+
+        try:
+            entry_attrs.setdefault('accesstimeexclude', []).remove(
+                options['accesstimeexclude']
+            )
+            ldap.update_entry(entry_attrs)
+        except (ValueError, errors.EmptyModlist):
+            pass
+
+        return dict(result=True)
+
+    def output_for_cli(self, textui, result, cn, **options):
+        textui.print_name(self.name)
+        textui.print_dashed(
+            'Removed access time exception "%s" from HBAC rule "%s"' % (
+                options['accesstimeexclude'], cn
+            )
+        )
 
 
 @register()
@@ -593,4 +723,3 @@ class hbacrule_remove_service(LDAPRemoveMember):
 
     member_attributes = ['memberservice']
     member_count_out = ('%i object removed.', '%i objects removed.')
-
-- 
2.4.3

From 66dc1fef59ed1935ceb659aff71a1581ff8d6159 Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Fri, 10 Jul 2015 14:23:03 +0200
Subject: [PATCH 4/4] 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..e670705d8680f64dbd94295e213f53e0bf982b0b 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: 'timezone',
+            widget: 'time_policies.timezone'
+        },
+        {
+            $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: 'timezone',
+                        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_exclude_accesstime',
+                        remove_command: 'remove_exclude_accesstime',
+                        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