class idoverridegroup(baseidoverride): object_name = _('Group ID override') object_name_plural = _('Group ID overrides') label = _('Group ID overrides') label_singular = _('Group ID override') rdn_is_primary_key = True permission_filter_objectclasses = ['ipaGroupOverride'] managed_permissions = { 'System: Read Group ID Overrides': { 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectClass', 'ipaAnchorUUID', 'gidNumber', 'description', 'cn', }, }, } object_class = baseidoverride.object_class + ['ipaGroupOverride'] default_attributes = baseidoverride.default_attributes + [ 'gidNumber', 'cn', ] takes_params = baseidoverride.takes_params + ( Str( 'cn?', pattern=PATTERN_GROUPUSER_NAME, pattern_errmsg='may only include letters, numbers, _, -, . and $', maxlength=255, cli_name='group_name', label=_('Group name'), normalizer=lambda value: value.lower(), ), Int( 'gidnumber?', cli_name='gid', label=_('GID'), doc=_('Group ID Number'), minvalue=1, ), ) override_object = 'group'
class deskprofileconfig(LDAPObject): """ Global configuration for desktop profiles """ object_name = _('configuration options') default_attributes = [ 'ipadeskprofilepriority', ] permission_filter_objectclasses = ['ipaguiconfig'] managed_permissions = { 'System: Read FleetCommander Desktop Profile Configuration': { 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'ipadeskprofilepriority', 'objectclass', }, }, 'System: Modify FleetCommander Desktop Profile Configuration': { 'ipapermbindruletype': 'permission', 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'ipadeskprofilepriority', }, 'default_privileges': {'FleetCommander Desktop Profile Administrators'}, }, } label = _('Desktop Profile Global Configuration') label_singular = _('Dekstop Profile Global Configuration') takes_params = (Int( 'ipadeskprofilepriority', cli_name='priority', label=_('Priority of profile application'), minvalue=1, maxvalue=24, ), ) # Inject constants into the api.env before it is locked down def _on_finalize(self): self.env._merge(**dict(PLUGIN_CONFIG)) self.container_dn = self.env.container_deskprofile super(deskprofileconfig, self)._on_finalize() def get_dn(self, *keys, **kwargs): return DN(self.container_dn, api.env.basedn)
class cert_revoke(VirtualCommand): __doc__ = _('Revoke a certificate.') takes_args = _serial_number has_output_params = (Flag( 'revoked', label=_('Revoked'), ), ) operation = "revoke certificate" # FIXME: The default is 0. Is this really an Int param? takes_options = (Int('revocation_reason', label=_('Reason'), doc=_('Reason for revoking the certificate (0-10)'), minvalue=0, maxvalue=10, default=0, autofill=True), ) def execute(self, serial_number, **kw): ca_enabled_check() hostname = None try: self.check_access() except errors.ACIError as acierr: self.debug( "Not granted by ACI to revoke certificate, looking at principal" ) try: # Let cert_show() handle verifying that the subject of the # cert we're dealing with matches the hostname in the principal result = api.Command['cert_show']( unicode(serial_number))['result'] except errors.NotImplementedError: pass revocation_reason = kw['revocation_reason'] if revocation_reason == 7: raise errors.CertificateOperationError( error=_('7 is not a valid revocation reason')) return dict(result=self.Backend.ra.revoke_certificate( serial_number, revocation_reason=revocation_reason))
class cosentry(LDAPObject): """ Class of Service object used for linking policies with groups """ NO_CLI = True container_dn = DN(('cn', 'costemplates'), api.env.container_accounts) object_class = ['top', 'costemplate', 'extensibleobject', 'krbcontainer'] default_attributes = ['cn', 'cospriority', 'krbpwdpolicyreference'] takes_params = ( Str('cn', primary_key=True), DNParam('krbpwdpolicyreference'), Int('cospriority', minvalue=0), ) priority_not_unique_msg = _( 'priority must be a unique value (%(prio)d already used by %(gname)s)') def get_dn(self, *keys, **options): group_dn = self.api.Object.group.get_dn(keys[-1]) return self.backend.make_dn_from_attr('cn', group_dn, self.container_dn) def check_priority_uniqueness(self, *keys, **options): if options.get('cospriority') is not None: entries = self.methods.find( cospriority=options['cospriority'])['result'] if len(entries) > 0: group_name = self.api.Object.group.get_primary_key_from_dn( DN(entries[0]['cn'][0])) raise errors.ValidationError( name='priority', error=self.priority_not_unique_msg % { 'prio': options['cospriority'], 'gname': group_name, })
class topologysegment(LDAPObject): """ Topology segment. """ parent_object = 'topologysuffix' container_dn = api.env.container_topology object_name = _('segment') object_name_plural = _('segments') object_class = ['iparepltoposegment'] default_attributes = [ 'cn', 'ipaReplTopoSegmentdirection', 'ipaReplTopoSegmentrightNode', 'ipaReplTopoSegmentLeftNode', 'nsds5replicastripattrs', 'nsds5replicatedattributelist', 'nsds5replicatedattributelisttotal', 'nsds5replicatimeout', 'nsds5replicaenabled' ] search_display_attributes = [ 'cn', 'ipaReplTopoSegmentdirection', 'ipaReplTopoSegmentrightNode', 'ipaReplTopoSegmentLeftNode' ] label = _('Topology Segments') label_singular = _('Topology Segment') takes_params = ( Str( 'cn', maxlength=255, cli_name='name', primary_key=True, label=_('Segment name'), default_from=lambda iparepltoposegmentleftnode, iparepltoposegmentrightnode: '%s-to-%s' % (iparepltoposegmentleftnode, iparepltoposegmentrightnode), normalizer=lambda value: value.lower(), doc=_('Arbitrary string identifying the segment'), ), Str( 'iparepltoposegmentleftnode', pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]*[a-zA-Z0-9.$-]?$', pattern_errmsg='may only include letters, numbers, -, . and $', maxlength=255, cli_name='leftnode', label=_('Left node'), normalizer=lambda value: value.lower(), doc=_('Left replication node - an IPA server'), flags={'no_update'}, ), Str( 'iparepltoposegmentrightnode', pattern='^[a-zA-Z0-9.][a-zA-Z0-9.-]*[a-zA-Z0-9.$-]?$', pattern_errmsg='may only include letters, numbers, -, . and $', maxlength=255, cli_name='rightnode', label=_('Right node'), normalizer=lambda value: value.lower(), doc=_('Right replication node - an IPA server'), flags={'no_update'}, ), StrEnum( 'iparepltoposegmentdirection', cli_name='direction', label=_('Connectivity'), values=(u'both', u'left-right', u'right-left'), default=u'both', autofill=True, doc=_( 'Direction of replication between left and right replication ' 'node'), flags={'no_option', 'no_update'}, ), Str('nsds5replicastripattrs?', cli_name='stripattrs', label=_('Attributes to strip'), normalizer=lambda value: value.lower(), doc=_( 'A space separated list of attributes which are removed from ' 'replication updates.')), Str( 'nsds5replicatedattributelist?', cli_name='replattrs', label='Attributes to replicate', doc=_('Attributes that are not replicated to a consumer server ' 'during a fractional update. E.g., `(objectclass=*) ' '$ EXCLUDE accountlockout memberof'), ), Str( 'nsds5replicatedattributelisttotal?', cli_name='replattrstotal', label=_('Attributes for total update'), doc=_('Attributes that are not replicated to a consumer server ' 'during a total update. E.g. (objectclass=*) $ EXCLUDE ' 'accountlockout'), ), Int( 'nsds5replicatimeout?', cli_name='timeout', label=_('Session timeout'), minvalue=0, doc=_('Number of seconds outbound LDAP operations waits for a ' 'response from the remote replica before timing out and ' 'failing'), ), StrEnum( 'nsds5replicaenabled?', cli_name='enabled', label=_('Replication agreement enabled'), doc=_('Whether a replication agreement is active, meaning whether ' 'replication is occurring per that agreement'), values=(u'on', u'off'), flags={'no_option'}, ), ) def validate_nodes(self, ldap, dn, entry_attrs, suffix): leftnode = entry_attrs.get('iparepltoposegmentleftnode') rightnode = entry_attrs.get('iparepltoposegmentrightnode') if not leftnode and not rightnode: return # nothing to check # check if nodes are IPA servers masters = self.api.Command.server_find('', sizelimit=0, no_members=False)['result'] m_hostnames = [master['cn'][0].lower() for master in masters] if leftnode and leftnode not in m_hostnames: raise errors.ValidationError( name='leftnode', error=_('left node is not a topology node: %(leftnode)s') % dict(leftnode=leftnode)) if rightnode and rightnode not in m_hostnames: raise errors.ValidationError( name='rightnode', error=_('right node is not a topology node: %(rightnode)s') % dict(rightnode=rightnode)) # prevent creation of reflexive relation key = 'leftnode' if not leftnode or not rightnode: # get missing end _entry_attrs = ldap.get_entry(dn, ['*']) if not leftnode: key = 'rightnode' leftnode = _entry_attrs['iparepltoposegmentleftnode'][0] else: rightnode = _entry_attrs['iparepltoposegmentrightnode'][0] if leftnode == rightnode: raise errors.ValidationError( name=key, error=_('left node and right node must not be the same')) # don't allow segment between nodes where both don't have the suffix masters_to_suffix = map_masters_to_suffixes(masters) suffix_masters = masters_to_suffix.get(suffix, []) suffix_m_hostnames = [m['cn'][0].lower() for m in suffix_masters] if leftnode not in suffix_m_hostnames: raise errors.ValidationError( name='leftnode', error=_("left node ({host}) does not support " "suffix '{suff}'").format(host=leftnode, suff=suffix)) if rightnode not in suffix_m_hostnames: raise errors.ValidationError( name='rightnode', error=_("right node ({host}) does not support " "suffix '{suff}'").format(host=rightnode, suff=suffix))
class cosentry(LDAPObject): """ Class of Service object used for linking policies with groups """ NO_CLI = True container_dn = DN(('cn', 'costemplates'), api.env.container_accounts) object_class = ['top', 'costemplate', 'extensibleobject', 'krbcontainer'] permission_filter_objectclasses = ['costemplate'] default_attributes = ['cn', 'cospriority', 'krbpwdpolicyreference'] managed_permissions = { 'System: Read Group Password Policy costemplate': { 'replaces_global_anonymous_aci': True, 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'cospriority', 'krbpwdpolicyreference', 'objectclass', }, 'default_privileges': { 'Password Policy Readers', 'Password Policy Administrator', }, }, 'System: Add Group Password Policy costemplate': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///cn=*,cn=costemplates,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Group Password Policy costemplate";allow (add) groupdn = "ldap:///cn=Add Group Password Policy costemplate,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, 'System: Delete Group Password Policy costemplate': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///cn=*,cn=costemplates,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Delete Group Password Policy costemplate";allow (delete) groupdn = "ldap:///cn=Delete Group Password Policy costemplate,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, 'System: Modify Group Password Policy costemplate': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'cospriority'}, 'replaces': [ '(targetattr = "cospriority")(target = "ldap:///cn=*,cn=costemplates,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group Password Policy costemplate";allow (write) groupdn = "ldap:///cn=Modify Group Password Policy costemplate,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, } takes_params = ( Str('cn', primary_key=True), DNParam('krbpwdpolicyreference'), Int('cospriority', minvalue=0), ) priority_not_unique_msg = _( 'priority must be a unique value (%(prio)d already used by %(gname)s)') def get_dn(self, *keys, **options): group_dn = self.api.Object.group.get_dn(keys[-1]) return self.backend.make_dn_from_attr( 'cn', group_dn, DN(self.container_dn, api.env.basedn)) def check_priority_uniqueness(self, *keys, **options): if options.get('cospriority') is not None: entries = self.methods.find( cospriority=options['cospriority'])['result'] if len(entries) > 0: group_name = self.api.Object.group.get_primary_key_from_dn( DN(entries[0]['cn'][0])) raise errors.ValidationError( name='priority', error=self.priority_not_unique_msg % { 'prio': options['cospriority'], 'gname': group_name, })
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'] permission_filter_objectclasses = ['ipaidrange'] possible_objectclasses = ['ipadomainidrange', 'ipatrustedaddomainrange'] default_attributes = [ 'cn', 'ipabaseid', 'ipaidrangesize', 'ipabaserid', 'ipasecondarybaserid', 'ipanttrusteddomainsid', 'iparangetype' ] managed_permissions = { 'System: Read ID Ranges': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'objectclass', 'ipabaseid', 'ipaidrangesize', 'iparangetype', 'ipabaserid', 'ipasecondarybaserid', 'ipanttrusteddomainsid', }, }, } label = _('ID Ranges') label_singular = _('ID Range') # The commented range types are planned but not yet supported range_types = { u'ipa-local': unicode(_('local domain range')), # u'ipa-ad-winsync': unicode(_('Active Directory winsync range')), u'ipa-ad-trust': unicode(_('Active Directory domain range')), u'ipa-ad-trust-posix': unicode(_('Active Directory trust range with ' 'POSIX attributes')), # u'ipa-ipa-trust': unicode(_('IPA trust 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', flags=('no_update', ), label=_('Domain SID of the trusted domain'), ), Str( 'ipanttrusteddomainname?', cli_name='dom_name', flags=('no_search', 'virtual_attribute', 'no_update'), label=_('Name of the trusted domain'), ), StrEnum( 'iparangetype?', label=_('Range type'), cli_name='type', doc=(_('ID range type, one of {vals}'.format( vals=', '.join(sorted(range_types))))), values=sorted(range_types), flags=['no_update'], )) def handle_iparangetype(self, entry_attrs, options, keep_objectclass=False): if not any( (options.get('pkey_only', False), options.get('raw', False))): range_type = entry_attrs['iparangetype'][0] entry_attrs['iparangetyperaw'] = [range_type] entry_attrs['iparangetype'] = [ self.range_types.get(range_type, None) ] # Remove the objectclass if not keep_objectclass: if not options.get('all', False) or options.get( 'pkey_only', False): entry_attrs.pop('objectclass', None) def handle_ipabaserid(self, entry_attrs, options): if any((options.get('pkey_only', False), options.get('raw', False))): return if entry_attrs['iparangetype'][0] == u'ipa-ad-trust-posix': entry_attrs.pop('ipabaserid', 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: 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 get_domain_validator(self): 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')) return domain_validator def validate_trusted_domain_sid(self, sid): domain_validator = self.get_domain_validator() if not domain_validator.is_trusted_domain_sid_valid(sid): raise errors.ValidationError( name='domain SID', error=_('SID is not recognized as a valid SID for a ' 'trusted domain')) def get_trusted_domain_sid_from_name(self, name): """ Returns unicode string representation for given trusted domain name or None if SID forthe given trusted domain name could not be found.""" domain_validator = self.get_domain_validator() sid = domain_validator.get_sid_from_domain_name(name) if sid is not None: sid = unicode(sid) return sid # checks that primary and secondary rid ranges do not overlap def are_rid_ranges_overlapping(self, rid_base, secondary_rid_base, size): # if any of these is None, the check does not apply if any(attr is None for attr in (rid_base, secondary_rid_base, size)): return False # sort the bases if rid_base > secondary_rid_base: rid_base, secondary_rid_base = secondary_rid_base, rid_base # rid_base is now <= secondary_rid_base, # so the following check is sufficient if rid_base + size <= secondary_rid_base: return False else: return True
class otptoken(LDAPObject): """ OTP Token object. """ container_dn = api.env.container_otp object_name = _('OTP token') object_name_plural = _('OTP tokens') object_class = ['ipatoken'] possible_objectclasses = ['ipatokentotp', 'ipatokenhotp'] default_attributes = [ 'ipatokenuniqueid', 'description', 'ipatokenowner', 'ipatokendisabled', 'ipatokennotbefore', 'ipatokennotafter', 'ipatokenvendor', 'ipatokenmodel', 'ipatokenserial', 'managedby' ] attribute_members = { 'managedby': ['user'], } relationships = { 'managedby': ('Managed by', 'man_by_', 'not_man_by_'), } allow_rename = True label = _('OTP Tokens') label_singular = _('OTP Token') takes_params = ( Str( 'ipatokenuniqueid', cli_name='id', label=_('Unique ID'), primary_key=True, flags=('optional_create'), ), StrEnum( 'type?', label=_('Type'), doc=_('Type of the token'), default=u'totp', autofill=True, values=tuple(list(TOKEN_TYPES) + [x.upper() for x in TOKEN_TYPES]), flags=('virtual_attribute', 'no_update'), ), Str( 'description?', cli_name='desc', label=_('Description'), doc=_('Token description (informational only)'), ), Str( 'ipatokenowner?', cli_name='owner', label=_('Owner'), doc=_('Assigned user of the token (default: self)'), ), Str( 'managedby_user?', label=_('Manager'), doc=_('Assigned manager of the token (default: self)'), flags=['no_create', 'no_update', 'no_search'], ), Bool('ipatokendisabled?', cli_name='disabled', label=_('Disabled'), doc=_('Mark the token as disabled (default: false)')), DateTime( 'ipatokennotbefore?', cli_name='not_before', label=_('Validity start'), doc=_('First date/time the token can be used'), ), DateTime( 'ipatokennotafter?', cli_name='not_after', label=_('Validity end'), doc=_('Last date/time the token can be used'), ), Str( 'ipatokenvendor?', cli_name='vendor', label=_('Vendor'), doc=_('Token vendor name (informational only)'), ), Str( 'ipatokenmodel?', cli_name='model', label=_('Model'), doc=_('Token model (informational only)'), ), Str( 'ipatokenserial?', cli_name='serial', label=_('Serial'), doc=_('Token serial (informational only)'), ), OTPTokenKey( 'ipatokenotpkey?', cli_name='key', label=_('Key'), doc=_('Token secret (Base32; default: random)'), default_from=lambda: os.urandom(KEY_LENGTH), autofill=True, # force server-side conversion normalizer=lambda x: x, flags=('no_display', 'no_update', 'no_search'), ), StrEnum( 'ipatokenotpalgorithm?', cli_name='algo', label=_('Algorithm'), doc=_('Token hash algorithm'), default=u'sha1', autofill=True, flags=('no_update'), values=(u'sha1', u'sha256', u'sha384', u'sha512'), ), IntEnum( 'ipatokenotpdigits?', cli_name='digits', label=_('Digits'), doc=_('Number of digits each token code will have'), values=(6, 8), default=6, autofill=True, flags=('no_update'), ), Int( 'ipatokentotpclockoffset?', cli_name='offset', label=_('Clock offset'), doc=_('TOTP token / FreeIPA server time difference'), default=0, autofill=True, flags=('no_update'), ), Int( 'ipatokentotptimestep?', cli_name='interval', label=_('Clock interval'), doc=_('Length of TOTP token code validity'), default=30, autofill=True, minvalue=5, flags=('no_update'), ), Int( 'ipatokenhotpcounter?', cli_name='counter', label=_('Counter'), doc=_('Initial counter for the HOTP token'), default=0, autofill=True, minvalue=0, flags=('no_update'), ), Str( 'uri?', label=_('URI'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), )
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', 'memberof' }, }, } object_class = baseidoverride.object_class + ['ipaUserOverride'] possible_objectclasses = ['ipasshuser', 'ipaSshGroupOfPubKeys', 'nsmemberof'] default_attributes = baseidoverride.default_attributes + [ 'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell', 'ipaSshPubkey', 'gidNumber', 'gecos', 'usercertificate;binary', 'memberofindirect', 'memberof' ] search_display_attributes = baseidoverride.default_attributes + [ 'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell', 'ipaSshPubkey', 'gidNumber', 'gecos', ] attribute_members = { 'memberof': ['group', 'role'], 'memberofindirect': ['group', 'role'], } 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 cert_find(Search, CertMethod): __doc__ = _('Search for existing certificates.') takes_options = ( Str( 'subject?', label=_('Subject'), doc=_('Subject'), autofill=False, ), Int( 'min_serial_number?', doc=_("minimum serial number"), autofill=False, minvalue=0, maxvalue=2147483647, ), Int( 'max_serial_number?', doc=_("maximum serial number"), autofill=False, minvalue=0, maxvalue=2147483647, ), Flag( 'exactly?', doc=_('match the common name exactly'), autofill=False, ), DateTime( 'validnotafter_from?', doc=_('Valid not after from this date (YYYY-mm-dd)'), normalizer=normalize_pkidate, autofill=False, ), DateTime( 'validnotafter_to?', doc=_('Valid not after to this date (YYYY-mm-dd)'), normalizer=normalize_pkidate, autofill=False, ), DateTime( 'validnotbefore_from?', doc=_('Valid not before from this date (YYYY-mm-dd)'), normalizer=normalize_pkidate, autofill=False, ), DateTime( 'validnotbefore_to?', doc=_('Valid not before to this date (YYYY-mm-dd)'), normalizer=normalize_pkidate, autofill=False, ), DateTime( 'issuedon_from?', doc=_('Issued on from this date (YYYY-mm-dd)'), normalizer=normalize_pkidate, autofill=False, ), DateTime( 'issuedon_to?', doc=_('Issued on to this date (YYYY-mm-dd)'), normalizer=normalize_pkidate, autofill=False, ), DateTime( 'revokedon_from?', doc=_('Revoked on from this date (YYYY-mm-dd)'), normalizer=normalize_pkidate, autofill=False, ), DateTime( 'revokedon_to?', doc=_('Revoked on to this date (YYYY-mm-dd)'), normalizer=normalize_pkidate, autofill=False, ), Flag( 'pkey_only?', label=_("Primary key only"), doc=_("Results should contain primary key attribute only " "(\"certificate\")"), ), Int( 'timelimit?', label=_('Time Limit'), doc=_('Time limit of search in seconds (0 is unlimited)'), minvalue=0, ), Int( 'sizelimit?', label=_("Size Limit"), doc=_("Maximum number of entries returned (0 is unlimited)"), minvalue=0, ), ) msg_summary = ngettext('%(count)d certificate matched', '%(count)d certificates matched', 0) def get_options(self): for option in super(cert_find, self).get_options(): if option.name == 'no_members': option = option.clone(default=True, flags=set(option.flags) | {'no_option'}) elif option.name == 'cacn': # make CA optional, so that user may directly # specify Issuer DN instead option = option.clone(default=None, autofill=None) yield option for owner in self.obj._owners(): yield owner.primary_key.clone_rename( '{0}'.format(owner.name), required=False, multivalue=True, primary_key=False, query=True, cli_name='{0}s'.format(owner.name), doc=(_("Search for certificates with these owner %s.") % owner.object_name_plural), label=owner.object_name, ) yield owner.primary_key.clone_rename( 'no_{0}'.format(owner.name), required=False, multivalue=True, primary_key=False, query=True, cli_name='no_{0}s'.format(owner.name), doc=(_("Search for certificates without these owner %s.") % owner.object_name_plural), label=owner.object_name, ) def _get_cert_key(self, cert): try: nss_cert = x509.load_certificate(cert, x509.DER) except NSPRError as e: message = messages.SearchResultTruncated( reason=_("failed to load certificate: %s") % e, ) self.add_message(message) raise ValueError("failed to load certificate") return (DN(unicode(nss_cert.issuer)), nss_cert.serial_number) def _get_cert_obj(self, cert, all, raw, pkey_only): obj = {'certificate': unicode(base64.b64encode(cert))} full = not pkey_only and all if not raw: self.obj._parse(obj, full) if not full: del obj['certificate'] return obj def _cert_search(self, all, raw, pkey_only, **options): result = collections.OrderedDict() try: cert = options['certificate'] except KeyError: return result, False, False try: key = self._get_cert_key(cert) except ValueError: return result, True, True result[key] = self._get_cert_obj(cert, all, raw, pkey_only) return result, False, True def _ca_search(self, all, raw, pkey_only, sizelimit, exactly, **options): ra_options = {} for name in ('revocation_reason', 'issuer', 'subject', 'min_serial_number', 'max_serial_number', 'validnotafter_from', 'validnotafter_to', 'validnotbefore_from', 'validnotbefore_to', 'issuedon_from', 'issuedon_to', 'revokedon_from', 'revokedon_to'): try: value = options[name] except KeyError: continue if isinstance(value, datetime.datetime): value = value.strftime(PKIDATE_FORMAT) elif isinstance(value, DN): value = unicode(value) ra_options[name] = value if sizelimit: ra_options['sizelimit'] = sizelimit if exactly: ra_options['exactly'] = True result = collections.OrderedDict() complete = bool(ra_options) try: ca_enabled_check() except errors.NotFound: if ra_options: raise return result, False, complete ca_objs = self.api.Command.ca_find()['result'] ca_objs = {DN(ca['ipacasubjectdn'][0]): ca for ca in ca_objs} ra = self.api.Backend.ra for ra_obj in ra.find(ra_options): issuer = DN(ra_obj['issuer']) serial_number = ra_obj['serial_number'] try: ca_obj = ca_objs[issuer] except KeyError: continue if pkey_only: obj = {'serial_number': serial_number} else: obj = ra_obj if all: obj.update(ra.get_certificate(str(serial_number))) if not raw: obj['issuer'] = issuer obj['subject'] = DN(ra_obj['subject']) obj['revoked'] = (ra_obj['status'] in (u'REVOKED', u'REVOKED_EXPIRED')) if all: obj['certificate'] = (obj['certificate'].replace( '\r\n', '')) self.obj._parse(obj) obj['cacn'] = ca_obj['cn'][0] result[issuer, serial_number] = obj return result, False, complete def _ldap_search(self, all, raw, pkey_only, no_members, timelimit, sizelimit, **options): ldap = self.api.Backend.ldap2 filters = [] for owner in self.obj._owners(): for prefix, rule in (('', ldap.MATCH_ALL), ('no_', ldap.MATCH_NONE)): try: value = options[prefix + owner.name] except KeyError: continue filter = ldap.make_filter_from_attr('objectclass', owner.object_class, ldap.MATCH_ALL) if filter not in filters: filters.append(filter) filter = ldap.make_filter_from_attr(owner.primary_key.name, value, rule) filters.append(filter) result = collections.OrderedDict() complete = bool(filters) cert = options.get('certificate') if cert is not None: filter = ldap.make_filter_from_attr('usercertificate', cert) else: filter = '(usercertificate=*)' filters.append(filter) filter = ldap.combine_filters(filters, ldap.MATCH_ALL) try: entries, truncated = ldap.find_entries( base_dn=self.api.env.basedn, filter=filter, attrs_list=['usercertificate'], time_limit=timelimit, size_limit=sizelimit, ) except errors.EmptyResult: entries = [] truncated = False else: try: ldap.handle_truncated_result(truncated) except errors.LimitsExceeded as e: self.add_message(messages.SearchResultTruncated(reason=e)) truncated = bool(truncated) for entry in entries: for attr in ('usercertificate', 'usercertificate;binary'): for cert in entry.get(attr, []): try: key = self._get_cert_key(cert) except ValueError: truncated = True continue try: obj = result[key] except KeyError: obj = self._get_cert_obj(cert, all, raw, pkey_only) result[key] = obj if not pkey_only and (all or not no_members): owners = obj.setdefault('owner', []) if entry.dn not in owners: owners.append(entry.dn) if not raw: for obj in six.itervalues(result): self.obj._fill_owners(obj) return result, truncated, complete def execute(self, criteria=None, all=False, raw=False, pkey_only=False, no_members=True, timelimit=None, sizelimit=None, **options): if 'cacn' in options: ca_obj = api.Command.ca_show(options['cacn'])['result'] ca_sdn = unicode(ca_obj['ipacasubjectdn'][0]) if 'issuer' in options: if DN(ca_sdn) != DN(options['issuer']): # client has provided both 'ca' and 'issuer' but # issuer DNs don't match; result must be empty return dict(result=[], count=0, truncated=False) else: options['issuer'] = ca_sdn if criteria is not None: return dict(result=[], count=0, truncated=False) result = collections.OrderedDict() truncated = False complete = False for sub_search in (self._cert_search, self._ca_search, self._ldap_search): sub_result, sub_truncated, sub_complete = sub_search( all=all, raw=raw, pkey_only=pkey_only, no_members=no_members, timelimit=timelimit, sizelimit=sizelimit, **options) if sub_complete: sizelimit = None for key in tuple(result): if key not in sub_result: del result[key] for key, sub_obj in six.iteritems(sub_result): try: obj = result[key] except KeyError: if complete: continue result[key] = sub_obj else: obj.update(sub_obj) truncated = truncated or sub_truncated complete = complete or sub_complete result = list(six.itervalues(result)) ret = dict(result=result) ret['count'] = len(ret['result']) ret['truncated'] = bool(truncated) return ret
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', 'krbauthindmaxticketlife', 'krbauthindmaxrenewableage' ] 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', 'krbauthindmaxticketlife', 'krbauthindmaxrenewableage', }, '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', 'krbauthindmaxticketlife', 'krbauthindmaxrenewableage', }, '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, ), Int('krbauthindmaxticketlife_otp?', cli_name='otp_maxlife', label=_('OTP max life'), doc=_('OTP token maximum ticket life (seconds)'), minvalue=1), Int('krbauthindmaxrenewableage_otp?', cli_name='otp_maxrenew', label=_('OTP max renew'), doc=_('OTP token ticket maximum renewable age (seconds)'), minvalue=1), Int('krbauthindmaxticketlife_radius?', cli_name='radius_maxlife', label=_('RADIUS max life'), doc=_('RADIUS maximum ticket life (seconds)'), minvalue=1), Int('krbauthindmaxrenewableage_radius?', cli_name='radius_maxrenew', label=_('RADIUS max renew'), doc=_('RADIUS ticket maximum renewable age (seconds)'), minvalue=1), Int('krbauthindmaxticketlife_pkinit?', cli_name='pkinit_maxlife', label=_('PKINIT max life'), doc=_('PKINIT maximum ticket life (seconds)'), minvalue=1), Int('krbauthindmaxrenewableage_pkinit?', cli_name='pkinit_maxrenew', label=_('PKINIT max renew'), doc=_('PKINIT ticket maximum renewable age (seconds)'), minvalue=1), Int('krbauthindmaxticketlife_hardened?', cli_name='hardened_maxlife', label=_('Hardened max life'), doc=_('Hardened ticket maximum ticket life (seconds)'), minvalue=1), Int('krbauthindmaxrenewableage_hardened?', cli_name='hardened_maxrenew', label=_('Hardened max renew'), doc=_('Hardened ticket 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 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' allow_rename = True 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 deskprofilerule(LDAPObject): """ Desktop profile object. """ container_dn = None object_name = _('FleetCommander Desktop Profile Rule Map') object_name_plural = _('FleetCommander Desktop Profile Rule Maps') object_class = ['ipaassociation', 'ipadeskprofilerule'] permission_filter_objectclasses = ['ipadeskprofilerule'] default_attributes = [ 'cn', 'ipaenabledflag', 'ipadeskprofiletarget' 'description', 'usercategory', 'hostcategory', 'memberuser', 'memberhost', 'ipadeskprofilepriority', 'seealso', ] uuid_attribute = 'ipauniqueid' rdn_is_primary_key = True attribute_members = { 'memberuser': ['user', 'group'], 'memberhost': ['host', 'hostgroup'], } managed_permissions = { 'System: Read FleetCommander Desktop Profile Rule Map': { 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'description', 'hostcategory', 'ipaenabledflag', 'ipauniqueid', 'memberhost', 'memberuser', 'seealso', 'usercategory', 'objectclass', 'member', 'ipadeskprofilepriority', 'ipadeskprofiletarget', }, }, 'System: Add FleetCommander Desktop Profile Rule Map': { 'ipapermbindruletype': 'permission', 'ipapermright': {'add'}, 'default_privileges': {'FleetCommander Desktop Profile Administrators'}, }, 'System: Modify FleetCommander Desktop Profile Rule Map': { 'ipapermbindruletype': 'permission', 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'cn', 'ipaenabledflag', 'memberhost', 'memberuser', 'seealso', 'ipadeskprofilepriority', 'ipadeskprofiletarget', }, 'default_privileges': {'FleetCommander Desktop Profile Administrators'}, }, 'System: Remove FleetCommander Desktop Profile Rule Map': { 'ipapermbindruletype': 'permission', 'ipapermright': {'delete'}, 'default_privileges': {'FleetCommander Desktop Profile Administrators'}, }, } label = _('FleetCommander Desktop Profile Rule Map') label_singular = _('FleetCommander Desktop Profile Rule Map') takes_params = ( Str( 'cn', cli_name='name', label=_('Rule name'), primary_key=True, ), Str( 'ipadeskprofiletarget', cli_name='profile', label=_('Desktop profile'), doc=_('Desktop profile associated with the rule'), ), Int( 'ipadeskprofilepriority', cli_name='prio', label=_('Desktop profile priority'), minvalue=1, maxvalue=100000, doc=_('Priority for desktop profile associated with the rule'), ), Str( 'seealso?', cli_name='hbacrule', label=_('HBAC Rule'), doc=_('HBAC Rule that defines the users, groups and hostgroups'), ), 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', ), ), 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'], ), ) # Inject constants into the api.env before it is locked down def _on_finalize(self): self.env._merge(**dict(PLUGIN_CONFIG)) self.container_dn = self.env.container_deskprofilerule super(deskprofilerule, self)._on_finalize() def _normalize_seealso(self, seealso): """ Given a HBAC rule name verify its existence and return the dn. """ if not seealso: return None try: dn = DN(seealso) return str(dn) except ValueError: try: entry_attrs = self.backend.find_entry_by_attr( self.api.Object['hbacrule'].primary_key.name, seealso, self.api.Object['hbacrule'].object_class, [''], DN(self.api.Object['hbacrule'].container_dn, api.env.basedn)) seealso = entry_attrs.dn except errors.NotFound: raise errors.NotFound( reason=_('HBAC rule %(rule)s not found') % dict(rule=seealso)) return seealso def _convert_seealso(self, ldap, entry_attrs, **options): """ Convert an HBAC rule dn into a name """ if options.get('raw', False): return if 'seealso' in entry_attrs: hbac_attrs = ldap.get_entry(entry_attrs['seealso'][0], ['cn']) entry_attrs['seealso'] = hbac_attrs['cn'][0] def _normalize_profile(self, profile): """ Given a Desktop Profile name verify its existence and return the dn. """ if not profile: return None try: dn = DN(profile) return str(dn) except ValueError: try: entry_attrs = self.backend.find_entry_by_attr( self.api.Object['deskprofile'].primary_key.name, profile, self.api.Object['deskprofile'].object_class, [''], DN(self.api.Object['deskprofile'].container_dn, api.env.basedn)) return entry_attrs.dn except errors.NotFound: raise errors.NotFound( reason=_('Desktop profile %(rule)s not found') % dict(rule=profile)) def _convert_profile(self, ldap, entry_attrs, **options): """ Convert an Desktop Profile dn into a name """ if options.get('raw', False): return if 'ipadeskprofiletarget' in entry_attrs: profile_attrs = ldap.get_entry( entry_attrs['ipadeskprofiletarget'][0], ['cn']) entry_attrs['ipadeskprofiletarget'] = profile_attrs['cn'][0]
class cert_find(Command): __doc__ = _('Search for existing certificates.') takes_options = ( Str( 'subject?', label=_('Subject'), doc=_('Subject'), autofill=False, ), Int( 'revocation_reason?', label=_('Reason'), doc=_('Reason for revoking the certificate (0-10)'), minvalue=0, maxvalue=10, autofill=False, ), Int( 'min_serial_number?', doc=_("minimum serial number"), autofill=False, minvalue=0, maxvalue=2147483647, ), Int( 'max_serial_number?', doc=_("maximum serial number"), autofill=False, minvalue=0, maxvalue=2147483647, ), Flag( 'exactly?', doc=_('match the common name exactly'), autofill=False, ), Str( 'validnotafter_from?', validate_pkidate, doc=_('Valid not after from this date (YYYY-mm-dd)'), autofill=False, ), Str( 'validnotafter_to?', validate_pkidate, doc=_('Valid not after to this date (YYYY-mm-dd)'), autofill=False, ), Str( 'validnotbefore_from?', validate_pkidate, doc=_('Valid not before from this date (YYYY-mm-dd)'), autofill=False, ), Str( 'validnotbefore_to?', validate_pkidate, doc=_('Valid not before to this date (YYYY-mm-dd)'), autofill=False, ), Str( 'issuedon_from?', validate_pkidate, doc=_('Issued on from this date (YYYY-mm-dd)'), autofill=False, ), Str( 'issuedon_to?', validate_pkidate, doc=_('Issued on to this date (YYYY-mm-dd)'), autofill=False, ), Str( 'revokedon_from?', validate_pkidate, doc=_('Revoked on from this date (YYYY-mm-dd)'), autofill=False, ), Str( 'revokedon_to?', validate_pkidate, doc=_('Revoked on to this date (YYYY-mm-dd)'), autofill=False, ), Int( 'sizelimit?', label=_('Size Limit'), doc=_('Maximum number of certs returned'), flags=['no_display'], minvalue=0, default=100, ), ) has_output = output.standard_list_of_entries has_output_params = ( Str( 'serial_number_hex', label=_('Serial number (hex)'), ), Str( 'serial_number', label=_('Serial number'), ), Str( 'status', label=_('Status'), ), ) msg_summary = ngettext('%(count)d certificate matched', '%(count)d certificates matched', 0) def execute(self, **options): ca_enabled_check() ret = dict(result=self.Backend.ra.find(options)) ret['count'] = len(ret['result']) ret['truncated'] = False return ret
class hbactest(Command): __doc__ = _('Simulate use of Host-based access controls') has_output = ( output.summary, output.Output('warning', (list, tuple, type(None)), _('Warning')), output.Output('matched', (list, tuple, type(None)), _('Matched rules')), output.Output('notmatched', (list, tuple, type(None)), _('Not matched rules')), output.Output('error', (list, tuple, type(None)), _('Non-existent or invalid rules')), output.Output('value', bool, _('Result of simulation'), ['no_display']), ) takes_options = ( Str('user', cli_name='user', label=_('User name'), primary_key=True, ), Str('sourcehost?', deprecated=True, cli_name='srchost', label=_('Source host'), flags={'no_option'}, ), Str('targethost', cli_name='host', label=_('Target host'), ), Str('service', cli_name='service', label=_('Service'), ), Str('rules*', cli_name='rules', label=_('Rules to test. If not specified, --enabled is assumed'), ), Flag('nodetail?', cli_name='nodetail', label=_('Hide details which rules are matched, not matched, or invalid'), ), Flag('enabled?', cli_name='enabled', label=_('Include all enabled IPA rules into test [default]'), ), Flag('disabled?', cli_name='disabled', label=_('Include all disabled IPA rules into test'), ), Int('sizelimit?', label=_('Size Limit'), doc=_('Maximum number of rules to process when no --rules is specified'), flags=['no_display'], minvalue=0, autofill=False, ), ) def canonicalize(self, host): """ Canonicalize the host name -- add default IPA domain if that is missing """ if host.find('.') == -1: return u'%s.%s' % (host, self.env.domain) return host def execute(self, *args, **options): # First receive all needed information: # 1. HBAC rules (whether enabled or disabled) # 2. Required options are (user, target host, service) # 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output rules = [] # Use all enabled IPA rules by default all_enabled = True all_disabled = False # We need a local copy of test rules in order find incorrect ones testrules = {} if 'rules' in options: testrules = list(options['rules']) # When explicit rules are provided, disable assumptions all_enabled = False all_disabled = False sizelimit = None if 'sizelimit' in options: sizelimit = int(options['sizelimit']) # Check if --disabled is specified, include all disabled IPA rules if options['disabled']: all_disabled = True all_enabled = False # Finally, if enabled is specified implicitly, override above decisions if options['enabled']: all_enabled = True hbacset = [] if len(testrules) == 0: hbacset = self.api.Command.hbacrule_find( sizelimit=sizelimit, no_members=False)['result'] else: for rule in testrules: try: hbacset.append(self.api.Command.hbacrule_show(rule)['result']) except Exception: pass # We have some rules, import them # --enabled will import all enabled rules (default) # --disabled will import all disabled rules # --rules will implicitly add the rules from a rule list for rule in hbacset: ipa_rule = convert_to_ipa_rule(rule) if ipa_rule.name in testrules: ipa_rule.enabled = True rules.append(ipa_rule) testrules.remove(ipa_rule.name) elif all_enabled and ipa_rule.enabled: # Option --enabled forces to include all enabled IPA rules into test rules.append(ipa_rule) elif all_disabled and not ipa_rule.enabled: # Option --disabled forces to include all disabled IPA rules into test ipa_rule.enabled = True rules.append(ipa_rule) # Check if there are unresolved rules left if len(testrules) > 0: # Error, unresolved rules are left in --rules return {'summary' : unicode(_(u'Unresolved rules in --rules')), 'error': testrules, 'matched': None, 'notmatched': None, 'warning' : None, 'value' : False} # Rules are converted to pyhbac format, build request and then test it request = pyhbac.HbacRequest() if options['user'] != u'all': # check first if this is not a trusted domain user if _dcerpc_bindings_installed: is_valid_sid = ipaserver.dcerpc.is_sid_valid(options['user']) else: is_valid_sid = False components = util.normalize_name(options['user']) if is_valid_sid or 'domain' in components or 'flatname' in components: # this is a trusted domain user if not _dcerpc_bindings_installed: raise errors.NotFound(reason=_( 'Cannot perform external member validation without ' 'Samba 4 support installed. Make sure you have installed ' 'server-trust-ad sub-package of IPA on the server')) domain_validator = ipaserver.dcerpc.DomainValidator(self.api) if not domain_validator.is_configured(): raise errors.NotFound(reason=_( 'Cannot search in trusted domains without own domain configured. ' 'Make sure you have run ipa-adtrust-install on the IPA server first')) user_sid, group_sids = domain_validator.get_trusted_domain_user_and_groups(options['user']) request.user.name = user_sid # Now search for all external groups that have this user or # any of its groups in its external members. Found entires # memberOf links will be then used to gather all groups where # this group is assigned, including the nested ones filter_sids = "(&(objectclass=ipaexternalgroup)(|(ipaExternalMember=%s)))" \ % ")(ipaExternalMember=".join(group_sids + [user_sid]) ldap = self.api.Backend.ldap2 group_container = DN(api.env.container_group, api.env.basedn) try: entries, _truncated = ldap.find_entries( filter_sids, ['memberof'], group_container) except errors.NotFound: request.user.groups = [] else: groups = [] for entry in entries: memberof_dns = entry.get('memberof', []) for memberof_dn in memberof_dns: if memberof_dn.endswith(group_container): groups.append(memberof_dn[0][0].value) request.user.groups = sorted(set(groups)) else: # try searching for a local user try: request.user.name = options['user'] search_result = self.api.Command.user_show(request.user.name)['result'] groups = search_result['memberof_group'] if 'memberofindirect_group' in search_result: groups += search_result['memberofindirect_group'] request.user.groups = sorted(set(groups)) except Exception: pass if options['service'] != u'all': try: request.service.name = options['service'] service_result = self.api.Command.hbacsvc_show(request.service.name)['result'] if 'memberof_hbacsvcgroup' in service_result: request.service.groups = service_result['memberof_hbacsvcgroup'] except Exception: pass if options['targethost'] != u'all': try: request.targethost.name = self.canonicalize(options['targethost']) tgthost_result = self.api.Command.host_show(request.targethost.name)['result'] groups = tgthost_result['memberof_hostgroup'] if 'memberofindirect_hostgroup' in tgthost_result: groups += tgthost_result['memberofindirect_hostgroup'] request.targethost.groups = sorted(set(groups)) except Exception: pass matched_rules = [] notmatched_rules = [] error_rules = [] warning_rules = [] result = {'warning':None, 'matched':None, 'notmatched':None, 'error':None} if not options['nodetail']: # Validate runs rules one-by-one and reports failed ones for ipa_rule in rules: try: res = request.evaluate([ipa_rule]) if res == pyhbac.HBAC_EVAL_ALLOW: matched_rules.append(ipa_rule.name) if res == pyhbac.HBAC_EVAL_DENY: notmatched_rules.append(ipa_rule.name) except pyhbac.HbacError as e: code, rule_name = e.args if code == pyhbac.HBAC_EVAL_ERROR: error_rules.append(rule_name) self.log.info('Native IPA HBAC rule "%s" parsing error: %s' % \ (rule_name, pyhbac.hbac_result_string(code))) except (TypeError, IOError) as info: self.log.error('Native IPA HBAC module error: %s' % info) access_granted = len(matched_rules) > 0 else: res = request.evaluate(rules) access_granted = (res == pyhbac.HBAC_EVAL_ALLOW) result['summary'] = _('Access granted: %s') % (access_granted) if len(matched_rules) > 0: result['matched'] = matched_rules if len(notmatched_rules) > 0: result['notmatched'] = notmatched_rules if len(error_rules) > 0: result['error'] = error_rules if len(warning_rules) > 0: result['warning'] = warning_rules result['value'] = access_granted return result
user.takes_params += (Str('primarymail?', cli_name='primary_mail', label=_('Primary mail address')), Str('alias*', cli_name='alias', label=_('Mail aliases')), Str('sendalias*', cli_name='send_alias', label=_('Allowed sender aliases')), Bool('canreceiveexternally?', cli_name='can_receive_externally', label='Can receive external mails'), Bool('cansendexternally?', cli_name='can_send_externally', label='Can send mails to external locations'), Int('mailboxquota?', cli_name='mailbox_quota', label='Mailbox quota [MB]'), Str('mailboxtransport?', cli_name='mailbox_transport', label=_('Mailbox transport string'))) user.default_attributes = user.default_attributes + [ 'alias', 'sendalias', 'mailboxquota', 'mailboxtransport' ] user.managed_permissions = { **user.managed_permissions, **{ 'System: Read User Mail Attributes': { 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'},
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 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', 'ipacarandomserialnumberversion', ] 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'}, ), Int( 'ipacarandomserialnumberversion', cli_name='randomserialnumberversion', label=_('RSN Version'), doc=_('Random Serial Number Version'), flags={'no_create', 'no_update'}, ), ) 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', 'ipacarandomserialnumberversion', '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'}, }, }
class BaseCertObject(Object): takes_params = ( Str( 'cacn?', cli_name='ca', default=IPA_CA_CN, autofill=True, label=_('Issuing CA'), doc=_('Name of issuing CA'), flags={'no_create', 'no_update', 'no_search'}, ), Bytes( 'certificate', validate_certificate, label=_("Certificate"), doc=_("Base-64 encoded certificate."), normalizer=x509.normalize_certificate, flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'subject', label=_('Subject'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_rfc822name*', label=_('Subject email address'), flags={'no_create', 'no_update', 'no_search'}, ), DNSNameParam( 'san_dnsname*', label=_('Subject DNS name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_x400address*', label=_('Subject X.400 address'), flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'san_directoryname*', label=_('Subject directory name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_edipartyname*', label=_('Subject EDI Party name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_uri*', label=_('Subject URI'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_ipaddress*', label=_('Subject IP Address'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_oid*', label=_('Subject OID'), flags={'no_create', 'no_update', 'no_search'}, ), Principal( 'san_other_upn*', label=_('Subject UPN'), flags={'no_create', 'no_update', 'no_search'}, ), Principal( 'san_other_kpn*', label=_('Subject Kerberos principal name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_other*', label=_('Subject Other Name'), flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'issuer', label=_('Issuer'), doc=_('Issuer DN'), flags={'no_create', 'no_update', 'no_search'}, ), DateTime( 'valid_not_before', label=_('Not Before'), flags={'no_create', 'no_update', 'no_search'}, ), DateTime( 'valid_not_after', label=_('Not After'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'md5_fingerprint', label=_('Fingerprint (MD5)'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'sha1_fingerprint', label=_('Fingerprint (SHA1)'), flags={'no_create', 'no_update', 'no_search'}, ), Int( 'serial_number', label=_('Serial number'), doc=_( 'Serial number in decimal or if prefixed with 0x in hexadecimal' ), normalizer=normalize_serial_number, flags={'no_create', 'no_update', 'no_search'}, ), Str( 'serial_number_hex', label=_('Serial number (hex)'), flags={'no_create', 'no_update', 'no_search'}, ), ) def _parse(self, obj, full=True): """Extract certificate-specific data into a result object. ``obj`` Result object containing certificate, into which extracted data will be inserted. ``full`` Whether to include all fields, or only the ones we guess people want to see most of the time. Also add recognised otherNames to the generic ``san_other`` attribute when ``True`` in addition to the specialised attribute. """ cert = obj.get('certificate') if cert is not None: cert = x509.load_certificate(cert) obj['subject'] = DN(unicode(cert.subject)) obj['issuer'] = DN(unicode(cert.issuer)) obj['serial_number'] = cert.serial_number if full: obj['valid_not_before'] = unicode(cert.valid_not_before_str) obj['valid_not_after'] = unicode(cert.valid_not_after_str) obj['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) obj['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) try: ext_san = cert.get_extension(nss.SEC_OID_X509_SUBJECT_ALT_NAME) general_names = x509.decode_generalnames(ext_san.value) except KeyError: general_names = [] for name_type, _desc, name, der_name in general_names: try: self._add_san_attribute(obj, full, name_type, name, der_name) except Exception: # Invalid GeneralName (i.e. not a valid X.509 cert); # don't fail but log something about it root_logger.warning( "Encountered bad GeneralName; skipping", exc_info=True) serial_number = obj.get('serial_number') if serial_number is not None: obj['serial_number_hex'] = u'0x%X' % serial_number def _add_san_attribute(self, obj, full, name_type, name, der_name): name_type_map = { nss.certRFC822Name: 'san_rfc822name', nss.certDNSName: 'san_dnsname', nss.certX400Address: 'san_x400address', nss.certDirectoryName: 'san_directoryname', nss.certEDIPartyName: 'san_edipartyname', nss.certURI: 'san_uri', nss.certIPAddress: 'san_ipaddress', nss.certRegisterID: 'san_oid', (nss.certOtherName, x509.SAN_UPN): 'san_other_upn', (nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME): 'san_other_kpn', } default_attrs = { 'san_rfc822name', 'san_dnsname', 'san_other_upn', 'san_other_kpn', } attr_name = name_type_map.get(name_type, 'san_other') if full or attr_name in default_attrs: if attr_name != 'san_other': name_formatted = name else: # display as "OID : b64(DER)" name_formatted = u'{}:{}'.format(name_type[1], base64.b64encode(der_name)) attr_value = self.params[attr_name].type(name_formatted) obj.setdefault(attr_name, []).append(attr_value) if full and attr_name.startswith('san_other_'): # also include known otherName in generic otherName attribute name_formatted = u'{}:{}'.format(name_type[1], base64.b64encode(der_name)) attr_value = self.params['san_other'].type(name_formatted) obj.setdefault('san_other', []).append(attr_value)
class pwpolicy(LDAPObject): """ Password Policy object """ container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos')) object_name = _('password policy') object_name_plural = _('password policies') object_class = ['top', 'nscontainer', 'krbpwdpolicy', 'ipapwdpolicy'] permission_filter_objectclasses = ['krbpwdpolicy', 'ipapwdpolicy'] default_attributes = [ 'cn', 'cospriority', 'krbmaxpwdlife', 'krbminpwdlife', 'krbpwdhistorylength', 'krbpwdmindiffchars', 'krbpwdminlength', 'krbpwdmaxfailure', 'krbpwdfailurecountinterval', 'krbpwdlockoutduration', 'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck', 'ipapwdusercheck', ] managed_permissions = { 'System: Read Group Password Policy': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'permission', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'cospriority', 'krbmaxpwdlife', 'krbminpwdlife', 'krbpwdfailurecountinterval', 'krbpwdhistorylength', 'krbpwdlockoutduration', 'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength', 'objectclass', 'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck', 'ipapwdusercheck', }, 'default_privileges': { 'Password Policy Readers', 'Password Policy Administrator', }, }, 'System: Add Group Password Policy': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Add Group Password Policy";allow (add) groupdn = "ldap:///cn=Add Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, 'System: Delete Group Password Policy': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Delete Group Password Policy";allow (delete) groupdn = "ldap:///cn=Delete Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, 'System: Modify Group Password Policy': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'krbmaxpwdlife', 'krbminpwdlife', 'krbpwdfailurecountinterval', 'krbpwdhistorylength', 'krbpwdlockoutduration', 'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength', 'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck', 'ipapwdusercheck', }, 'replaces': [ '(targetattr = "krbmaxpwdlife || krbminpwdlife || krbpwdhistorylength || krbpwdmindiffchars || krbpwdminlength || krbpwdmaxfailure || krbpwdfailurecountinterval || krbpwdlockoutduration")(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=Modify Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, } label = _('Password Policies') label_singular = _('Password Policy') takes_params = ( Str( 'cn?', cli_name='group', label=_('Group'), doc=_('Manage password policy for specific group'), primary_key=True, ), Int( 'krbmaxpwdlife?', cli_name='maxlife', label=_('Max lifetime (days)'), doc=_('Maximum password lifetime (in days)'), minvalue=0, maxvalue=20000, # a little over 54 years ), Int( 'krbminpwdlife?', cli_name='minlife', label=_('Min lifetime (hours)'), doc=_('Minimum password lifetime (in hours)'), minvalue=0, ), Int( 'krbpwdhistorylength?', cli_name='history', label=_('History size'), doc=_('Password history size'), minvalue=0, ), Int( 'krbpwdmindiffchars?', cli_name='minclasses', label=_('Character classes'), doc=_('Minimum number of character classes'), minvalue=0, maxvalue=5, ), Int( 'krbpwdminlength?', cli_name='minlength', label=_('Min length'), doc=_('Minimum length of password'), minvalue=0, ), Int( 'cospriority', cli_name='priority', label=_('Priority'), doc=_( 'Priority of the policy (higher number means lower priority'), minvalue=0, flags=('virtual_attribute', ), ), Int( 'krbpwdmaxfailure?', cli_name='maxfail', label=_('Max failures'), doc=_('Consecutive failures before lockout'), minvalue=0, ), Int( 'krbpwdfailurecountinterval?', cli_name='failinterval', label=_('Failure reset interval'), doc=_('Period after which failure count will be reset (seconds)'), minvalue=0, ), Int( 'krbpwdlockoutduration?', cli_name='lockouttime', label=_('Lockout duration'), doc=_('Period for which lockout is enforced (seconds)'), minvalue=0, ), Int( 'ipapwdmaxrepeat?', cli_name='maxrepeat', label=_('Max repeat'), doc=_('Maximum number of same consecutive characters'), minvalue=0, maxvalue=256, default=0, ), Int( 'ipapwdmaxsequence?', cli_name='maxsequence', label=_('Max sequence'), doc=_('The max. length of monotonic character sequences (abcd)'), minvalue=0, maxvalue=256, default=0, ), Bool( 'ipapwddictcheck?', cli_name='dictcheck', label=_('Dictionary check'), doc=_('Check if the password is a dictionary word'), default=False, ), Bool( 'ipapwdusercheck?', cli_name='usercheck', label=_('User check'), doc=_('Check if the password contains the username'), default=False, ), ) def get_dn(self, *keys, **options): if keys[-1] is not None: return self.backend.make_dn_from_attr( self.primary_key.name, keys[-1], DN(self.container_dn, api.env.basedn)) return global_policy_dn def convert_time_for_output(self, entry_attrs, **options): # Convert seconds to hours and days for displaying to user if not options.get('raw', False): if 'krbmaxpwdlife' in entry_attrs: entry_attrs['krbmaxpwdlife'][0] = unicode( int(entry_attrs['krbmaxpwdlife'][0]) // 86400) if 'krbminpwdlife' in entry_attrs: entry_attrs['krbminpwdlife'][0] = unicode( int(entry_attrs['krbminpwdlife'][0]) // 3600) def convert_time_on_input(self, entry_attrs): # Convert hours and days to seconds for writing to LDAP if 'krbmaxpwdlife' in entry_attrs and entry_attrs['krbmaxpwdlife']: entry_attrs['krbmaxpwdlife'] = entry_attrs['krbmaxpwdlife'] * 86400 if 'krbminpwdlife' in entry_attrs and entry_attrs['krbminpwdlife']: entry_attrs['krbminpwdlife'] = entry_attrs['krbminpwdlife'] * 3600 def validate_minlength(self, ldap, entry_attrs, add=False, *keys): """ If any of the libpwquality options are used then the minimum length must be >= 6 which is the built-in default of libpwquality. Allowing a lower value to be set will result in a failed policy check and a generic error message. """ def get_val(entry, attr): """Get a single value from a list or a string""" val = entry.get(attr, 0) if isinstance(val, list): val = val[0] return val def has_pwquality_set(entry): for attr in [ 'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck', 'ipapwdusercheck' ]: val = get_val(entry, attr) if val not in ('FALSE', '0', 0, None): return True return False has_pwquality_value = False if not add: if len(keys) > 0: existing_entry = self.api.Command.pwpolicy_show( keys[-1], all=True, )['result'] else: existing_entry = self.api.Command.pwpolicy_show( all=True, )['result'] existing_entry.update(entry_attrs) min_length = int(get_val(existing_entry, 'krbpwdminlength')) has_pwquality_value = has_pwquality_set(existing_entry) else: min_length = int(get_val(entry_attrs, 'krbpwdminlength')) has_pwquality_value = has_pwquality_set(entry_attrs) if min_length and min_length < 6 and has_pwquality_value: raise errors.ValidationError( name='minlength', error=_('Minimum length must be >= 6 if maxrepeat, ' 'maxsequence, dictcheck or usercheck are defined')) def validate_lifetime(self, entry_attrs, add=False, *keys): """ Ensure that the maximum lifetime is greater than the minimum. If there is no minimum lifetime set then don't return an error. """ maxlife = entry_attrs.get('krbmaxpwdlife', None) minlife = entry_attrs.get('krbminpwdlife', None) existing_entry = {} if not add: # then read existing entry existing_entry = self.api.Command.pwpolicy_show( keys[-1], all=True, )['result'] if minlife is None and 'krbminpwdlife' in existing_entry: minlife = int(existing_entry['krbminpwdlife'][0]) * 3600 if maxlife is None and 'krbmaxpwdlife' in existing_entry: maxlife = int(existing_entry['krbmaxpwdlife'][0]) * 86400 if maxlife not in (None, 0) and minlife is not None: if minlife > maxlife: raise errors.ValidationError( name='maxlife', error=_("Maximum password life must be equal to " "or greater than the minimum."), ) def add_cospriority(self, entry, pwpolicy_name, rights=True): if pwpolicy_name and pwpolicy_name != global_policy_name: cos_entry = self.api.Command.cosentry_show(pwpolicy_name, rights=rights, all=rights)['result'] if cos_entry.get('cospriority') is not None: entry['cospriority'] = cos_entry['cospriority'] if rights: entry['attributelevelrights']['cospriority'] = \ cos_entry['attributelevelrights']['cospriority']
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 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', ] label = _('Configuration') label_singular = _('Configuration') takes_params = ( Int( 'ipamaxusernamelength', cli_name='maxusername', label=_('Maximum username length'), minvalue=1, ), 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', validate_searchtimelimit, cli_name='searchtimelimit', label=_('Search time limit'), doc= _('Maximum amount of time (seconds) for a search (> 0, or -1 for unlimited)' ), minvalue=-1, ), Int( 'ipasearchrecordslimit', cli_name='searchrecordslimit', label=_('Search size limit'), doc=_('Maximum number of records to search (-1 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)'), csv=True, ), Str( 'ipauserobjectclasses+', cli_name='userobjectclasses', label=_('Default user objectclasses'), doc=_('Default user objectclasses (comma-separated list)'), csv=True, ), 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'AllowLMhash', u'AllowNThash', u'KDC:Disable Last Success', u'KDC:Disable Lockout'), csv=True, ), 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'), csv=True, ), ) def get_dn(self, *keys, **kwargs): return DN(('cn', 'ipaconfig'), ('cn', 'etc'))
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 try: role_config = backend.config_retrieve(role_name) except errors.EmptyResult: # No role config means current user identity # has no rights to see it, return with no action return for key, value in role_config.items(): try: entry_attrs.update({key: value}) except errors.EmptyResult: # An update that doesn't change an entry is fine here # Just ignore and move to the next key pair pass 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 idoverrideuser(baseidoverride): object_name = _('User ID override') object_name_plural = _('User ID overrides') label = _('User ID overrides') label_singular = _('User ID override') rdn_is_primary_key = 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', }, }, } object_class = baseidoverride.object_class + ['ipaUserOverride'] possible_objectclasses = ['ipasshuser', 'ipaSshGroupOfPubKeys'] default_attributes = baseidoverride.default_attributes + [ 'homeDirectory', 'uidNumber', 'uid', 'ipaOriginalUid', 'loginShell', 'ipaSshPubkey', 'gidNumber', 'gecos', ] takes_params = baseidoverride.takes_params + ( Str( 'uid?', pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', pattern_errmsg='may only include letters, numbers, _, -, . and $', maxlength=255, cli_name='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, csv=True, 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
class pwpolicy(LDAPObject): """ Password Policy object """ container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos')) object_name = _('password policy') object_name_plural = _('password policies') object_class = ['top', 'nscontainer', 'krbpwdpolicy'] permission_filter_objectclasses = ['krbpwdpolicy'] default_attributes = [ 'cn', 'cospriority', 'krbmaxpwdlife', 'krbminpwdlife', 'krbpwdhistorylength', 'krbpwdmindiffchars', 'krbpwdminlength', 'krbpwdmaxfailure', 'krbpwdfailurecountinterval', 'krbpwdlockoutduration', ] managed_permissions = { 'System: Read Group Password Policy': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'permission', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'cn', 'cospriority', 'krbmaxpwdlife', 'krbminpwdlife', 'krbpwdfailurecountinterval', 'krbpwdhistorylength', 'krbpwdlockoutduration', 'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength', 'objectclass', }, 'default_privileges': { 'Password Policy Readers', 'Password Policy Administrator', }, }, 'System: Add Group Password Policy': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Add Group Password Policy";allow (add) groupdn = "ldap:///cn=Add Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, 'System: Delete Group Password Policy': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Delete Group Password Policy";allow (delete) groupdn = "ldap:///cn=Delete Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, 'System: Modify Group Password Policy': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'krbmaxpwdlife', 'krbminpwdlife', 'krbpwdfailurecountinterval', 'krbpwdhistorylength', 'krbpwdlockoutduration', 'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength' }, 'replaces': [ '(targetattr = "krbmaxpwdlife || krbminpwdlife || krbpwdhistorylength || krbpwdmindiffchars || krbpwdminlength || krbpwdmaxfailure || krbpwdfailurecountinterval || krbpwdlockoutduration")(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=Modify Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Password Policy Administrator'}, }, } label = _('Password Policies') label_singular = _('Password Policy') takes_params = ( Str( 'cn?', cli_name='group', label=_('Group'), doc=_('Manage password policy for specific group'), primary_key=True, ), Int( 'krbmaxpwdlife?', cli_name='maxlife', label=_('Max lifetime (days)'), doc=_('Maximum password lifetime (in days)'), minvalue=0, maxvalue=20000, # a little over 54 years ), Int( 'krbminpwdlife?', cli_name='minlife', label=_('Min lifetime (hours)'), doc=_('Minimum password lifetime (in hours)'), minvalue=0, ), Int( 'krbpwdhistorylength?', cli_name='history', label=_('History size'), doc=_('Password history size'), minvalue=0, ), Int( 'krbpwdmindiffchars?', cli_name='minclasses', label=_('Character classes'), doc=_('Minimum number of character classes'), minvalue=0, maxvalue=5, ), Int( 'krbpwdminlength?', cli_name='minlength', label=_('Min length'), doc=_('Minimum length of password'), minvalue=0, ), Int( 'cospriority', cli_name='priority', label=_('Priority'), doc=_( 'Priority of the policy (higher number means lower priority'), minvalue=0, flags=('virtual_attribute', ), ), Int( 'krbpwdmaxfailure?', cli_name='maxfail', label=_('Max failures'), doc=_('Consecutive failures before lockout'), minvalue=0, ), Int( 'krbpwdfailurecountinterval?', cli_name='failinterval', label=_('Failure reset interval'), doc=_('Period after which failure count will be reset (seconds)'), minvalue=0, ), Int( 'krbpwdlockoutduration?', cli_name='lockouttime', label=_('Lockout duration'), doc=_('Period for which lockout is enforced (seconds)'), minvalue=0, ), ) def get_dn(self, *keys, **options): if keys[-1] is not None: return self.backend.make_dn_from_attr( self.primary_key.name, keys[-1], DN(self.container_dn, api.env.basedn)) return global_policy_dn def convert_time_for_output(self, entry_attrs, **options): # Convert seconds to hours and days for displaying to user if not options.get('raw', False): if 'krbmaxpwdlife' in entry_attrs: entry_attrs['krbmaxpwdlife'][0] = unicode( int(entry_attrs['krbmaxpwdlife'][0]) // 86400) if 'krbminpwdlife' in entry_attrs: entry_attrs['krbminpwdlife'][0] = unicode( int(entry_attrs['krbminpwdlife'][0]) // 3600) def convert_time_on_input(self, entry_attrs): # Convert hours and days to seconds for writing to LDAP if 'krbmaxpwdlife' in entry_attrs and entry_attrs['krbmaxpwdlife']: entry_attrs['krbmaxpwdlife'] = entry_attrs['krbmaxpwdlife'] * 86400 if 'krbminpwdlife' in entry_attrs and entry_attrs['krbminpwdlife']: entry_attrs['krbminpwdlife'] = entry_attrs['krbminpwdlife'] * 3600 def validate_lifetime(self, entry_attrs, add=False, *keys): """ Ensure that the maximum lifetime is greater than the minimum. If there is no minimum lifetime set then don't return an error. """ maxlife = entry_attrs.get('krbmaxpwdlife', None) minlife = entry_attrs.get('krbminpwdlife', None) existing_entry = {} if not add: # then read existing entry existing_entry = self.api.Command.pwpolicy_show( keys[-1], all=True, )['result'] if minlife is None and 'krbminpwdlife' in existing_entry: minlife = int(existing_entry['krbminpwdlife'][0]) * 3600 if maxlife is None and 'krbmaxpwdlife' in existing_entry: maxlife = int(existing_entry['krbmaxpwdlife'][0]) * 86400 if maxlife not in (None, 0) and minlife is not None: if minlife > maxlife: raise errors.ValidationError( name='maxlife', error=_( 'Maximum password life must be greater than minimum.'), ) def add_cospriority(self, entry, pwpolicy_name, rights=True): if pwpolicy_name and pwpolicy_name != global_policy_name: cos_entry = self.api.Command.cosentry_show(pwpolicy_name, rights=rights, all=rights)['result'] if cos_entry.get('cospriority') is not None: entry['cospriority'] = cos_entry['cospriority'] if rights: entry['attributelevelrights']['cospriority'] = \ cos_entry['attributelevelrights']['cospriority']
class user(LDAPObject): """ User object. """ container_dn = api.env.container_user object_name = _('user') object_name_plural = _('users') object_class = ['posixaccount'] object_class_config = 'ipauserobjectclasses' possible_objectclasses = ['meporiginentry'] disallow_object_classes = ['krbticketpolicyaux'] search_attributes_config = 'ipausersearchfields' default_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'uidnumber', 'gidnumber', 'mail', 'ou', 'telephonenumber', 'title', 'memberof', 'nsaccountlock', 'memberofindirect', ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'mail', 'telephonenumber', 'title', 'nsaccountlock', 'uidnumber', 'gidnumber', 'sshpubkeyfp', ] uuid_attribute = 'ipauniqueid' attribute_members = { 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], } rdn_is_primary_key = True bindable = True password_attributes = [('userpassword', 'has_password'), ('krbprincipalkey', 'has_keytab')] label = _('Users') label_singular = _('User') takes_params = ( Str('uid', pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', pattern_errmsg='may only include letters, numbers, _, -, . and $', maxlength=255, cli_name='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 field'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('loginshell?', cli_name='shell', label=_('Login shell'), ), Str('krbprincipalname?', validate_principal, cli_name='principal', label=_('Kerberos principal'), default_from=lambda uid: '%s@%s' % (uid.lower(), api.env.realm), autofill=True, flags=['no_update'], normalizer=lambda value: normalize_principal(value), ), 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)'), autofill=True, default=DNA_MAGIC, minvalue=1, ), Int('gidnumber', label=_('GID'), doc=_('Group ID Number'), minvalue=1, default=DNA_MAGIC, autofill=True, ), 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'), ), Str('manager?', label=_('Manager'), ), Str('carlicense?', label=_('Car License'), ), Bool('nsaccountlock?', label=_('Account disabled'), flags=['no_option'], ), Str('ipasshpubkey*', validate_sshpubkey, cli_name='sshpubkey', label=_('SSH public key'), normalizer=normalize_sshpubkey, csv=True, flags=['no_search'], ), ) def _normalize_and_validate_email(self, email, config=None): if not config: config = self.backend.get_ipa_config()[1] # 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, basestring): 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): """ Given a userid verify the user's existence and return the dn. """ if not manager: return None if not isinstance(manager, list): manager = [manager] try: container_dn = DN(self.container_dn, api.env.basedn) for m in xrange(len(manager)): if isinstance(manager[m], DN) and manager[m].endswith(container_dn): continue (dn, entry_attrs) = self.backend.find_entry_by_attr( self.primary_key.name, manager[m], self.object_class, [''], self.container_dn ) manager[m] = dn except errors.NotFound: raise errors.NotFound(reason=_('manager %(manager)s not found') % dict(manager=manager[m])) return manager def _convert_manager(self, entry_attrs, **options): """ Convert a manager dn into a userid """ if options.get('raw', False): return if 'manager' in entry_attrs: for m in xrange(len(entry_attrs['manager'])): entry_attrs['manager'][m] = self.get_primary_key_from_dn(entry_attrs['manager'][m])
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' ] 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', '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'} ) ) def get_dn(self, *keys, **kwargs): return DN(('cn', 'ipaconfig'), ('cn', 'etc'), api.env.basedn) def show_servroles_attributes(self, entry_attrs, **options): if options.get('raw', False): return backend = self.api.Backend.serverroles for role in ("CA server", "IPA master", "NTP server"): config = backend.config_retrieve(role) entry_attrs.update(config)
class group(LDAPObject): """ Group object. """ container_dn = api.env.container_group object_name = _('group') object_name_plural = _('groups') object_class = ['ipausergroup'] object_class_config = 'ipagroupobjectclasses' possible_objectclasses = ['posixGroup', 'mepManagedEntry', 'ipaExternalGroup'] permission_filter_objectclasses = ['posixgroup', 'ipausergroup'] search_attributes_config = 'ipagroupsearchfields' default_attributes = [ 'cn', 'description', 'gidnumber', 'member', 'memberof', 'memberindirect', 'memberofindirect', 'ipaexternalmember', ] uuid_attribute = 'ipauniqueid' attribute_members = { 'member': ['user', 'group'], 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], 'memberindirect': ['user', 'group'], 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], } rdn_is_primary_key = True managed_permissions = { 'System: Read Groups': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'anonymous', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'businesscategory', 'cn', 'description', 'gidnumber', 'ipaexternalmember', 'ipauniqueid', 'mepmanagedby', 'o', 'objectclass', 'ou', 'owner', 'seealso', 'ipantsecurityidentifier' }, }, 'System: Read Group Membership': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'member', 'memberof', 'memberuid', 'memberuser', 'memberhost', }, }, 'System: Add Groups': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Groups";allow (add) groupdn = "ldap:///cn=Add Groups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Group Administrators'}, }, 'System: Modify Group Membership': { 'ipapermright': {'write'}, 'ipapermtargetfilter': [ '(objectclass=ipausergroup)', '(!(cn=admins))', ], 'ipapermdefaultattr': {'member'}, 'replaces': [ '(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)', '(targetfilter = "(!(cn=admins))")(targetattr = "member")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Group membership";allow (write) groupdn = "ldap:///cn=Modify Group membership,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': { 'Group Administrators', 'Modify Group membership' }, }, 'System: Modify Groups': { 'ipapermright': {'write'}, 'ipapermdefaultattr': { 'cn', 'description', 'gidnumber', 'ipauniqueid', 'mepmanagedby', 'objectclass' }, 'replaces': [ '(targetattr = "cn || description || gidnumber || objectclass || mepmanagedby || ipauniqueid")(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Groups";allow (write) groupdn = "ldap:///cn=Modify Groups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Group Administrators'}, }, 'System: Remove Groups': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///cn=*,cn=groups,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Groups";allow (delete) groupdn = "ldap:///cn=Remove Groups,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Group Administrators'}, }, 'System: Read Group Compat Tree': { 'non_object': True, 'ipapermbindruletype': 'anonymous', 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN('cn=groups', 'cn=compat', api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'memberuid', 'gidnumber', }, }, 'System: Read Group Views Compat Tree': { 'non_object': True, 'ipapermbindruletype': 'anonymous', 'ipapermlocation': api.env.basedn, 'ipapermtarget': DN('cn=groups', 'cn=*', 'cn=views', 'cn=compat', api.env.basedn), 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'cn', 'memberuid', 'gidnumber', }, }, } label = _('User Groups') label_singular = _('User Group') takes_params = ( Str('cn', pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', pattern_errmsg='may only include letters, numbers, _, -, . and $', maxlength=255, cli_name='group_name', label=_('Group name'), primary_key=True, normalizer=lambda value: value.lower(), ), Str('description?', cli_name='desc', label=_('Description'), doc=_('Group description'), ), Int('gidnumber?', cli_name='gid', label=_('GID'), doc=_('GID (use this option to set it manually)'), minvalue=1, ), )
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"', ), Bytes('usercertificate*', validate_certificate, 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 BaseCertObject(Object): takes_params = ( Str( 'cacn?', cli_name='ca', default=IPA_CA_CN, autofill=True, label=_('Issuing CA'), doc=_('Name of issuing CA'), flags={'no_create', 'no_update', 'no_search'}, ), Bytes( 'certificate', validate_certificate, label=_("Certificate"), doc=_("Base-64 encoded certificate."), normalizer=x509.normalize_certificate, flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'subject', label=_('Subject'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_rfc822name*', label=_('Subject email address'), flags={'no_create', 'no_update', 'no_search'}, ), DNSNameParam( 'san_dnsname*', label=_('Subject DNS name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_x400address*', label=_('Subject X.400 address'), flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'san_directoryname*', label=_('Subject directory name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_edipartyname*', label=_('Subject EDI Party name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_uri*', label=_('Subject URI'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_ipaddress*', label=_('Subject IP Address'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_oid*', label=_('Subject OID'), flags={'no_create', 'no_update', 'no_search'}, ), Principal( 'san_other_upn*', label=_('Subject UPN'), flags={'no_create', 'no_update', 'no_search'}, ), Principal( 'san_other_kpn*', label=_('Subject Kerberos principal name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_other*', label=_('Subject Other Name'), flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'issuer', label=_('Issuer'), doc=_('Issuer DN'), flags={'no_create', 'no_update', 'no_search'}, ), DateTime( 'valid_not_before', label=_('Not Before'), flags={'no_create', 'no_update', 'no_search'}, ), DateTime( 'valid_not_after', label=_('Not After'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'md5_fingerprint', label=_('Fingerprint (MD5)'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'sha1_fingerprint', label=_('Fingerprint (SHA1)'), flags={'no_create', 'no_update', 'no_search'}, ), Int( 'serial_number', label=_('Serial number'), doc=_( 'Serial number in decimal or if prefixed with 0x in hexadecimal' ), normalizer=normalize_serial_number, flags={'no_create', 'no_update', 'no_search'}, ), Str( 'serial_number_hex', label=_('Serial number (hex)'), flags={'no_create', 'no_update', 'no_search'}, ), ) def _parse(self, obj, full=True): """Extract certificate-specific data into a result object. ``obj`` Result object containing certificate, into which extracted data will be inserted. ``full`` Whether to include all fields, or only the ones we guess people want to see most of the time. Also add recognised otherNames to the generic ``san_other`` attribute when ``True`` in addition to the specialised attribute. """ if 'certificate' in obj: cert = x509.load_certificate(obj['certificate']) obj['subject'] = DN(cert.subject) obj['issuer'] = DN(cert.issuer) obj['serial_number'] = cert.serial obj['valid_not_before'] = x509.format_datetime( cert.not_valid_before) obj['valid_not_after'] = x509.format_datetime(cert.not_valid_after) if full: obj['md5_fingerprint'] = x509.to_hex_with_colons( cert.fingerprint(hashes.MD5())) obj['sha1_fingerprint'] = x509.to_hex_with_colons( cert.fingerprint(hashes.SHA1())) general_names = x509.process_othernames( x509.get_san_general_names(cert)) for gn in general_names: try: self._add_san_attribute(obj, full, gn) except Exception: # Invalid GeneralName (i.e. not a valid X.509 cert); # don't fail but log something about it root_logger.warning( "Encountered bad GeneralName; skipping", exc_info=True) serial_number = obj.get('serial_number') if serial_number is not None: obj['serial_number_hex'] = u'0x%X' % serial_number def _add_san_attribute(self, obj, full, gn): name_type_map = { cryptography.x509.RFC822Name: ('san_rfc822name', attrgetter('value')), cryptography.x509.DNSName: ('san_dnsname', attrgetter('value')), # cryptography.x509.???: 'san_x400address', cryptography.x509.DirectoryName: ('san_directoryname', lambda x: DN(x.value)), # cryptography.x509.???: 'san_edipartyname', cryptography.x509.UniformResourceIdentifier: ('san_uri', attrgetter('value')), cryptography.x509.IPAddress: ('san_ipaddress', attrgetter('value')), cryptography.x509.RegisteredID: ('san_oid', attrgetter('value.dotted_string')), cryptography.x509.OtherName: ('san_other', _format_othername), x509.UPN: ('san_other_upn', attrgetter('name')), x509.KRB5PrincipalName: ('san_other_kpn', attrgetter('name')), } default_attrs = { 'san_rfc822name', 'san_dnsname', 'san_other_upn', 'san_other_kpn', } if type(gn) not in name_type_map: return attr_name, format_name = name_type_map[type(gn)] if full or attr_name in default_attrs: attr_value = self.params[attr_name].type(format_name(gn)) obj.setdefault(attr_name, []).append(attr_value) if full and attr_name.startswith('san_other_'): # also include known otherName in generic otherName attribute attr_value = self.params['san_other'].type(_format_othername(gn)) obj.setdefault('san_other', []).append(attr_value)