commit 7c6a6bb5895523220192082b19871777cb433c40
Author: Alexander Bokovoy <abokovoy@redhat.com>
Date:   Mon Aug 5 19:07:33 2013 +0300

    WIP: service constraints support with S4U2Proxy

diff --git a/API.txt b/API.txt
index f1103e0..92f53e0 100644
--- a/API.txt
+++ b/API.txt
@@ -3060,6 +3060,51 @@ 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: Output('value', <type 'unicode'>, None)
+command: serviceconstraint_add
+args: 1,7,3
+arg: Str('cn', attribute=True, cli_name='constraint_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][ a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.-]?$', primary_key=True, 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: Flag('source', autofill=True, cli_name='source', default=False)
+option: Flag('target', autofill=True, cli_name='target', default=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: Output('value', <type 'unicode'>, None)
+command: serviceconstraint_del
+args: 1,2,3
+arg: Str('cn', attribute=True, cli_name='constraint_name', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.][ a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.-]?$', 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: Output('value', <type 'unicode'>, None)
+command: serviceconstraint_find
+args: 1,7,4
+arg: Str('criteria?', noextrawhitespace=False)
+option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
+option: Str('cn', attribute=True, autofill=False, cli_name='constraint_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][ a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.-]?$', 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: serviceconstraint_show
+args: 1,4,3
+arg: Str('cn', attribute=True, cli_name='constraint_name', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][ a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.-]?$', primary_key=True, query=True, required=True)
+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: Flag('rights', autofill=True, default=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: Output('value', <type 'unicode'>, None)
 command: sidgen_was_run
 args: 0,1,1
 option: Str('version?', exclude='webui')
diff --git a/ipalib/constants.py b/ipalib/constants.py
index d3e61ca..d02605d 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -110,6 +110,7 @@ DEFAULT_CONFIG = (
     ('container_realm_domains', DN(('cn', 'Realm Domains'), ('cn', 'ipa'), ('cn', 'etc'))),
     ('container_otp', DN(('cn', 'otp'))),
     ('container_radiusproxy', DN(('cn', 'radiusproxy'))),
+    ('container_serviceconstraint', DN(('cn', 's4u2proxy'), ('cn', 'etc'))),
 
     # Ports, hosts, and URIs:
     ('xmlrpc_uri', 'http://localhost:8888/ipa/xml'),
diff --git a/ipalib/plugins/serviceconstraint.py b/ipalib/plugins/serviceconstraint.py
new file mode 100644
index 0000000..f3a5e24
--- /dev/null
+++ b/ipalib/plugins/serviceconstraint.py
@@ -0,0 +1,253 @@
+# Author:
+#   Alexander Bokovoy <abokovoy@redhat.com>
+#
+# Copyright (C) 2013  Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from ipalib import api
+from ipalib import Int, Str
+from ipalib.plugins.baseldap import *
+from ipalib.plugins import baseldap
+from ipalib import _, ngettext
+if api.env.in_server and api.env.context in ['lite', 'server']:
+    try:
+        import ipaserver.dcerpc
+        _dcerpc_bindings_installed = True
+    except ImportError:
+        _dcerpc_bindings_installed = False
+
+__doc__ = _("""
+Groups of users
+
+Manage groups of users. By default, new groups are POSIX groups. You
+can add the --nonposix option to the group-add command to mark a new group
+as non-POSIX. You can use the --posix argument with the group-mod command
+to convert a non-POSIX group into a POSIX group. POSIX groups cannot be
+converted to non-POSIX groups.
+
+Every group must have a description.
+
+POSIX groups must have a Group ID (GID) number. Changing a GID is
+supported but can have an impact on your file permissions. It is not necessary
+to supply a GID when creating a group. IPA will generate one automatically
+if it is not provided.
+
+EXAMPLES:
+
+ Add a new group:
+   ipa group-add --desc='local administrators' localadmins
+
+ Add a new non-POSIX group:
+   ipa group-add --nonposix --desc='remote administrators' remoteadmins
+
+ Convert a non-POSIX group to posix:
+   ipa group-mod --posix remoteadmins
+
+ Add a new POSIX group with a specific Group ID number:
+   ipa group-add --gid=500 --desc='unix admins' unixadmins
+
+ Add a new POSIX group and let IPA assign a Group ID number:
+   ipa group-add --desc='printer admins' printeradmins
+
+ Remove a group:
+   ipa group-del unixadmins
+
+ To add the "remoteadmins" group to the "localadmins" group:
+   ipa group-add-member --groups=remoteadmins localadmins
+
+ Add multiple users to the "localadmins" group:
+   ipa group-add-member --users=test1 --users=test2 localadmins
+
+ Remove a user from the "localadmins" group:
+   ipa group-remove-member --users=test2 localadmins
+
+ Display information about a named group.
+   ipa group-show localadmins
+
+External group membership is designed to allow users from trusted domains
+to be mapped to local POSIX groups in order to actually use IPA resources.
+External members should be added to groups that specifically created as
+external and non-POSIX. Such group later should be included into one of POSIX
+groups.
+
+An external group member is currently a Security Identifier (SID) as defined by
+the trusted domain. When adding external group members, it is possible to
+specify them in either SID, or DOM\\name, or name@domain format. IPA will attempt
+to resolve passed name to SID with the use of Global Catalog of the trusted domain.
+
+Example:
+
+1. Create group for the trusted domain admins' mapping and their local POSIX group:
+
+   ipa group-add --desc='<ad.domain> admins external map' ad_admins_external --external
+   ipa group-add --desc='<ad.domain> admins' ad_admins
+
+2. Add security identifier of Domain Admins of the <ad.domain> to the ad_admins_external
+   group:
+
+   ipa group-add-member ad_admins_external --external 'AD\\Domain Admins'
+
+3. Allow members of ad_admins_external group to be associated with ad_admins POSIX group:
+
+   ipa group-add-member ad_admins --groups ad_admins_external
+
+4. List members of external members of ad_admins_external group to see their SIDs:
+
+   ipa group-show ad_admins_external
+""")
+
+PROTECTED_CONSTRAINTS = (u'ipa-cifs-delegation-targets', u'ipa-ldap-delegation-targets', u'ipa-http-delegation')
+
+def verify_principal(self, principal):
+    ldap = self.obj.backend
+    search_kw = {
+        'objectclass' : ['ipakrbprincipal'],
+        'krbprincipalname' : principal,
+    }
+    attr_filter = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)
+
+def verify_target(self, target):
+    pass
+
+class serviceconstraint(LDAPObject):
+    """
+    Group object.
+    """
+    container_dn = api.env.container_serviceconstraint
+    object_name = _('service constraint')
+    object_name_plural = _('service constraints')
+    object_class = ['groupofprincipals', 'top']
+    possible_objectclasses = ['ipaKrb5DelegationACL']
+    default_attributes = [
+        'cn', 'memberprincipal', 'ipaallowedtarget', 'ipaallowedimpersonate',
+    ]
+
+    rdn_is_primary_key = True
+
+    label = _('Service constraints')
+    label_singular = _('Service constraint')
+
+    takes_params = (
+        Str('cn',
+            pattern='^[a-zA-Z0-9_.][ a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.-]?$',
+            pattern_errmsg='may only include letters, numbers, _, -, ., and a space inside',
+            maxlength=255,
+            cli_name='constraint_name',
+            label=_('Constraint name'),
+            primary_key=True,
+        ),
+    )
+
+api.register(serviceconstraint)
+
+class serviceconstraint_add(LDAPCreate):
+    __doc__ = _('Create a new service constraint.')
+
+    msg_summary = _('Added service constraint "%(value)s"')
+
+    takes_options = LDAPCreate.takes_options + (
+        Flag('source',
+             cli_name='source',
+             doc=_('Source of the service constraint'),
+             default=False,
+        ),
+        Flag('target',
+             cli_name='target',
+             doc=_('Target of the service constraint'),
+             default=False,
+        ),
+    )
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        assert isinstance(dn, DN)
+        if 'target' in options:
+            entry_attrs['objectclass'].append('ipakrb5delegationacl')
+            try:
+                tdn, result = self.obj.methods.show(self.obj.get_primary_key_from_dn(options['target']))
+            except errors.NotFound:
+                raise errors.NotFound(reason=_('Service constraint "%(name)s" was not found'))
+            entry_attrs['ipaallowedtarget'] = [tdn]
+        return dn
+
+api.register(serviceconstraint_add)
+
+
+class serviceconstraint_del(LDAPDelete):
+    __doc__ = _('Delete service constraint.')
+
+    msg_summary = _('Deleted service constraint "%(value)s"')
+
+    def pre_callback(self, ldap, dn, *keys, **options):
+        assert isinstance(dn, DN)
+        constraint_attrs = self.obj.methods.show(
+            self.obj.get_primary_key_from_dn(dn), all=True
+        )['result']
+        if keys[0] in PROTECTED_CONSTRAINTS:
+            raise errors.ProtectedEntryError(label=_(u'service constraint'), key=keys[0],
+                reason=_(u'privileged service constraint'))
+        return dn
+
+api.register(serviceconstraint_del)
+
+class serviceconstraint_find(LDAPSearch):
+    __doc__ = _('Search for service constraints.')
+
+    #member_attributes = ['ipaallowedtarget', 'ipaallowedimpersonate']
+
+    msg_summary = ngettext(
+        '%(count)d service constraints matched', '%(count)d service constraints matched', 0
+    )
+
+api.register(serviceconstraint_find)
+
+
+class serviceconstraint_show(LDAPRetrieve):
+    __doc__ = _('Display information about a named service constraint.')
+api.register(serviceconstraint_show)
+
+#class serviceconstraint_add_member(LDAPAddMember):
+#    __doc__ = _('Add member principal to a named service constraint.')
+#
+#    attribute_members = {
+#        'memberprincipal' : [verify_principal],
+#        'ipaallowedtarget' : [verify_target],
+#        'ipaallowedimpersonate' : [verify_principal]
+#    }
+#    member_attributes = ['memberprincipal']
+#
+#    def get_member_dns(self, **options):
+#        dns = {}
+#        failed = {}
+#        for attr in self.member_attributes:
+#            dns[attr] = {}
+#            failed[attr] = {}
+#            for ldap_obj_name in self.obj.attribute_members[attr]:
+#                dns[attr][ldap_obj_name] = []
+#                failed[attr][ldap_obj_name] = []
+#                names = options.get(to_cli(ldap_obj_name), [])
+#                if not names:
+#                    continue
+#                for name in names:
+#                    if not name:
+#                        continue
+#                    ldap_obj = self.api.Object[ldap_obj_name]
+#                    try:
+#                        dns[attr][ldap_obj_name].append(ldap_obj.get_dn(name))
+#                    except errors.PublicError, e:
+#                        failed[attr][ldap_obj_name].append((name, unicode(e)))
+#        return (dns, failed)
+#api.register(serviceconstraint_add_member)
+
