summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--install/updates/40-delegation.update16
-rw-r--r--ipalib/errors.py15
-rw-r--r--ipalib/plugins/group.py59
-rw-r--r--tests/test_xmlrpc/test_group_plugin.py78
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'),
+ ),
]