diff options
Diffstat (limited to 'ipalib')
-rw-r--r-- | ipalib/errors.py | 50 | ||||
-rw-r--r-- | ipalib/plugins/baseldap.py | 23 | ||||
-rw-r--r-- | ipalib/plugins/group.py | 108 | ||||
-rw-r--r-- | ipalib/plugins/trust.py | 4 |
4 files changed, 174 insertions, 11 deletions
diff --git a/ipalib/errors.py b/ipalib/errors.py index 407d9f7db..c25560b8e 100644 --- a/ipalib/errors.py +++ b/ipalib/errors.py @@ -1277,6 +1277,56 @@ class SingleMatchExpected(ExecutionError): format = _('The search criteria was not specific enough. Expected 1 and found %(found)d.') +class AlreadyExternalGroup(ExecutionError): + """ + **4028** Raised when a group is already an external member group + + For example: + + >>> raise AlreadyExternalGroup + Traceback (most recent call last): + ... + AlreadyExternalGroup: This group already allows external members + + """ + + errno = 4028 + format = _('This group already allows external members') + +class ExternalGroupViolation(ExecutionError): + """ + **4029** Raised when a group is already an external member group + and an attempt is made to use it as posix group + + For example: + + >>> raise ExternalGroupViolation + Traceback (most recent call last): + ... + ExternalGroupViolation: This group cannot be posix because it is external + + """ + + errno = 4029 + format = _('This group cannot be posix because it is external') + +class PosixGroupViolation(ExecutionError): + """ + **4030** Raised when a group is already a posix group + and cannot be converted to external + + For example: + + >>> raise PosixGroupViolation + Traceback (most recent call last): + ... + PosixGroupViolation: This is already a posix group and cannot be converted to external one + + """ + + errno = 4030 + format = _('This is already a posix group and cannot be converted to external one') + class BuiltinError(ExecutionError): """ **4100** Base class for builtin execution errors (*4100 - 4199*). diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py index 475222a6a..b841435fd 100644 --- a/ipalib/plugins/baseldap.py +++ b/ipalib/plugins/baseldap.py @@ -362,23 +362,29 @@ def add_external_post_callback(memberattr, membertype, externalattr, ldap, compl externalattr is one of externaluser, """ completed_external = 0 + normalize = options.get('external_callback_normalize', True) # Sift through the failures. We assume that these are all # entries that aren't stored in IPA, aka external entries. if memberattr in failed and membertype in failed[memberattr]: (dn, entry_attrs_) = ldap.get_entry(dn, [externalattr]) members = entry_attrs.get(memberattr, []) external_entries = entry_attrs_.get(externalattr, []) + lc_external_entries = set(e.lower() for e in external_entries) failed_entries = [] for entry in failed[memberattr][membertype]: membername = entry[0].lower() member_dn = api.Object[membertype].get_dn(membername) - if membername not in external_entries and \ - member_dn not in members: + if (membername not in lc_external_entries and + member_dn not in members): # Not an IPA entry, assume external - external_entries.append(membername) + if normalize: + external_entries.append(membername) + else: + external_entries.append(entry[0]) + lc_external_entries.add(membername) completed_external += 1 - elif membername in external_entries and \ - member_dn not in members: + elif (membername in lc_external_entries and + member_dn not in members): # Already an external member, reset the error message msg = unicode(errors.AlreadyGroupMember().message) newerror = (entry[0], msg) @@ -409,8 +415,11 @@ def remove_external_post_callback(memberattr, membertype, externalattr, ldap, co completed_external = 0 for entry in failed[memberattr][membertype]: membername = entry[0].lower() - if membername in external_entries: - external_entries.remove(membername) + if membername in external_entries or entry[0] in external_entries: + try: + external_entries.remove(membername) + except ValueError: + external_entries.remove(entry[0]) completed_external += 1 else: failed_entries.append(membername) diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py index 65657363a..74bea1700 100644 --- a/ipalib/plugins/group.py +++ b/ipalib/plugins/group.py @@ -22,6 +22,12 @@ from ipalib import api from ipalib import Int, Str from ipalib.plugins.baseldap import * from ipalib import _, ngettext +if api.env.in_server and api.env.context in ['lite', 'server']: + try: + import ipaserver.dcerpc + _dcerpc_bindings_installed = True + except Exception, e: + _dcerpc_bindings_installed = False __doc__ = _(""" Groups of users @@ -83,11 +89,11 @@ class group(LDAPObject): object_name_plural = _('groups') object_class = ['ipausergroup'] object_class_config = 'ipagroupobjectclasses' - possible_objectclasses = ['posixGroup', 'mepManagedEntry'] + possible_objectclasses = ['posixGroup', 'mepManagedEntry', 'ipaExternalGroup'] search_attributes_config = 'ipagroupsearchfields' default_attributes = [ 'cn', 'description', 'gidnumber', 'member', 'memberof', - 'memberindirect', 'memberofindirect', + 'memberindirect', 'memberofindirect', 'ipaexternalmember', ] uuid_attribute = 'ipauniqueid' attribute_members = { @@ -139,10 +145,22 @@ class group_add(LDAPCreate): 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): - if not options['nonposix']: + # 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 + if options['external']: + entry_attrs['objectclass'].append('ipaexternalgroup') + if 'gidnumber' in options: + raise errors.RequirementError(name='gid') + elif not options['nonposix']: entry_attrs['objectclass'].append('posixgroup') if not 'gidnumber' in options: entry_attrs['gidnumber'] = 999 @@ -194,11 +212,18 @@ class group_mod(LDAPUpdate): 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): - if options['posix'] or 'gidnumber' in options: + if ('posix' in options and options['posix']) or 'gidnumber' in options: (dn, old_entry_attrs) = ldap.get_entry(dn, ['objectclass']) + if 'ipaexternalgroup' in old_entry_attrs['objectclass']: + raise errors.ExternalGroupViolation() if 'posixgroup' in old_entry_attrs['objectclass']: if options['posix']: raise errors.AlreadyPosixGroup() @@ -207,6 +232,15 @@ class group_mod(LDAPUpdate): entry_attrs['objectclass'] = old_entry_attrs['objectclass'] if not 'gidnumber' in options: entry_attrs['gidnumber'] = 999 + if options['external']: + (dn, old_entry_attrs) = ldap.get_entry(dn, ['objectclass']) + 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='gid') @@ -274,12 +308,64 @@ api.register(group_show) class group_add_member(LDAPAddMember): __doc__ = _('Add members to a group.') + takes_options = ( + Str('ipaexternalmember*', + cli_name='external', + label=_('External member'), + doc=_('comma-separated SIDs of members of a trusted domain'), + csv=True, + flags=['no_create', 'no_update', 'no_search'], + ), + ) + + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + result = (completed, dn) + if 'ipaexternalmember' in options: + if not _dcerpc_bindings_installed: + raise errors.NotFound(name=_('AD Trust'), + 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(name=_('AD Trust setup'), + 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: + failed_sids.append((sid, 'Not a trusted domain SID')) + if len(sids) == 0: + raise errors.ValidationError(name=_('external member'), + error=_('values are not recognized as valid SIDs from trusted domain')) + 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('member', 'group', 'ipaexternalmember', + ldap, completed, failed, dn, entry_attrs, + keys, options, external_callback_normalize=False) + failed['member']['group'] = restore + failed_sids + return result + api.register(group_add_member) class group_remove_member(LDAPRemoveMember): __doc__ = _('Remove members from a group.') + takes_options = ( + Str('ipaexternalmember*', + cli_name='external', + label=_('External member'), + doc=_('comma-separated SIDs of members of a trusted domain'), + csv=True, + flags=['no_create', 'no_update', 'no_search'], + ), + ) + def pre_callback(self, ldap, dn, found, not_found, *keys, **options): if keys[0] == protected_group_name: result = api.Command.group_show(protected_group_name) @@ -290,6 +376,20 @@ class group_remove_member(LDAPRemoveMember): label=_(u'group'), container=protected_group_name) return dn + def post_callback(self, ldap, completed, failed, dn, entry_attrs, *keys, **options): + result = (completed, dn) + if 'ipaexternalmember' in options: + sids = options['ipaexternalmember'] + restore = list() + 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('member', 'group', 'ipaexternalmember', + ldap, completed, failed, dn, entry_attrs, + keys, options) + failed['member']['group'] = restore + return result + api.register(group_remove_member) diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py index 71ca23aba..40bd93e65 100644 --- a/ipalib/plugins/trust.py +++ b/ipalib/plugins/trust.py @@ -171,6 +171,10 @@ class trust_add(LDAPCreate): realm_server = options['realm_server'] trustinstance = ipaserver.dcerpc.TrustDomainJoins(self.api) + if not trustinstance.configured: + raise errors.NotFound(name=_('AD Trust setup'), + reason=_('''Cannot perform join operation without own domain configured. + Make sure you have run ipa-adtrust-install on the IPA server first''')) # 1. Full access to the remote domain. Use admin credentials and # generate random trustdom password to do work on both sides |