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='^(([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?' \ + '(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;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, six.string_types): 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 idoverrideuser(baseidoverride): object_name = _('User ID override') object_name_plural = _('User ID overrides') label = _('User ID overrides') label_singular = _('User ID override') allow_rename = True # ID user overrides are bindable because we map SASL GSSAPI # authentication of trusted users to ID user overrides in the # default trust view. bindable = True permission_filter_objectclasses = ['ipaUserOverride'] managed_permissions = { 'System: Read User ID Overrides': { 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectClass', 'ipaAnchorUUID', 'uidNumber', 'description', 'homeDirectory', 'uid', 'ipaOriginalUid', 'loginShell', 'gecos', 'gidNumber', 'ipaSshPubkey', 'usercertificate' }, }, } object_class = baseidoverride.object_class + ['ipaUserOverride'] possible_objectclasses = ['ipasshuser', 'ipaSshGroupOfPubKeys'] default_attributes = baseidoverride.default_attributes + [ 'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell', 'ipaSshPubkey', 'gidNumber', 'gecos', 'usercertificate;binary', ] search_display_attributes = baseidoverride.default_attributes + [ 'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell', 'ipaSshPubkey', 'gidNumber', 'gecos', ] takes_params = baseidoverride.takes_params + ( Str( 'uid?', pattern=PATTERN_GROUPUSER_NAME, pattern_errmsg='may only include letters, numbers, _, -, . and $', maxlength=255, cli_name='login', label=_('User login'), normalizer=lambda value: value.lower(), ), Int( 'uidnumber?', cli_name='uid', label=_('UID'), doc=_('User ID Number'), minvalue=1, ), Str( 'gecos?', label=_('GECOS'), ), Int( 'gidnumber?', label=_('GID'), doc=_('Group ID Number'), minvalue=1, ), Str( 'homedirectory?', cli_name='homedir', label=_('Home directory'), ), Str( 'loginshell?', cli_name='shell', label=_('Login shell'), ), Str('ipaoriginaluid?', flags=['no_option', 'no_output']), Str( 'ipasshpubkey*', validate_sshpubkey, cli_name='sshpubkey', label=_('SSH public key'), normalizer=normalize_sshpubkey, flags=['no_search'], ), Certificate( 'usercertificate*', cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded user certificate'), flags=[ 'no_search', ], ), ) override_object = 'user' def update_original_uid_reference(self, entry_attrs): anchor = entry_attrs.single_value['ipaanchoruuid'] try: original_uid = resolve_anchor_to_object_name( self.backend, self.override_object, anchor) entry_attrs['ipaOriginalUid'] = original_uid except (errors.NotFound, errors.ValidationError): # Anchor could not be resolved, this means we had to specify the # object to manipulate using a raw anchor value already, hence # we have no way to update the original_uid pass 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')
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'}, }, } 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'), ), Str('krbprincipalauthind*', cli_name='auth_ind', label=_('Authentication Indicators'), doc=_("Defines a whitelist for Authentication Indicators." " Use 'otp' to allow OTP-based 2FA authentications." " Use 'radius' to allow RADIUS-based 2FA authentications." " Other values may be used for custom configurations."), ), ) + 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, six.text_type): 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 ModCertMapData(LDAPModAttribute): attribute = 'ipacertmapdata' takes_options = ( DNParam('issuer?', cli_name='issuer', label=_('Issuer'), doc=_('Issuer of the certificate'), flags=['virtual_attribute']), DNParam('subject?', cli_name='subject', label=_('Subject'), doc=_('Subject of the certificate'), flags=['virtual_attribute']), Certificate('certificate*', cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded user certificate'), flags=['virtual_attribute']), ) @staticmethod def _build_mapdata(subject, issuer): return u'X509:<I>{issuer}<S>{subject}'.format( issuer=issuer.x500_text(), subject=subject.x500_text()) @classmethod def _convert_options_to_certmap(cls, entry_attrs, issuer=None, subject=None, certificates=()): """ Converts options to ipacertmapdata When --subject --issuer or --certificate options are used, the value for ipacertmapdata is built from extracting subject and issuer, converting their values to X500 ordering and using the format X509:<I>issuer<S>subject For instance: X509:<I>O=DOMAIN,CN=Certificate Authority<S>O=DOMAIN,CN=user A list of values can be returned if --certificate is used multiple times, or in conjunction with --subject --issuer. """ data = [] data.extend(entry_attrs.get(cls.attribute, list())) if issuer or subject: data.append(cls._build_mapdata(subject, issuer)) for cert in certificates: issuer = DN(cert.issuer) subject = DN(cert.subject) if not subject: raise errors.ValidationError( name='certificate', error=_('cannot have an empty subject')) data.append(cls._build_mapdata(subject, issuer)) entry_attrs[cls.attribute] = data def get_args(self): # ipacertmapdata is not mandatory as it can be built # from the values subject+issuer or from reading certificate for arg in super(ModCertMapData, self).get_args(): if arg.name == 'ipacertmapdata': yield arg.clone(required=False, alwaysask=False) else: yield arg.clone() def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): # The 3 valid calls are # ipa user-add-certmapdata LOGIN --subject xx --issuer yy # ipa user-add-certmapdata LOGIN [DATA] --certificate xx # ipa user-add-certmapdata LOGIN DATA # Check that at least one of the 3 formats is used try: certmapdatas = keys[1] or [] except IndexError: certmapdatas = [] issuer = options.get('issuer') subject = options.get('subject') certificates = options.get('certificate', []) # If only LOGIN is supplied, then we need either subject or issuer or # certificate if (not certmapdatas and not issuer and not subject and not certificates): raise errors.RequirementError(name='ipacertmapdata') # If subject or issuer is provided, other options are not allowed if subject or issuer: if certificates: raise errors.MutuallyExclusiveError( reason=_('cannot specify both subject/issuer ' 'and certificate')) if certmapdatas: raise errors.MutuallyExclusiveError( reason=_('cannot specify both subject/issuer ' 'and ipacertmapdata')) # If subject or issuer is provided, then the other one is required if not subject: raise errors.RequirementError(name='subject') if not issuer: raise errors.RequirementError(name='issuer') # if the command is called with --subject --issuer or --certificate # we need to add ipacertmapdata to the attrs_list in order to # display the resulting value in the command output if 'ipacertmapdata' not in attrs_list: attrs_list.append('ipacertmapdata') self._convert_options_to_certmap(entry_attrs, issuer=issuer, subject=subject, certificates=certificates) return dn