Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# Authors: # Jason Gerard DeRose <jderose@redhat.com> # Pavel Zuna <pzuna@redhat.com> # # Copyright (C) 2008 Red Hat # see file 'COPYING' for use and warranty information # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
convert_sshpubkey_post)
Users
Manage user entries. All users are POSIX users.
IPA supports a wide range of username formats, but you need to be aware of any restrictions that may apply to your particular environment. For example, usernames that start with a digit or usernames that exceed a certain length may cause problems for some UNIX systems. Use 'ipa config-mod' to change the username format allowed by IPA tools.
Disabling a user account prevents that user from obtaining new Kerberos credentials. It does not invalidate any credentials that have already been issued.
Password management is not a part of this module. For more information about this topic please see: ipa help passwd
Account lockout on password failure happens per IPA master. The user-status command can be used to identify which master the user is locked out on. It is on that master the administrator must unlock the user.
EXAMPLES:
Add a new user: ipa user-add --first=Tim --last=User --password tuser1
Find all users whose entries include the string "Tim": ipa user-find Tim
Find all users with "Tim" as the first name: ipa user-find --first=Tim
Disable a user account: ipa user-disable tuser1
Enable a user account: ipa user-enable tuser1
Delete a user: ipa user-del tuser1 """)
Flag('has_keytab', label=_('Kerberos keys available'), ), Str('sshpubkeyfp*', label=_('SSH public key fingerprint'), ), )
Str('server', label=_('Server'), ), Str('krbloginfailedcount', label=_('Failed logins'), ), Str('krblastsuccessfulauth', label=_('Last successful authentication'), ), Str('krblastfailedauth', label=_('Last failed authentication'), ), Str('now', label=_('Time now'), ), )
# characters to be used for generating random user passwords
if not isinstance(nsaccountlock, basestring): raise errors.OnlyOneValueAllowed(attr='nsaccountlock') if nsaccountlock.lower() not in ('true', 'false'): raise errors.ValidationError(name='nsaccountlock', error=_('must be TRUE or FALSE'))
else:
""" Split the principal into its components and do some basic validation.
Automatically append our realm if it wasn't provided. """
# At some point we'll support multiple realms else:
""" All the real work is done in split_principal. """
""" Ensure that the name in the principal is lower-case. The realm is upper-case by convention but it isn't required.
The principal is validated at this point. """
''' Ensure the last enabled member of a protected group cannot be deleted or disabled by raising LastMemberError. '''
# Get all users in the protected group
# Build list of users in the protected group who are enabled
# If the user is the last enabled user raise LastMemberError exception container=protected_group_name)
""" User object. """ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'uidnumber', 'gidnumber', 'mail', 'ou', 'telephonenumber', 'title', 'memberof', 'nsaccountlock', 'memberofindirect', ] 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'mail', 'telephonenumber', 'title', 'nsaccountlock', 'uidnumber', 'gidnumber', 'sshpubkeyfp', ] 'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], } ('krbprincipalkey', 'has_keytab')]
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)'), 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'), ), 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'], ), )
# check if default email domain should be added raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=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)
""" Given a userid verify the user's existence and return the dn. """ return None
continue self.primary_key.name, manager[m], self.object_class, [''], container_dn )
""" Convert a manager dn into a userid """ return
Flag('noprivate', cli_name='noprivate', doc=_('Don\'t create user private group'), ), )
# The Managed Entries plugin will allow a user to be created # even if a group has a duplicate name. This would leave a user # without a private group. Check for both the group and the user. else: # we don't want an user private group to be created for this user # add NO_UPG_MAGIC description attribute to let the DS plugin know
options['version'], 'optional_uid_params'): # https://fedorahosted.org/freeipa/ticket/2886 # Old clients say 999 (OLD_DNA_MAGIC) when they really mean # "assign a value dynamically". entry_attrs['gidnumber'] = baseldap.DNA_MAGIC
name=self.obj.primary_key.cli_name, error=_('can be at most %(len)d characters') % dict( len = int(config.get('ipamaxusernamelength')[0]) ) ) # hack so we can request separate first and last name in CLI # get home's root directory from config # build user's home directory based on his uid DN(('cn', 'global_policy'), ('cn', api.env.realm), ('cn', 'kerberos'), api.env.basedn))
# gidNumber wasn't specified explicity, find out what it should be # User Private Groups - uidNumber == gidNumber else: # we're adding new users to a default group, get its gidNumber # get default group name from config except errors.NotFound: error_msg = _('Default group for new users not found') raise errors.NotFound(reason=error_msg)
# save the password so it can be displayed in post_callback
else: # No e-mail passed in. If we have a default e-mail domain set # then we'll add it automatically.
# add the user we just created into the default primary group
# if the user is already a member of default primary group, # do not raise error # this can happen if automember rule or default group is set
# delete description attribute NO_UPG_MAGIC if present except (errors.EmptyModlist, errors.NotFound): pass
# Fetch the entry again to update memberof, mep data, etc updated # at the end of the transaction.
except AttributeError: # if both randompassword and userpassword options were used pass
name=self.obj.primary_key.cli_name, error=_('can be at most %(len)d characters') % dict( len = int(config.get('ipamaxusernamelength')[0]) ) ) # save the password so it can be displayed in post_callback if 'objectclass' in entry_attrs: obj_classes = entry_attrs['objectclass'] else: (_dn, _entry_attrs) = ldap.get_entry(dn, ['objectclass']) obj_classes = entry_attrs['objectclass'] = _entry_attrs['objectclass'] if 'ipasshuser' not in obj_classes: obj_classes.append('ipasshuser')
except AttributeError: # if both randompassword and userpassword options were used pass
Flag('whoami', label=_('Self'), doc=_('Display user record for current Kerberos principal'), ), )
# assure the manager attr is a dn, not just a bare uid
return ("(&(objectclass=posixaccount)(krbprincipalname=%s))"%\ getattr(context, 'principal'), base_dn, scope)
'%(count)d user matched', '%(count)d users matched', 0 )
result=True, value=keys[0], )
result=True, value=keys[0], )
Unlock a user account
An account may become locked if the password is entered incorrectly too many times within a specific time period as controlled by password policy. A locked account is a temporary condition and may be unlocked by an administrator.""")
dn = self.obj.get_dn(*keys, **options) entry_attrs = {'krbLastAdminUnlock': strftime("%Y%m%d%H%M%SZ",gmtime()), 'krbLoginFailedCount': '0'}
self.obj.backend.update_entry(dn, entry_attrs)
return dict( result=True, value=keys[0], )
Lockout status of a user account
An account may become locked if the password is entered incorrectly too many times within a specific time period as controlled by password policy. A locked account is a temporary condition and may be unlocked by an administrator.
This connects to each IPA master and displays the lockout status on each one.
To determine whether an account is locked on a given server you need to compare the number of failed logins and the time of the last failure. For an account to be locked it must exceed the maxfail failures within the failinterval duration as specified in the password policy associated with the user.
The failed login counter is modified only when a user attempts a log in so it is possible that an account may appear locked but the last failed login attempt is older than the lockouttime of the password policy. This means that the user may attempt a login again. """)
ldap = self.obj.backend dn = self.obj.get_dn(*keys, **options) attr_list = ['krbloginfailedcount', 'krblastsuccessfulauth', 'krblastfailedauth', 'nsaccountlock']
disabled = False masters = [] # Get list of masters try: (masters, truncated) = ldap.find_entries( None, ['*'], DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), api.env.basedn), ldap.SCOPE_ONELEVEL ) except errors.NotFound: # If this happens we have some pretty serious problems self.error('No IPA masters found!') pass
entries = [] count = 0 for master in masters: host = master[1]['cn'][0] if host == api.env.host: other_ldap = self.obj.backend else: other_ldap = ldap2(shared_instance=False, ldap_uri='ldap://%s' % host, base_dn=self.api.env.basedn) try: other_ldap.connect(ccache=os.environ['KRB5CCNAME']) except Exception, e: self.error("user_status: Connecting to %s failed with %s" % (host, str(e))) newresult = ldap.make_entry(dn) newresult['server'] = _("%(host)s failed: %(error)s") % dict(host=host, error=str(e)) entries.append(newresult) count += 1 continue try: entry = other_ldap.get_entry(dn, attr_list) newresult = ldap.make_entry(dn) for attr in ['krblastsuccessfulauth', 'krblastfailedauth']: newresult[attr] = entry[1].get(attr, [u'N/A']) newresult['krbloginfailedcount'] = entry[1].get('krbloginfailedcount', u'0') if not options.get('raw', False): for attr in ['krblastsuccessfulauth', 'krblastfailedauth']: try: if newresult[attr][0] == u'N/A': continue newtime = time.strptime(newresult[attr][0], '%Y%m%d%H%M%SZ') newresult[attr][0] = unicode(time.strftime('%Y-%m-%dT%H:%M:%SZ', newtime)) except Exception, e: self.debug("time conversion failed with %s" % str(e)) pass newresult['server'] = host if options.get('raw', False): time_format = '%Y%m%d%H%M%SZ' else: time_format = '%Y-%m-%dT%H:%M:%SZ' newresult['now'] = unicode(strftime(time_format, gmtime())) convert_nsaccountlock(entry[1]) if 'nsaccountlock' in entry[1].keys(): disabled = entry[1]['nsaccountlock'] entries.append(newresult) count += 1 except errors.NotFound: self.obj.handle_not_found(*keys) except Exception, e: self.error("user_status: Retrieving status for %s failed with %s" % (dn, str(e))) newresult = ldap.make_entry(dn) newresult['server'] = _("%(host)s failed") % dict(host=host) entries.append(newresult) count += 1
if host != api.env.host: other_ldap.destroy_connection()
return dict(result=entries, count=count, truncated=False, summary=unicode(_('Account disabled: %(disabled)s' % dict(disabled=disabled))), )
|