Hello,

So in the previous month and a bit I was reworking the time-based policies according to the changes we agreed on (http://pad.engineering.redhat.com/ipa-time-based-HBAC-design, line 83). Let me briefly walk you through what was done (no TLDR, sorry, but split the text in chapters):

*Time rule templates*
In the attachment is the proposal how this could be done using costemplates. Currently, the time rule templates have their own directory in the realm tree. The idea is that it could be used for both HBAC and Sudo rules so it needs to be in a location both should be able to reach. Should we not want them used in Sudo rules, the template directory could be moved to HBAC directory. There are also some new permissions for accessing these time rule templates which may need to be revised if the templates should be used both for sudo and HBAC rules.

*iCalendar format validation
*So there is an iCalendar string validation now. During its creation, I came across several issues with python-icalendar which is basically why it took me so long to write the validation. I made several fixes to the python-icalendar library, most of them are already merged in the repository master (https://github.com/collective/icalendar), one should be pushed in the next library major release.

My pull requests:
https://github.com/collective/icalendar/pull/175
https://github.com/collective/icalendar/pull/179
https://github.com/collective/icalendar/pull/180
https://github.com/collective/icalendar/pull/183
https://github.com/collective/icalendar/pull/189

I still have one fix in the making, that one should force the strong types in iCalendar as these are also missing in python-icalendar but required by the RFC.

Also, obviously, if you want to try the patches, you will need the current python-icalendar implementation from Github. I haven't put python-icalendar dependency into the .spec file yet for this reason.
*
**Summary
*We are now able to import iCalendar strings from files and more or less be sure that the parts we need will be consistent with the RFC 5545 (basically, we are only checking that VEVENT components are correct, to bring strict checking to python-icalendar would take some time and I believe I spent way too much time with it already (there is an issue on their github page, though, it's 4 years old)).

*TODO now
*0)**Update the design*
*1a) The hbacrule-*-accesstime should probably be split into 2 commands, one that reads iCalendar strings from files, and one that creates those strings from "some kind of user input" (similarly for timeruletemplates). 1b) Create the format of user input we could expect for the second kind of command from 1a). We need to be able to convert it to iCalendar string and back so that we are able to present the data stored on the server in human readable form. http://jkbrzt.github.io/rrule/ NL part might be of help although it aims mostly on RRULE property of VEVENT components, whereas we may want to use DTEND, EXDATE, RDATE and DURATION as well to be able to specify events more properly. 2) Represent the HBAC time rules on SSSD side. I already have a skeleton of this based on libical (https://github.com/libical/libical), which hopefully seems to be more viable than python-icalendar. I do not mean to do the validation of received iCalendar string on the SSSD side anymore (at least not in an excessive way), just get the required properties from VEVENT components and evaluate them accordingly.

*Discuss
*I would really appreciate your input on these topics:*
*1)**How to represent the iCalendar strings on the client side in CLI (while thinking about WebUI as well)?
2a) Do we want to use the time rules for Sudo rules as well?
2b) If 2a), is the proposed location of time rule templates along with the privileges ok?

Standa
**
From 68ca0b4606f16a4906a991da32f689f840233231 Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Fri, 19 Feb 2016 08:35:31 +0100
Subject: [PATCH 1/2] HBAC Access Time Rules: icalendar format validation

https://fedorahosted.org/freeipa/ticket/547
---
 API.txt                    |  31 +++++-
 ipalib/plugins/hbacrule.py | 231 ++++++++++++++++++++++++++++-----------------
 2 files changed, 173 insertions(+), 89 deletions(-)

