mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
690
ipaserver/plugins/group.py
Normal file
690
ipaserver/plugins/group.py
Normal file
@@ -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),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user