summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2010-08-09 16:40:51 -0400
committerRob Crittenden <rcritten@redhat.com>2010-08-10 16:41:47 -0400
commit5b894d1fb76f176b71aed6b8f6c2ea1ce4158af8 (patch)
tree61c0cb31c211500320d420c0b456ecaa4b4d16b9
parent719592a209a1d3d41565284ebfc79fc76e9f5164 (diff)
downloadfreeipa-5b894d1fb76f176b71aed6b8f6c2ea1ce4158af8.tar.gz
freeipa-5b894d1fb76f176b71aed6b8f6c2ea1ce4158af8.tar.xz
freeipa-5b894d1fb76f176b71aed6b8f6c2ea1ce4158af8.zip
Allow decoupling of user-private groups.
To do this we need to break the link manually on both sides, the user and the group. We also have to verify in advance that the user performing this is allowed to do both. Otherwise the user could be decoupled but not the group leaving it in a quasi broken state that only ldapmodify could fix. ticket 75
-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'),
+ ),
]