""") register = Registry() # Options used by Condition Add and Remove. INCLUDE_RE = 'automemberinclusiveregex' EXCLUDE_RE = 'automemberexclusiveregex' REBUILD_TASK_CONTAINER = DN(('cn', 'automember rebuild membership'), ('cn', 'tasks'), ('cn', 'config')) regex_attrs = ( Str( 'automemberinclusiveregex*', cli_name='inclusive_regex', label=_('Inclusive Regex'), doc=_('Inclusive Regex'), alwaysask=True, flags={'no_create', 'no_update', 'no_search'}, ), Str( 'automemberexclusiveregex*', cli_name='exclusive_regex', label=_('Exclusive Regex'), doc=_('Exclusive Regex'), alwaysask=True, flags={'no_create', 'no_update', 'no_search'}, ), ) regex_key = (Str( 'key',
class json_metadata(Command): """ Export plugin meta-data for the webUI. """ NO_CLI = True takes_args = ( Str('objname?', doc=_('Name of object to export'), ), Str('methodname?', doc=_('Name of method to export'), ), ) takes_options = ( Str('object?', doc=_('Name of object to export'), ), Str('method?', doc=_('Name of method to export'), ), Str('command?', doc=_('Name of command to export'), ), ) has_output = ( Output('objects', dict, doc=_('Dict of JSON encoded IPA Objects')), Output('methods', dict, doc=_('Dict of JSON encoded IPA Methods')), Output('commands', dict, doc=_('Dict of JSON encoded IPA Commands')), ) def execute(self, objname=None, methodname=None, **options): objects = dict() methods = dict() commands = dict() empty = True try: if not objname: objname = options['object'] if objname in self.api.Object: o = self.api.Object[objname] objects = dict([(o.name, json_serialize(o))]) elif objname == "all": objects = dict( (o.name, json_serialize(o)) for o in self.api.Object() if o is self.api.Object[o.name] ) empty = False except KeyError: pass try: if not methodname: methodname = options['method'] if (methodname in self.api.Method and not isinstance(self.api.Method[methodname], Local)): m = self.api.Method[methodname] methods = dict([(m.name, json_serialize(m))]) elif methodname == "all": methods = dict( (m.name, json_serialize(m)) for m in self.api.Method() if (m is self.api.Method[m.name] and not isinstance(m, Local)) ) empty = False except KeyError: pass try: cmdname = options['command'] if (cmdname in self.api.Command and not isinstance(self.api.Command[cmdname], Local)): c = self.api.Command[cmdname] commands = dict([(c.name, json_serialize(c))]) elif cmdname == "all": commands = dict( (c.name, json_serialize(c)) for c in self.api.Command() if (c is self.api.Command[c.name] and not isinstance(c, Local)) ) empty = False except KeyError: pass if empty: objects = dict( (o.name, json_serialize(o)) for o in self.api.Object() if o is self.api.Object[o.name] ) methods = dict( (m.name, json_serialize(m)) for m in self.api.Method() if (m is self.api.Method[m.name] and not isinstance(m, Local)) ) commands = dict( (c.name, json_serialize(c)) for c in self.api.Command() if (c is self.api.Command[c.name] and not isinstance(c, Local)) ) retval = dict([ ("objects", objects), ("methods", methods), ("commands", commands), ]) return retval
""") register = Registry() topic = 'sudo' def deprecated(attribute): raise errors.ValidationError(name=attribute, error=_('this option has been deprecated.')) hostmask_membership_param = Str( 'hostmask?', validate_hostmask, label=_('host masks of allowed hosts'), flags=['no_create', 'no_update', 'no_search'], multivalue=True, ) def validate_externaluser(ugettext, value): deprecated('externaluser') def validate_runasextuser(ugettext, value): deprecated('runasexternaluser') def validate_runasextgroup(ugettext, value): deprecated('runasexternalgroup')
class vault_archive(ModVaultData): __doc__ = _('Archive data into a vault.') takes_options = ( Bytes( 'data?', doc=_('Binary data to archive'), ), Str( # TODO: use File parameter 'in?', doc=_('File containing data to archive'), ), Str( 'password?', cli_name='password', doc=_('Vault password'), ), Str( # TODO: use File parameter 'password_file?', cli_name='password_file', doc=_('File containing the vault password'), ), Flag( 'override_password?', doc=_('Override existing password'), ), ) @classmethod def __NO_CLI_getter(cls): # pylint: disable=unused-private-member, #4756 return (api.Command.get_plugin('vault_archive_internal') is _fake_vault_archive_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_archive_internal.api_version def get_args(self): for arg in self.api.Command.vault_archive_internal.args(): yield arg for arg in super(vault_archive, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_archive_internal.options(): if option.name not in ('nonce', 'session_key', 'vault_data', 'version'): yield option for option in super(vault_archive, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_archive_internal.output_params(): yield param for param in super(vault_archive, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_archive_internal.output() def _wrap_data(self, algo, json_vault_data): """Encrypt data with wrapped session key and transport cert :param algo: wrapping algorithm instance :param bytes json_vault_data: dumped vault data :return: """ nonce = os.urandom(algo.block_size // 8) # wrap vault_data with session key padder = PKCS7(algo.block_size).padder() padded_data = padder.update(json_vault_data) padded_data += padder.finalize() cipher = Cipher(algo, modes.CBC(nonce), backend=default_backend()) encryptor = cipher.encryptor() wrapped_vault_data = encryptor.update( padded_data) + encryptor.finalize() return nonce, wrapped_vault_data def forward(self, *args, **options): data = options.get('data') input_file = options.get('in') password = options.get('password') password_file = options.get('password_file') override_password = options.pop('override_password', False) # don't send these parameters to server if 'data' in options: del options['data'] if 'in' in options: del options['in'] if 'password' in options: del options['password'] if 'password_file' in options: del options['password_file'] # get data if data and input_file: raise errors.MutuallyExclusiveError( reason=_('Input data specified multiple times')) elif data: if len(data) > MAX_VAULT_DATA_SIZE: raise errors.ValidationError( name="data", error= _("Size of data exceeds the limit. Current vault data size " "limit is %(limit)d B") % {'limit': MAX_VAULT_DATA_SIZE}) elif input_file: try: stat = os.stat(input_file) except OSError as exc: raise errors.ValidationError( name="in", error=_("Cannot read file '%(filename)s': %(exc)s") % { 'filename': input_file, 'exc': exc.args[1] }) if stat.st_size > MAX_VAULT_DATA_SIZE: raise errors.ValidationError( name="in", error= _("Size of data exceeds the limit. Current vault data size " "limit is %(limit)d B") % {'limit': MAX_VAULT_DATA_SIZE}) data = validated_read('in', input_file, mode='rb') else: data = b'' if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] vault_type = vault['ipavaulttype'][0] if vault_type == u'standard': encrypted_key = None elif vault_type == u'symmetric': # get password if password and password_file: raise errors.MutuallyExclusiveError( reason=_('Password specified multiple times')) elif password: pass elif password_file: password = validated_read('password-file', password_file, encoding='utf-8') password = password.rstrip('\n') else: if override_password: password = self.api.Backend.textui.prompt_password( 'New password') else: password = self.api.Backend.textui.prompt_password( 'Password', confirm=False) if not override_password: # verify password by retrieving existing data opts = options.copy() opts['password'] = password try: self.api.Command.vault_retrieve(*args, **opts) except errors.NotFound: pass salt = vault['ipavaultsalt'][0] # generate encryption key from vault password encryption_key = generate_symmetric_key(password, salt) # encrypt data with encryption key data = encrypt(data, symmetric_key=encryption_key) encrypted_key = None elif vault_type == u'asymmetric': public_key = vault['ipavaultpublickey'][0] # generate encryption key encryption_key = base64.b64encode(os.urandom(32)) # encrypt data with encryption key data = encrypt(data, symmetric_key=encryption_key) # encrypt encryption key with public key encrypted_key = encrypt(encryption_key, public_key=public_key) else: raise errors.ValidationError(name='vault_type', error=_('Invalid vault type')) vault_data = {'data': base64.b64encode(data).decode('utf-8')} if encrypted_key: vault_data[u'encrypted_key'] = base64.b64encode(encrypted_key)\ .decode('utf-8') json_vault_data = json.dumps(vault_data).encode('utf-8') # get config transport_cert, wrapping_algo = self._get_vaultconfig() # let options override wrapping algo # For backwards compatibility do not send old legacy wrapping algo # to server. Only send the option when non-3DES is used. wrapping_algo = options.pop('wrapping_algo', wrapping_algo) if wrapping_algo != constants.VAULT_WRAPPING_3DES: options['wrapping_algo'] = wrapping_algo # generate session key algo = self._generate_session_key(wrapping_algo) # wrap vault data nonce, wrapped_vault_data = self._wrap_data(algo, json_vault_data) options.update(nonce=nonce, vault_data=wrapped_vault_data) return self.internal(algo, transport_cert, *args, **options)
class krbtpolicy(baseldap.LDAPObject): """ Kerberos Ticket Policy object """ container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos')) object_name = _('kerberos ticket policy settings') default_attributes = ['krbmaxticketlife', 'krbmaxrenewableage'] limit_object_classes = ['krbticketpolicyaux'] # permission_filter_objectclasses is deliberately missing, # so it is not possible to create a permission of `--type krbtpolicy`. # This is because we need two permissions to cover both global and per-user # policies. managed_permissions = { 'System: Read Default Kerberos Ticket Policy': { 'non_object': True, 'replaces_global_anonymous_aci': True, 'ipapermtargetfilter': ['(objectclass=krbticketpolicyaux)'], 'ipapermlocation': DN(container_dn, api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'krbdefaultencsalttypes', 'krbmaxrenewableage', 'krbmaxticketlife', 'krbsupportedencsalttypes', 'objectclass', }, 'default_privileges': { 'Kerberos Ticket Policy Readers', }, }, 'System: Read User Kerberos Ticket Policy': { 'non_object': True, 'replaces_global_anonymous_aci': True, 'ipapermlocation': DN(api.env.container_user, api.env.basedn), 'ipapermtargetfilter': ['(objectclass=krbticketpolicyaux)'], 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'krbmaxrenewableage', 'krbmaxticketlife', }, 'default_privileges': { 'Kerberos Ticket Policy Readers', }, }, } label = _('Kerberos Ticket Policy') label_singular = _('Kerberos Ticket Policy') takes_params = ( Str('uid?', cli_name='user', label=_('User name'), doc=_('Manage ticket policy for specific user'), primary_key=True, ), Int('krbmaxticketlife?', cli_name='maxlife', label=_('Max life'), doc=_('Maximum ticket life (seconds)'), minvalue=1, ), Int('krbmaxrenewableage?', cli_name='maxrenew', label=_('Max renew'), doc=_('Maximum renewable age (seconds)'), minvalue=1, ), ) def get_dn(self, *keys, **kwargs): if keys[-1] is not None: return self.api.Object.user.get_dn(*keys, **kwargs) return DN(self.container_dn, api.env.basedn)
class caacl(LDAPObject): """ CA ACL object. """ container_dn = api.env.container_caacl object_name = _('CA ACL') object_name_plural = _('CA ACLs') object_class = ['ipaassociation', 'ipacaacl'] permission_filter_objectclasses = ['ipacaacl'] default_attributes = [ 'cn', 'description', 'ipaenabledflag', 'ipacacategory', 'ipamemberca', 'ipacertprofilecategory', 'ipamembercertprofile', 'usercategory', 'memberuser', 'hostcategory', 'memberhost', 'servicecategory', 'memberservice', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'memberservice': ['service'], 'ipamembercertprofile': ['certprofile'], } managed_permissions = { 'System: Read CA ACLs': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'description', 'ipaenabledflag', 'ipacacategory', 'ipamemberca', 'ipacertprofilecategory', 'ipamembercertprofile', 'usercategory', 'memberuser', 'hostcategory', 'memberhost', 'servicecategory', 'memberservice', 'ipauniqueid', 'objectclass', 'member', }, }, 'System: Add CA ACL': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Add CA ACL";allow (add) groupdn = "ldap:///cn=Add CA ACL,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, 'System: Delete CA ACL': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete CA ACL";allow (delete) groupdn = "ldap:///cn=Delete CA ACL,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, 'System: Manage CA ACL Membership': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'ipacacategory', 'ipamemberca', 'ipacertprofilecategory', 'ipamembercertprofile', 'usercategory', 'memberuser', 'hostcategory', 'memberhost', 'servicecategory', 'memberservice' }, 'replaces': [ '(targetattr = "ipamemberca || ipamembercertprofile || memberuser || memberservice || memberhost || ipacacategory || ipacertprofilecategory || usercategory || hostcategory || servicecategory")(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Manage CA ACL membership";allow (write) groupdn = "ldap:///cn=Manage CA ACL membership,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, 'System: Modify CA ACL': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'cn', 'description', 'ipaenabledflag', }, 'replaces': [ '(targetattr = "cn || description || ipaenabledflag")(target = "ldap:///ipauniqueid=*,cn=caacls,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify CA ACL";allow (write) groupdn = "ldap:///cn=Modify CA ACL,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, } label = _('CA ACLs') label_singular = _('CA ACL') takes_params = ( Str( 'cn', cli_name='name', label=_('ACL name'), primary_key=True, ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), # Commented until subca plugin arrives #StrEnum('ipacacategory?', # cli_name='cacat', # label=_('CA category'), # doc=_('CA category the ACL applies to'), # values=(u'all', ), #), StrEnum( 'ipacertprofilecategory?', cli_name='profilecat', label=_('Profile category'), doc=_('Profile category the ACL applies to'), values=(u'all', ), ), StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the ACL applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the ACL applies to'), values=(u'all', ), ), StrEnum( 'servicecategory?', cli_name='servicecat', label=_('Service category'), doc=_('Service category the ACL applies to'), values=(u'all', ), ), # Commented until subca plugin arrives #Str('ipamemberca_subca?', # label=_('CAs'), # flags=['no_create', 'no_update', 'no_search'], #), Str( 'ipamembercertprofile_certprofile?', label=_('Profiles'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberservice_service?', label=_('Services'), flags=['no_create', 'no_update', 'no_search'], ), )
class vault_add(Local): __doc__ = _('Create a new vault.') takes_options = ( Str( 'password?', cli_name='password', doc=_('Vault password'), ), Str( # TODO: use File parameter 'password_file?', cli_name='password_file', doc=_('File containing the vault password'), ), Str( # TODO: use File parameter 'public_key_file?', cli_name='public_key_file', doc=_('File containing the vault public key'), ), ) @classmethod def __NO_CLI_getter(cls): # pylint: disable=unused-private-member, #4756 return (api.Command.get_plugin('vault_add_internal') is _fake_vault_add_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_add_internal.api_version def get_args(self): for arg in self.api.Command.vault_add_internal.args(): yield arg for arg in super(vault_add, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_add_internal.options(): if option.name not in ('ipavaultsalt', 'version'): yield option for option in super(vault_add, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_add_internal.output_params(): yield param for param in super(vault_add, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_add_internal.output() def forward(self, *args, **options): vault_type = options.get('ipavaulttype') if vault_type is None: internal_cmd = self.api.Command.vault_add_internal vault_type = internal_cmd.params.ipavaulttype.default password = options.get('password') password_file = options.get('password_file') public_key = options.get('ipavaultpublickey') public_key_file = options.get('public_key_file') # don't send these parameters to server if 'password' in options: del options['password'] if 'password_file' in options: del options['password_file'] if 'public_key_file' in options: del options['public_key_file'] if vault_type != u'symmetric' and (password or password_file): raise errors.MutuallyExclusiveError( reason=_('Password can be specified only for ' 'symmetric vault')) if vault_type != u'asymmetric' and (public_key or public_key_file): raise errors.MutuallyExclusiveError( reason=_('Public key can be specified only for ' 'asymmetric vault')) if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() if vault_type == u'standard': pass elif vault_type == u'symmetric': # get password if password and password_file: raise errors.MutuallyExclusiveError( reason=_('Password specified multiple times')) elif password: pass elif password_file: password = validated_read('password-file', password_file, encoding='utf-8') password = password.rstrip('\n') else: password = self.api.Backend.textui.prompt_password( 'New password') # generate vault salt options['ipavaultsalt'] = os.urandom(16) elif vault_type == u'asymmetric': # get new vault public key if public_key and public_key_file: raise errors.MutuallyExclusiveError( reason=_('Public key specified multiple times')) elif public_key: pass elif public_key_file: public_key = validated_read('public-key-file', public_key_file, mode='rb') # store vault public key options['ipavaultpublickey'] = public_key else: raise errors.ValidationError( name='ipavaultpublickey', error=_('Missing vault public key')) # validate public key and prevent users from accidentally # sending a private key to the server. try: load_pem_public_key(data=public_key, backend=default_backend()) except ValueError as e: raise errors.ValidationError( name='ipavaultpublickey', error=_('Invalid or unsupported vault public key: %s') % e, ) # create vault response = self.api.Command.vault_add_internal(*args, **options) # prepare parameters for archival opts = options.copy() if 'description' in opts: del opts['description'] if 'ipavaulttype' in opts: del opts['ipavaulttype'] if vault_type == u'symmetric': opts['password'] = password del opts['ipavaultsalt'] elif vault_type == u'asymmetric': del opts['ipavaultpublickey'] # archive blank data self.api.Command.vault_archive(*args, **opts) return response
class idrange(LDAPObject): """ Range object. """ range_type = ('domain', 'ad', 'ipa') container_dn = api.env.container_ranges object_name = ('range') object_name_plural = ('ranges') object_class = ['ipaIDrange'] possible_objectclasses = ['ipadomainidrange', 'ipatrustedaddomainrange'] default_attributes = [ 'cn', 'ipabaseid', 'ipaidrangesize', 'ipabaserid', 'ipasecondarybaserid', 'ipanttrusteddomainsid', 'iparangetype' ] label = _('ID Ranges') label_singular = _('ID Range') takes_params = (Str( 'cn', cli_name='name', label=_('Range name'), primary_key=True, ), Int( 'ipabaseid', cli_name='base_id', label=_("First Posix ID of the range"), ), Int( 'ipaidrangesize', cli_name='range_size', label=_("Number of IDs in the range"), ), Int( 'ipabaserid?', cli_name='rid_base', label=_('First RID of the corresponding RID range'), ), Int( 'ipasecondarybaserid?', cli_name='secondary_rid_base', label=_('First RID of the secondary RID range'), ), Str( 'ipanttrusteddomainsid?', cli_name='dom_sid', label=_('Domain SID of the trusted domain'), ), Str( 'iparangetype?', label=_('Range type'), flags=['no_option'], )) def handle_iparangetype(self, entry_attrs, options, keep_objectclass=False): if not options.get('pkey_only', False): if 'ipatrustedaddomainrange' in entry_attrs.get('objectclass', []): entry_attrs['iparangetype'] = [ unicode(_('Active Directory domain range')) ] else: entry_attrs['iparangetype'] = [ unicode(_(u'local domain range')) ] if not keep_objectclass: if not options.get('all', False) or options.get( 'pkey_only', False): entry_attrs.pop('objectclass', None) def check_ids_in_modified_range(self, old_base, old_size, new_base, new_size): if new_base is None and new_size is None: # nothing to check return if new_base is None: new_base = old_base if new_size is None: new_size = old_size old_interval = (old_base, old_base + old_size - 1) new_interval = (new_base, new_base + new_size - 1) checked_intervals = [] low_diff = new_interval[0] - old_interval[0] if low_diff > 0: checked_intervals.append( (old_interval[0], min(old_interval[1], new_interval[0] - 1))) high_diff = old_interval[1] - new_interval[1] if high_diff > 0: checked_intervals.append( (max(old_interval[0], new_interval[1] + 1), old_interval[1])) if not checked_intervals: # range is equal or covers the entire old range, nothing to check return ldap = self.backend id_filter_base = [ "(objectclass=posixAccount)", "(objectclass=posixGroup)", "(objectclass=ipaIDObject)" ] id_filter_ids = [] for id_low, id_high in checked_intervals: id_filter_ids.append( "(&(uidNumber>=%(low)d)(uidNumber<=%(high)d))" % dict(low=id_low, high=id_high)) id_filter_ids.append( "(&(gidNumber>=%(low)d)(gidNumber<=%(high)d))" % dict(low=id_low, high=id_high)) id_filter = ldap.combine_filters([ ldap.combine_filters(id_filter_base, "|"), ldap.combine_filters(id_filter_ids, "|") ], "&") try: (objects, truncated) = ldap.find_entries( filter=id_filter, attrs_list=['uid', 'cn'], base_dn=DN(api.env.container_accounts, api.env.basedn)) except errors.NotFound: # no objects in this range found, allow the command pass else: raise errors.ValidationError( name="ipabaseid,ipaidrangesize", error=_('range modification leaving objects with ID out ' 'of the defined range is not allowed')) def validate_trusted_domain_sid(self, sid): if not _dcerpc_bindings_installed: raise errors.NotFound(reason=_( 'Cannot perform SID 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=_( 'Cross-realm trusts are not configured. ' 'Make sure you have run ipa-adtrust-install on the IPA server first' )) if not domain_validator.is_trusted_sid_valid(sid): raise errors.ValidationError( name='domain SID', error=_( 'SID is not recognized as a valid SID for a trusted domain' ))
class baseuser(LDAPObject): """ baseuser object. """ stage_container_dn = api.env.container_stageuser active_container_dn = api.env.container_user delete_container_dn = api.env.container_deleteuser object_class = ['posixaccount'] object_class_config = 'ipauserobjectclasses' possible_objectclasses = [ 'meporiginentry', 'ipauserauthtypeclass', 'ipauser', 'ipatokenradiusproxyuser', 'ipacertmapobject' ] disallow_object_classes = ['krbticketpolicyaux'] permission_filter_objectclasses = ['posixaccount'] search_attributes_config = 'ipausersearchfields' default_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'uidnumber', 'gidnumber', 'mail', 'ou', 'telephonenumber', 'title', 'memberof', 'nsaccountlock', 'memberofindirect', 'ipauserauthtype', 'userclass', 'ipatokenradiusconfiglink', 'ipatokenradiususername', 'krbprincipalexpiration', 'usercertificate;binary', 'krbprincipalname', 'krbcanonicalname', 'ipacertmapdata' ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname', 'krbprincipalname', 'loginshell', 'mail', 'telephonenumber', 'title', 'nsaccountlock', 'uidnumber', 'gidnumber', 'sshpubkeyfp', ] uuid_attribute = 'ipauniqueid' attribute_members = { 'manager': ['user'], 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], } allow_rename = True bindable = True password_attributes = [('userpassword', 'has_password'), ('krbprincipalkey', 'has_keytab')] label = _('Users') label_singular = _('User') takes_params = ( Str('uid', pattern=PATTERN_GROUPUSER_NAME, pattern_errmsg='may only include letters, numbers, _, -, . and $', maxlength=255, cli_name='login', label=_('User login'), primary_key=True, default_from=lambda givenname, sn: givenname[0] + sn, normalizer=lambda value: value.lower(), ), Str('givenname', cli_name='first', label=_('First name'), ), Str('sn', cli_name='last', label=_('Last name'), ), Str('cn', label=_('Full name'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('displayname?', label=_('Display name'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('initials?', label=_('Initials'), default_from=lambda givenname, sn: '%c%c' % (givenname[0], sn[0]), autofill=True, ), Str('homedirectory?', cli_name='homedir', label=_('Home directory'), ), Str('gecos?', label=_('GECOS'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('loginshell?', cli_name='shell', label=_('Login shell'), ), Principal( 'krbcanonicalname?', validate_realm, label=_('Principal name'), flags={'no_option', 'no_create', 'no_update', 'no_search'}, normalizer=normalize_user_principal ), Principal( 'krbprincipalname*', validate_realm, cli_name='principal', label=_('Principal alias'), default_from=lambda uid: kerberos.Principal( uid.lower(), realm=api.env.realm), autofill=True, normalizer=normalize_user_principal, ), DateTime('krbprincipalexpiration?', cli_name='principal_expiration', label=_('Kerberos principal expiration'), ), DateTime('krbpasswordexpiration?', cli_name='password_expiration', label=_('User password expiration'), ), Str('mail*', cli_name='email', label=_('Email address'), ), Password('userpassword?', cli_name='password', label=_('Password'), doc=_('Prompt to set the user password'), # FIXME: This is temporary till bug is fixed causing updates to # bomb out via the webUI. exclude='webui', ), Flag('random?', doc=_('Generate a random user password'), flags=('no_search', 'virtual_attribute'), default=False, ), Str('randompassword?', label=_('Random password'), flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'), ), Int('uidnumber?', cli_name='uid', label=_('UID'), doc=_('User ID Number (system will assign one if not provided)'), minvalue=1, ), Int('gidnumber?', label=_('GID'), doc=_('Group ID Number'), minvalue=1, ), Str('street?', cli_name='street', label=_('Street address'), ), Str('l?', cli_name='city', label=_('City'), ), Str('st?', cli_name='state', label=_('State/Province'), ), Str('postalcode?', label=_('ZIP'), ), Str('telephonenumber*', cli_name='phone', label=_('Telephone Number') ), Str('mobile*', label=_('Mobile Telephone Number') ), Str('pager*', label=_('Pager Number') ), Str('facsimiletelephonenumber*', cli_name='fax', label=_('Fax Number'), ), Str('ou?', cli_name='orgunit', label=_('Org. Unit'), ), Str('title?', label=_('Job Title'), ), # keep backward compatibility using single value manager option Str('manager?', label=_('Manager'), ), Str('carlicense*', label=_('Car License'), ), Str('ipasshpubkey*', validate_sshpubkey, cli_name='sshpubkey', label=_('SSH public key'), normalizer=normalize_sshpubkey, flags=['no_search'], ), Str('sshpubkeyfp*', label=_('SSH public key fingerprint'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), StrEnum('ipauserauthtype*', cli_name='user_auth_type', label=_('User authentication types'), doc=_('Types of supported user authentication'), values=(u'password', u'radius', u'otp'), ), Str('userclass*', cli_name='class', label=_('Class'), doc=_('User category (semantics placed on this attribute are for ' 'local interpretation)'), ), Str('ipatokenradiusconfiglink?', cli_name='radius', label=_('RADIUS proxy configuration'), ), Str('ipatokenradiususername?', cli_name='radius_username', label=_('RADIUS proxy username'), ), Str('departmentnumber*', label=_('Department Number'), ), Str('employeenumber?', label=_('Employee Number'), ), Str('employeetype?', label=_('Employee Type'), ), Str('preferredlanguage?', label=_('Preferred Language'), pattern=( r'^(([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?' r'(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?' r'(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?' r'(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?)*)|(\*))$' ), pattern_errmsg='must match RFC 2068 - 14.4, e.g., "da, en-gb;q=0.8, en;q=0.7"', ), Certificate('usercertificate*', cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded user certificate'), ), Str( 'ipacertmapdata*', cli_name='certmapdata', label=_('Certificate mapping data'), doc=_('Certificate mapping data'), flags=['no_create', 'no_update', 'no_search'], ), ) def normalize_and_validate_email(self, email, config=None): if not config: config = self.backend.get_ipa_config() # check if default email domain should be added defaultdomain = config.get('ipadefaultemaildomain', [None])[0] if email: norm_email = [] if not isinstance(email, (list, tuple)): email = [email] for m in email: if isinstance(m, str): if '@' not in m and defaultdomain: m = m + u'@' + defaultdomain if not Email(m): raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m)) norm_email.append(m) else: if not Email(m): raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m)) norm_email.append(m) return norm_email return email def normalize_manager(self, manager, container): """ Given a userid verify the user's existence (in the appropriate containter) and return the dn. """ if not manager: return None if not isinstance(manager, list): manager = [manager] try: container_dn = DN(container, api.env.basedn) for i, mgr in enumerate(manager): if isinstance(mgr, DN) and mgr.endswith(container_dn): continue entry_attrs = self.backend.find_entry_by_attr( self.primary_key.name, mgr, self.object_class, [''], container_dn ) manager[i] = entry_attrs.dn except errors.NotFound: raise errors.NotFound(reason=_('manager %(manager)s not found') % dict(manager=mgr)) return manager def _user_status(self, user, container): assert isinstance(user, DN) return user.endswith(container) def active_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.active_container_dn, api.env.basedn)) def stage_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.stage_container_dn, api.env.basedn)) def delete_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.delete_container_dn, api.env.basedn)) def convert_usercertificate_pre(self, entry_attrs): if 'usercertificate' in entry_attrs: entry_attrs['usercertificate;binary'] = entry_attrs.pop( 'usercertificate') def convert_usercertificate_post(self, entry_attrs, **options): if 'usercertificate;binary' in entry_attrs: entry_attrs['usercertificate'] = entry_attrs.pop( 'usercertificate;binary') def convert_attribute_members(self, entry_attrs, *keys, **options): super(baseuser, self).convert_attribute_members( entry_attrs, *keys, **options) if options.get("raw", False): return # due the backward compatibility, managers have to be returned in # 'manager' attribute instead of 'manager_user' try: entry_attrs['failed_manager'] = entry_attrs.pop('manager') except KeyError: pass try: entry_attrs['manager'] = entry_attrs.pop('manager_user') except KeyError: pass
class server(LDAPObject): """ IPA server """ container_dn = api.env.container_masters object_name = _('server') object_name_plural = _('servers') object_class = ['top'] possible_objectclasses = ['ipaLocationMember'] search_attributes = ['cn'] default_attributes = [ 'cn', 'iparepltopomanagedsuffix', 'ipamindomainlevel', 'ipamaxdomainlevel', 'ipalocation', 'ipaserviceweight' ] label = _('IPA Servers') label_singular = _('IPA Server') attribute_members = { 'iparepltopomanagedsuffix': ['topologysuffix'], 'ipalocation': ['location'], 'role': ['servrole'], } relationships = { 'iparepltopomanagedsuffix': ('Managed', '', 'no_'), 'ipalocation': ('IPA', 'in_', 'not_in_'), 'role': ('Enabled', '', 'no_'), } permission_filter_objectclasses = ['ipaConfigObject'] managed_permissions = { 'System: Read Locations of IPA Servers': { 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'ipalocation', 'ipaserviceweight', }, 'default_privileges': {'DNS Administrators'}, }, 'System: Read Status of Services on IPA Servers': { 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': {'objectclass', 'cn', 'ipaconfigstring'}, 'default_privileges': {'DNS Administrators'}, } } takes_params = ( Str( 'cn', cli_name='name', primary_key=True, label=_('Server name'), doc=_('IPA server hostname'), ), Str( 'iparepltopomanagedsuffix*', flags={'no_create', 'no_update', 'no_search'}, ), Str( 'iparepltopomanagedsuffix_topologysuffix*', label=_('Managed suffixes'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Int( 'ipamindomainlevel', cli_name='minlevel', label=_('Min domain level'), doc=_('Minimum domain level'), flags={'no_create', 'no_update'}, ), Int( 'ipamaxdomainlevel', cli_name='maxlevel', label=_('Max domain level'), doc=_('Maximum domain level'), flags={'no_create', 'no_update'}, ), DNSNameParam( 'ipalocation_location?', cli_name='location', label=_('Location'), doc=_('Server location'), only_relative=True, flags={'no_search'}, ), Int( 'ipaserviceweight?', cli_name='service_weight', label=_('Service weight'), doc=_('Weight for server services'), minvalue=0, maxvalue=65535, flags={'no_search'}, ), Str( 'service_relative_weight', label=_('Service relative weight'), doc=_('Relative weight for server services (counts per location)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str('enabled_role_servrole*', label=_('Enabled server roles'), doc=_('List of enabled roles'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}), ) def _get_suffixes(self): suffixes = self.api.Command.topologysuffix_find( all=True, raw=True, )['result'] suffixes = [(s['iparepltopoconfroot'][0], s['dn']) for s in suffixes] return suffixes def _apply_suffixes(self, entry, suffixes): # change suffix DNs to topologysuffix entry DNs # this fixes LDAPObject.convert_attribute_members() for suffixes suffixes = dict(suffixes) if 'iparepltopomanagedsuffix' in entry: entry['iparepltopomanagedsuffix'] = [ suffixes.get(m, m) for m in entry['iparepltopomanagedsuffix'] ] def normalize_location(self, kw, **options): """ Return the DN of location """ if 'ipalocation_location' in kw: location = kw.pop('ipalocation_location') kw['ipalocation'] = ([self.api.Object.location.get_dn(location)] if location is not None else location) def convert_location(self, entry_attrs, **options): """ Return a location name from DN """ if options.get('raw'): return converted_locations = [ DNSName(location_dn['idnsname']) for location_dn in entry_attrs.pop('ipalocation', []) ] if converted_locations: entry_attrs['ipalocation_location'] = converted_locations def get_enabled_roles(self, entry_attrs, **options): if not options.get('all', False) and options.get('no_members', False): return if options.get('raw', False): return enabled_roles = self.api.Command.server_role_find( server_server=entry_attrs['cn'][0], status=ENABLED, include_master=True, )['result'] enabled_role_names = [r[u'role_servrole'] for r in enabled_roles] entry_attrs['enabled_role_servrole'] = enabled_role_names
class radiusproxy(LDAPObject): """ RADIUS Server object. """ container_dn = api.env.container_radiusproxy object_name = _('RADIUS proxy server') object_name_plural = _('RADIUS proxy servers') object_class = ['ipatokenradiusconfiguration'] default_attributes = [ 'cn', 'description', 'ipatokenradiusserver', 'ipatokenradiustimeout', 'ipatokenradiusretries', 'ipatokenusermapattribute' ] search_attributes = ['cn', 'description', 'ipatokenradiusserver'] rdn_is_primary_key = True label = _('RADIUS Servers') label_singular = _('RADIUS Server') takes_params = ( Str( 'cn', cli_name='name', label=_('RADIUS proxy server name'), primary_key=True, ), Str( 'description?', cli_name='desc', label=_('Description'), doc=_('A description of this RADIUS proxy server'), ), Str( 'ipatokenradiusserver+', validate_radiusserver, cli_name='server', label=_('Server'), doc=_('The hostname or IP (with or without port)'), ), Password( 'ipatokenradiussecret', cli_name='secret', label=_('Secret'), doc=_('The secret used to encrypt data'), confirm=True, flags=['no_option'], ), Int( 'ipatokenradiustimeout?', cli_name='timeout', label=_('Timeout'), doc=_('The total timeout across all retries (in seconds)'), minvalue=1, ), Int( 'ipatokenradiusretries?', cli_name='retries', label=_('Retries'), doc=_('The number of times to retry authentication'), minvalue=0, maxvalue=10, ), Str( 'ipatokenusermapattribute?', validate_attributename, cli_name='userattr', label=_('User attribute'), doc=_('The username attribute on the user object'), ), )
class automember_rebuild(Method): __doc__ = _('Rebuild auto membership.') obj_name = 'automember_task' attr_name = 'rebuild' # TODO: Add a --dry-run option: # https://fedorahosted.org/freeipa/ticket/3936 takes_options = ( group_type[0].clone( required=False, label=_('Rebuild membership for all members of a grouping')), Str( 'users*', label=_('Users'), doc=_('Rebuild membership for specified users'), ), Str( 'hosts*', label=_('Hosts'), doc=_('Rebuild membership for specified hosts'), ), Flag( 'no_wait?', default=False, label=_('No wait'), doc=_("Don't wait for rebuilding membership"), ), ) has_output = output.standard_entry def validate(self, **kw): """ Validation rules: - at least one of 'type', 'users', 'hosts' is required - 'users' and 'hosts' cannot be combined together - if 'users' and 'type' are specified, 'type' must be 'group' - if 'hosts' and 'type' are specified, 'type' must be 'hostgroup' """ super(automember_rebuild, self).validate(**kw) users, hosts, gtype = kw.get('users'), kw.get('hosts'), kw.get('type') if not (gtype or users or hosts): raise errors.MutuallyExclusiveError( reason=_('at least one of options: type, users, hosts must be ' 'specified')) if users and hosts: raise errors.MutuallyExclusiveError( reason=_("users and hosts cannot both be set")) if gtype == 'group' and hosts: raise errors.MutuallyExclusiveError( reason=_("hosts cannot be set when type is 'group'")) if gtype == 'hostgroup' and users: raise errors.MutuallyExclusiveError( reason=_("users cannot be set when type is 'hostgroup'")) def execute(self, *keys, **options): ldap = self.api.Backend.ldap2 cn = str(uuid.uuid4()) gtype = options.get('type') if not gtype: gtype = 'group' if options.get('users') else 'hostgroup' types = { 'group': ('user', 'users', DN(api.env.container_user, api.env.basedn)), 'hostgroup': ('host', 'hosts', DN(api.env.container_host, api.env.basedn)), } obj_name, opt_name, basedn = types[gtype] obj = self.api.Object[obj_name] names = options.get(opt_name) if names: for name in names: try: obj.get_dn_if_exists(name) except errors.NotFound: raise obj.handle_not_found(name) search_filter = ldap.make_filter_from_attr(obj.primary_key.name, names, rules=ldap.MATCH_ANY) else: search_filter = '(%s=*)' % obj.primary_key.name task_dn = DN(('cn', cn), REBUILD_TASK_CONTAINER) entry = ldap.make_entry(task_dn, objectclass=['top', 'extensibleObject'], cn=[cn], basedn=[basedn], filter=[search_filter], scope=['sub'], ttl=[3600]) ldap.add_entry(entry) summary = _('Automember rebuild membership task started') result = {'dn': task_dn} if not options.get('no_wait'): summary = _('Automember rebuild membership task completed') result = {} start_time = time.time() while True: try: task = ldap.get_entry(task_dn) except errors.NotFound: break if 'nstaskexitcode' in task: if str(task.single_value['nstaskexitcode']) == '0': summary = task.single_value['nstaskstatus'] break raise errors.DatabaseError( desc=task.single_value['nstaskstatus'], info=_("Task DN = '%s'" % task_dn)) time.sleep(1) if time.time() > (start_time + 60): raise errors.TaskTimeout(task=_('Automember'), task_dn=task_dn) return dict(result=result, summary=unicode(summary), value=pkey_to_value(None, options))
class automember_add_condition(LDAPUpdate): __doc__ = _(""" Add conditions to an automember rule. """) has_output_params = (Str( 'failed', label=_('Failed to add'), flags=['suppress_empty'], ), ) takes_options = regex_attrs + regex_key + group_type msg_summary = _('Added condition(s) to "%(value)s"') # Prepare the output to expect failed results has_output = ( output.summary, output.Entry('result'), output.value, output.Output( 'failed', type=dict, doc=_('Conditions that could not be added'), ), output.Output( 'completed', type=int, doc=_('Number of conditions added'), ), ) def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) # Check to see if the automember rule exists try: dn = ldap.get_entry(dn, []).dn except errors.NotFound: raise errors.NotFound( reason=_(u'Auto member rule: %s not found!') % keys[0]) # Define container key key = options['key'] # Check to see if the attribute is valid self.obj.check_attr(key) key = '%s=' % key completed = 0 failed = {'failed': {}} for attr in (INCLUDE_RE, EXCLUDE_RE): failed['failed'][attr] = [] if attr in options and options[attr]: entry_attrs[attr] = [ key + condition for condition in options[attr] ] completed += len(entry_attrs[attr]) try: old_entry = ldap.get_entry(dn, [attr]) for regex in old_entry.keys(): if not isinstance(entry_attrs[regex], (list, tuple)): entry_attrs[regex] = [entry_attrs[regex]] duplicate = set(old_entry[regex]) & set( entry_attrs[regex]) if len(duplicate) > 0: completed -= 1 else: entry_attrs[regex] = list( entry_attrs[regex]) + old_entry[regex] except errors.NotFound: failed['failed'][attr].append(regex) entry_attrs = entry_to_dict(entry_attrs, **options) # Set failed and completed to they can be harvested in the execute super setattr(context, 'failed', failed) setattr(context, 'completed', completed) setattr(context, 'entry_attrs', entry_attrs) # Make sure to returned the failed results if there is nothing to remove if completed == 0: ldap.get_entry(dn, attrs_list) raise errors.EmptyModlist return dn def execute(self, *keys, **options): __doc__ = _(""" Override this so we can add completed and failed to the return result. """) try: result = super(automember_add_condition, self).execute(*keys, **options) except errors.EmptyModlist: result = { 'result': getattr(context, 'entry_attrs'), 'value': keys[-1] } result['failed'] = getattr(context, 'failed') result['completed'] = getattr(context, 'completed') result['value'] = pkey_to_value(keys[-1], options) return result
class automember(LDAPObject): """ Bring automember to a hostgroup with an Auto Membership Rule. """ container_dn = api.env.container_automember object_name = 'Automember rule' object_name_plural = 'Automember rules' object_class = ['top', 'automemberregexrule'] permission_filter_objectclasses = ['automemberregexrule'] default_attributes = [ 'automemberinclusiveregex', 'automemberexclusiveregex', 'cn', 'automembertargetgroup', 'description', 'automemberdefaultgroup' ] managed_permissions = { 'System: Read Automember Definitions': { 'non_object': True, 'ipapermlocation': DN(container_dn, api.env.basedn), 'ipapermtargetfilter': {'(objectclass=automemberdefinition)'}, 'replaces_global_anonymous_aci': True, 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'automemberscope', 'automemberfilter', 'automembergroupingattr', 'automemberdefaultgroup', 'automemberdisabled', }, 'default_privileges': {'Automember Readers', 'Automember Task Administrator'}, }, 'System: Read Automember Rules': { 'replaces_global_anonymous_aci': True, 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'objectclass', 'automembertargetgroup', 'description', 'automemberexclusiveregex', 'automemberinclusiveregex', }, 'default_privileges': {'Automember Readers', 'Automember Task Administrator'}, }, 'System: Read Automember Tasks': { 'non_object': True, 'ipapermlocation': DN('cn=tasks', 'cn=config'), 'ipapermtarget': DN('cn=*', REBUILD_TASK_CONTAINER), 'replaces_global_anonymous_aci': True, 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': {'*'}, 'default_privileges': {'Automember Task Administrator'}, }, } label = _('Auto Membership Rule') takes_params = ( Str( 'cn', cli_name='automember_rule', label=_('Automember Rule'), doc=_('Automember Rule'), primary_key=True, normalizer=lambda value: value.lower(), flags={'no_search'}, ), Str( 'description?', cli_name='desc', label=_('Description'), doc=_('A description of this auto member rule'), ), Str('automemberdefaultgroup?', cli_name='default_group', label=_('Default (fallback) Group'), doc=_('Default group for entries to land'), flags=['no_create', 'no_update', 'no_search']), ) + regex_attrs def dn_exists(self, otype, oname): ldap = self.api.Backend.ldap2 dn = self.api.Object[otype].get_dn(oname) try: entry = ldap.get_entry(dn, []) except errors.NotFound: raise errors.NotFound( reason=_(u'%(otype)s "%(oname)s" not found') % dict(otype=otype, oname=oname)) return entry.dn def get_dn(self, *keys, **options): if self.parent_object: parent_dn = self.api.Object[self.parent_object].get_dn(*keys[:-1]) else: parent_dn = DN(self.container_dn, api.env.basedn) grouptype = options['type'] try: ndn = DN(('cn', keys[-1]), ('cn', grouptype), parent_dn) except IndexError: ndn = DN(('cn', grouptype), parent_dn) return ndn def check_attr(self, attr): """ Verify that the user supplied key is a valid attribute in the schema """ ldap = self.api.Backend.ldap2 obj = ldap.schema.get_obj(_ldap.schema.AttributeType, attr) if obj is not None: return obj else: raise errors.NotFound(reason=_('%s is not a valid attribute.') % attr)
class hbacrule(LDAPObject): """ HBAC object. """ container_dn = api.env.container_hbac object_name = _('HBAC rule') object_name_plural = _('HBAC rules') object_class = ['ipaassociation', 'ipahbacrule'] permission_filter_objectclasses = ['ipahbacrule'] default_attributes = [ 'cn', 'ipaenabledflag', 'description', 'usercategory', 'hostcategory', 'servicecategory', 'ipaenabledflag', 'memberuser', 'sourcehost', 'memberhost', 'memberservice', 'externalhost', ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'sourcehost': ['host', 'hostgroup'], 'memberservice': ['hbacsvc', 'hbacsvcgroup'], } managed_permissions = { 'System: Read HBAC Rules': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'accessruletype', 'accesstime', 'cn', 'description', 'externalhost', 'hostcategory', 'ipaenabledflag', 'ipauniqueid', 'memberhost', 'memberservice', 'memberuser', 'servicecategory', 'sourcehost', 'sourcehostcategory', 'usercategory', 'objectclass', 'member', }, }, 'System: Add HBAC Rule': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Add HBAC rule";allow (add) groupdn = "ldap:///cn=Add HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Delete HBAC Rule': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Delete HBAC rule";allow (delete) groupdn = "ldap:///cn=Delete HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Manage HBAC Rule Membership': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'externalhost', 'memberhost', 'memberservice', 'memberuser' }, 'replaces': [ '(targetattr = "memberuser || externalhost || memberservice || memberhost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Manage HBAC rule membership";allow (write) groupdn = "ldap:///cn=Manage HBAC rule membership,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, 'System: Modify HBAC Rule': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'accessruletype', 'accesstime', 'cn', 'description', 'hostcategory', 'ipaenabledflag', 'servicecategory', 'sourcehost', 'sourcehostcategory', 'usercategory' }, 'replaces': [ '(targetattr = "servicecategory || sourcehostcategory || cn || description || ipaenabledflag || accesstime || usercategory || hostcategory || accessruletype || sourcehost")(target = "ldap:///ipauniqueid=*,cn=hbac,$SUFFIX")(version 3.0;acl "permission:Modify HBAC rule";allow (write) groupdn = "ldap:///cn=Modify HBAC rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'HBAC Administrator'}, }, } label = _('HBAC Rules') label_singular = _('HBAC Rule') takes_params = ( Str('cn', cli_name='name', label=_('Rule name'), primary_key=True, ), StrEnum('accessruletype', validate_type, cli_name='type', doc=_('Rule type (allow)'), label=_('Rule type'), values=(u'allow', u'deny'), default=u'allow', autofill=True, exclude='webui', flags=['no_option', 'no_output'], ), # FIXME: {user,host,service}categories should expand in the future StrEnum('usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum('hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), StrEnum('sourcehostcategory?', deprecated=True, cli_name='srchostcat', label=_('Source host category'), doc=_('Source host category the rule applies to'), values=(u'all', ), flags={'no_option'}, ), StrEnum('servicecategory?', cli_name='servicecat', label=_('Service category'), doc=_('Service category the rule applies to'), values=(u'all', ), ), # AccessTime('accesstime?', # cli_name='time', # label=_('Access time'), # ), Str('description?', cli_name='desc', label=_('Description'), ), Bool('ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), Str('memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str('sourcehost_host?', deprecated=True, label=_('Source Hosts'), flags=['no_create', 'no_update', 'no_search', 'no_option'], ), Str('sourcehost_hostgroup?', deprecated=True, label=_('Source Host Groups'), flags=['no_create', 'no_update', 'no_search', 'no_option'], ), Str('memberservice_hbacsvc?', label=_('Services'), flags=['no_create', 'no_update', 'no_search'], ), Str('memberservice_hbacsvcgroup?', label=_('Service Groups'), flags=['no_create', 'no_update', 'no_search'], ), external_host_param, )
class ca(LDAPObject): """ Lightweight CA Object """ container_dn = api.env.container_ca object_name = _('Certificate Authority') object_name_plural = _('Certificate Authorities') object_class = ['ipaca'] permission_filter_objectclasses = ['ipaca'] default_attributes = [ 'cn', 'description', 'ipacaid', 'ipacaissuerdn', 'ipacasubjectdn', ] rdn_attribute = 'cn' allow_rename = True label = _('Certificate Authorities') label_singular = _('Certificate Authority') takes_params = ( Str('cn', primary_key=True, cli_name='name', label=_('Name'), doc=_('Name for referencing the CA'), ), Str('description?', cli_name='desc', label=_('Description'), doc=_('Description of the purpose of the CA'), ), Str('ipacaid', cli_name='id', label=_('Authority ID'), doc=_('Dogtag Authority ID'), flags=['no_create', 'no_update'], ), DNParam('ipacasubjectdn', cli_name='subject', label=_('Subject DN'), doc=_('Subject Distinguished Name'), flags=['no_update'], ), DNParam('ipacaissuerdn', cli_name='issuer', label=_('Issuer DN'), doc=_('Issuer Distinguished Name'), flags=['no_create', 'no_update'], ), Bytes( 'certificate', label=_("Certificate"), doc=_("Base-64 encoded certificate."), flags={'no_create', 'no_update', 'no_search'}, ), Bytes( 'certificate_chain*', label=_("Certificate chain"), doc=_("X.509 certificate chain"), flags={'no_create', 'no_update', 'no_search'}, ), ) permission_filter_objectclasses = ['ipaca'] managed_permissions = { 'System: Read CAs': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'description', 'ipacaid', 'ipacaissuerdn', 'ipacasubjectdn', 'objectclass', }, }, 'System: Add CA': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///cn=*,cn=cas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Add CA";allow (add) groupdn = "ldap:///cn=Add CA,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, 'System: Delete CA': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///cn=*,cn=cas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Delete CA";allow (delete) groupdn = "ldap:///cn=Delete CA,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, 'System: Modify CA': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'cn', 'description', }, 'replaces': [ '(targetattr = "cn || description")(target = "ldap:///cn=*,cn=cas,cn=ca,$SUFFIX")(version 3.0;acl "permission:Modify CA";allow (write) groupdn = "ldap:///cn=Modify CA,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'CA Administrator'}, }, }
raise errors.MutuallyExclusiveError(reason=_( "services cannot be added when service category='all'")) return dn @register() class caacl_remove_service(LDAPRemoveMember): __doc__ = _('Remove services from a CA ACL.') member_attributes = ['memberservice'] member_count_out = (_('%i service removed.'), _('%i services removed.')) caacl_output_params = global_output_params + ( Str( 'ipamembercertprofile', label=_('Failed profiles'), ), # Commented until caacl plugin arrives #Str('ipamemberca', # label=_('Failed CAs'), #), ) @register() class caacl_add_profile(LDAPAddMember): __doc__ = _('Add profiles to a CA ACL.') has_output_params = caacl_output_params member_attributes = ['ipamembercertprofile']
Generate and retrieve a keytab for an IPA service: ipa-getkeytab -s ipa.example.com -p HTTP/web.example.com -k /etc/httpd/httpd.keytab """) logger = logging.getLogger(__name__) register = Registry() output_params = ( Flag( 'has_keytab', label=_('Keytab'), ), Str( 'managedby_host', label='Managed by', ), Str( 'ipaallowedtoperform_read_keys_user', label=_('Users allowed to retrieve keytab'), ), Str( 'ipaallowedtoperform_read_keys_group', label=_('Groups allowed to retrieve keytab'), ), Str( 'ipaallowedtoperform_read_keys_host', label=_('Hosts allowed to retrieve keytab'), ), Str( 'ipaallowedtoperform_read_keys_hostgroup',
class migrate_ds(Command): __doc__ = _('Migrate users and groups from DS to IPA.') migrate_objects = { # OBJECT_NAME: (search_filter, pre_callback, post_callback) # # OBJECT_NAME - is the name of an LDAPObject subclass # search_filter - is the filter to retrieve objects from DS # pre_callback - is called for each object just after it was # retrieved from DS and before being added to IPA # post_callback - is called for each object after it was added to IPA # exc_callback - is called when adding entry to IPA raises an exception # # {pre, post}_callback parameters: # ldap - ldap2 instance connected to IPA # pkey - primary key value of the object (uid for users, etc.) # dn - dn of the object as it (will be/is) stored in IPA # entry_attrs - attributes of the object # failed - a list of so-far failed objects # config - IPA config entry attributes # ctx - object context, used to pass data between callbacks # # If pre_callback return value evaluates to False, migration # of the current object is aborted. 'user': { 'filter_template': '(&(|%s)(uid=*))', 'oc_option': 'userobjectclass', 'oc_blocklist_option': 'userignoreobjectclass', 'attr_blocklist_option': 'userignoreattribute', 'pre_callback': _pre_migrate_user, 'post_callback': _post_migrate_user, 'exc_callback': None }, 'group': { 'filter_template': '(&(|%s)(cn=*))', 'oc_option': 'groupobjectclass', 'oc_blocklist_option': 'groupignoreobjectclass', 'attr_blocklist_option': 'groupignoreattribute', 'pre_callback': _pre_migrate_group, 'post_callback': None, 'exc_callback': _group_exc_callback, }, } migrate_order = ('user', 'group') takes_args = ( Str( 'ldapuri', validate_ldapuri, cli_name='ldap_uri', label=_('LDAP URI'), doc=_('LDAP URI of DS server to migrate from'), ), Password( 'bindpw', cli_name='password', label=_('Password'), confirm=False, doc=_('bind password'), ), ) takes_options = ( DNParam('binddn?', cli_name='bind_dn', label=_('Bind DN'), default=DN(('cn', 'directory manager')), autofill=True, ), DNParam('usercontainer', cli_name='user_container', label=_('User container'), doc=_('DN of container for users in DS relative to base DN'), default=DN(('ou', 'people')), autofill=True, ), DNParam('groupcontainer', cli_name='group_container', label=_('Group container'), doc=_('DN of container for groups in DS relative to base DN'), default=DN(('ou', 'groups')), autofill=True, ), Str('userobjectclass+', cli_name='user_objectclass', label=_('User object class'), doc=_('Objectclasses used to search for user entries in DS'), default=(u'person',), autofill=True, ), Str('groupobjectclass+', cli_name='group_objectclass', label=_('Group object class'), doc=_('Objectclasses used to search for group entries in DS'), default=(u'groupOfUniqueNames', u'groupOfNames'), autofill=True, ), Str('userignoreobjectclass*', cli_name='user_ignore_objectclass', label=_('Ignore user object class'), doc=_('Objectclasses to be ignored for user entries in DS'), default=tuple(), autofill=True, ), Str('userignoreattribute*', cli_name='user_ignore_attribute', label=_('Ignore user attribute'), doc=_('Attributes to be ignored for user entries in DS'), default=tuple(), autofill=True, ), Str('groupignoreobjectclass*', cli_name='group_ignore_objectclass', label=_('Ignore group object class'), doc=_('Objectclasses to be ignored for group entries in DS'), default=tuple(), autofill=True, ), Str('groupignoreattribute*', cli_name='group_ignore_attribute', label=_('Ignore group attribute'), doc=_('Attributes to be ignored for group entries in DS'), default=tuple(), autofill=True, ), Flag('groupoverwritegid', cli_name='group_overwrite_gid', label=_('Overwrite GID'), doc=_('When migrating a group already existing in IPA domain overwrite the '\ 'group GID and report as success'), ), StrEnum('schema?', cli_name='schema', label=_('LDAP schema'), doc=_('The schema used on the LDAP server. Supported values are RFC2307 and RFC2307bis. The default is RFC2307bis'), values=_supported_schemas, default=_supported_schemas[0], autofill=True, ), Flag('continue?', label=_('Continue'), doc=_('Continuous operation mode. Errors are reported but the process continues'), default=False, ), DNParam('basedn?', cli_name='base_dn', label=_('Base DN'), doc=_('Base DN on remote LDAP server'), ), Flag('compat?', cli_name='with_compat', label=_('Ignore compat plugin'), doc=_('Allows migration despite the usage of compat plugin'), default=False, ), Str('cacertfile?', cli_name='ca_cert_file', label=_('CA certificate'), doc=_('Load CA certificate of LDAP server from FILE'), default=None, noextrawhitespace=False, ), Bool('use_def_group?', cli_name='use_default_group', label=_('Add to default group'), doc=_('Add migrated users without a group to a default group ' '(default: true)'), default=True, autofill=True, ), StrEnum('scope', cli_name='scope', label=_('Search scope'), doc=_('LDAP search scope for users and groups: base, ' 'onelevel, or subtree. Defaults to onelevel'), values=sorted(_supported_scopes), default=_default_scope, autofill=True, ), ) has_output = ( output.Output( 'result', type=dict, doc=_('Lists of objects migrated; categorized by type.'), ), output.Output( 'failed', type=dict, doc= _('Lists of objects that could not be migrated; categorized by type.' ), ), output.Output( 'enabled', type=bool, doc=_('False if migration mode was disabled.'), ), output.Output( 'compat', type=bool, doc= _('False if migration fails because the compatibility plug-in is enabled.' ), ), ) exclude_doc = _('%s to exclude from migration') truncated_err_msg = _('''\ search results for objects to be migrated have been truncated by the server; migration process might be incomplete\n''') def get_options(self): """ Call get_options of the baseclass and add "exclude" options for each type of object being migrated. """ for option in super(migrate_ds, self).get_options(): yield option for ldap_obj_name in self.migrate_objects: ldap_obj = self.api.Object[ldap_obj_name] name = 'exclude_%ss' % to_cli(ldap_obj_name) doc = self.exclude_doc % ldap_obj.object_name_plural yield Str('%s*' % name, cli_name=name, doc=doc, default=tuple(), autofill=True) def normalize_options(self, options): """ Convert all "exclude" option values to lower-case. Also, empty List parameters are converted to None, but the migration plugin doesn't like that - convert back to empty lists. """ names = [ 'userobjectclass', 'groupobjectclass', 'userignoreobjectclass', 'userignoreattribute', 'groupignoreobjectclass', 'groupignoreattribute' ] names.extend('exclude_%ss' % to_cli(n) for n in self.migrate_objects) for name in names: if options[name]: options[name] = tuple(v.lower() for v in options[name]) else: options[name] = tuple() def _get_search_bases(self, options, ds_base_dn, migrate_order): search_bases = dict() for ldap_obj_name in migrate_order: container = options.get('%scontainer' % to_cli(ldap_obj_name)) if container: # Don't append base dn if user already appended it in the container dn if container.endswith(ds_base_dn): search_base = container else: search_base = DN(container, ds_base_dn) else: search_base = ds_base_dn search_bases[ldap_obj_name] = search_base return search_bases def migrate(self, ldap, config, ds_ldap, ds_base_dn, options): """ Migrate objects from DS to LDAP. """ assert isinstance(ds_base_dn, DN) migrated = {} # {'OBJ': ['PKEY1', 'PKEY2', ...], ...} failed = {} # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...} search_bases = self._get_search_bases(options, ds_base_dn, self.migrate_order) migration_start = datetime.datetime.now() scope = _supported_scopes[options.get('scope')] for ldap_obj_name in self.migrate_order: ldap_obj = self.api.Object[ldap_obj_name] template = self.migrate_objects[ldap_obj_name]['filter_template'] oc_list = options[to_cli( self.migrate_objects[ldap_obj_name]['oc_option'])] search_filter = construct_filter(template, oc_list) exclude = options['exclude_%ss' % to_cli(ldap_obj_name)] context = dict(ds_ldap=ds_ldap) migrated[ldap_obj_name] = [] failed[ldap_obj_name] = {} try: entries, truncated = ds_ldap.find_entries( search_filter, ['*'], search_bases[ldap_obj_name], scope, time_limit=0, size_limit=-1) except errors.NotFound: if not options.get('continue', False): raise errors.NotFound( reason= _('%(container)s LDAP search did not return any result ' '(search base: %(search_base)s, ' 'objectclass: %(objectclass)s)') % { 'container': ldap_obj_name, 'search_base': search_bases[ldap_obj_name], 'objectclass': ', '.join(oc_list) }) else: truncated = False entries = [] if truncated: logger.error('%s: %s', ldap_obj.name, self.truncated_err_msg) blocklists = {} for blocklist in ('oc_blocklist', 'attr_blocklist'): blocklist_option = ( self.migrate_objects[ldap_obj_name][blocklist + '_option']) if blocklist_option is not None: blocklists[blocklist] = options.get( blocklist_option, tuple()) else: blocklists[blocklist] = tuple() # get default primary group for new users if 'def_group_dn' not in context and options.get('use_def_group'): def_group = config.get('ipadefaultprimarygroup') context['def_group_dn'] = api.Object.group.get_dn(def_group) try: ldap.get_entry(context['def_group_dn'], ['gidnumber', 'cn']) except errors.NotFound: error_msg = _('Default group for new users not found') raise errors.NotFound(reason=error_msg) context['has_upg'] = ldap.has_upg() valid_gids = set() invalid_gids = set() migrate_cnt = 0 context['migrate_cnt'] = 0 for entry_attrs in entries: context['migrate_cnt'] = migrate_cnt s = datetime.datetime.now() ava = entry_attrs.dn[0][0] if ava.attr == ldap_obj.primary_key.name: # In case if pkey attribute is in the migrated object DN # and the original LDAP is multivalued, make sure that # we pick the correct value (the unique one stored in DN) pkey = ava.value.lower() else: pkey = entry_attrs[ldap_obj.primary_key.name][0].lower() if pkey in exclude: continue entry_attrs.dn = ldap_obj.get_dn(pkey) entry_attrs['objectclass'] = list( set( config.get(ldap_obj.object_class_config, ldap_obj.object_class) + [o.lower() for o in entry_attrs['objectclass']])) entry_attrs[ldap_obj.primary_key.name][0] = entry_attrs[ ldap_obj.primary_key.name][0].lower() callback = self.migrate_objects[ldap_obj_name]['pre_callback'] if callable(callback): try: entry_attrs.dn = callback(ldap, pkey, entry_attrs.dn, entry_attrs, failed[ldap_obj_name], config, context, schema=options['schema'], search_bases=search_bases, valid_gids=valid_gids, invalid_gids=invalid_gids, **blocklists) if not entry_attrs.dn: continue except errors.NotFound as e: failed[ldap_obj_name][pkey] = unicode(e.reason) continue try: ldap.add_entry(entry_attrs) except errors.ExecutionError as e: callback = self.migrate_objects[ldap_obj_name][ 'exc_callback'] if callable(callback): try: callback(ldap, entry_attrs.dn, entry_attrs, e, options) except errors.ExecutionError as e2: failed[ldap_obj_name][pkey] = unicode(e2) continue else: failed[ldap_obj_name][pkey] = unicode(e) continue migrated[ldap_obj_name].append(pkey) callback = self.migrate_objects[ldap_obj_name]['post_callback'] if callable(callback): callback(ldap, pkey, entry_attrs.dn, entry_attrs, failed[ldap_obj_name], config, context) e = datetime.datetime.now() d = e - s total_dur = e - migration_start migrate_cnt += 1 if migrate_cnt > 0 and migrate_cnt % 100 == 0: logger.info("%d %ss migrated. %s elapsed.", migrate_cnt, ldap_obj_name, total_dur) logger.debug("%d %ss migrated, duration: %s (total %s)", migrate_cnt, ldap_obj_name, d, total_dur) if 'def_group_dn' in context: _update_default_group(ldap, context, True) return (migrated, failed) def execute(self, ldapuri, bindpw, **options): ldap = self.api.Backend.ldap2 self.normalize_options(options) config = ldap.get_ipa_config() ds_base_dn = options.get('basedn') if ds_base_dn is not None: assert isinstance(ds_base_dn, DN) # check if migration mode is enabled if config.get('ipamigrationenabled', ('FALSE', ))[0] == 'FALSE': return dict(result={}, failed={}, enabled=False, compat=True) # connect to DS if options.get('cacertfile') is not None: # store CA cert into file tmp_ca_cert_f = write_tmp_file(options['cacertfile']) cacert = tmp_ca_cert_f.name # start TLS connection or STARTTLS ds_ldap = LDAPClient(ldapuri, cacert=cacert, start_tls=True) ds_ldap.simple_bind(options['binddn'], bindpw) tmp_ca_cert_f.close() else: ds_ldap = LDAPClient(ldapuri) ds_ldap.simple_bind(options['binddn'], bindpw, insecure_bind=True) # check whether the compat plugin is enabled if not options.get('compat'): try: ldap.get_entry( DN(('cn', 'users'), ('cn', 'compat'), (api.env.basedn))) return dict(result={}, failed={}, enabled=True, compat=False) except errors.NotFound: pass if not ds_base_dn: # retrieve base DN from remote LDAP server entries, _truncated = ds_ldap.find_entries( '', ['namingcontexts', 'defaultnamingcontext'], DN(''), ds_ldap.SCOPE_BASE, size_limit=-1, time_limit=0, ) if 'defaultnamingcontext' in entries[0]: ds_base_dn = DN(entries[0]['defaultnamingcontext'][0]) assert isinstance(ds_base_dn, DN) else: try: ds_base_dn = DN(entries[0]['namingcontexts'][0]) assert isinstance(ds_base_dn, DN) except (IndexError, KeyError) as e: raise Exception(str(e)) # migrate! (migrated, failed) = self.migrate(ldap, config, ds_ldap, ds_base_dn, options) return dict(result=migrated, failed=failed, enabled=True, compat=True)
class service(LDAPObject): """ Service object. """ container_dn = api.env.container_service object_name = _('service') object_name_plural = _('services') object_class = [ 'krbprincipal', 'krbprincipalaux', 'krbticketpolicyaux', 'ipaobject', 'ipaservice', 'pkiuser' ] possible_objectclasses = ['ipakrbprincipal', 'ipaallowedoperations'] permission_filter_objectclasses = ['ipaservice'] search_attributes = ['krbprincipalname', 'managedby', 'ipakrbauthzdata'] default_attributes = [ 'krbprincipalname', 'krbcanonicalname', 'usercertificate', 'managedby', 'ipakrbauthzdata', 'memberof', 'ipaallowedtoperform', 'krbprincipalauthind' ] uuid_attribute = 'ipauniqueid' attribute_members = { 'managedby': ['host'], 'memberof': ['role'], 'ipaallowedtoperform_read_keys': ['user', 'group', 'host', 'hostgroup'], 'ipaallowedtoperform_write_keys': ['user', 'group', 'host', 'hostgroup'], } bindable = True relationships = { 'managedby': ('Managed by', 'man_by_', 'not_man_by_'), 'ipaallowedtoperform_read_keys': ('Allow to retrieve keytab by', 'retrieve_keytab_by_', 'not_retrieve_keytab_by_'), 'ipaallowedtoperform_write_keys': ('Allow to create keytab by', 'write_keytab_by_', 'not_write_keytab_by'), } password_attributes = [('krbprincipalkey', 'has_keytab')] managed_permissions = { 'System: Read Services': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'ipauniqueid', 'managedby', 'memberof', 'usercertificate', 'krbprincipalname', 'krbcanonicalname', 'krbprincipalaliases', 'krbprincipalexpiration', 'krbpasswordexpiration', 'krblastpwdchange', 'ipakrbauthzdata', 'ipakrbprincipalalias', 'krbobjectreferences', 'krbprincipalauthind', }, }, 'System: Add Services': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Services";allow (add) groupdn = "ldap:///cn=Add Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, 'System: Manage Service Keytab': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'krblastpwdchange', 'krbprincipalkey'}, 'replaces': [ '(targetattr = "krbprincipalkey || krblastpwdchange")(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage service keytab";allow (write) groupdn = "ldap:///cn=Manage service keytab,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators', 'Host Administrators'}, }, 'System: Manage Service Keytab Permissions': { 'ipapermright': {'read', 'search', 'compare', 'write'}, 'ipapermdefaultattr': { 'ipaallowedtoperform;write_keys', 'ipaallowedtoperform;read_keys', 'objectclass' }, 'default_privileges': {'Service Administrators', 'Host Administrators'}, }, 'System: Modify Services': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'usercertificate', 'krbprincipalauthind'}, 'replaces': [ '(targetattr = "usercertificate")(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Services";allow (write) groupdn = "ldap:///cn=Modify Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, 'System: Manage Service Principals': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'krbprincipalname', 'krbcanonicalname'}, 'default_privileges': { 'Service Administrators', }, }, 'System: Remove Services': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Services";allow (delete) groupdn = "ldap:///cn=Remove Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, 'System: Read POSIX details of SMB services': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'uid', 'gecos', 'gidnumber', 'homedirectory', 'loginshell', 'uidnumber', 'ipantsecurityidentifier', }, } } label = _('Services') label_singular = _('Service') takes_params = ( Principal('krbcanonicalname', validate_realm, cli_name='canonical_principal', label=_('Principal name'), doc=_('Service principal'), primary_key=True, normalizer=normalize_principal, require_service=True), Principal('krbprincipalname*', validate_realm, cli_name='principal', label=_('Principal alias'), doc=_('Service principal alias'), normalizer=normalize_principal, require_service=True, flags={'no_create'}), Certificate( 'usercertificate*', cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded service certificate'), flags=[ 'no_search', ], ), Str( 'subject', label=_('Subject'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'serial_number', label=_('Serial Number'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'serial_number_hex', label=_('Serial Number (hex)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'issuer', label=_('Issuer'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'valid_not_before', label=_('Not Before'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'valid_not_after', label=_('Not After'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'sha1_fingerprint', label=_('Fingerprint (SHA1)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'sha256_fingerprint', label=_('Fingerprint (SHA256)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'revocation_reason?', label=_('Revocation reason'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), StrEnum( 'ipakrbauthzdata*', cli_name='pac_type', label=_('PAC type'), doc=_("Override default list of supported PAC types." " Use 'NONE' to disable PAC support for this service," " e.g. this might be necessary for NFS services."), values=(u'MS-PAC', u'PAD', u'NONE'), ), StrEnum( 'krbprincipalauthind*', cli_name='auth_ind', label=_('Authentication Indicators'), doc=_("Defines an allow list for Authentication Indicators." " Use 'otp' to allow OTP-based 2FA authentications." " Use 'radius' to allow RADIUS-based 2FA authentications." " Use 'pkinit' to allow PKINIT-based 2FA authentications." " Use 'hardened' to allow brute-force hardened password" " authentication by SPAKE or FAST." " With no indicator specified," " all authentication mechanisms are allowed."), values=(u'radius', u'otp', u'pkinit', u'hardened', u'idp'), ), ) + ticket_flags_params def validate_ipakrbauthzdata(self, entry): new_value = entry.get('ipakrbauthzdata', []) if not new_value: return if not isinstance(new_value, (list, tuple)): new_value = set([new_value]) else: new_value = set(new_value) if u'NONE' in new_value and len(new_value) > 1: raise errors.ValidationError( name='ipakrbauthzdata', error=_('NONE value cannot be combined with other PAC types')) def get_dn(self, *keys, **kwargs): key = keys[0] if isinstance(key, str): key = kerberos.Principal(key) key = unicode(normalize_principal(key)) parent_dn = DN(self.container_dn, self.api.env.basedn) true_rdn = 'krbprincipalname' return self.backend.make_dn_from_attr(true_rdn, key, parent_dn) def get_primary_key_from_dn(self, dn): """ If the entry has krbcanonicalname set return the value of the attribute. If the attribute is not found, assume old-style entry which should have only single value of krbprincipalname and return it. Otherwise return input DN. """ assert isinstance(dn, DN) try: entry_attrs = self.backend.get_entry(dn, [self.primary_key.name]) try: return entry_attrs[self.primary_key.name][0] except (KeyError, IndexError): return '' except errors.NotFound: pass try: return dn['krbprincipalname'] except KeyError: return unicode(dn) def populate_krbcanonicalname(self, entry_attrs, options): if options.get('raw', False): return entry_attrs.setdefault('krbcanonicalname', entry_attrs['krbprincipalname'])
class vault_mod(Local): __doc__ = _('Modify a vault.') takes_options = ( Flag( 'change_password?', doc=_('Change password'), ), Str( 'old_password?', cli_name='old_password', doc=_('Old vault password'), ), Str( # TODO: use File parameter 'old_password_file?', cli_name='old_password_file', doc=_('File containing the old vault password'), ), Str( 'new_password?', cli_name='new_password', doc=_('New vault password'), ), Str( # TODO: use File parameter 'new_password_file?', cli_name='new_password_file', doc=_('File containing the new vault password'), ), Bytes( 'private_key?', cli_name='private_key', doc=_('Old vault private key'), ), Str( # TODO: use File parameter 'private_key_file?', cli_name='private_key_file', doc=_('File containing the old vault private key'), ), Str( # TODO: use File parameter 'public_key_file?', cli_name='public_key_file', doc=_('File containing the new vault public key'), ), ) @classmethod def __NO_CLI_getter(cls): # pylint: disable=unused-private-member, #4756 return (api.Command.get_plugin('vault_mod_internal') is _fake_vault_mod_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_mod_internal.api_version def get_args(self): for arg in self.api.Command.vault_mod_internal.args(): yield arg for arg in super(vault_mod, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_mod_internal.options(): if option.name != 'version': yield option for option in super(vault_mod, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_mod_internal.output_params(): yield param for param in super(vault_mod, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_mod_internal.output() def forward(self, *args, **options): vault_type = options.pop('ipavaulttype', False) salt = options.pop('ipavaultsalt', False) change_password = options.pop('change_password', False) old_password = options.pop('old_password', None) old_password_file = options.pop('old_password_file', None) new_password = options.pop('new_password', None) new_password_file = options.pop('new_password_file', None) old_private_key = options.pop('private_key', None) old_private_key_file = options.pop('private_key_file', None) new_public_key = options.pop('ipavaultpublickey', None) new_public_key_file = options.pop('public_key_file', None) if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() # determine the vault type based on parameters specified if vault_type: pass elif change_password or new_password or new_password_file or salt: vault_type = u'symmetric' elif new_public_key or new_public_key_file: vault_type = u'asymmetric' # if vault type is specified, retrieve existing secret if vault_type: opts = options.copy() opts.pop('description', None) opts['password'] = old_password opts['password_file'] = old_password_file opts['private_key'] = old_private_key opts['private_key_file'] = old_private_key_file response = self.api.Command.vault_retrieve(*args, **opts) data = response['result']['data'] opts = options.copy() # if vault type is specified, update crypto attributes if vault_type: opts['ipavaulttype'] = vault_type if vault_type == u'standard': opts['ipavaultsalt'] = None opts['ipavaultpublickey'] = None elif vault_type == u'symmetric': if salt: opts['ipavaultsalt'] = salt else: opts['ipavaultsalt'] = os.urandom(16) opts['ipavaultpublickey'] = None elif vault_type == u'asymmetric': # get new vault public key if new_public_key and new_public_key_file: raise errors.MutuallyExclusiveError( reason=_('New public key specified multiple times')) elif new_public_key: pass elif new_public_key_file: new_public_key = validated_read('public_key_file', new_public_key_file, mode='rb') else: raise errors.ValidationError( name='ipavaultpublickey', error=_('Missing new vault public key')) opts['ipavaultsalt'] = None opts['ipavaultpublickey'] = new_public_key response = self.api.Command.vault_mod_internal(*args, **opts) # if vault type is specified, rearchive existing secret if vault_type: opts = options.copy() opts.pop('description', None) opts['data'] = data opts['password'] = new_password opts['password_file'] = new_password_file opts['override_password'] = True self.api.Command.vault_archive(*args, **opts) return response
class service_add_smb(LDAPCreate): __doc__ = _('Add a new SMB service.') msg_summary = _('Added service "%(value)s"') member_attributes = ['managedby'] has_output_params = LDAPCreate.has_output_params + output_params smb_takes_args = ( Str( 'fqdn', util.hostname_validator, cli_name='hostname', label=_('Host name'), primary_key=True, normalizer=util.normalize_hostname, flags={ 'virtual_attribute', 'no_display', 'no_update', 'no_search' }, ), Str( 'ipantflatname?', cli_name='netbiosname', label=_('SMB service NetBIOS name'), flags={ 'virtual_attribute', 'no_display', 'no_update', 'no_search' }, ), ) takes_options = LDAPCreate.takes_options def get_args(self): """ Rewrite arguments to service-add-smb command to make sure we accept hostname instead of a principal as we'll be constructing the principal ourselves """ for arg in self.smb_takes_args: yield arg for arg in super(service_add_smb, self).get_args(): if arg not in self.smb_takes_args and not arg.primary_key: yield arg def get_options(self): """ Rewrite options to service-add-smb command to filter out cannonical principal which is autoconstructed. Also filter out options which make no sense for SMB service. """ excluded = ('ipakrbauthzdata', 'krbprincipalauthind', 'ipakrbrequirespreauth') for arg in self.takes_options: yield arg for arg in super(service_add_smb, self).get_options(): check = all([ arg not in self.takes_options, not arg.primary_key, arg.name not in excluded ]) if check: yield arg def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) hostname = keys[0] if len(keys) == 2: netbiosname = keys[1] else: # By default take leftmost label from the host name netbiosname = DNSName.from_text(hostname)[0].decode().upper() # SMB service requires existence of the host object # because DCE RPC calls authenticated with GSSAPI are using # host/.. principal by default for validation try: hostresult = self.api.Command['host_show'](hostname)['result'] except errors.NotFound: raise errors.NotFound( reason=_("The host '%s' does not exist to add a service to.") % hostname) # We cannot afford the host not being resolvable even for # clustered environments with CTDB because the target name # has to exist even in that case util.verify_host_resolvable(hostname) smbaccount = '{name}$'.format(name=netbiosname) smbprincipal = 'cifs/{hostname}'.format(hostname=hostname) entry_attrs['krbprincipalname'] = [ str(kerberos.Principal(smbprincipal, realm=self.api.env.realm)), str(kerberos.Principal(smbaccount, realm=self.api.env.realm)) ] entry_attrs['krbcanonicalname'] = entry_attrs['krbprincipalname'][0] # Rewrite DN using proper rdn and new canonical name because when # LDAPCreate.execute() was called, it set DN to krbcanonicalname=$value dn = DN(('krbprincipalname', entry_attrs['krbcanonicalname']), DN(self.obj.container_dn, api.env.basedn)) # Enforce ipaKrbPrincipalAlias to aid case-insensitive searches as # krbPrincipalName/krbCanonicalName are case-sensitive in Kerberos # schema entry_attrs['ipakrbprincipalalias'] = entry_attrs['krbcanonicalname'] for o in ('ipakrbprincipal', 'ipaidobject', 'krbprincipalaux', 'posixaccount'): if o not in entry_attrs['objectclass']: entry_attrs['objectclass'].append(o) entry_attrs['uid'] = [ '/'.join(kerberos.Principal(smbprincipal).components) ] entry_attrs['uid'].append(smbaccount) entry_attrs['cn'] = netbiosname entry_attrs['homeDirectory'] = '/dev/null' entry_attrs['uidNumber'] = DNA_MAGIC entry_attrs['gidNumber'] = DNA_MAGIC self.obj.validate_ipakrbauthzdata(entry_attrs) if 'managedby' not in entry_attrs: entry_attrs['managedby'] = hostresult['dn'] update_krbticketflags(ldap, entry_attrs, attrs_list, options, False) return dn def post_callback(self, ldap, dn, entry_attrs, *keys, **options): set_kerberos_attrs(entry_attrs, options) rename_ipaallowedtoperform_from_ldap(entry_attrs, options) self.obj.populate_krbcanonicalname(entry_attrs, options) return dn
class vault_retrieve(ModVaultData): __doc__ = _('Retrieve a data from a vault.') takes_options = ( Str( 'out?', doc=_('File to store retrieved data'), ), Str( 'password?', cli_name='password', doc=_('Vault password'), ), Str( # TODO: use File parameter 'password_file?', cli_name='password_file', doc=_('File containing the vault password'), ), Bytes( 'private_key?', cli_name='private_key', doc=_('Vault private key'), ), Str( # TODO: use File parameter 'private_key_file?', cli_name='private_key_file', doc=_('File containing the vault private key'), ), ) has_output_params = (Bytes( 'data', label=_('Data'), ), ) @classmethod def __NO_CLI_getter(cls): # pylint: disable=unused-private-member, #4756 return (api.Command.get_plugin('vault_retrieve_internal') is _fake_vault_retrieve_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_retrieve_internal.api_version def get_args(self): for arg in self.api.Command.vault_retrieve_internal.args(): yield arg for arg in super(vault_retrieve, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_retrieve_internal.options(): if option.name not in ('session_key', 'version'): yield option for option in super(vault_retrieve, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_retrieve_internal.output_params(): yield param for param in super(vault_retrieve, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_retrieve_internal.output() def _unwrap_response(self, algo, nonce, vault_data): cipher = Cipher(algo, modes.CBC(nonce), backend=default_backend()) # decrypt decryptor = cipher.decryptor() padded_data = decryptor.update(vault_data) padded_data += decryptor.finalize() # remove padding unpadder = PKCS7(algo.block_size).unpadder() json_vault_data = unpadder.update(padded_data) json_vault_data += unpadder.finalize() # load JSON return json.loads(json_vault_data.decode('utf-8')) def forward(self, *args, **options): output_file = options.get('out') password = options.get('password') password_file = options.get('password_file') private_key = options.get('private_key') private_key_file = options.get('private_key_file') # don't send these parameters to server if 'out' in options: del options['out'] if 'password' in options: del options['password'] if 'password_file' in options: del options['password_file'] if 'private_key' in options: del options['private_key'] if 'private_key_file' in options: del options['private_key_file'] if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] vault_type = vault['ipavaulttype'][0] # get config transport_cert, wrapping_algo = self._get_vaultconfig() # let options override wrapping algo # For backwards compatibility do not send old legacy wrapping algo # to server. Only send the option when non-3DES is used. wrapping_algo = options.pop('wrapping_algo', wrapping_algo) if wrapping_algo != constants.VAULT_WRAPPING_3DES: options['wrapping_algo'] = wrapping_algo # generate session key algo = self._generate_session_key(wrapping_algo) # send retrieval request to server response = self.internal(algo, transport_cert, *args, **options) # unwrap data with session key vault_data = self._unwrap_response(algo, response['result']['nonce'], response['result']['vault_data']) del algo data = base64.b64decode(vault_data[u'data'].encode('utf-8')) encrypted_key = None if 'encrypted_key' in vault_data: encrypted_key = base64.b64decode( vault_data[u'encrypted_key'].encode('utf-8')) if vault_type == u'standard': pass elif vault_type == u'symmetric': salt = vault['ipavaultsalt'][0] # get encryption key from vault password if password and password_file: raise errors.MutuallyExclusiveError( reason=_('Password specified multiple times')) elif password: pass elif password_file: password = validated_read('password-file', password_file, encoding='utf-8') password = password.rstrip('\n') else: password = self.api.Backend.textui.prompt_password( 'Password', confirm=False) # generate encryption key from password encryption_key = generate_symmetric_key(password, salt) # decrypt data with encryption key data = decrypt(data, symmetric_key=encryption_key) elif vault_type == u'asymmetric': # get encryption key with vault private key if private_key and private_key_file: raise errors.MutuallyExclusiveError( reason=_('Private key specified multiple times')) elif private_key: pass elif private_key_file: private_key = validated_read('private-key-file', private_key_file, mode='rb') else: raise errors.ValidationError( name='private_key', error=_('Missing vault private key')) # decrypt encryption key with private key encryption_key = decrypt(encrypted_key, private_key=private_key) # decrypt data with encryption key data = decrypt(data, symmetric_key=encryption_key) else: raise errors.ValidationError(name='vault_type', error=_('Invalid vault type')) if output_file: with open(output_file, 'wb') as f: f.write(data) else: response['result'] = {'data': data} return response
class config(LDAPObject): """ IPA configuration object """ object_name = _('configuration options') default_attributes = [ 'ipamaxusernamelength', 'ipahomesrootdir', 'ipadefaultloginshell', 'ipadefaultprimarygroup', 'ipadefaultemaildomain', 'ipasearchtimelimit', 'ipasearchrecordslimit', 'ipausersearchfields', 'ipagroupsearchfields', 'ipamigrationenabled', 'ipacertificatesubjectbase', 'ipapwdexpadvnotify', 'ipaselinuxusermaporder', 'ipaselinuxusermapdefault', 'ipaconfigstring', 'ipakrbauthzdata', 'ipauserauthtype', 'ipadomainresolutionorder' ] container_dn = DN(('cn', 'ipaconfig'), ('cn', 'etc')) permission_filter_objectclasses = ['ipaguiconfig'] managed_permissions = { 'System: Read Global Configuration': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'objectclass', 'ipacertificatesubjectbase', 'ipaconfigstring', 'ipadefaultemaildomain', 'ipadefaultloginshell', 'ipadefaultprimarygroup', 'ipadomainresolutionorder', 'ipagroupobjectclasses', 'ipagroupsearchfields', 'ipahomesrootdir', 'ipakrbauthzdata', 'ipamaxusernamelength', 'ipamigrationenabled', 'ipapwdexpadvnotify', 'ipaselinuxusermapdefault', 'ipaselinuxusermaporder', 'ipasearchrecordslimit', 'ipasearchtimelimit', 'ipauserauthtype', 'ipauserobjectclasses', 'ipausersearchfields', 'ipacustomfields', }, }, } label = _('Configuration') label_singular = _('Configuration') takes_params = ( Int( 'ipamaxusernamelength', cli_name='maxusername', label=_('Maximum username length'), minvalue=1, maxvalue=255, ), IA5Str( 'ipahomesrootdir', cli_name='homedirectory', label=_('Home directory base'), doc=_('Default location of home directories'), ), Str( 'ipadefaultloginshell', cli_name='defaultshell', label=_('Default shell'), doc=_('Default shell for new users'), ), Str( 'ipadefaultprimarygroup', cli_name='defaultgroup', label=_('Default users group'), doc=_('Default group for new users'), ), Str( 'ipadefaultemaildomain?', cli_name='emaildomain', label=_('Default e-mail domain'), doc=_('Default e-mail domain'), ), Int( 'ipasearchtimelimit', cli_name='searchtimelimit', label=_('Search time limit'), doc= _('Maximum amount of time (seconds) for a search (-1 or 0 is unlimited)' ), minvalue=-1, ), Int( 'ipasearchrecordslimit', cli_name='searchrecordslimit', label=_('Search size limit'), doc=_( 'Maximum number of records to search (-1 or 0 is unlimited)'), minvalue=-1, ), IA5Str( 'ipausersearchfields', cli_name='usersearch', label=_('User search fields'), doc= _('A comma-separated list of fields to search in when searching for users' ), ), IA5Str( 'ipagroupsearchfields', cli_name='groupsearch', label=_('Group search fields'), doc= _('A comma-separated list of fields to search in when searching for groups' ), ), Bool( 'ipamigrationenabled', cli_name='enable_migration', label=_('Enable migration mode'), doc=_('Enable migration mode'), ), DNParam( 'ipacertificatesubjectbase', cli_name='subject', label=_('Certificate Subject base'), doc=_('Base for certificate subjects (OU=Test,O=Example)'), flags=['no_update'], ), Str( 'ipagroupobjectclasses+', cli_name='groupobjectclasses', label=_('Default group objectclasses'), doc=_('Default group objectclasses (comma-separated list)'), ), Str( 'ipauserobjectclasses+', cli_name='userobjectclasses', label=_('Default user objectclasses'), doc=_('Default user objectclasses (comma-separated list)'), ), Int( 'ipapwdexpadvnotify', cli_name='pwdexpnotify', label=_('Password Expiration Notification (days)'), doc=_('Number of days\'s notice of impending password expiration'), minvalue=0, ), StrEnum( 'ipaconfigstring*', cli_name='ipaconfigstring', label=_('Password plugin features'), doc=_('Extra hashes to generate in password plug-in'), values=(u'AllowNThash', u'KDC:Disable Last Success', u'KDC:Disable Lockout', u'KDC:Disable Default Preauth for SPNs'), ), Str( 'ipaselinuxusermaporder', label=_('SELinux user map order'), doc=_( 'Order in increasing priority of SELinux users, delimited by $' ), ), Str( 'ipaselinuxusermapdefault?', label=_('Default SELinux user'), doc= _('Default SELinux user when no match is found in SELinux map rule' ), ), StrEnum( 'ipakrbauthzdata*', cli_name='pac_type', label=_('Default PAC types'), doc=_('Default types of PAC supported for services'), values=(u'MS-PAC', u'PAD', u'nfs:NONE'), ), StrEnum( 'ipauserauthtype*', cli_name='user_auth_type', label=_('Default user authentication types'), doc=_('Default types of supported user authentication'), values=(u'password', u'radius', u'otp', u'disabled'), ), Str('ipa_master_server*', label=_('IPA masters'), doc=_('List of all IPA masters'), flags={'virtual_attribute', 'no_create', 'no_update'}), Str('ca_server_server*', label=_('IPA CA servers'), doc=_('IPA servers configured as certificate authority'), flags={'virtual_attribute', 'no_create', 'no_update'}), Str('ntp_server_server*', label=_('IPA NTP servers'), doc=_('IPA servers with enabled NTP'), flags={'virtual_attribute', 'no_create', 'no_update'}), Str('ca_renewal_master_server?', label=_('IPA CA renewal master'), doc=_('Renewal master for IPA certificate authority'), flags={'virtual_attribute', 'no_create'}), Str('pkinit_server_server*', label=_('IPA master capable of PKINIT'), doc=_('IPA master which can process PKINIT requests'), flags={'virtual_attribute', 'no_create', 'no_update'}), Str('ipadomainresolutionorder?', cli_name='domain_resolution_order', label=_('Domain resolution order'), doc=_('colon-separated list of domains used for short name' ' qualification'))) def get_dn(self, *keys, **kwargs): return DN(('cn', 'ipaconfig'), ('cn', 'etc'), api.env.basedn) def update_entry_with_role_config(self, role_name, entry_attrs): backend = self.api.Backend.serverroles role_config = backend.config_retrieve(role_name) for key, value in role_config.items(): if value: entry_attrs.update({key: value}) def show_servroles_attributes(self, entry_attrs, *roles, **options): if options.get('raw', False): return for role in roles: self.update_entry_with_role_config(role, entry_attrs) def gather_trusted_domains(self): """ Aggregate all trusted domains into a dict keyed by domain names with values corresponding to domain status (enabled/disabled) """ command = self.api.Command try: ad_forests = command.trust_find(sizelimit=0)['result'] except errors.NotFound: return {} trusted_domains = {} for forest_name in [a['cn'][0] for a in ad_forests]: forest_domains = command.trustdomain_find(forest_name, sizelimit=0)['result'] trusted_domains.update({ dom['cn'][0]: dom['domain_enabled'][0] for dom in forest_domains if 'domain_enabled' in dom }) return trusted_domains def _validate_single_domain(self, attr_name, domain, known_domains): """ Validate a single domain from domain resolution order :param attr_name: name of attribute that holds domain resolution order :param domain: domain name :param known_domains: dict of domains known to IPA keyed by domain name and valued by boolean value corresponding to domain status (enabled/disabled) :raises: ValidationError if the domain name is empty, syntactically invalid or corresponds to a disable domain NotFound if a syntactically correct domain name unknown to IPA is supplied (not IPA domain and not any of trusted domains) """ if not domain: raise errors.ValidationError( name=attr_name, error=_("Empty domain is not allowed")) try: validate_domain_name(domain) except ValueError as e: raise errors.ValidationError( name=attr_name, error=_("Invalid domain name '%(domain)s': %(e)s") % dict(domain=domain, e=e)) if domain not in known_domains: raise errors.NotFound(reason=_( "Server has no information about domain '%(domain)s'") % dict(domain=domain)) if not known_domains[domain]: raise errors.ValidationError( name=attr_name, error=_("Disabled domain '%(domain)s' is not allowed") % dict(domain=domain)) def validate_domain_resolution_order(self, entry_attrs): """ Validate domain resolution order, e.g. split by the delimiter (colon) and check each domain name for non-emptiness, syntactic correctness, and status (enabled/disabled). supplying empty order (':') bypasses validations and allows to specify empty attribute value. """ attr_name = 'ipadomainresolutionorder' if attr_name not in entry_attrs: return domain_resolution_order = entry_attrs[attr_name] # setting up an empty string means that the previous configuration has # to be cleaned up/removed. So, do nothing and let it pass if not domain_resolution_order: return # empty resolution order is signalized by single separator, do nothing # and let it pass if domain_resolution_order == DOMAIN_RESOLUTION_ORDER_SEPARATOR: return submitted_domains = domain_resolution_order.split( DOMAIN_RESOLUTION_ORDER_SEPARATOR) known_domains = self.gather_trusted_domains() # add FreeIPA domain to the list of domains. This one is always enabled known_domains.update({self.api.env.domain: True}) for domain in submitted_domains: self._validate_single_domain(attr_name, domain, known_domains)
class location(LDAPObject): """ IPA locations """ container_dn = api.env.container_locations object_name = _('location') object_name_plural = _('locations') object_class = ['top', 'ipaLocationObject'] search_attributes = ['idnsName'] default_attributes = ['idnsname', 'description'] label = _('IPA Locations') label_singular = _('IPA Location') permission_filter_objectclasses = ['ipaLocationObject'] managed_permissions = { 'System: Read IPA Locations': { 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'idnsname', 'description', }, 'default_privileges': {'DNS Administrators'}, }, 'System: Add IPA Locations': { 'ipapermright': {'add'}, 'default_privileges': {'DNS Administrators'}, }, 'System: Remove IPA Locations': { 'ipapermright': {'delete'}, 'default_privileges': {'DNS Administrators'}, }, 'System: Modify IPA Locations': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'description', }, 'default_privileges': {'DNS Administrators'}, }, } takes_params = ( DNSNameParam( 'idnsname', cli_name='name', primary_key=True, label=_('Location name'), doc=_('IPA location name'), # dns name must be relative, we will put it into middle of # location domain name for location records only_relative=True, ), Str( 'description?', label=_('Description'), doc=_('IPA Location description'), ), Str( 'servers_server*', label=_('Servers'), doc=_('Servers that belongs to the IPA location'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'dns_server*', label=_('Advertised by servers'), doc=_('List of servers which advertise the given location'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), ) def get_dn(self, *keys, **options): loc = keys[0] assert isinstance(loc, DNSName) loc_a = loc.ToASCII() return super(location, self).get_dn(loc_a, **options)
class privilege(LDAPObject): """ Privilege object. """ container_dn = api.env.container_privilege object_name = _('privilege') object_name_plural = _('privileges') object_class = ['nestedgroup', 'groupofnames'] permission_filter_objectclasses = ['groupofnames'] default_attributes = ['cn', 'description', 'member', 'memberof'] attribute_members = { 'member': ['role'], 'memberof': ['permission'], } reverse_members = { 'member': ['permission'], } allow_rename = True managed_permissions = { 'System: Read Privileges': { 'replaces_global_anonymous_aci': True, 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'businesscategory', 'cn', 'description', 'member', 'memberof', 'o', 'objectclass', 'ou', 'owner', 'seealso', 'memberuser', 'memberhost', }, 'default_privileges': {'RBAC Readers'}, }, 'System: Add Privileges': { 'ipapermright': {'add'}, 'default_privileges': {'Delegation Administrator'}, }, 'System: Modify Privileges': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'businesscategory', 'cn', 'description', 'o', 'ou', 'owner', 'seealso', }, 'default_privileges': {'Delegation Administrator'}, }, 'System: Remove Privileges': { 'ipapermright': {'delete'}, 'default_privileges': {'Delegation Administrator'}, }, } label = _('Privileges') label_singular = _('Privilege') takes_params = ( Str( 'cn', cli_name='name', label=_('Privilege name'), primary_key=True, ), Str( 'description?', cli_name='desc', label=_('Description'), doc=_('Privilege description'), ), )
class json_metadata(Command): """ Export plugin meta-data for the webUI. """ NO_CLI = True takes_args = ( Str( 'objname?', doc=_('Name of object to export'), ), Str( 'methodname?', doc=_('Name of method to export'), ), ) takes_options = ( Str( 'object?', doc=_('Name of object to export'), ), Str( 'method?', doc=_('Name of method to export'), ), Str( 'command?', doc=_('Name of command to export'), ), ) has_output = ( Output('objects', dict, doc=_('Dict of JSON encoded IPA Objects')), Output('methods', dict, doc=_('Dict of JSON encoded IPA Methods')), Output('commands', dict, doc=_('Dict of JSON encoded IPA Commands')), ) def execute(self, objname, methodname, **options): objects = dict() methods = dict() commands = dict() empty = True try: if not objname: objname = options['object'] if objname in self.api.Object: o = self.api.Object[objname] objects = dict([(o.name, json_serialize(o))]) elif objname == "all": objects = dict( (o.name, json_serialize(o)) for o in self.api.Object()) empty = False except KeyError: pass try: if not methodname: methodname = options['method'] if methodname in self.api.Method: m = self.api.Method[methodname] methods = dict([(m.name, json_serialize(m))]) elif methodname == "all": methods = dict( (m.name, json_serialize(m)) for m in self.api.Method()) empty = False except KeyError: pass try: cmdname = options['command'] if cmdname in self.api.Command: c = self.api.Command[cmdname] commands = dict([(c.name, json_serialize(c))]) elif cmdname == "all": commands = dict( (c.name, json_serialize(c)) for c in self.api.Command()) empty = False except KeyError: pass if empty: objects = dict( (o.name, json_serialize(o)) for o in self.api.Object()) methods = dict( (m.name, json_serialize(m)) for m in self.api.Method()) commands = dict( (c.name, json_serialize(c)) for c in self.api.Command()) retval = dict([ ("objects", objects), ("methods", methods), ("commands", commands), ]) return retval def output_for_cli(self, textui, result, *args, **options): print(json.dumps(result, default=json_serialize))
class passwd(Command): __doc__ = _("Set a user's password.") takes_args = ( Str( 'principal', validate_principal, cli_name='user', label=_('User name'), primary_key=True, autofill=True, default_from=lambda: util.get_current_principal(), normalizer=lambda value: normalize_principal(value), ), Password( 'password', label=_('New Password'), ), Password( 'current_password', label=_('Current Password'), confirm=False, default_from=lambda principal: get_current_password(principal), autofill=True, sortorder=-1, ), ) takes_options = (Password( 'otp?', label=_('OTP'), doc=_('One Time Password'), confirm=False, ), ) has_output = output.standard_value msg_summary = _('Changed password for "%(value)s"') def execute(self, principal, password, current_password, **options): """ Execute the passwd operation. The dn should not be passed as a keyword argument as it is constructed by this method. Returns the entry :param principal: The login name or principal of the user :param password: the new password :param current_password: the existing password, if applicable """ ldap = self.api.Backend.ldap2 entry_attrs = ldap.find_entry_by_attr( 'krbprincipalname', principal, 'posixaccount', [''], DN(api.env.container_user, api.env.basedn)) if principal == getattr(context, 'principal') and \ current_password == MAGIC_VALUE: # No cheating self.log.warn( 'User attempted to change password using magic value') raise errors.ACIError(info=_('Invalid credentials')) if current_password == MAGIC_VALUE: ldap.modify_password(entry_attrs.dn, password) else: otp = options.get('otp') ldap.modify_password(entry_attrs.dn, password, current_password, otp) return dict( result=True, value=principal, )
class sudorule(LDAPObject): """ Sudo Rule object. """ container_dn = api.env.container_sudorule object_name = _('sudo rule') object_name_plural = _('sudo rules') object_class = ['ipaassociation', 'ipasudorule'] permission_filter_objectclasses = ['ipasudorule'] default_attributes = [ 'cn', 'ipaenabledflag', 'externaluser', 'description', 'usercategory', 'hostcategory', 'cmdcategory', 'memberuser', 'memberhost', 'memberallowcmd', 'memberdenycmd', 'ipasudoopt', 'ipasudorunas', 'ipasudorunasgroup', 'ipasudorunasusercategory', 'ipasudorunasgroupcategory', 'sudoorder', 'hostmask', 'externalhost', 'ipasudorunasextusergroup', 'ipasudorunasextgroup', 'ipasudorunasextuser' ] uuid_attribute = 'ipauniqueid' rdn_attribute = 'ipauniqueid' attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], 'memberallowcmd': ['sudocmd', 'sudocmdgroup'], 'memberdenycmd': ['sudocmd', 'sudocmdgroup'], 'ipasudorunas': ['user', 'group'], 'ipasudorunasgroup': ['group'], } managed_permissions = { 'System: Read Sudo Rules': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cmdcategory', 'cn', 'description', 'externalhost', 'externaluser', 'hostcategory', 'hostmask', 'ipaenabledflag', 'ipasudoopt', 'ipasudorunas', 'ipasudorunasextgroup', 'ipasudorunasextuser', 'ipasudorunasextusergroup', 'ipasudorunasgroup', 'ipasudorunasgroupcategory', 'ipasudorunasusercategory', 'ipauniqueid', 'memberallowcmd', 'memberdenycmd', 'memberhost', 'memberuser', 'sudonotafter', 'sudonotbefore', 'sudoorder', 'usercategory', 'objectclass', 'member', }, }, 'System: Read Sudoers compat tree': { 'non_object': True, 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN('ou=sudoers', api.env.basedn), 'ipapermbindruletype': 'anonymous', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'ou', 'sudouser', 'sudohost', 'sudocommand', 'sudorunas', 'sudorunasuser', 'sudorunasgroup', 'sudooption', 'sudonotbefore', 'sudonotafter', 'sudoorder', 'description', }, }, 'System: Add Sudo rule': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Add Sudo rule";allow (add) groupdn = "ldap:///cn=Add Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Sudo Administrator'}, }, 'System: Delete Sudo rule': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Delete Sudo rule";allow (delete) groupdn = "ldap:///cn=Delete Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Sudo Administrator'}, }, 'System: Modify Sudo rule': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'description', 'ipaenabledflag', 'usercategory', 'hostcategory', 'cmdcategory', 'ipasudorunasusercategory', 'ipasudorunasgroupcategory', 'externaluser', 'ipasudorunasextusergroup', 'ipasudorunasextuser', 'ipasudorunasextgroup', 'memberdenycmd', 'memberallowcmd', 'memberuser', 'memberhost', 'externalhost', 'sudonotafter', 'hostmask', 'sudoorder', 'sudonotbefore', 'ipasudorunas', 'externalhost', 'ipasudorunasgroup', 'ipasudoopt', 'memberhost', }, 'replaces': [ '(targetattr = "description || ipaenabledflag || usercategory || hostcategory || cmdcategory || ipasudorunasusercategory || ipasudorunasgroupcategory || externaluser || ipasudorunasextuser || ipasudorunasextgroup || memberdenycmd || memberallowcmd || memberuser")(target = "ldap:///ipauniqueid=*,cn=sudorules,cn=sudo,$SUFFIX")(version 3.0;acl "permission:Modify Sudo rule";allow (write) groupdn = "ldap:///cn=Modify Sudo rule,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Sudo Administrator'}, }, } label = _('Sudo Rules') label_singular = _('Sudo Rule') takes_params = ( Str( 'cn', cli_name='sudorule_name', label=_('Rule name'), primary_key=True, ), Str( 'description?', cli_name='desc', label=_('Description'), ), Bool( 'ipaenabledflag?', label=_('Enabled'), flags=['no_option'], ), StrEnum( 'usercategory?', cli_name='usercat', label=_('User category'), doc=_('User category the rule applies to'), values=(u'all', ), ), StrEnum( 'hostcategory?', cli_name='hostcat', label=_('Host category'), doc=_('Host category the rule applies to'), values=(u'all', ), ), StrEnum( 'cmdcategory?', cli_name='cmdcat', label=_('Command category'), doc=_('Command category the rule applies to'), values=(u'all', ), ), StrEnum( 'ipasudorunasusercategory?', cli_name='runasusercat', label=_('RunAs User category'), doc=_('RunAs User category the rule applies to'), values=(u'all', ), ), StrEnum( 'ipasudorunasgroupcategory?', cli_name='runasgroupcat', label=_('RunAs Group category'), doc=_('RunAs Group category the rule applies to'), values=(u'all', ), ), Int( 'sudoorder?', cli_name='order', label=_('Sudo order'), doc=_('integer to order the Sudo rules'), default=0, minvalue=0, ), Str( 'memberuser_user?', label=_('Users'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberuser_group?', label=_('User Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'externaluser?', validate_externaluser, cli_name='externaluser', label=_('External User'), doc=_('External User the rule applies to (sudorule-find only)'), ), Str( 'memberhost_host?', label=_('Hosts'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberhost_hostgroup?', label=_('Host Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'hostmask', validate_hostmask, normalizer=lambda x: unicode(netaddr.IPNetwork(x).cidr), label=_('Host Masks'), flags=['no_create', 'no_update', 'no_search'], multivalue=True, ), external_host_param, Str( 'memberallowcmd_sudocmd?', label=_('Sudo Allow Commands'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberdenycmd_sudocmd?', label=_('Sudo Deny Commands'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberallowcmd_sudocmdgroup?', label=_('Sudo Allow Command Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'memberdenycmd_sudocmdgroup?', label=_('Sudo Deny Command Groups'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunas_user?', label=_('RunAs Users'), doc=_('Run as a user'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunas_group?', label=_('Groups of RunAs Users'), doc=_('Run as any user within a specified group'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunasextuser?', validate_runasextuser, cli_name='runasexternaluser', label=_('RunAs External User'), doc=_( 'External User the commands can run as (sudorule-find only)'), ), Str( 'ipasudorunasextusergroup?', cli_name='runasexternalusergroup', label=_('External Groups of RunAs Users'), doc=_('External Groups of users that the command can run as'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunasgroup_group?', label=_('RunAs Groups'), doc=_('Run with the gid of a specified POSIX group'), flags=['no_create', 'no_update', 'no_search'], ), Str( 'ipasudorunasextgroup?', validate_runasextgroup, cli_name='runasexternalgroup', label=_('RunAs External Group'), doc=_( 'External Group the commands can run as (sudorule-find only)'), ), Str( 'ipasudoopt?', label=_('Sudo Option'), flags=['no_create', 'no_update', 'no_search'], ), ) order_not_unique_msg = _( 'order must be a unique value (%(order)d already used by %(rule)s)') def check_order_uniqueness(self, *keys, **options): if options.get('sudoorder') is not None: entries = self.methods.find( sudoorder=options['sudoorder'])['result'] if len(entries) > 0: rule_name = entries[0]['cn'][0] raise errors.ValidationError(name='order', error=self.order_not_unique_msg % { 'order': options['sudoorder'], 'rule': rule_name, })
class hostgroup(LDAPObject): """ Hostgroup object. """ container_dn = api.env.container_hostgroup object_name = _('host group') object_name_plural = _('host groups') object_class = ['ipaobject', 'ipahostgroup'] permission_filter_objectclasses = ['ipahostgroup'] search_attributes = ['cn', 'description', 'member', 'memberof'] default_attributes = [ 'cn', 'description', 'member', 'memberof', 'memberindirect', 'memberofindirect', 'membermanager', ] uuid_attribute = 'ipauniqueid' allow_rename = True attribute_members = { 'member': ['host', 'hostgroup'], 'membermanager': ['user', 'group'], 'memberof': ['hostgroup', 'netgroup', 'hbacrule', 'sudorule'], 'memberindirect': ['host', 'hostgroup'], 'memberofindirect': ['hostgroup', 'hbacrule', 'sudorule'], } managed_permissions = { 'System: Read Hostgroups': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'businesscategory', 'cn', 'description', 'ipauniqueid', 'o', 'objectclass', 'ou', 'owner', 'seealso', 'membermanager', }, }, 'System: Read Hostgroup Membership': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'member', 'memberof', 'memberuser', 'memberhost', }, }, 'System: Add Hostgroups': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Hostgroups";allow (add) groupdn = "ldap:///cn=Add Hostgroups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Host Group Administrators'}, }, 'System: Modify Hostgroup Membership': { 'ipapermright': {'write'}, 'ipapermtargetfilter': [ '(objectclass=ipahostgroup)', '(!(cn=ipaservers))', ], 'ipapermdefaultattr': {'member'}, 'replaces': [ '(targetattr = "member")(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Hostgroup membership";allow (write) groupdn = "ldap:///cn=Modify Hostgroup membership,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Host Group Administrators'}, }, 'System: Modify Hostgroups': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'cn', 'description', 'membermanager'}, 'replaces': [ '(targetattr = "cn || description")(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0; acl "permission:Modify Hostgroups";allow (write) groupdn = "ldap:///cn=Modify Hostgroups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Host Group Administrators'}, }, 'System: Remove Hostgroups': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///cn=*,cn=hostgroups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Hostgroups";allow (delete) groupdn = "ldap:///cn=Remove Hostgroups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Host Group Administrators'}, }, } label = _('Host Groups') label_singular = _('Host Group') takes_params = ( Str( 'cn', pattern=NETGROUP_PATTERN, pattern_errmsg=NETGROUP_PATTERN_ERRMSG, cli_name='hostgroup_name', label=_('Host-group'), doc=_('Name of host-group'), primary_key=True, normalizer=lambda value: value.lower(), ), Str( 'description?', cli_name='desc', label=_('Description'), doc=_('A description of this host-group'), ), ) def suppress_netgroup_memberof(self, ldap, dn, entry_attrs): """ We don't want to show managed netgroups so remove them from the memberOf list. """ hgdn = DN(dn) for member in list(entry_attrs.get('memberof', [])): ngdn = DN(member) if ngdn['cn'] != hgdn['cn']: continue filter = ldap.make_filter({'objectclass': 'mepmanagedentry'}) try: ldap.get_entries(ngdn, ldap.SCOPE_BASE, filter, ['']) except errors.NotFound: pass else: entry_attrs['memberof'].remove(member)