diff --git a/API.txt b/API.txt
index e2976e0e2897355bdb7ead438d4b67524f2fb1e8..f54f00036d277094eb3568805169ae337d10561e 100644
--- a/API.txt
+++ b/API.txt
@@ -1656,9 +1656,10 @@ 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: File('accesstime', attribute=True, cli_name='time', multivalue=True, 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)
@@ -1677,6 +1678,17 @@ 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: File('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_host
 args: 1,6,3
 arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
@@ -1748,9 +1760,10 @@ 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,19,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: File('accesstime', attribute=True, autofill=False, cli_name='time', multivalue=True, 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)
@@ -1773,9 +1786,10 @@ 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: File('accesstime', attribute=True, autofill=False, cli_name='time', multivalue=True, 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')
@@ -1796,6 +1810,17 @@ 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: File('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_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/ipalib/plugins/hbacrule.py b/ipalib/plugins/hbacrule.py
index 54487eded21637bcd9d78179ad51c4abdedfc561..426e1688848008762981d9d149cadd1ff313f504 100644
--- a/ipalib/plugins/hbacrule.py
+++ b/ipalib/plugins/hbacrule.py
@@ -17,8 +17,9 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os
 from ipalib import api, errors
-from ipalib import AccessTime, Str, StrEnum, Bool, DeprecatedParam
+from ipalib import Str, StrEnum, Bool, File, DeprecatedParam
 from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import (
     pkey_to_value,
@@ -31,11 +32,16 @@ from ipalib.plugins.baseldap import (
     LDAPSearch,
     LDAPQuery,
     LDAPAddMember,
-    LDAPRemoveMember)
+    LDAPRemoveMember,
+    LDAPAddAttribute,
+    LDAPRemoveAttribute)
 from ipalib import _, ngettext
 from ipalib import output
 from ipapython.dn import DN
 
+import icalendar
+from datetime import date
+
 __doc__ = _("""
 Host-based access control
 
@@ -101,6 +107,127 @@ register = Registry()
 #   ipa hbacrule-add-accesstime --time='absolute 201012161032 ~ 201012161033' test1
 
 
+def validate_ical_component(comp, name):
+    if comp.errors:
+        ical_errors = ('{comp}: {err}'
+                       .format(comp=x, err=y) for x, y in comp.errors)
+        raise errors.ValidationError(
+            name=name,
+            error=_('There were errors parsing the iCalendar string:\n{errs}'
+                    .format(errs='\n'.join(ical_errors)))
+            )
+
+    for prop in comp.required:
+        if prop not in comp.keys():
+            raise errors.ValidationError(
+                name=name,
+                error=_('A required property "{prop}" not found '
+                        'in "{comp}".'.format(prop=prop, comp=comp.name))
+                )
+
+    for prop in comp.keys():
+        # TODO: comp.required might be removed when
+        # https://github.com/collective/icalendar/pull/183 is merged
+        if prop not in (comp.singletons + comp.multiple + comp.required):
+            raise errors.ValidationError(
+                name=name,
+                error=_('A "{comp}" component can\'t contain '
+                        'property "{prop}".'
+                        .format(comp=comp.name, prop=prop))
+                )
+
+        if (prop in comp.singletons and isinstance(comp[prop], list)
+                and len(comp[prop]) > 1):
+            raise errors.ValidationError(
+                name=name,
+                errors=_('A "{comp}" component can\'t have more than '
+                         'one "{prop}" property."'
+                         .format(comp=comp.name, prop=prop))
+                )
+
+
+def validate_icalfile(ugettext, ics):
+    name = 'accesstime'
+
+    if api.env.context == 'cli':
+        if ics and os.path.exists(ics):
+            return
+
+    icalstr = ics
+    try:
+        vcal = icalendar.cal.Calendar().from_ical(icalstr)
+    except ValueError as e:
+        raise errors.ValidationError(
+            name=name,
+            error=_('Couln\'t parse iCalendar string: {err}'
+                    .format(err=e))
+            )
+
+    if(vcal.name != 'VCALENDAR'):
+        raise errors.ValidationError(
+            name=name,
+            error=_('Received object is not a VCALENDAR')
+            )
+
+    validate_ical_component(vcal, name)
+
+    # get a list of all components of a VCALENDAR
+    for comp in vcal.subcomponents:
+        if comp.name != 'VEVENT':
+            print('INFO: Found "{comp}" but only VEVENT component is supported.'
+                  .format(comp=comp.name))
+            continue
+
+        validate_ical_component(comp, name)
+        for sub in comp.subcomponents:
+            if sub.name != 'VALARM':
+                raise errors.ValidationError(
+                    name=name,
+                    error=_('A VEVENT component can\'t contain '
+                            'subcomponent "{}".'.format(sub.name))
+                    )
+            else:
+                print('INFO: Found "{comp}" but only VEVENT component '
+                      'is supported.'
+                      .format(comp=sub.name))
+
+        # we WILL require DTSTART for VEVENTs
+        if 'DTSTART' not in comp.keys():
+            raise errors.ValidationError(
+                name=name,
+                error=_('DTSTART property is required in VEVENT.')
+                )
+
+        if 'DTEND' in comp.keys() and 'DURATION' in comp.keys():
+            raise errors.ValidationError(
+                name=name,
+                error=_('Both DURATION and DTEND set in a VEVENT.')
+            )
+
+        elif 'DTEND' in comp.keys():
+            if type(comp['DTSTART'].dt) != type(comp['DTEND'].dt):
+                raise errors.ValidationError(
+                    name=name,
+                    error=_('Different types of DTSTART and DTEND '
+                            'component in VEVENT.')
+                    )
+
+        elif 'DURATION' in comp.keys() and isinstance(comp['DTSTART'].dt, date):
+            """
+            python-icalendar represents DURATION as datetime.timedelta. This,
+            in some cases, blocks us from checking whether it was originally
+            set correctly.
+
+            Example: If DTSTART has value of type DATE, DURATION should be set
+            only as dur-day or dur-week. However, DURATION:PT24H will evaluate
+            as timedelta(1)
+            """
+            if comp['DURATION'].dt.seconds:
+                raise errors.ValidationError(
+                    name=name,
+                    error=_('DURATION is not of type dur-day or dur-week '
+                            'when DTSTART value type is DATE.')
+                    )
 topic = ('hbac', _('Host-based access control commands'))
 
 def validate_type(ugettext, type):
@@ -237,10 +364,10 @@ class hbacrule(LDAPObject):
             doc=_('Service category the rule applies to'),
             values=(u'all', ),
         ),
-#        AccessTime('accesstime?',
-#            cli_name='time',
-#            label=_('Access time'),
-#        ),
+        File('accesstime*', validate_icalfile,
+             cli_name='time',
+             label=_('Access time'),
+        ),
         Str('description?',
             cli_name='desc',
             label=_('Description'),
@@ -410,86 +537,18 @@ class hbacrule_disable(LDAPQuery):
         )
 
 
-
-class hbacrule_add_accesstime(LDAPQuery):
-    """
-    Add an access time to an 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'])
-        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_add_accesstime(LDAPAddAttribute):
+    __doc__ = _('Add an access time to an HBAC rule.')
+    msg_summary = _('Added allowed access times to the rule "%(value)s"')
+    attribute = 'accesstime'
+
+
+@register()
+class hbacrule_remove_accesstime(LDAPRemoveAttribute):
+    __doc__ = _('Remove access times from an HBAC Rule')
+    msg_summary = _('Removed access times from the rule "%(value)s"')
+    attribute = 'accesstime'
 
 
 @register()
-- 
2.5.0

From 7c79d2301bfc734f610002b7fcf0f87d33ebae8a Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Fri, 19 Feb 2016 08:40:12 +0100
Subject: [PATCH 2/2] Templating of access time rules for HBAC

https://fedorahosted.org/freeipa/ticket/547
---
 ACI.txt                               |   8 +++
 API.txt                               |  80 ++++++++++++++++++++++++-
 install/share/60basev2.ldif           |   3 +-
 install/share/bootstrap-template.ldif |  16 +++++
 ipalib/plugins/hbacrule.py            | 109 ++++++++++++++++++++++++++++++++++
 5 files changed, 212 insertions(+), 4 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 24cb332ce6e10c82a5bfab76d084fb6c0277800d..892aa182b6b0e1e40dfe1c432937042da7350b8c 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -298,6 +298,14 @@ dn: cn=sudorules,cn=sudo,dc=ipa,dc=example
 aci: (targetattr = "cmdcategory || cn || createtimestamp || description || entryusn || externalhost || externaluser || hostcategory || hostmask || ipaenabledflag || ipasudoopt || ipasudorunas || ipasudorunasextgroup || ipasudorunasextuser || ipasudorunasextusergroup || ipasudorunasgroup || ipasudorunasgroupcategory || ipasudorunasusercategory || ipauniqueid || member || memberallowcmd || memberdenycmd || memberhost || memberuser || modifytimestamp || objectclass || sudonotafter || sudonotbefore || sudoorder || usercategory")(targetfilter = "(objectclass=ipasudorule)")(version 3.0;acl "permission:System: Read Sudo Rules";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || description || entryusn || modifytimestamp || objectclass || ou || sudocommand || sudohost || sudonotafter || sudonotbefore || sudooption || sudoorder || sudorunas || sudorunasgroup || sudorunasuser || sudouser")(target = "ldap:///ou=sudoers,dc=ipa,dc=example";)(version 3.0;acl "permission:System: Read Sudoers compat tree";allow (compare,read,search) userdn = "ldap:///anyone";;)
+dn: cn=timeruletemplates,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=costemplate)")(version 3.0;acl "permission:System: Add Time Rule Template";allow (add) groupdn = "ldap:///cn=System: Add Time Rule Template,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=timeruletemplates,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=costemplate)")(version 3.0;acl "permission:System: Delete Time Rule Template";allow (delete) groupdn = "ldap:///cn=System: Delete Time Rule Template,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=timeruletemplates,dc=ipa,dc=example
+aci: (targetattr = "accesstime")(targetfilter = "(objectclass=costemplate)")(version 3.0;acl "permission:System: Modify Time Rule Template";allow (write) groupdn = "ldap:///cn=System: Modify Time Rule Template,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=timeruletemplates,dc=ipa,dc=example
+aci: (targetattr = "accesstime || cn || createtimestamp || entryusn || modifytimestamp || objectclass")(targetfilter = "(objectclass=costemplate)")(version 3.0;acl "permission:System: Read Time Rule Template";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Time Rule Template,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=trusts,dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || entryusn || ipantflatname || ipantsecurityidentifier || ipantsidblacklistincoming || ipantsidblacklistoutgoing || ipanttrustdirection || ipanttrusteddomainsid || ipanttrustpartner || modifytimestamp || objectclass")(version 3.0;acl "permission:System: Read Trust Information";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=trusts,dc=ipa,dc=example
diff --git a/API.txt b/API.txt
index f54f00036d277094eb3568805169ae337d10561e..46faec71f4331ba7de5048d99ddf180da0a9e429 100644
--- a/API.txt
+++ b/API.txt
@@ -1656,7 +1656,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,17,3
+args: 1,18,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: File('accesstime', attribute=True, cli_name='time', multivalue=True, required=False)
@@ -1673,6 +1673,7 @@ 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: Str('timeruleclass', attribute=True, cli_name='class', multivalue=True, 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))
@@ -1689,6 +1690,17 @@ 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_template
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+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('timeruleclass', alwaysask=True, attribute=True, cli_name='class', multivalue=True, required=False)
+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)
@@ -1760,7 +1772,7 @@ output: Output('result', <type 'bool'>, None)
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: hbacrule_find
-args: 1,19,4
+args: 1,20,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: File('accesstime', attribute=True, autofill=False, cli_name='time', multivalue=True, query=True, required=False)
@@ -1779,6 +1791,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: Str('timeruleclass', attribute=True, autofill=False, cli_name='class', multivalue=True, 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)
@@ -1786,7 +1799,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,19,3
+args: 1,20,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: File('accesstime', attribute=True, autofill=False, cli_name='time', multivalue=True, required=False)
@@ -1805,6 +1818,7 @@ 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: Str('timeruleclass', attribute=True, autofill=False, cli_name='class', multivalue=True, 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))
@@ -1821,6 +1835,17 @@ 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_template
+args: 1,5,3
+arg: Str('cn', attribute=True, cli_name='name', multivalue=False, primary_key=True, query=True, required=True)
+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('timeruleclass', alwaysask=True, attribute=True, cli_name='class', multivalue=True, required=False)
+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)
@@ -4859,6 +4884,55 @@ 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: timeruletemplate_add
+args: 1,6,3
+arg: Str('cn', attribute=True, cli_name='cn', multivalue=False, primary_key=True, required=True)
+option: File('accesstime', attribute=True, cli_name='time', multivalue=False, required=True)
+option: Str('addattr*', cli_name='addattr', exclude='webui')
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Str('setattr*', cli_name='setattr', 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: timeruletemplate_del
+args: 1,2,3
+arg: Str('cn', attribute=True, cli_name='cn', multivalue=True, primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('value', None, None)
+command: timeruletemplate_find
+args: 1,8,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: File('accesstime', attribute=True, autofill=False, cli_name='time', 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='cn', multivalue=False, primary_key=True, query=True, required=False)
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Int('sizelimit?', autofill=False, minvalue=0)
+option: Int('timelimit?', autofill=False, minvalue=0)
+option: Str('version?', exclude='webui')
+output: Output('count', <type 'int'>, None)
+output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: Output('truncated', <type 'bool'>, None)
+command: timeruletemplate_mod
+args: 1,8,3
+arg: Str('cn', attribute=True, cli_name='cn', multivalue=False, primary_key=True, query=True, required=True)
+option: File('accesstime', attribute=True, autofill=False, cli_name='time', 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')
+option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr', 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: topologysegment_add
 args: 2,13,3
 arg: Str('topologysuffixcn', cli_name='topologysuffix', multivalue=False, primary_key=True, query=True, required=True)
diff --git a/install/share/60basev2.ldif b/install/share/60basev2.ldif
index 00712ddda2c548b7f7924a012f3f68499f2f01da..a0a47c0167e978ccfdb14085276fe7a6f6e56cf4 100644
--- a/install/share/60basev2.ldif
+++ b/install/share/60basev2.ldif
@@ -37,7 +37,8 @@ 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 'timeruleClass' DESC 'CNs of the timerule classes' EQUALITY caseIgnoreMatch ORDERING caseIgnoreOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v4.3' )
+objectClasses: (2.16.840.1.113730.3.8.4.7 NAME 'ipaHBACRule' SUP ipaAssociation STRUCTURAL MUST accessRuleType MAY ( sourceHost $ sourceHostCategory $ serviceCategory $ memberService $ externalHost $ timeruleClass $ accessTime ) 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' )
diff --git a/install/share/bootstrap-template.ldif b/install/share/bootstrap-template.ldif
index 628a8e2e0f5483b9f6f565b0c7d11eb000a5912d..ede48184591f41179c772e0dbfc3e94d5d3eba10 100644
--- a/install/share/bootstrap-template.ldif
+++ b/install/share/bootstrap-template.ldif
@@ -272,6 +272,22 @@ description: IPA server hosts
 cn: ipaservers
 ipaUniqueID: autogenerate
 
+dn: cn=timeruleTemplates,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: nsContainer
+cn: timeruleTemplates
+
+dn: cn=cosTimerulesDef,cn=hbac,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: ldapsubentry
+objectClass: cosSuperDefinition
+objectClass: cosClassicDefinition
+cosTemplateDn: cn=timeruleTemplates,$SUFFIX
+cosAttribute: accessTime default merge-schemes
+cosSpecifier: timeruleClass
+
 dn: cn=sshd,cn=hbacservices,cn=hbac,$SUFFIX
 changetype: add
 objectclass: ipahbacservice
diff --git a/ipalib/plugins/hbacrule.py b/ipalib/plugins/hbacrule.py
index 426e1688848008762981d9d149cadd1ff313f504..6dad5dfe3d99abfc5c09d9066df40406834f42a3 100644
--- a/ipalib/plugins/hbacrule.py
+++ b/ipalib/plugins/hbacrule.py
@@ -228,6 +228,90 @@ def validate_icalfile(ugettext, ics):
                     error=_('DURATION is not of type dur-day or dur-week '
                             'when DTSTART value type is DATE.')
                     )
+
+
+@register()
+class timeruletemplate(LDAPObject):
+    """
+    Timerule templates
+    """
+    container_dn = DN(('cn', 'timeruletemplates'))
+    object_name = _('Time rule template')
+    object_name_plural = _('Time rule templates')
+    object_class = ['top', 'costemplate', 'extensibleobject']
+    permission_filter_objectclasses = ['costemplate']
+    default_attributes = ['cn', 'accesstime']
+    managed_permissions = {
+        'System: Read Time Rule Template': {
+            'replaces_global_anonymous_aci': True,
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'cn', 'objectclass', 'accesstime'
+            },
+        },
+        'System: Add Time Rule Template': {
+            'ipapermright': {'add'},
+            'replaces': [
+                '(target = "ldap:///cn=*,cn=timeruletemplates,$SUFFIX";)(version 3.0;acl "permission:Add Time Rule Template";allow (add) groupdn = "ldap:///cn=Add Time Rule Template,cn=permissions,cn=pbac,$SUFFIX";)',
+            ],
+            'default_privileges': {'HBAC Administrator'},
+        },
+        'System: Delete Time Rule Template': {
+            'ipapermright': {'delete'},
+            'replaces': [
+                '(target = "ldap:///cn=*,cn=timeruletemplates,$SUFFIX";)(version 3.0;acl "permission:Delete Time Rule Template";allow (delete) groupdn = "ldap:///cn=Delete Time Rule Template,cn=permissions,cn=pbac,$SUFFIX";)',
+            ],
+            'default_privileges': {'HBAC Administrator'},
+        },
+        'System: Modify Time Rule Template': {
+            'ipapermright': {'write'},
+            'ipapermdefaultattr': {'accesstime'},
+            'replaces': [
+                '(targetattr = "accesstime")(target = "ldap:///cn=*,cn=timeruletemplates,$SUFFIX";)(version 3.0;acl "permission:Modify Time Rule Template";allow (write) groupdn = "ldap:///cn=Modify Time Rule Template,cn=permissions,cn=pbac,$SUFFIX";)',
+            ],
+            'default_privileges': {'HBAC Administrator'},
+        },
+    }
+
+    takes_params = (
+        Str('cn', primary_key=True),
+        File('accesstime', validate_icalfile,
+             cli_name='time',
+             label=_('Access time'),
+             ),
+    )
+
+
+@register()
+class timeruletemplate_add(LDAPCreate):
+    __doc__ = _('Create new timerule template.')
+
+
+@register()
+class timeruletemplate_del(LDAPDelete):
+    __doc__ = _('Delete a timerule template.')
+
+    msg_summary = _('Deleted template "%(value)s"')
+
+
+@register()
+class timeruletemplate_mod(LDAPUpdate):
+    __doc__ = _('Modify a timerule template.')
+
+    msg_summary = _('Modified a template "%(value)s"')
+
+
+@register()
+class timeruletemplate_find(LDAPSearch):
+    __doc__ = _('Search for timerule templates.')
+
+    msg_summary = ngettext(
+        '%(count)d timerule template matched',
+        '%(count)d timerule templates matched',
+        0,
+    )
+
+
 topic = ('hbac', _('Host-based access control commands'))
 
 def validate_type(ugettext, type):
@@ -368,6 +452,8 @@ class hbacrule(LDAPObject):
              cli_name='time',
              label=_('Access time'),
         ),
+        Str('timeruleclass*',
+                cli_name='class'),
         Str('description?',
             cli_name='desc',
             label=_('Description'),
@@ -552,6 +638,29 @@ class hbacrule_remove_accesstime(LDAPRemoveAttribute):
 
 
 @register()
+class hbacrule_add_accesstime_template(LDAPAddAttribute):
+    __doc__ = _('Add access time from a timerule template to an HBAC rule')
+    msg_summary = _('Added template to the rule "%(value)s"')
+    attribute = 'timeruleclass'
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        try:
+            result = ldap.get_entry(DN(('cn', options['timeruleclass'][0]),
+                                       ('cn', 'timeruletemplates'),
+                                       api.env.basedn))
+        except errors.NotFound:
+            raise errors.NotFound('cn={}'.format(options['timeruleclass']))
+
+        return dn
+
+@register()
+class hbacrule_remove_accesstime_template(LDAPRemoveAttribute):
+    __doc__ = _('Remove access time template from an HBAC rule')
+    msg_summary = _('Removed template from the rule "%(value)s"')
+    attribute = 'timeruleclass'
+
+@register()
 class hbacrule_add_user(LDAPAddMember):
     __doc__ = _('Add users and groups to an HBAC rule.')
 
-- 
2.5.0

-- 
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