diff options
-rw-r--r-- | install/updates/40-delegation.update | 16 | ||||
-rw-r--r-- | ipalib/errors.py | 15 | ||||
-rw-r--r-- | ipalib/plugins/group.py | 59 | ||||
-rw-r--r-- | tests/test_xmlrpc/test_group_plugin.py | 78 |
4 files changed, 160 insertions, 8 deletions
diff --git a/install/updates/40-delegation.update b/install/updates/40-delegation.update index f63534c8d..451919b51 100644 --- a/install/updates/40-delegation.update +++ b/install/updates/40-delegation.update @@ -154,10 +154,10 @@ add:aci: '(targetattr = "givenName || sn || cn || displayName || title || initia || loginShell || gecos || homePhone || mobile || pager || facsimileTelephoneN umber || telephoneNumber || street || roomNumber || l || st || postalCode || manager || secretary || description || carLicense || labeledURI || inetUserHT - TPURL || seeAlso || employeeType || businessCategory || ou")(target = "ldap:/ - //uid=*,cn=users,cn=accounts,$SUFFIX")(version 3.0;acl "Modify User - s";allow (write) groupdn = "ldap:///cn=modifyusers,cn=taskgroups,cn=accounts, - $SUFFIX";)' + TPURL || seeAlso || employeeType || businessCategory || ou || mepManagedEntry + || objectclass")(target = "ldap:///uid=*,cn=users,cn=accounts,$SUFFIX") + (version 3.0;acl "Modify Users";allow (write) groupdn = + "ldap:///cn=modifyusers,cn=taskgroups,cn=accounts,$SUFFIX";)' # Add the taskgroups referenced by the ACIs for group administration @@ -204,10 +204,10 @@ add:aci: '(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version askgroups,cn=accounts,$SUFFIX";)' # we need objectclass and gidnumber in modify so a non-posix group can be # promoted -add:aci: '(targetattr = "cn || description || gidnumber || objectclass")(target - = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "Modify Group - s";allow (write) groupdn = "ldap:///cn=modifygroups,cn=taskgroups,cn=accounts, - $SUFFIX";)' +add:aci: '(targetattr = "cn || description || gidnumber || objectclass || + mepManagedBy")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX") + (version 3.0;acl "Modify Groups";allow (write) groupdn = + "ldap:///cn=modifygroups,cn=taskgroups,cn=accounts,$SUFFIX";)' # Add the taskgroups referenced by the ACIs for host administration diff --git a/ipalib/errors.py b/ipalib/errors.py index c35d424a7..efd0d640f 100644 --- a/ipalib/errors.py +++ b/ipalib/errors.py @@ -1070,6 +1070,21 @@ class DNSNotARecordError(ExecutionError): errno = 4019 format = _('Host does not have corresponding DNS A record') +class ManagedGroupError(ExecutionError): + """ + **4020** Raised when a managed group is deleted + + For example: + + >>> raise ManagedGroupError() + Traceback (most recent call last): + ... + ManagedGroupError: Deleting a managed group is not allowed. It must be detached first. + """ + + errno = 4020 + format = _('Deleting a managed group is not allowed. It must be detached first.') + class BuiltinError(ExecutionError): """ **4100** Base class for builtin execution errors (*4100 - 4199*). diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py index f0e9f7724..9bf5b1de8 100644 --- a/ipalib/plugins/group.py +++ b/ipalib/plugins/group.py @@ -158,6 +158,9 @@ class group_del(LDAPDelete): def_primary_group_dn = group_dn = self.obj.get_dn(def_primary_group) if dn == def_primary_group_dn: raise errors.DefaultGroup() + (group_dn, group_attrs) = ldap.get_entry(dn) + if 'mepmanagedby' in group_attrs: + raise errors.ManagedGroupError() return dn def post_callback(self, ldap, dn, *keys, **options): @@ -235,3 +238,59 @@ class group_remove_member(LDAPRemoveMember): """ api.register(group_remove_member) + + +class group_detach(LDAPRemoveMember): + """ + Detach a managed group from a user + """ + has_output = output.standard_value + msg_summary = _('Detached group "%(value)s" from user "%(value)s"') + + def execute(self, *keys, **options): + """ + This requires updating both the user and the group. We first need to + verify that both the user and group can be updated, then we go + about our work. We don't want a situation where only the user or + group can be modified and we're left in a bad state. + """ + ldap = self.obj.backend + + group_dn = self.obj.get_dn(*keys, **options) + user_dn = self.api.Object['user'].get_dn(*keys) + + if (not ldap.can_write(user_dn, "objectclass") or + not ldap.can_write(user_dn, "mepManagedEntry")): + raise errors.ACIError(info=_('not allowed to modify user entries')) + + if (not ldap.can_write(group_dn, "objectclass") or + not ldap.can_write(group_dn, "mepManagedBy")): + raise errors.ACIError(info=_('not allowed to modify group entries')) + + (user_dn, user_attrs) = ldap.get_entry(user_dn) + objectclasses = user_attrs['objectclass'] + try: + i = objectclasses.index('mepOriginEntry') + except ValueError: + raise NotFound(reason=_('Not a managed group')) + del objectclasses[i] + update_attrs = {'objectclass': objectclasses, 'mepManagedEntry': None} + ldap.update_entry(user_dn, update_attrs) + + (group_dn, group_attrs) = ldap.get_entry(group_dn) + objectclasses = group_attrs['objectclass'] + try: + i = objectclasses.index('mepManagedEntry') + except ValueError: + # this should never happen + raise NotFound(reason=_('Not a managed group')) + del objectclasses[i] + update_attrs = {'objectclass': objectclasses, 'mepManagedBy': None} + ldap.update_entry(group_dn, update_attrs) + + return dict( + result=True, + value=keys[0], + ) + +api.register(group_detach) diff --git a/tests/test_xmlrpc/test_group_plugin.py b/tests/test_xmlrpc/test_group_plugin.py index a8940a0d0..f1b1f3270 100644 --- a/tests/test_xmlrpc/test_group_plugin.py +++ b/tests/test_xmlrpc/test_group_plugin.py @@ -27,6 +27,7 @@ from xmlrpc_test import Declarative, fuzzy_digits, fuzzy_uuid group1 = u'testgroup1' group2 = u'testgroup2' +user1 = u'tuser1' invalidgroup1=u'+tgroup1' invalidgroup2=u'tgroup1234567890123456789012345678901234567890' @@ -36,6 +37,7 @@ class test_group(Declarative): cleanup_commands = [ ('group_del', [group1], {}), ('group_del', [group2], {}), + ('user_del', [user1], {}), ] tests = [ @@ -527,5 +529,81 @@ class test_group(Declarative): expected=errors.ValidationError(name='cn', error='can be at most 33 characters'), ), + ##### managed entry tests + dict( + desc='Create %r' % user1, + command=( + 'user_add', [], dict(givenname=u'Test', sn=u'User1') + ), + expected=dict( + value=user1, + summary=u'Added user "%s"' % user1, + result=dict( + gecos=[user1], + givenname=[u'Test'], + homedirectory=[u'/home/%s' % user1], + krbprincipalname=[u'%s@%s' % (user1, api.env.realm)], + loginshell=[u'/bin/sh'], + objectclass=objectclasses.user, + sn=[u'User1'], + uid=[user1], + uidnumber=[fuzzy_digits], + ipauniqueid=[fuzzy_uuid], + dn=u'uid=%s,cn=users,cn=accounts,%s' % (user1, api.env.basedn), + ), + ), + ), + + + dict( + desc='Verify the managed group %r was created' % user1, + command=('group_show', [user1], {}), + expected=dict( + value=user1, + summary=None, + result=dict( + cn=[user1], + description=[u'User private group for %s' % user1], + gidnumber=[fuzzy_digits], + dn=u'cn=%s,cn=groups,cn=accounts,%s' % (user1, api.env.basedn), + ), + ), + ), + + + dict( + desc='Try to delete a managed group %r' % user1, + command=('group_del', [user1], {}), + expected=errors.ManagedGroupError(), + ), + + + dict( + desc='Detach managed group %r' % user1, + command=('group_detach', [user1], {}), + expected=dict( + result=True, + value=user1, + summary=u'Detached group "%s" from user "%s"' % (user1, user1), + ), + ), + + + dict( + desc='Now delete the unmanaged group %r' % user1, + command=('group_del', [user1], {}), + expected=dict( + result=True, + value=user1, + summary=u'Deleted group "%s"' % user1, + ) + ), + + + dict( + desc='Verify that %r is really gone' % user1, + command=('group_show', [user1], {}), + expected=errors.NotFound(reason='no such entry'), + ), ] |