stlaz's pull request #23: "Time-Based HBAC Policies" was synchronize

See the full pull-request at https://github.com/freeipa/freeipa/pull/23
... or pull the PR as Git branch:
git remote add ghfreeipa https://github.com/freeipa/freeipa
git fetch ghfreeipa pull/23/head:pr23
git checkout pr23
From 5098b2f62790d65ce2abc084465cedc6de790b00 Mon Sep 17 00:00:00 2001
From: Stanislav Laznicka <slazn...@redhat.com>
Date: Fri, 19 Feb 2016 08:35:31 +0100
Subject: [PATCH] Added Time Rules functionality to IPA

Time Rules are rules based on the iCalendar format. They are now
currently used for restriction of access in the HBAC Rules.
To learn more about the time rules, see their design at
http://www.freeipa.org/page/V4/Time-Based_Account_Policies.

https://fedorahosted.org/freeipa/ticket/547
---
 ACI.txt                               |  20 ++-
 API.txt                               | 100 +++++++++++-
 VERSION                               |   4 +-
 freeipa.spec.in                       |   2 +
 install/share/60basev2.ldif           |   3 +
 install/share/bootstrap-template.ldif |   6 +
 install/share/indices.ldif            |  10 ++
 install/share/memberof-conf.ldif      |   4 +-
 install/updates/20-indices.update     |   9 ++
 install/updates/40-delegation.update  |   8 +
 install/updates/45-roles.update       |   3 +
 install/updates/45-timerules.update   |   5 +
 install/updates/Makefile.am           |   1 +
 ipaclient/plugins/timerule.py         |  59 +++++++
 ipalib/constants.py                   |   1 +
 ipaserver/plugins/hbacrule.py         | 177 +++++++++++---------
 ipaserver/plugins/timerule.py         | 295 ++++++++++++++++++++++++++++++++++
 17 files changed, 620 insertions(+), 87 deletions(-)
 create mode 100644 install/updates/45-timerules.update
 create mode 100644 ipaclient/plugins/timerule.py
 create mode 100644 ipaserver/plugins/timerule.py

