diff options
author | Rob Crittenden <rcritten@redhat.com> | 2010-12-10 13:31:58 -0500 |
---|---|---|
committer | Adam Young <ayoung@redhat.com> | 2010-12-13 20:15:46 -0500 |
commit | cd7b64103b24ce4b71420c8c93707046169c2c22 (patch) | |
tree | 23f9d54d58b983d87b59426520a49a70e19966d8 | |
parent | 8a534bf07b55b20566c50211c9f90d638aead3da (diff) | |
download | freeipa-cd7b64103b24ce4b71420c8c93707046169c2c22.tar.gz freeipa-cd7b64103b24ce4b71420c8c93707046169c2c22.tar.xz freeipa-cd7b64103b24ce4b71420c8c93707046169c2c22.zip |
Add group to group delegation plugin.
This is a thin wrapper around the ACI plugin that manages granting group A
the ability to write a set of attributes of group B.
ticket 532
-rw-r--r-- | ipalib/plugins/aci.py | 48 | ||||
-rw-r--r-- | ipalib/plugins/delegation.py | 231 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_delegation_plugin.py | 198 |
3 files changed, 470 insertions, 7 deletions
diff --git a/ipalib/plugins/aci.py b/ipalib/plugins/aci.py index d5f7d996f..5a57a309a 100644 --- a/ipalib/plugins/aci.py +++ b/ipalib/plugins/aci.py @@ -124,6 +124,8 @@ from ipalib import Flag, Int, List, Str, StrEnum from ipalib.aci import ACI from ipalib import output from ipalib import _, ngettext +if api.env.in_server and api.env.context in ['lite', 'server']: + from ldap import explode_dn import logging _type_map = { @@ -272,7 +274,9 @@ def _aci_to_kw(ldap, a, test=False): # See if the target is a group. If so we set the # targetgroup attr, otherwise we consider it a subtree if api.env.container_group in target: - kw['targetgroup'] = unicode(target) + targetdn = unicode(target.replace('ldap:///','')) + (dn, entry_attrs) = ldap.get_entry(targetdn, ['cn']) + kw['targetgroup'] = entry_attrs['cn'][0] else: kw['subtree'] = unicode(target) @@ -638,9 +642,10 @@ class aci_find(crud.Search): if 'memberof' in kw: try: - self.api.Command['group_show']( + result = self.api.Command['group_show']( kw['memberof'] - ) + )['result'] + dn = result['dn'] except errors.NotFound: pass else: @@ -652,11 +657,9 @@ class aci_find(crud.Search): results.remove(a) else: results.remove(a) - # uncomment next line if you add more search criteria - # acis = list(results) - for a in acis: - if 'type' in kw: + if 'type' in kw: + for a in acis: if 'target' in a.target: target = a.target['target']['expression'] else: @@ -681,6 +684,37 @@ class aci_find(crud.Search): except ValueError: pass + if 'group' in kw: + for a in acis: + groupdn = a.bindrule['expression'] + groupdn = groupdn.replace('ldap:///','') + cn = None + if groupdn.startswith('cn='): + cn = explode_dn(groupdn)[0] + cn = cn.replace('cn=','') + if cn is None or cn != kw['group']: + try: + results.remove(a) + except ValueError: + pass + + if 'targetgroup' in kw: + for a in acis: + found = False + if 'target' in a.target: + target = a.target['target']['expression'] + if api.env.container_group in target: + targetdn = unicode(target.replace('ldap:///','')) + cn = explode_dn(targetdn)[0] + cn = cn.replace('cn=','') + if cn == kw['targetgroup']: + found = True + if not found: + try: + results.remove(a) + except ValueError: + pass + # TODO: searching by: filter, subtree acis = [] diff --git a/ipalib/plugins/delegation.py b/ipalib/plugins/delegation.py new file mode 100644 index 000000000..de39a8500 --- /dev/null +++ b/ipalib/plugins/delegation.py @@ -0,0 +1,231 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" +Group to Group Delegation + +A permission enables fine-grained delegation of permissions. Access Control +Rules, or instructions (ACIs), grant permission to permissions to perform +given tasks such as adding a user, modifying a group, etc. + +Group to Group Delegations grants the members of one group to update a set +of attributes of members of another group. + +EXAMPLES: + + Add a self-service rule to allow users to manage their address: + ipa selfservice-add --permissions=write --attrs=street,postalCode,l,c,st "User's manage their own address" + + When managing the list of attributes you need to include all attributes + in the list, including existing ones. Add telephoneNumber to the list: + ipa selfservice-mod --attrs=street,postalCode,l,c,st,telephoneNumber "User's manage their own address" + + Display our updated rule: + ipa selfservice-show "User's manage their own address" + + Delete a rule: + ipa selfservice-del "User's manage their own address" +""" + +import copy +from ipalib import api, _, ngettext +from ipalib import Flag, Str, List +from ipalib.request import context +from ipalib import api, crud, errors +from ipalib import output +from ipalib import Object, Command + +def convert_delegation(aci): + """ + memberOf is in filter but we want to pull out the group for easier + displaying. + """ + filter = aci['filter'] + st = filter.find('memberOf=') + if st == -1: + raise errors.NotFound(reason=_('Delegation \'%(permission)s\' not found') % dict(permission=aci['aciname'])) + en = filter.find(')', st) + membergroup = filter[st+9:en] + aci['membergroup'] = membergroup + + return aci + +def is_delegation(aciname): + """ + Determine if the ACI is a Delegation ACI and raise an exception if it + isn't. + + Return the result if it is a delegation ACI, adding a new attribute + membergroup. + """ + result = api.Command['aci_show'](aciname)['result'] + if 'filter' in result: + result = convert_delegation(result) + else: + raise errors.NotFound(reason=_('Delegation \'%(permission)s\' not found') % dict(permission=aciname)) + return result + + +class delegation(Object): + """ + Delegation object. + """ + + label = _('Delegation') + + takes_params = ( + Str('aciname', + cli_name='name', + label=_('Delegation name'), + doc=_('Delegation name'), + primary_key=True, + ), + List('permissions?', + cli_name='permissions', + label=_('Permissions'), + doc=_('Comma-separated list of permissions to grant ' \ + '(read, write). Default is write.'), + ), + List('attrs', + cli_name='attrs', + label=_('Attributes'), + doc=_('Comma-separated list of attributes'), + ), + Str('memberof', + cli_name='membergroup', + label=_('Member user group'), + doc=_('User group to apply delegation to'), + ), + Str('group', + cli_name='group', + label=_('User group'), + doc=_('User group ACI grants access to'), + ), + ) + +api.register(delegation) + + +class delegation_add(crud.Create): + """ + Add a new delegation. + """ + + msg_summary = _('Added delegation "%(value)s"') + + def execute(self, aciname, **kw): + if not 'permissions' in kw: + kw['permissions'] = (u'write',) + result = api.Command['aci_add'](aciname, **kw)['result'] + if 'filter' in result: + result = convert_delegation(result) + + return dict( + result=result, + value=aciname, + ) + +api.register(delegation_add) + + +class delegation_del(crud.Delete): + """ + Delete a delegation. + """ + + has_output = output.standard_delete + msg_summary = _('Deleted delegation "%(value)s"') + + def execute(self, aciname, **kw): + is_delegation(aciname) + result = api.Command['aci_del'](aciname, **kw) + return dict( + result=True, + value=aciname, + ) + +api.register(delegation_del) + + +class delegation_mod(crud.Update): + """ + Modify a delegation. + """ + + msg_summary = _('Modified delegation "%(value)s"') + + def execute(self, aciname, **kw): + is_delegation(aciname) + result = api.Command['aci_mod'](aciname, **kw)['result'] + if 'filter' in result: + result = convert_delegation(result) + return dict( + result=result, + value=aciname, + ) + +api.register(delegation_mod) + + +class delegation_find(crud.Search): + """ + Search for delegations. + """ + + msg_summary = ngettext( + '%(count)d delegation matched', '%(count)d delegations matched' + ) + + def execute(self, term, **kw): + acis = api.Command['aci_find'](term, **kw)['result'] + results = [] + for aci in acis: + try: + if 'filter' in aci: + aci = convert_delegation(aci) + results.append(aci) + except errors.NotFound: + pass + + return dict( + result=results, + count=len(results), + truncated=False, + ) + +api.register(delegation_find) + + +class delegation_show(crud.Retrieve): + """ + Display information about a delegation. + """ + has_output_params = ( + Str('aci', + label=_('ACI'), + ), + ) + + def execute(self, aciname, **kw): + result = is_delegation(aciname) + return dict( + result=result, + value=aciname, + ) + +api.register(delegation_show) diff --git a/tests/test_xmlrpc/test_delegation_plugin.py b/tests/test_xmlrpc/test_delegation_plugin.py new file mode 100644 index 000000000..ded6d4f0c --- /dev/null +++ b/tests/test_xmlrpc/test_delegation_plugin.py @@ -0,0 +1,198 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# +# Copyright (C) 2010 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; version 2 only +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Test the `ipalib/plugins/delegation.py` module. +""" + +from ipalib import api, errors +from tests.test_xmlrpc import objectclasses +from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid + +delegation1 = u'testdelegation' +memberdn1 = u'cn=admins,cn=groups,cn=accounts,%s' % api.env.basedn + +class test_delegation(Declarative): + + cleanup_commands = [ + ('delegation_del', [delegation1], {}), + ] + + tests = [ + + dict( + desc='Try to retrieve non-existent %r' % delegation1, + command=('delegation_show', [delegation1], {}), + expected=errors.NotFound(reason='no such entry'), + ), + + + dict( + desc='Try to update non-existent %r' % delegation1, + command=('delegation_mod', [delegation1], dict(description=u'Foo')), + expected=errors.NotFound(reason='no such entry'), + ), + + + dict( + desc='Try to delete non-existent %r' % delegation1, + command=('delegation_del', [delegation1], {}), + expected=errors.NotFound(reason='no such entry'), + ), + + + dict( + desc='Search for non-existent %r' % delegation1, + command=('delegation_find', [delegation1], {}), + expected=dict( + count=0, + truncated=False, + summary=u'0 delegations matched', + result=[], + ), + ), + + + dict( + desc='Create %r' % delegation1, + command=( + 'delegation_add', [delegation1], dict( + attrs=u'street,c,l,st,postalCode', + permissions=u'write', + group=u'editors', + memberof=u'admins', + ) + ), + expected=dict( + value=delegation1, + summary=u'Added delegation "%s"' % delegation1, + result=dict( + attrs=[u'street', u'c', u'l', u'st', u'postalCode'], + permissions=[u'write'], + aciname=delegation1, + group=u'editors', + membergroup=u'%s' % memberdn1, + filter = u'(memberOf=%s)' % memberdn1 + ), + ), + ), + + + dict( + desc='Try to create duplicate %r' % delegation1, + command=( + 'delegation_add', [delegation1], dict( + attrs=u'street,c,l,st,postalCode', + permissions=u'write', + group=u'editors', + memberof=u'admins', + ), + ), + expected=errors.DuplicateEntry(), + ), + + + dict( + desc='Retrieve %r' % delegation1, + command=('delegation_show', [delegation1], {}), + expected=dict( + value=delegation1, + summary=None, + result={ + 'attrs': [u'street', u'c', u'l', u'st', u'postalCode'], + 'permissions': [u'write'], + 'aciname': delegation1, + 'group': u'editors', + 'filter': u'(memberOf=%s)' % memberdn1, + 'membergroup': u'%s' % memberdn1 + }, + ), + ), + + + dict( + desc='Search for %r' % delegation1, + command=('delegation_find', [delegation1], {}), + expected=dict( + count=1, + truncated=False, + summary=u'1 delegation matched', + result=[ + { + 'attrs': [u'street', u'c', u'l', u'st', u'postalCode'], + 'permissions': [u'write'], + 'aciname': delegation1, + 'group': u'editors', + 'membergroup': u'%s' % memberdn1, + 'filter': u'(memberOf=%s)' % memberdn1 + }, + ], + ), + ), + + + dict( + desc='Update %r' % delegation1, + command=( + 'delegation_mod', [delegation1], dict(permissions=u'read') + ), + expected=dict( + value=delegation1, + summary=u'Modified delegation "%s"' % delegation1, + result=dict( + attrs=[u'street', u'c', u'l', u'st', u'postalCode'], + permissions=[u'read'], + aciname=delegation1, + group=u'editors', + membergroup=u'%s' % memberdn1, + filter=u'(memberOf=%s)' % memberdn1 + ), + ), + ), + + + dict( + desc='Retrieve %r to verify update' % delegation1, + command=('delegation_show', [delegation1], {}), + expected=dict( + value=delegation1, + summary=None, + result={ + 'attrs': [u'street', u'c', u'l', u'st', u'postalCode'], + 'permissions': [u'read'], + 'aciname': delegation1, + 'group': u'editors', + 'membergroup': u'%s' % memberdn1, + 'filter': u'(memberOf=%s)' % memberdn1 + }, + ), + ), + + + dict( + desc='Delete %r' % delegation1, + command=('delegation_del', [delegation1], {}), + expected=dict( + result=True, + value=delegation1, + summary=u'Deleted delegation "%s"' % delegation1, + ) + ), + + ] |