diff options
author | Jan Cholasta <jcholast@redhat.com> | 2016-04-28 10:30:05 +0200 |
---|---|---|
committer | Jan Cholasta <jcholast@redhat.com> | 2016-06-03 09:00:34 +0200 |
commit | 6e44557b601f769d23ee74555a72e8b5cc62c0c9 (patch) | |
tree | eedd3e054b0709341b9f58c190ea54f999f7d13a /ipaserver/plugins/group.py | |
parent | ec841e5d7ab29d08de294b3fa863a631cd50e30a (diff) | |
download | freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.tar.gz freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.tar.xz freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.zip |
ipalib: move server-side plugins to ipaserver
Move the remaining plugin code from ipalib.plugins to ipaserver.plugins.
Remove the now unused ipalib.plugins package.
https://fedorahosted.org/freeipa/ticket/4739
Reviewed-By: David Kupka <dkupka@redhat.com>
Diffstat (limited to 'ipaserver/plugins/group.py')
-rw-r--r-- | ipaserver/plugins/group.py | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/ipaserver/plugins/group.py b/ipaserver/plugins/group.py new file mode 100644 index 000000000..2b0c08050 --- /dev/null +++ b/ipaserver/plugins/group.py @@ -0,0 +1,690 @@ +# Authors: +# Rob Crittenden <rcritten@redhat.com> +# Pavel Zuna <pzuna@redhat.com> +# +# Copyright (C) 2009 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/>. + +import six + +from ipalib import api +from ipalib import Int, Str, Flag +from ipalib.plugable import Registry +from .baseldap import ( + add_external_post_callback, + pkey_to_value, + remove_external_post_callback, + LDAPObject, + LDAPCreate, + LDAPUpdate, + LDAPDelete, + LDAPSearch, + LDAPRetrieve, + LDAPAddMember, + LDAPRemoveMember, + LDAPQuery, +) +from .idviews import remove_ipaobject_overrides +from . import baseldap +from ipalib import _, ngettext +from ipalib import errors +from ipalib import output +from ipapython.dn import DN + +if six.PY3: + unicode = str + +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 +""") + +register = Registry() + +PROTECTED_GROUPS = (u'admins', u'trust admins', u'default smb group') + + +@register() +class group(LDAPObject): + """ + Group object. + """ + container_dn = api.env.container_group + object_name = _('group') + object_name_plural = _('groups') + object_class = ['ipausergroup'] + object_class_config = 'ipagroupobjectclasses' + possible_objectclasses = ['posixGroup', 'mepManagedEntry', 'ipaExternalGroup'] + permission_filter_objectclasses = ['posixgroup', 'ipausergroup'] + search_attributes_config = 'ipagroupsearchfields' + default_attributes = [ + 'cn', 'description', 'gidnumber', 'member', 'memberof', + 'memberindirect', 'memberofindirect', 'ipaexternalmember', + ] + uuid_attribute = 'ipauniqueid' + attribute_members = { + 'member': ['user', 'group'], + 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], + 'memberindirect': ['user', 'group'], + 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', + 'sudorule'], + } + rdn_is_primary_key = True + managed_permissions = { + 'System: Read Groups': { + 'replaces_global_anonymous_aci': True, + 'ipapermbindruletype': 'anonymous', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'businesscategory', 'cn', 'description', 'gidnumber', + 'ipaexternalmember', 'ipauniqueid', 'mepmanagedby', 'o', + 'objectclass', 'ou', 'owner', 'seealso', + 'ipantsecurityidentifier' + }, + }, + 'System: Read Group Membership': { + 'replaces_global_anonymous_aci': True, + 'ipapermbindruletype': 'all', + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'member', 'memberof', 'memberuid', 'memberuser', 'memberhost', + }, + }, + 'System: Add Groups': { + 'ipapermright': {'add'}, + 'replaces': [ + '(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Groups";allow (add) groupdn = "ldap:///cn=Add Groups,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'Group Administrators'}, + }, + 'System: Modify Group Membership': { + 'ipapermright': {'write'}, + 'ipapermtargetfilter': [ + '(objectclass=ipausergroup)', + '(!(cn=admins))', + ], + 'ipapermdefaultattr': {'member'}, + 'replaces': [ + '(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)', + '(targetfilter = "(!(cn=admins))")(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': { + 'Group Administrators', 'Modify Group membership' + }, + }, + 'System: Modify Groups': { + 'ipapermright': {'write'}, + 'ipapermdefaultattr': { + 'cn', 'description', 'gidnumber', 'ipauniqueid', + 'mepmanagedby', 'objectclass' + }, + 'replaces': [ + '(targetattr = "cn || description || gidnumber || objectclass || mepmanagedby || ipauniqueid")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Groups";allow (write) groupdn = "ldap:///cn=Modify Groups,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'Group Administrators'}, + }, + 'System: Remove Groups': { + 'ipapermright': {'delete'}, + 'replaces': [ + '(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Groups";allow (delete) groupdn = "ldap:///cn=Remove Groups,cn=permissions,cn=pbac,$SUFFIX";)', + ], + 'default_privileges': {'Group Administrators'}, + }, + 'System: Read Group Compat Tree': { + 'non_object': True, + 'ipapermbindruletype': 'anonymous', + 'ipapermlocation': api.env.basedn, + 'ipapermtarget': DN('cn=groups', 'cn=compat', api.env.basedn), + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'objectclass', 'cn', 'memberuid', 'gidnumber', + }, + }, + 'System: Read Group Views Compat Tree': { + 'non_object': True, + 'ipapermbindruletype': 'anonymous', + 'ipapermlocation': api.env.basedn, + 'ipapermtarget': DN('cn=groups', 'cn=*', 'cn=views', 'cn=compat', api.env.basedn), + 'ipapermright': {'read', 'search', 'compare'}, + 'ipapermdefaultattr': { + 'objectclass', 'cn', 'memberuid', 'gidnumber', + }, + }, + } + + label = _('User Groups') + label_singular = _('User Group') + + 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 $', + maxlength=255, + cli_name='group_name', + label=_('Group name'), + primary_key=True, + normalizer=lambda value: value.lower(), + ), + Str('description?', + cli_name='desc', + label=_('Description'), + doc=_('Group description'), + ), + Int('gidnumber?', + cli_name='gid', + label=_('GID'), + doc=_('GID (use this option to set it manually)'), + minvalue=1, + ), + ) + + +ipaexternalmember_param = Str('ipaexternalmember*', + cli_name='external', + label=_('External member'), + doc=_('Members of a trusted domain in DOM\\name or name@domain form'), + flags=['no_create', 'no_update', 'no_search'], + ) + + +@register() +class group_add(LDAPCreate): + __doc__ = _('Create a new group.') + + msg_summary = _('Added group "%(value)s"') + + takes_options = LDAPCreate.takes_options + ( + Flag('nonposix', + cli_name='nonposix', + doc=_('Create as a non-POSIX group'), + default=False, + ), + Flag('external', + cli_name='external', + doc=_('Allow adding external non-IPA members from trusted domains'), + default=False, + ), + ) + + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): + # As both 'external' and 'nonposix' options have default= set for + # them, they will always be present in options dict, thus we can + # safely reference the values + assert isinstance(dn, DN) + if options['external']: + entry_attrs['objectclass'].append('ipaexternalgroup') + if 'gidnumber' in options: + raise errors.MutuallyExclusiveError(reason=_('gid cannot be set for external group')) + elif not options['nonposix']: + entry_attrs['objectclass'].append('posixgroup') + if not 'gidnumber' in options: + entry_attrs['gidnumber'] = baseldap.DNA_MAGIC + return dn + + +@register() +class group_del(LDAPDelete): + __doc__ = _('Delete group.') + + msg_summary = _('Deleted group "%(value)s"') + + def pre_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) + config = ldap.get_ipa_config() + def_primary_group = config.get('ipadefaultprimarygroup', '') + def_primary_group_dn = group_dn = self.obj.get_dn(def_primary_group) + if dn == def_primary_group_dn: + raise errors.DefaultGroupError() + group_attrs = self.obj.methods.show( + self.obj.get_primary_key_from_dn(dn), all=True + )['result'] + if keys[0] in PROTECTED_GROUPS: + raise errors.ProtectedEntryError(label=_(u'group'), key=keys[0], + reason=_(u'privileged group')) + if 'mepmanagedby' in group_attrs: + raise errors.ManagedGroupError() + + # Remove any ID overrides tied with this group + remove_ipaobject_overrides(ldap, self.obj.api, dn) + + return dn + + def post_callback(self, ldap, dn, *keys, **options): + assert isinstance(dn, DN) + try: + api.Command['pwpolicy_del'](keys[-1]) + except errors.NotFound: + pass + + return True + + +@register() +class group_mod(LDAPUpdate): + __doc__ = _('Modify a group.') + + msg_summary = _('Modified group "%(value)s"') + + takes_options = LDAPUpdate.takes_options + ( + Flag('posix', + cli_name='posix', + doc=_('change to a POSIX group'), + ), + Flag('external', + cli_name='external', + doc=_('change to support external non-IPA members from trusted domains'), + default=False, + ), + ) + + def pre_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) + + is_protected_group = keys[-1] in PROTECTED_GROUPS + + if 'rename' in options or 'cn' in entry_attrs: + if is_protected_group: + raise errors.ProtectedEntryError(label=u'group', key=keys[-1], + reason=u'Cannot be renamed') + + if ('posix' in options and options['posix']) or 'gidnumber' in options: + old_entry_attrs = ldap.get_entry(dn, ['objectclass']) + dn = old_entry_attrs.dn + if 'ipaexternalgroup' in old_entry_attrs['objectclass']: + raise errors.ExternalGroupViolation() + if 'posixgroup' in old_entry_attrs['objectclass']: + if options['posix']: + raise errors.AlreadyPosixGroup() + else: + old_entry_attrs['objectclass'].append('posixgroup') + entry_attrs['objectclass'] = old_entry_attrs['objectclass'] + if not 'gidnumber' in options: + entry_attrs['gidnumber'] = baseldap.DNA_MAGIC + + if options['external']: + if is_protected_group: + raise errors.ProtectedEntryError(label=u'group', key=keys[-1], + reason=u'Cannot support external non-IPA members') + old_entry_attrs = ldap.get_entry(dn, ['objectclass']) + dn = old_entry_attrs.dn + if 'posixgroup' in old_entry_attrs['objectclass']: + raise errors.PosixGroupViolation() + if 'ipaexternalgroup' in old_entry_attrs['objectclass']: + raise errors.AlreadyExternalGroup() + else: + old_entry_attrs['objectclass'].append('ipaexternalgroup') + entry_attrs['objectclass'] = old_entry_attrs['objectclass'] + + # Can't check for this in a validator because we lack context + if 'gidnumber' in options and options['gidnumber'] is None: + raise errors.RequirementError(name='gidnumber') + return dn + + def exc_callback(self, keys, options, exc, call_func, *call_args, **call_kwargs): + # Check again for GID requirement in case someone tried to clear it + # using --setattr. + if call_func.__name__ == 'update_entry': + if isinstance(exc, errors.ObjectclassViolation): + if 'gidNumber' in exc.message and 'posixGroup' in exc.message: + raise errors.RequirementError(name='gidnumber') + raise exc + + +@register() +class group_find(LDAPSearch): + __doc__ = _('Search for groups.') + + member_attributes = ['member', 'memberof'] + + msg_summary = ngettext( + '%(count)d group matched', '%(count)d groups matched', 0 + ) + + takes_options = LDAPSearch.takes_options + ( + Flag('private', + cli_name='private', + doc=_('search for private groups'), + ), + Flag('posix', + cli_name='posix', + doc=_('search for POSIX groups'), + ), + Flag('external', + cli_name='external', + doc=_('search for groups with support of external non-IPA members from trusted domains'), + ), + Flag('nonposix', + cli_name='nonposix', + doc=_('search for non-POSIX groups'), + ), + ) + + def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, + criteria=None, **options): + assert isinstance(base_dn, DN) + + # filter groups by pseudo type + filters = [] + if options['posix']: + search_kw = {'objectclass': ['posixGroup']} + filters.append(ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)) + if options['external']: + search_kw = {'objectclass': ['ipaExternalGroup']} + filters.append(ldap.make_filter(search_kw, rules=ldap.MATCH_ALL)) + if options['nonposix']: + search_kw = {'objectclass': ['posixGroup' , 'ipaExternalGroup']} + filters.append(ldap.make_filter(search_kw, rules=ldap.MATCH_NONE)) + + # if looking for private groups, we need to create a new search filter, + # because private groups have different object classes + if options['private']: + # filter based on options, oflt + search_kw = self.args_options_2_entry(**options) + search_kw['objectclass'] = ['posixGroup', 'mepManagedEntry'] + oflt = ldap.make_filter(search_kw, rules=ldap.MATCH_ALL) + + # filter based on 'criteria' argument + search_kw = {} + config = ldap.get_ipa_config() + attrs = config.get(self.obj.search_attributes_config, []) + if len(attrs) == 1 and isinstance(attrs[0], six.string_types): + search_attrs = attrs[0].split(',') + for a in search_attrs: + search_kw[a] = criteria + cflt = ldap.make_filter(search_kw, exact=False) + + filter = ldap.combine_filters((oflt, cflt), rules=ldap.MATCH_ALL) + elif filters: + filters.append(filter) + filter = ldap.combine_filters(filters, rules=ldap.MATCH_ALL) + return (filter, base_dn, scope) + + +@register() +class group_show(LDAPRetrieve): + __doc__ = _('Display information about a named group.') + has_output_params = LDAPRetrieve.has_output_params + (ipaexternalmember_param,) + def post_callback(self, ldap, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) + if ('ipaexternalmember' in entry_attrs and + len(entry_attrs['ipaexternalmember']) > 0 and + 'trust_resolve' in self.Command and + not options.get('raw', False)): + sids = entry_attrs['ipaexternalmember'] + result = self.Command.trust_resolve(sids=sids) + for entry in result['result']: + try: + idx = sids.index(entry['sid'][0]) + sids[idx] = entry['name'][0] + except ValueError: + pass + return dn + + +@register() +class group_add_member(LDAPAddMember): + __doc__ = _('Add members to a group.') + + takes_options = (ipaexternalmember_param,) + + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) + result = (completed, dn) + if 'ipaexternalmember' in options: + if not _dcerpc_bindings_installed: + raise errors.NotFound(reason=_('Cannot perform external member validation without ' + 'Samba 4 support installed. Make sure you have installed ' + 'server-trust-ad sub-package of IPA on the server')) + domain_validator = ipaserver.dcerpc.DomainValidator(self.api) + if not domain_validator.is_configured(): + raise errors.NotFound(reason=_('Cannot perform join operation without own domain configured. ' + 'Make sure you have run ipa-adtrust-install on the IPA server first')) + sids = [] + failed_sids = [] + for sid in options['ipaexternalmember']: + if domain_validator.is_trusted_sid_valid(sid): + sids.append(sid) + else: + try: + actual_sid = domain_validator.get_trusted_domain_object_sid(sid) + except errors.PublicError as e: + failed_sids.append((sid, e.strerror)) + else: + sids.append(actual_sid) + restore = [] + if 'member' in failed and 'group' in failed['member']: + restore = failed['member']['group'] + failed['member']['group'] = list((id, id) for id in sids) + result = add_external_post_callback(ldap, dn, entry_attrs, + failed=failed, + completed=completed, + memberattr='member', + membertype='group', + externalattr='ipaexternalmember', + normalize=False) + failed['member']['group'] += restore + failed_sids + return result + + +@register() +class group_remove_member(LDAPRemoveMember): + __doc__ = _('Remove members from a group.') + + takes_options = (ipaexternalmember_param,) + + def pre_callback(self, ldap, dn, found, not_found, *keys, **options): + assert isinstance(dn, DN) + if keys[0] in PROTECTED_GROUPS and 'user' in options: + protected_group_name = keys[0] + result = api.Command.group_show(protected_group_name) + users_left = set(result['result'].get('member_user', [])) + users_deleted = set(options['user']) + if users_left.issubset(users_deleted): + raise errors.LastMemberError(key=sorted(users_deleted)[0], + label=_(u'group'), container=protected_group_name) + return dn + + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + assert isinstance(dn, DN) + result = (completed, dn) + if 'ipaexternalmember' in options: + if not _dcerpc_bindings_installed: + raise errors.NotFound(reason=_('Cannot perform external member validation without ' + 'Samba 4 support installed. Make sure you have installed ' + 'server-trust-ad sub-package of IPA on the server')) + domain_validator = ipaserver.dcerpc.DomainValidator(self.api) + if not domain_validator.is_configured(): + raise errors.NotFound(reason=_('Cannot perform join operation without own domain configured. ' + 'Make sure you have run ipa-adtrust-install on the IPA server first')) + sids = [] + failed_sids = [] + for sid in options['ipaexternalmember']: + if domain_validator.is_trusted_sid_valid(sid): + sids.append(sid) + else: + try: + actual_sid = domain_validator.get_trusted_domain_object_sid(sid) + except errors.PublicError as e: + failed_sids.append((sid, unicode(e))) + else: + sids.append(actual_sid) + restore = [] + if 'member' in failed and 'group' in failed['member']: + restore = failed['member']['group'] + failed['member']['group'] = list((id, id) for id in sids) + result = remove_external_post_callback(ldap, dn, entry_attrs, + failed=failed, + completed=completed, + memberattr='member', + membertype='group', + externalattr='ipaexternalmember', + ) + failed['member']['group'] += restore + failed_sids + return result + + +@register() +class group_detach(LDAPQuery): + __doc__ = _('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) + + try: + user_attrs = ldap.get_entry(user_dn) + except errors.NotFound: + self.obj.handle_not_found(*keys) + is_managed = self.obj.has_objectclass(user_attrs['objectclass'], 'mepmanagedentry') + if (not ldap.can_write(user_dn, "objectclass") or + not (ldap.can_write(user_dn, "mepManagedEntry")) and is_managed): + raise errors.ACIError(info=_('not allowed to modify user entries')) + + group_attrs = ldap.get_entry(group_dn) + is_managed = self.obj.has_objectclass(group_attrs['objectclass'], 'mepmanagedby') + if (not ldap.can_write(group_dn, "objectclass") or + not (ldap.can_write(group_dn, "mepManagedBy")) and is_managed): + raise errors.ACIError(info=_('not allowed to modify group entries')) + + objectclasses = user_attrs['objectclass'] + try: + i = objectclasses.index('mepOriginEntry') + del objectclasses[i] + user_attrs['mepManagedEntry'] = None + ldap.update_entry(user_attrs) + except ValueError: + # Somehow the user isn't managed, let it pass for now. We'll + # let the group throw "Not managed". + pass + + group_attrs = ldap.get_entry(group_dn) + objectclasses = group_attrs['objectclass'] + try: + i = objectclasses.index('mepManagedEntry') + except ValueError: + # this should never happen + raise errors.NotFound(reason=_('Not a managed group')) + del objectclasses[i] + + # Make sure the resulting group has the default group objectclasses + config = ldap.get_ipa_config() + def_objectclass = config.get( + self.obj.object_class_config, objectclasses + ) + objectclasses = list(set(def_objectclass + objectclasses)) + + group_attrs['mepManagedBy'] = None + group_attrs['objectclass'] = objectclasses + ldap.update_entry(group_attrs) + + return dict( + result=True, + value=pkey_to_value(keys[0], options), + ) + |