diff --git a/ACI.txt b/ACI.txt
index fddd598..95f4643 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -97,15 +97,15 @@ aci: (targetattr = "businesscategory || cn || createtimestamp || description ||
 dn: cn=groups,cn=accounts,dc=ipa,dc=example
 aci: (targetfilter = "(|(objectclass=ipausergroup)(objectclass=posixgroup))")(version 3.0;acl "permission:System: Remove Groups";allow (delete) groupdn = "ldap:///cn=System: Remove Groups,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hbac,dc=ipa,dc=example
-aci: (targetfilter = "(objectclass=ipahbacrule)")(version 3.0;acl "permission:System: Add HBAC Rule";allow (add) groupdn = "ldap:///cn=System: Add HBAC Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+aci: (targetfilter = "(|(objectclass=ipahbacrule)(objectclass=ipahbacrulev2))")(version 3.0;acl "permission:System: Add HBAC Rule";allow (add) groupdn = "ldap:///cn=System: Add HBAC Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=hbac,dc=ipa,dc=example
-aci: (targetfilter = "(objectclass=ipahbacrule)")(version 3.0;acl "permission:System: Delete HBAC Rule";allow (delete) groupdn = "ldap:///cn=System: Delete HBAC Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+aci: (targetfilter = "(|(objectclass=ipahbacrule)(objectclass=ipahbacrulev2))")(version 3.0;acl "permission:System: Delete HBAC Rule";allow (delete) groupdn = "ldap:///cn=System: Delete HBAC Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 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";)
+aci: (targetattr = "externalhost || ipamembertimerule || memberhost || memberservice || memberuser")(targetfilter = "(|(objectclass=ipahbacrule)(objectclass=ipahbacrulev2))")(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 || cn || description || hostcategory || ipaenabledflag || ipamembertimerule || servicecategory || sourcehost || sourcehostcategory || usercategory")(targetfilter = "(|(objectclass=ipahbacrule)(objectclass=ipahbacrulev2))")(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 || cn || createtimestamp || description || entryusn || externalhost || hostcategory || ipaenabledflag || ipamembertimerule || ipauniqueid || member || memberhost || memberservice || memberuser || modifytimestamp || objectclass || servicecategory || sourcehost || sourcehostcategory || usercategory")(targetfilter = "(|(objectclass=ipahbacrule)(objectclass=ipahbacrulev2))")(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
@@ -326,6 +326,16 @@ 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=timerules,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=ipatimerule)")(version 3.0;acl "permission:System: Add Time Rule";allow (add) groupdn = "ldap:///cn=System: Add Time Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=timerules,dc=ipa,dc=example
+aci: (targetfilter = "(objectclass=ipatimerule)")(version 3.0;acl "permission:System: Delete Time Rule";allow (delete) groupdn = "ldap:///cn=System: Delete Time Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=timerules,dc=ipa,dc=example
+aci: (targetattr = "accesstime || description")(targetfilter = "(objectclass=ipatimerule)")(version 3.0;acl "permission:System: Modify Time Rule";allow (write) groupdn = "ldap:///cn=System: Modify Time Rule,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: cn=timerules,dc=ipa,dc=example
+aci: (targetattr = "accesstime || cn || createtimestamp || description || entryusn || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipatimerule)")(version 3.0;acl "permission:System: Read Time Rule";allow (compare,read,search) userdn = "ldap:///all";;)
+dn: cn=timerules,dc=ipa,dc=example
+aci: (targetattr = "memberof")(targetfilter = "(objectclass=ipatimerule)")(version 3.0;acl "permission:System: Read Time Rule Membership";allow (compare,read,search) userdn = "ldap:///all";;)
 dn: cn=trusts,dc=ipa,dc=example
 aci: (targetattr = "cn || createtimestamp || entryusn || ipantadditionalsuffixes || 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 5b83bfb..fa66e00 100644
--- a/API.txt
+++ b/API.txt
@@ -1902,7 +1902,7 @@ output: PrimaryKey('value')
 command: hbacrule_add/1
 args: 1,14,3
 arg: Str('cn', cli_name='name')
-option: StrEnum('accessruletype', autofill=True, cli_name='type', default=u'allow', values=[u'allow', u'deny'])
+option: StrEnum('accessruletype?', autofill=True, cli_name='type', default=u'allow', values=[u'allow', u'deny'])
 option: Str('addattr*', cli_name='addattr')
 option: Flag('all', autofill=True, cli_name='all', default=False)
 option: Str('description?', cli_name='desc')
@@ -1955,6 +1955,17 @@ option: Str('version?')
 output: Output('completed', type=[<type 'int'>])
 output: Output('failed', type=[<type 'dict'>])
 output: Entry('result')
+command: hbacrule_add_timerule/1
+args: 1,5,3
+arg: Str('cn', cli_name='name')
+option: Flag('all', autofill=True, cli_name='all', default=False)
+option: Flag('no_members', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Str('timerule*', alwaysask=True, cli_name='timerules')
+option: Str('version?')
+output: Output('completed', type=[<type 'int'>])
+output: Output('failed', type=[<type 'dict'>])
+output: Entry('result')
 command: hbacrule_add_user/1
 args: 1,6,3
 arg: Str('cn', cli_name='name')
@@ -2070,6 +2081,17 @@ option: Str('version?')
 output: Output('completed', type=[<type 'int'>])
 output: Output('failed', type=[<type 'dict'>])
 output: Entry('result')
+command: hbacrule_remove_timerule/1
+args: 1,5,3
+arg: Str('cn', cli_name='name')
+option: Flag('all', autofill=True, cli_name='all', default=False)
+option: Flag('no_members', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Str('timerule*', alwaysask=True, cli_name='timerules')
+option: Str('version?')
+output: Output('completed', type=[<type 'int'>])
+output: Output('failed', type=[<type 'dict'>])
+output: Entry('result')
 command: hbacrule_remove_user/1
 args: 1,6,3
 arg: Str('cn', cli_name='name')
@@ -5297,6 +5319,74 @@ option: Str('version?')
 output: Entry('result')
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: PrimaryKey('value')
+command: timerule_add/1
+args: 1,8,3
+arg: Str('cn', cli_name='name')
+option: Str('accesstime?', cli_name='time')
+option: Str('addattr*', cli_name='addattr')
+option: Flag('all', autofill=True, cli_name='all', default=False)
+option: Str('description?')
+option: Flag('no_members', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Str('setattr*', cli_name='setattr')
+option: Str('version?')
+output: Entry('result')
+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
+output: PrimaryKey('value')
+command: timerule_del/1
+args: 1,2,3
+arg: Str('cn+', cli_name='name')
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?')
+output: Output('result', type=[<type 'dict'>])
+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
+output: ListOfPrimaryKeys('value')
+command: timerule_find/1
+args: 1,12,4
+arg: Str('criteria?')
+option: Str('accesstime?', autofill=False, cli_name='time')
+option: Flag('all', autofill=True, cli_name='all', default=False)
+option: Str('cn?', autofill=False, cli_name='name')
+option: Str('description?', autofill=False)
+option: Str('in_hbacrule*', cli_name='in_hbacrules')
+option: Flag('no_members', autofill=True, default=True)
+option: Str('not_in_hbacrule*', cli_name='not_in_hbacrules')
+option: Flag('pkey_only?', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Int('sizelimit?', autofill=False)
+option: Int('timelimit?', autofill=False)
+option: Str('version?')
+output: Output('count', type=[<type 'int'>])
+output: ListOfEntries('result')
+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
+output: Output('truncated', type=[<type 'bool'>])
+command: timerule_mod/1
+args: 1,10,3
+arg: Str('cn', cli_name='name')
+option: Str('accesstime?', autofill=False, cli_name='time')
+option: Str('addattr*', cli_name='addattr')
+option: Flag('all', autofill=True, cli_name='all', default=False)
+option: Str('delattr*', cli_name='delattr')
+option: Str('description?', autofill=False)
+option: Flag('no_members', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Flag('rights', autofill=True, default=False)
+option: Str('setattr*', cli_name='setattr')
+option: Str('version?')
+output: Entry('result')
+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
+output: PrimaryKey('value')
+command: timerule_show/1
+args: 1,5,3
+arg: Str('cn', cli_name='name')
+option: Flag('all', autofill=True, cli_name='all', default=False)
+option: Flag('no_members', autofill=True, default=False)
+option: Flag('raw', autofill=True, cli_name='raw', default=False)
+option: Flag('rights', autofill=True, default=False)
+option: Str('version?')
+output: Entry('result')
+output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
+output: PrimaryKey('value')
 command: topic_find/1
 args: 1,4,4
 arg: Str('criteria?')
@@ -6366,6 +6456,7 @@ default: hbacrule_add/1
 default: hbacrule_add_host/1
 default: hbacrule_add_service/1
 default: hbacrule_add_sourcehost/1
+default: hbacrule_add_timerule/1
 default: hbacrule_add_user/1
 default: hbacrule_del/1
 default: hbacrule_disable/1
@@ -6375,6 +6466,7 @@ default: hbacrule_mod/1
 default: hbacrule_remove_host/1
 default: hbacrule_remove_service/1
 default: hbacrule_remove_sourcehost/1
+default: hbacrule_remove_timerule/1
 default: hbacrule_remove_user/1
 default: hbacrule_show/1
 default: hbacsvc/1
@@ -6644,6 +6736,12 @@ default: sudorule_remove_runasgroup/1
 default: sudorule_remove_runasuser/1
 default: sudorule_remove_user/1
 default: sudorule_show/1
+default: timerule/1
+default: timerule_add/1
+default: timerule_del/1
+default: timerule_find/1
+default: timerule_mod/1
+default: timerule_show/1
 default: topic/1
 default: topic_find/1
 default: topic_show/1
diff --git a/VERSION b/VERSION
index a8b89ed..5559e7e 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=212
-# Last change: ab: service: add flag to allow S4U2Self
+IPA_API_VERSION_MINOR=213
+# Last change: slaznick: added commands for handling time rules
diff --git a/freeipa.spec.in b/freeipa.spec.in
index e3ad5b6..38172eb 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -109,6 +109,7 @@ BuildRequires:  dbus-python
 BuildRequires:  python-netifaces >= 0.10.4
 BuildRequires:  python-libsss_nss_idmap
 BuildRequires:  python-sss
+BuildRequires:  python-icalendar >= 3.10
 
 # Build dependencies for unit tests
 BuildRequires:  libcmocka-devel
@@ -344,6 +345,7 @@ Requires: libsss_autofs
 Requires: autofs
 Requires: libnfsidmap
 Requires: nfs-utils
+Requires: python-icalendar >= 3.10
 Requires(post): policycoreutils
 
 Provides: %{alt_name}-client = %{version}
diff --git a/install/share/60basev2.ldif b/install/share/60basev2.ldif
index 00712dd..322f248 100644
--- a/install/share/60basev2.ldif
+++ b/install/share/60basev2.ldif
@@ -38,6 +38,9 @@ attributeTypes: (2.16.840.1.113730.3.8.3.12 NAME 'sourceHostCategory' DESC 'Addi
 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' )
+objectClasses: (2.16.840.1.113730.3.8.12.37 NAME 'ipaTimeRule' SUP top STRUCTURAL MUST ( cn $ accessTime ) MAY ( memberOf $ description ) X-ORIGIN 'IPA v4.4')
+attributeTypes: (2.16.840.1.113730.3.8.11.76 NAME 'ipaMemberTimeRule' DESC 'Reference to a time rule describing some period of time' SUP distinguishedName EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v4.4' )
+objectClasses: (2.16.840.1.113730.3.8.12.38 NAME 'ipaHBACRuleV2' SUP ipaAssociation STRUCTURAL MAY ( serviceCategory $ memberService $ externalHost $ ipaMemberTimeRule ) X-ORIGIN 'IPA v4.4' )
 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 da12ddf..08ef422 100644
--- a/install/share/bootstrap-template.ldif
+++ b/install/share/bootstrap-template.ldif
@@ -284,6 +284,12 @@ description: IPA server hosts
 cn: ipaservers
 ipaUniqueID: autogenerate
 
+dn: cn=timerules,$SUFFIX
+changetype: add
+objectClass: top
+objectClass: nsContainer
+cn: timerules
+
 dn: cn=sshd,cn=hbacservices,cn=hbac,$SUFFIX
 changetype: add
 objectclass: ipahbacservice
diff --git a/install/share/indices.ldif b/install/share/indices.ldif
index d853266..3dd2f56 100644
--- a/install/share/indices.ldif
+++ b/install/share/indices.ldif
@@ -158,6 +158,16 @@ nsIndexType: eq
 nsIndexType: pres
 nsIndexType: sub
 
+dn: cn=ipaMemberTimeRule,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+changetype: add
+cn: ipaMemberTimeRule
+ObjectClass: top
+ObjectClass: nsIndex
+nsSystemIndex: false
+nsIndexType: eq
+nsIndexType: pres
+nsIndexType: sub
+
 dn: cn=managedby,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
 changetype: add
 cn: managedby
diff --git a/install/share/memberof-conf.ldif b/install/share/memberof-conf.ldif
index 79ad647..7a83b0c 100644
--- a/install/share/memberof-conf.ldif
+++ b/install/share/memberof-conf.ldif
@@ -8,4 +8,6 @@ memberofgroupattr: memberUser
 -
 add: memberofgroupattr
 memberofgroupattr: memberHost
-
+-
+add: memberofgroupattr
+memberofgroupattr: ipaMemberTimeRule
diff --git a/install/updates/20-indices.update b/install/updates/20-indices.update
index 74961d7..049b0fe 100644
--- a/install/updates/20-indices.update
+++ b/install/updates/20-indices.update
@@ -99,6 +99,15 @@ only:nsIndexType: eq
 only:nsIndexType: pres
 only:nsIndexType: sub
 
+dn: cn=ipaMemberTimeRule,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
+default:cn: ipaMemberTimeRule
+default:ObjectClass: top
+default:ObjectClass: nsIndex
+default:nsSystemIndex: false
+only:nsIndexType: eq
+only:nsIndexType: pres
+only:nsIndexType: sub
+
 dn: cn=managedby,cn=index,cn=userRoot,cn=ldbm database,cn=plugins,cn=config
 default:cn: managedby
 default:ObjectClass: top
diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update
index 259cbdb..2ef98f2 100644
--- a/install/updates/40-delegation.update
+++ b/install/updates/40-delegation.update
@@ -34,6 +34,14 @@ default:objectClass: top
 default:cn: Sudo Administrator
 default:description: Sudo Administrator
 
+# Time Rules
+dn: cn=Time Rules Administrator,cn=privileges,cn=pbac,$SUFFIX
+default:objectClass: nestedgroup
+default:objectClass: groupofnames
+default:objectClass: top
+default:cn: Time Rules Administrator
+default:description: Time Rules Administrator
+
 # Password Policy
 dn: cn=Password Policy Administrator,cn=privileges,cn=pbac,$SUFFIX
 default:objectClass: nestedgroup
diff --git a/install/updates/45-roles.update b/install/updates/45-roles.update
index fb28464..f2863f1 100644
--- a/install/updates/45-roles.update
+++ b/install/updates/45-roles.update
@@ -71,6 +71,9 @@ add:member: cn=IT Security Specialist,cn=roles,cn=accounts,$SUFFIX
 dn: cn=Sudo administrator,cn=privileges,cn=pbac,$SUFFIX
 add:member: cn=IT Security Specialist,cn=roles,cn=accounts,$SUFFIX
 
+dn: cn=Time Rules Administrator,cn=privileges,cn=pbac,$SUFFIX
+add:member: cn=IT Security Specialist,cn=roles,cn=accounts,$SUFFIX
+
 dn: cn=Security Architect,cn=roles,cn=accounts,$SUFFIX
 default:objectClass: groupofnames
 default:objectClass: nestedgroup
diff --git a/install/updates/45-timerules.update b/install/updates/45-timerules.update
new file mode 100644
index 0000000..6dade65
--- /dev/null
+++ b/install/updates/45-timerules.update
@@ -0,0 +1,5 @@
+# Add needed LDAP objects
+dn: cn=timerules,$SUFFIX
+default: objectClass: top
+default: objectClass: nsContainer
+default: cn: timerules
diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am
index 455fd20..15ec842 100644
--- a/install/updates/Makefile.am
+++ b/install/updates/Makefile.am
@@ -41,6 +41,7 @@ app_DATA =				\
 	41-caacl.update			\
 	41-lightweight-cas.update	\
 	45-roles.update			\
+	45-timerules.update		\
 	50-7_bit_check.update	        \
 	50-dogtag10-migration.update	\
 	50-groupuuid.update		\
diff --git a/ipaclient/plugins/timerule.py b/ipaclient/plugins/timerule.py
new file mode 100644
index 0000000..989b26f
--- /dev/null
+++ b/ipaclient/plugins/timerule.py
@@ -0,0 +1,59 @@
+#
+# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
+#
+
+import six
+
+from ipaclient.frontend import MethodOverride
+from ipalib import File
+from ipalib import errors
+from ipalib.plugable import Registry
+from ipalib import _
+
+if six.PY3:
+    unicode = str
+
+register = Registry()
+
+
+def get_accesstime(options):
+    if not('icalfile' in options or 'accesstime' in options):
+        raise errors.RequirementError(name='accesstime')
+    if 'icalfile' in options:
+        if 'accesstime' in options:
+            raise errors.OverlapError(names=['time', 'icalfile'])
+        options['accesstime'] = unicode(
+            options['icalfile'].encode('unicode-escape'))
+        del(options['icalfile'])
+    return options['accesstime']
+
+
+@register(override=True, no_fail=True)
+class timerule_add(MethodOverride):
+    takes_options = (
+        File('icalfile?',
+             label=_("iCalendar file"),
+             doc=_("File containing the iCalendar string"),
+             ),
+    )
+
+    def forward(self, *args, **options):
+        options['accesstime'] = get_accesstime(options)
+        return super(timerule_add, self).forward(*args, **options)
+
+
+@register(override=True, no_fail=True)
+class timerule_mod(MethodOverride):
+    takes_options = (
+        File('icalfile?',
+             label=_("iCalendar file"),
+             doc=_("File containing the iCalendar string"),
+             ),
+    )
+
+    def forward(self, *args, **options):
+        try:
+            options['accesstime'] = get_accesstime(options)
+        except errors.RequirementError:
+            pass
+        return super(timerule_mod, self).forward(*args, **options)
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 9b351e2..03ed289 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -125,6 +125,7 @@
     ('container_ca', DN(('cn', 'cas'), ('cn', 'ca'))),
     ('container_dnsservers', DN(('cn', 'servers'), ('cn', 'dns'))),
     ('container_custodia', DN(('cn', 'custodia'), ('cn', 'ipa'), ('cn', 'etc'))),
+    ('container_timerules', DN(('cn', 'timerules'))),
 
     # Ports, hosts, and URIs:
     ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
diff --git a/ipaserver/plugins/hbacrule.py b/ipaserver/plugins/hbacrule.py
index 7d3e485..20e995e 100644
--- a/ipaserver/plugins/hbacrule.py
+++ b/ipaserver/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, Str, StrEnum, Bool
+from ipalib import Str, StrEnum, Bool
 from ipalib.plugable import Registry
 from .baseldap import (
     pkey_to_value,
@@ -86,21 +86,6 @@
 
 register = Registry()
 
-# AccessTime support is being removed for now.
-#
-# You can also control the times that the rule is active.
-#
-# The access time(s) of a host are cumulative and are not guaranteed to be
-# applied in the order displayed.
-#
-# Specify that the rule "test1" be active every day between 0800 and 1400:
-#   ipa hbacrule-add-accesstime --time='periodic daily 0800-1400' test1
-#
-# Specify that the rule "test1" be active once, from 10:32 until 10:33 on
-# December 16, 2010:
-#   ipa hbacrule-add-accesstime --time='absolute 201012161032 ~ 201012161033' test1
-
-
 topic = 'hbac'
 
 def validate_type(ugettext, type):
@@ -122,6 +107,13 @@ def is_all(options, attribute):
         return False
 
 
+def replace_attr_value(attr_vals, replace, replacement):
+    lower_replace = replace.lower()
+    ret = [val for val in attr_vals if val.lower() != lower_replace]
+    ret.append(replacement)
+    return ret
+
+
 @register()
 class hbacrule(LDAPObject):
     """
@@ -130,14 +122,15 @@ class hbacrule(LDAPObject):
     container_dn = api.env.container_hbac
     object_name = _('HBAC rule')
     object_name_plural = _('HBAC rules')
-    object_class = ['ipaassociation', 'ipahbacrule']
-    permission_filter_objectclasses = ['ipahbacrule']
+    object_class = ['ipaassociation']
+    possible_objectclasses = ['ipahbacrule', 'ipahbacrulev2']
+    permission_filter_objectclasses = ['ipahbacrule', 'ipahbacrulev2']
     default_attributes = [
         'cn', 'ipaenabledflag',
         'description', 'usercategory', 'hostcategory',
         'servicecategory', 'ipaenabledflag',
         'memberuser', 'sourcehost', 'memberhost', 'memberservice',
-        'externalhost',
+        'externalhost', 'ipamembertimerule'
     ]
     uuid_attribute = 'ipauniqueid'
     rdn_attribute = 'ipauniqueid'
@@ -146,6 +139,7 @@ class hbacrule(LDAPObject):
         'memberhost': ['host', 'hostgroup'],
         'sourcehost': ['host', 'hostgroup'],
         'memberservice': ['hbacsvc', 'hbacsvcgroup'],
+        'ipamembertimerule': ['timerule'],
     }
     managed_permissions = {
         'System: Read HBAC Rules': {
@@ -157,7 +151,7 @@ class hbacrule(LDAPObject):
                 'externalhost', 'hostcategory', 'ipaenabledflag',
                 'ipauniqueid', 'memberhost', 'memberservice', 'memberuser',
                 'servicecategory', 'sourcehost', 'sourcehostcategory',
-                'usercategory', 'objectclass', 'member',
+                'usercategory', 'objectclass', 'member', 'ipamembertimerule',
             },
         },
         'System: Add HBAC Rule': {
@@ -177,7 +171,8 @@ class hbacrule(LDAPObject):
         'System: Manage HBAC Rule Membership': {
             'ipapermright': {'write'},
             'ipapermdefaultattr': {
-                'externalhost', 'memberhost', 'memberservice', 'memberuser'
+                'externalhost', 'memberhost', 'memberservice', 'memberuser',
+                'ipamembertimerule'
             },
             'replaces': [
                 '(targetattr = "memberuser || externalhost || memberservice || memberhost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX";)(version 3.0;acl "permission:Manage HBAC rule membership";allow (write) groupdn = "ldap:///cn=Manage HBAC rule membership,cn=permissions,cn=pbac,$SUFFIX";)',
@@ -189,7 +184,8 @@ class hbacrule(LDAPObject):
             'ipapermdefaultattr': {
                 'accessruletype', 'accesstime', 'cn', 'description',
                 'hostcategory', 'ipaenabledflag', 'servicecategory',
-                'sourcehost', 'sourcehostcategory', 'usercategory'
+                'sourcehost', 'sourcehostcategory', 'usercategory',
+                'ipamembertimerule'
             },
             '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";)',
@@ -207,14 +203,14 @@ class hbacrule(LDAPObject):
             label=_('Rule name'),
             primary_key=True,
         ),
-        StrEnum('accessruletype', validate_type,
+        StrEnum('accessruletype?', validate_type,
             cli_name='type',
             doc=_('Rule type (allow)'),
             label=_('Rule type'),
             values=(u'allow', u'deny'),
             default=u'allow',
             autofill=True,
-            exclude='webui',
+            exclude=('webui', 'cli'),
             flags=['no_option', 'no_output'],
         ),
         # FIXME: {user,host,service}categories should expand in the future
@@ -244,10 +240,6 @@ class hbacrule(LDAPObject):
             doc=_('Service category the rule applies to'),
             values=(u'all', ),
         ),
-#        AccessTime('accesstime?',
-#            cli_name='time',
-#            label=_('Access time'),
-#        ),
         Str('description?',
             cli_name='desc',
             label=_('Description'),
@@ -272,6 +264,10 @@ class hbacrule(LDAPObject):
             label=_('Host Groups'),
             flags=['no_create', 'no_update', 'no_search'],
         ),
+        Str('ipamembertimerule_timerule?',
+            label=_('Time Rules'),
+            flags=['no_create', 'no_update', 'no_search']
+        ),
         Str('sourcehost_host?',
             deprecated=True,
             label=_('Source Hosts'),
@@ -305,6 +301,8 @@ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         assert isinstance(dn, DN)
         # HBAC rules are enabled by default
         entry_attrs['ipaenabledflag'] = 'TRUE'
+        # start as an old type HBAC
+        entry_attrs['objectclass'].append('ipahbacrule')
         return dn
 
 
@@ -349,7 +347,6 @@ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
         return dn
 
 
-
 @register()
 class hbacrule_find(LDAPSearch):
     __doc__ = _('Search for HBAC rules.')
@@ -358,6 +355,16 @@ class hbacrule_find(LDAPSearch):
         '%(count)d HBAC rule matched', '%(count)d HBAC rules matched', 0
     )
 
+    def pre_callback(self, ldap, filter, attrs_list, base_dn,
+                     scope, *args, **options):
+        assert isinstance(base_dn, DN)
+        filters = [
+            ldap.make_filter({'objectclass': ['ipahbacrule', 'ipahbacrulev2']},
+                             rules=ldap.MATCH_ANY)
+        ]
+        filters.append(filter)
+        filter = ldap.combine_filters(filters, rules=ldap.MATCH_ALL)
+        return (filter, base_dn, scope)
 
 
 @register()
@@ -425,67 +432,82 @@ def execute(self, cn, **options):
         )
 
 
-# @register()
-class hbacrule_add_accesstime(LDAPQuery):
-    """
-    Add an access time to an HBAC rule.
-    """
+@register()
+class hbacrule_add_timerule(LDAPAddMember):
+    __doc__ = _('Add time rules to an HBAC rule.')
 
-    takes_options = (
-        AccessTime('accesstime',
-            cli_name='time',
-            label=_('Access time'),
-        ),
-    )
+    member_attributes = ['ipamembertimerule']
+    member_count_out = ('%i object added.', '%i objects added.')
 
-    def execute(self, cn, **options):
+    def execute(self, *args, **options):
         ldap = self.obj.backend
+        dn = self.obj.get_dn(*args, **options)
+        assert(isinstance(dn, DN))
 
-        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
+            entry_attrs = ldap.get_entry(dn, ['objectclass', 'accessruletype'])
         except errors.NotFound:
-            self.obj.handle_not_found(cn)
+            self.obj.handle_not_found(*args)
+        objclass_updated = False
+        # ipaHBACRuleV2 objectclass marks new version HBAC rules with new
+        # capabilities such as time policies
+        if ('ipahbacrulev2' not in
+                (o.lower() for o in entry_attrs['objectclass'])):
+            entry_attrs['objectclass'] = replace_attr_value(
+                                            entry_attrs['objectclass'],
+                                            'ipahbacrule',
+                                            'ipahbacrulev2')
+            type_backup = entry_attrs['accessruletype']
+            entry_attrs['accessruletype'] = []
+            ldap.update_entry(entry_attrs)
+            objclass_updated = True
 
-        return dict(result=True)
+        try:
+            result = super(hbacrule_add_timerule, self).execute(*args,
+                                                                **options)
+        except Exception as e:
+            self.log.error("Failed to add a timerule: {err}".format(err=e))
+            if objclass_updated:
+                # there was an error adding time rule to an HBAC rule which was
+                # of old version before, switch it back to ipaHBACRule class
+                entry_attrs['objectclass'] = replace_attr_value(
+                                                entry_attrs['objectclass'],
+                                                'ipahbacrulev2',
+                                                'ipahbacrule')
+                entry_attrs['accessruletype'] = type_backup
+                ldap.update_entry(entry_attrs)
+            raise
+        return result
 
 
-# @register()
-class hbacrule_remove_accesstime(LDAPQuery):
-    """
-    Remove access time to HBAC rule.
-    """
-    takes_options = (
-        AccessTime('accesstime?',
-            cli_name='time',
-            label=_('Access time'),
-        ),
-    )
+@register()
+class hbacrule_remove_timerule(LDAPRemoveMember):
+    __doc__ = _('Remove users and groups from an HBAC rule.')
 
-    def execute(self, cn, **options):
-        ldap = self.obj.backend
+    member_attributes = ['ipamembertimerule']
+    member_count_out = ('%i object removed.', '%i objects removed.')
 
-        dn = self.obj.get_dn(cn)
+    def execute(self, *args, **options):
+        result = super(hbacrule_remove_timerule, self).execute(*args,
+                                                               **options)
+        dn = result['result']['dn']
+        assert(isinstance(dn, DN))
+        timerules = result['result'].get('membertimerule_timerule', [])
 
-        entry_attrs = ldap.get_entry(dn, ['accesstime'])
-        try:
-            entry_attrs.setdefault('accesstime', []).remove(
-                options['accesstime']
-            )
+        ldap = self.obj.backend
+        entry_attrs = ldap.get_entry(dn, ['objectclass'])
+        if (not timerules and 'ipahbacrulev2' in
+           (o.lower() for o in entry_attrs['objectclass'])):
+            # there are no more time rules left in the HBAC rule, switch
+            # to old type rules
+            entry_attrs['objectclass'] = replace_attr_value(
+                                            entry_attrs['objectclass'],
+                                            'ipahbacrulev2',
+                                            'ipahbacrule')
+            # accessRuleType is MUST attribute in ipaHBACRule
+            entry_attrs['accessruletype'] = 'allow'
             ldap.update_entry(entry_attrs)
-        except (ValueError, errors.EmptyModlist):
-            pass
-        except errors.NotFound:
-            self.obj.handle_not_found(cn)
-
-        return dict(result=True)
+        return result
 
 
 @register()
@@ -602,4 +624,3 @@ class hbacrule_remove_service(LDAPRemoveMember):
 
     member_attributes = ['memberservice']
     member_count_out = ('%i object removed.', '%i objects removed.')
-
diff --git a/ipaserver/plugins/timerule.py b/ipaserver/plugins/timerule.py
new file mode 100644
index 0000000..a70f086
--- /dev/null
+++ b/ipaserver/plugins/timerule.py
@@ -0,0 +1,295 @@
+#
+# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
+#
+
+import icalendar
+from datetime import date
+from ipalib import api, errors
+from ipalib import Str
+from ipalib import _, ngettext
+from ipalib.plugable import Registry
+from ipaserver.plugins.baseldap import (
+    LDAPObject,
+    LDAPCreate,
+    LDAPDelete,
+    LDAPRetrieve,
+    LDAPUpdate,
+    LDAPSearch)
+from ipapython.ipa_log_manager import root_logger
+from ipapython.dn import DN
+
+__doc__ = _("""
+Time Rules
+
+Time Rules are rules used to describe time periods (mostly recurring events)
+for different purposes. Most of the time the  purpose of a time rule is to
+set a restrictive time policy, e.g. in HBAC rules where time rules are used
+to restrict when the HBAC rule should apply.
+
+These time rules are based on the iCalendar format
+(https://tools.ietf.org/html/rfc5545).
+
+There are multiple ways to create a time rule:
+1) from an iCalendar string
+    ipa timerule-add ruleName \\
+    --time="BEGIN:VCALENDAR\\nPRODID:-//The Empire//iCal4j 1.0//EN\\n
+VERSION:2.0\\nCALSCALE:GREGORIAN\\nMETHOD:REQUEST\\nBEGIN:VEVENT\\n
+DTSTAMP:20160406T112129Z\\nDTSTART;VALUE=DATE:20160505\\nUID:1...@darkside.com\\n
+END:VEVENT\\nEND:VCALENDAR\\n"
+    - note that the --time option requires an escaped string
+
+2) from an iCalendar file (must be run from the client context)
+    ipa timerule-add ruleName --icalfile=icalfile.ics
+    - the --icalfile option requires an iCalendar file containing valid
+      iCalendar string of a VCALENDAR component containing a single VEVENT
+      subcomponent
+""")
+
+register = Registry()
+
+topic = 'timerule'
+
+
+def validate_ical_component(comp, name):
+    if comp.errors:
+        ical_errors = ('{prop}: {err}'
+                       .format(prop=prop, err=e) for prop, e in comp.errors)
+        raise errors.ValidationError(
+            name=name,
+            error=_('There were errors parsing the iCalendar string:\n%(errs)s'
+                    ) % {'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)s" not found '
+                        'in "%(comp)s".') % {'prop': prop, 'comp': comp.name}
+                )
+
+    for prop in comp.keys():
+        if prop not in (comp.singletons + comp.multiple):
+            raise errors.ValidationError(
+                name=name,
+                error=_('A "%(comp)s" component can\'t contain '
+                        'property "%(prop)s".'
+                        ) % {'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,
+                error=_('A "%(comp)s" component can\'t have more than '
+                        'one "%(prop)s" property."'
+                        ) % {'comp': comp.name, 'prop': prop}
+                )
+
+
+def validate_icalstring(ics):
+    name = 'accesstime'
+
+    try:
+        vcal = icalendar.cal.Calendar().from_ical(ics)
+    except ValueError as e:
+        raise errors.ValidationError(
+            name=name,
+            error=_('Couln\'t parse iCalendar string: %(err)s'
+                    ) % {'err': e}
+            )
+
+    if(vcal.name != 'VCALENDAR'):  # pylint: disable=no-member
+        raise errors.ValidationError(
+            name=name,
+            error=_('Received object is not a VCALENDAR')
+            )
+
+    validate_ical_component(vcal, name)
+
+    if len(vcal.subcomponents) > 1:  # pylint: disable=no-member
+        raise errors.ValidationError(
+            name=name,
+            error=_('Only one VEVENT component may be present in the '
+                    'iCalendar string at a time in this IPA version.'))
+
+    comp = vcal.subcomponents[0]  # pylint: disable=no-member
+
+    if comp.name != 'VEVENT':
+        raise errors.ValidationError(
+            name=name,
+            error=_('Found "%(comp)s" but only VEVENT component is '
+                    'supported.') % {'comp': comp.name}
+            )
+
+    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 "%(sub)s".') % {'sub': sub.name}
+                )
+        else:
+            root_logger.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():
+        if 'DURATION' in comp.keys():
+            raise errors.ValidationError(
+                name=name,
+                error=_('Both DURATION and DTEND set in a VEVENT.')
+            )
+
+        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.')
+                )
+
+
+@register()
+class timerule(LDAPObject):
+    """
+    Time Rule object
+    """
+    container_dn = api.env.container_timerules
+    object_name = _('Time Rule')
+    object_name_plural = _('Time Rules')
+    object_class = ['ipatimerule']
+    permission_filter_objectclasses = ['ipatimerule']
+    default_attributes = ['cn', 'description', 'accesstime', 'memberof']
+    attribute_members = {
+        'memberof': ['hbacrule']
+    }
+    label = _('Time Rules')
+    label_singular = _('Time Rule')
+    managed_permissions = {
+        'System: Read Time Rule': {
+            'replaces_global_anonymous_aci': True,
+            'ipapermbindruletype': 'all',
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'cn', 'objectclass', 'description', 'accesstime'
+            },
+        },
+        'System: Read Time Rule Membership': {
+            'replaces_global_anonymous_aci': True,
+            'ipapermbindruletype': 'all',
+            'ipapermright': {'read', 'search', 'compare'},
+            'ipapermdefaultattr': {
+                'memberof',
+            },
+        },
+        'System: Add Time Rule': {
+            'ipapermright': {'add'},
+            'default_privileges': {'Time Rules Administrator'},
+        },
+        'System: Delete Time Rule': {
+            'ipapermright': {'delete'},
+            'default_privileges': {'Time Rules Administrator'},
+        },
+        'System: Modify Time Rule': {
+            'ipapermright': {'write'},
+            'ipapermdefaultattr': {'description', 'accesstime'},
+            'default_privileges': {'Time Rules Administrator'},
+        },
+    }
+
+    takes_params = (
+        Str('cn', cli_name='name',
+            label=_('Rule name'),
+            primary_key=True),
+        Str('description?',
+            label=_('Description')
+            ),
+        # we need to have '?' here so that accessTime is not asked for if it is
+        # set using options/from icalfile
+        Str('accesstime?',
+            cli_name='time',
+            label=_('Access time'),
+            ),
+    )
+
+
+@register()
+class timerule_add(LDAPCreate):
+    __doc__ = _('Create new time rule.')
+
+    msg_summary = _('Added time rule "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        assert(isinstance(dn, DN))
+        if not options.get('accesstime', False):
+            raise errors.RequirementError(name='accesstime')
+        entry_attrs['accesstime'] = \
+            entry_attrs['accesstime'].decode('unicode-escape')
+        # perform the validation here so that there's no need to decode
+        # the string twice (validation and before storing it to LDAP)
+        validate_icalstring(entry_attrs['accesstime'])
+        return dn
+
+
+@register()
+class timerule_del(LDAPDelete):
+    __doc__ = _('Delete a time rule.')
+
+    msg_summary = _('Deleted time rule "%(value)s"')
+
+
+@register()
+class timerule_mod(LDAPUpdate):
+    __doc__ = _('Modify a time rule.')
+
+    msg_summary = _('Modified a time rule "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        if 'accesstime' in entry_attrs:
+            entry_attrs['accesstime'] = \
+                entry_attrs['accesstime'].decode('unicode-escape')
+            validate_icalstring(entry_attrs['accesstime'])
+        return dn
+
+
+@register()
+class timerule_find(LDAPSearch):
+    __doc__ = _('Search for time rules.')
+
+    member_attributes = ['memberof']
+
+    msg_summary = ngettext(
+        '%(count)d time rule matched',
+        '%(count)d time rules matched',
+        0,
+    )
+
+
+@register()
+class timerule_show(LDAPRetrieve):
+    __doc__ = _('Display the properties of a time rule object')
-- 
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