mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 15:40:01 -06:00
User life cycle: stageuser-add verb
Add a accounts plugin (accounts class) that defines variables and methods common to 'users' and 'stageuser'. accounts is a superclass of users/stageuser Add the stageuser plugin, with support of stageuser-add verb. Reviewed By: David Kupka, Martin Basti, Jan Cholasta https://fedorahosted.org/freeipa/ticket/3813 Reviewed-By: Jan Cholasta <jcholast@redhat.com> Reviewed-By: David Kupka <dkupka@redhat.com>
This commit is contained in:
parent
c3ede5f1e9
commit
d1691eee88
49
API.txt
49
API.txt
@ -3691,6 +3691,55 @@ command: sidgen_was_run
|
||||
args: 0,1,1
|
||||
option: Str('version?', exclude='webui')
|
||||
output: Output('result', None, None)
|
||||
command: stageuser_add
|
||||
args: 1,43,3
|
||||
arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=False, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', primary_key=True, required=True)
|
||||
option: Str('addattr*', cli_name='addattr', exclude='webui')
|
||||
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
|
||||
option: Str('carlicense', attribute=True, cli_name='carlicense', multivalue=True, required=False)
|
||||
option: Str('cn', attribute=True, autofill=True, cli_name='cn', multivalue=False, required=True)
|
||||
option: Str('departmentnumber', attribute=True, cli_name='departmentnumber', multivalue=True, required=False)
|
||||
option: Str('displayname', attribute=True, autofill=True, cli_name='displayname', multivalue=False, required=False)
|
||||
option: Str('employeenumber', attribute=True, cli_name='employeenumber', multivalue=False, required=False)
|
||||
option: Str('employeetype', attribute=True, cli_name='employeetype', multivalue=False, required=False)
|
||||
option: Str('facsimiletelephonenumber', attribute=True, cli_name='fax', multivalue=True, required=False)
|
||||
option: Flag('from_delete?', autofill=True, cli_name='from_delete', default=False)
|
||||
option: Str('gecos', attribute=True, autofill=True, cli_name='gecos', multivalue=False, required=False)
|
||||
option: Int('gidnumber', attribute=True, cli_name='gidnumber', minvalue=1, multivalue=False, required=False)
|
||||
option: Str('givenname', attribute=True, cli_name='first', multivalue=False, required=True)
|
||||
option: Str('homedirectory', attribute=True, cli_name='homedir', multivalue=False, required=False)
|
||||
option: Str('initials', attribute=True, autofill=True, cli_name='initials', multivalue=False, required=False)
|
||||
option: Str('ipasshpubkey', attribute=True, cli_name='sshpubkey', csv=True, multivalue=True, required=False)
|
||||
option: Str('ipatokenradiusconfiglink', attribute=True, cli_name='radius', multivalue=False, required=False)
|
||||
option: Str('ipatokenradiususername', attribute=True, cli_name='radius_username', multivalue=False, required=False)
|
||||
option: StrEnum('ipauserauthtype', attribute=True, cli_name='user_auth_type', csv=True, multivalue=True, required=False, values=(u'password', u'radius', u'otp'))
|
||||
option: DateTime('krbprincipalexpiration', attribute=True, cli_name='principal_expiration', multivalue=False, required=False)
|
||||
option: Str('krbprincipalname', attribute=True, autofill=True, cli_name='principal', multivalue=False, required=False)
|
||||
option: Str('l', attribute=True, cli_name='city', multivalue=False, required=False)
|
||||
option: Str('loginshell', attribute=True, cli_name='shell', multivalue=False, required=False)
|
||||
option: Str('mail', attribute=True, cli_name='email', multivalue=True, required=False)
|
||||
option: Str('manager', attribute=True, cli_name='manager', multivalue=False, required=False)
|
||||
option: Str('mobile', attribute=True, cli_name='mobile', multivalue=True, required=False)
|
||||
option: Flag('no_members', autofill=True, default=False, exclude='webui')
|
||||
option: Str('ou', attribute=True, cli_name='orgunit', multivalue=False, required=False)
|
||||
option: Str('pager', attribute=True, cli_name='pager', multivalue=True, required=False)
|
||||
option: Str('postalcode', attribute=True, cli_name='postalcode', multivalue=False, required=False)
|
||||
option: Str('preferredlanguage', attribute=True, cli_name='preferredlanguage', multivalue=False, 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})?)))?)*)|(\\*))$', required=False)
|
||||
option: Flag('random', attribute=False, autofill=True, cli_name='random', default=False, multivalue=False, required=False)
|
||||
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
|
||||
option: Str('setattr*', cli_name='setattr', exclude='webui')
|
||||
option: Str('sn', attribute=True, cli_name='last', multivalue=False, required=True)
|
||||
option: Str('st', attribute=True, cli_name='state', multivalue=False, required=False)
|
||||
option: Str('street', attribute=True, cli_name='street', multivalue=False, required=False)
|
||||
option: Str('telephonenumber', attribute=True, cli_name='phone', multivalue=True, required=False)
|
||||
option: Str('title', attribute=True, cli_name='title', multivalue=False, required=False)
|
||||
option: Int('uidnumber', attribute=True, cli_name='uid', minvalue=1, multivalue=False, required=False)
|
||||
option: Str('userclass', attribute=True, cli_name='class', multivalue=True, required=False)
|
||||
option: Password('userpassword', attribute=True, cli_name='password', exclude='webui', multivalue=False, required=False)
|
||||
option: Str('version?', exclude='webui')
|
||||
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
|
||||
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
|
||||
output: PrimaryKey('value', None, None)
|
||||
command: sudocmd_add
|
||||
args: 1,7,3
|
||||
arg: Str('sudocmd', attribute=True, cli_name='command', multivalue=False, primary_key=True, required=True)
|
||||
|
4
VERSION
4
VERSION
@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
|
||||
# #
|
||||
########################################################
|
||||
IPA_API_VERSION_MAJOR=2
|
||||
IPA_API_VERSION_MINOR=115
|
||||
# Last change: mbasti - Remove NSEC3PARAM record from dnsrecord-* commands
|
||||
IPA_API_VERSION_MINOR=116
|
||||
# Last change: tbordaz - Add stageuser_add command"
|
||||
|
@ -1,21 +1,26 @@
|
||||
# bootstrap the user life cycle DIT structure.
|
||||
|
||||
dn: cn=provisioning,$SUFFIX
|
||||
add: objectclass: top
|
||||
add: objectclass: nsContainer
|
||||
add: cn: provisioning
|
||||
default: objectclass: top
|
||||
default: objectclass: nsContainer
|
||||
default: cn: provisioning
|
||||
|
||||
dn: cn=accounts,cn=provisioning,$SUFFIX
|
||||
add: objectclass: top
|
||||
add: objectclass: nsContainer
|
||||
add: cn: accounts
|
||||
default: objectclass: top
|
||||
default: objectclass: nsContainer
|
||||
default: cn: accounts
|
||||
|
||||
dn: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
|
||||
add: objectclass: top
|
||||
add: objectclass: nsContainer
|
||||
add: cn: staged users
|
||||
default: objectclass: top
|
||||
default: objectclass: nsContainer
|
||||
default: cn: staged users
|
||||
|
||||
dn: cn=deleted users,cn=accounts,cn=provisioning,$SUFFIX
|
||||
add: objectclass: top
|
||||
add: objectclass: nsContainer
|
||||
add: cn: staged users
|
||||
default: objectclass: top
|
||||
default: objectclass: nsContainer
|
||||
default: cn: staged users
|
||||
|
||||
# This is used for the admin to know if credential are set for stage users
|
||||
# We can do a query on a DN to see if an attribute exists.
|
||||
dn: cn=staged users,cn=accounts,cn=provisioning,$SUFFIX
|
||||
add:aci: '(targetattr="userPassword || krbPrincipalKey")(version 3.0; acl "Search existence of password and kerberos keys"; allow(search) userdn = "ldap:///uid=admin,cn=users,cn=accounts,$SUFFIX";)'
|
||||
|
@ -78,6 +78,8 @@ DEFAULT_CONFIG = (
|
||||
# LDAP containers:
|
||||
('container_accounts', DN(('cn', 'accounts'))),
|
||||
('container_user', DN(('cn', 'users'), ('cn', 'accounts'))),
|
||||
('container_deleteuser', DN(('cn', 'deleted users'), ('cn', 'accounts'), ('cn', 'provisioning'))),
|
||||
('container_stageuser', DN(('cn', 'staged users'), ('cn', 'accounts'), ('cn', 'provisioning'))),
|
||||
('container_group', DN(('cn', 'groups'), ('cn', 'accounts'))),
|
||||
('container_service', DN(('cn', 'services'), ('cn', 'accounts'))),
|
||||
('container_host', DN(('cn', 'computers'), ('cn', 'accounts'))),
|
||||
|
471
ipalib/plugins/baseuser.py
Normal file
471
ipalib/plugins/baseuser.py
Normal file
@ -0,0 +1,471 @@
|
||||
# Authors:
|
||||
# Thierry Bordaz <tbordaz@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 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/>.
|
||||
|
||||
from time import gmtime, strftime
|
||||
import string
|
||||
import posixpath
|
||||
import os
|
||||
|
||||
from ipalib import api, errors
|
||||
from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.plugins.baseldap import DN, LDAPObject, \
|
||||
LDAPCreate, LDAPUpdate, LDAPSearch, LDAPDelete, LDAPRetrieve
|
||||
from ipalib.plugins import baseldap
|
||||
from ipalib.request import context
|
||||
from ipalib import _, ngettext
|
||||
from ipalib import output
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.ipautil import ipa_generate_password
|
||||
from ipapython.ipavalidate import Email
|
||||
from ipalib.capabilities import client_has_capability
|
||||
from ipalib.util import (normalize_sshpubkey, validate_sshpubkey,
|
||||
convert_sshpubkey_post)
|
||||
if api.env.in_server and api.env.context in ['lite', 'server']:
|
||||
from ipaserver.plugins.ldap2 import ldap2
|
||||
|
||||
__doc__ = _("""
|
||||
Baseuser
|
||||
|
||||
This contains common definitions for user/stageuser
|
||||
""")
|
||||
|
||||
register = Registry()
|
||||
|
||||
NO_UPG_MAGIC = '__no_upg__'
|
||||
|
||||
baseuser_output_params = (
|
||||
Flag('has_keytab',
|
||||
label=_('Kerberos keys available'),
|
||||
),
|
||||
Str('sshpubkeyfp*',
|
||||
label=_('SSH public key fingerprint'),
|
||||
),
|
||||
)
|
||||
|
||||
status_baseuser_output_params = (
|
||||
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'),
|
||||
),
|
||||
)
|
||||
|
||||
UPG_DEFINITION_DN = DN(('cn', 'UPG Definition'),
|
||||
('cn', 'Definitions'),
|
||||
('cn', 'Managed Entries'),
|
||||
('cn', 'etc'),
|
||||
api.env.basedn)
|
||||
|
||||
# characters to be used for generating random user passwords
|
||||
baseuser_pwdchars = string.digits + string.ascii_letters + '_,.@+-='
|
||||
|
||||
def validate_nsaccountlock(entry_attrs):
|
||||
if 'nsaccountlock' in entry_attrs:
|
||||
nsaccountlock = entry_attrs['nsaccountlock']
|
||||
if not isinstance(nsaccountlock, (bool, Bool)):
|
||||
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'))
|
||||
|
||||
def radius_dn2pk(api, entry_attrs):
|
||||
cl = entry_attrs.get('ipatokenradiusconfiglink', None)
|
||||
if cl:
|
||||
pk = api.Object['radiusproxy'].get_primary_key_from_dn(cl[0])
|
||||
entry_attrs['ipatokenradiusconfiglink'] = [pk]
|
||||
|
||||
def convert_nsaccountlock(entry_attrs):
|
||||
if not 'nsaccountlock' in entry_attrs:
|
||||
entry_attrs['nsaccountlock'] = False
|
||||
else:
|
||||
nsaccountlock = Bool('temp')
|
||||
entry_attrs['nsaccountlock'] = nsaccountlock.convert(entry_attrs['nsaccountlock'][0])
|
||||
|
||||
def split_principal(principal):
|
||||
"""
|
||||
Split the principal into its components and do some basic validation.
|
||||
|
||||
Automatically append our realm if it wasn't provided.
|
||||
"""
|
||||
realm = None
|
||||
parts = principal.split('@')
|
||||
user = parts[0].lower()
|
||||
if len(parts) > 2:
|
||||
raise errors.MalformedUserPrincipal(principal=principal)
|
||||
|
||||
if len(parts) == 2:
|
||||
realm = parts[1].upper()
|
||||
# At some point we'll support multiple realms
|
||||
if realm != api.env.realm:
|
||||
raise errors.RealmMismatch()
|
||||
else:
|
||||
realm = api.env.realm
|
||||
|
||||
return (user, realm)
|
||||
|
||||
def validate_principal(ugettext, principal):
|
||||
"""
|
||||
All the real work is done in split_principal.
|
||||
"""
|
||||
(user, realm) = split_principal(principal)
|
||||
return None
|
||||
|
||||
def normalize_principal(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.
|
||||
"""
|
||||
(user, realm) = split_principal(principal)
|
||||
return unicode('%s@%s' % (user, realm))
|
||||
|
||||
|
||||
|
||||
def fix_addressbook_permission_bindrule(name, template, is_new,
|
||||
anonymous_read_aci,
|
||||
**other_options):
|
||||
"""Fix bind rule type for Read User Addressbook/IPA Attributes permission
|
||||
|
||||
When upgrading from an old IPA that had the global read ACI,
|
||||
or when installing the first replica with granular read permissions,
|
||||
we need to keep allowing anonymous access to many user attributes.
|
||||
This fixup_function changes the bind rule type accordingly.
|
||||
"""
|
||||
if is_new and anonymous_read_aci:
|
||||
template['ipapermbindruletype'] = 'anonymous'
|
||||
|
||||
|
||||
|
||||
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'
|
||||
]
|
||||
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'
|
||||
]
|
||||
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'),
|
||||
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),
|
||||
),
|
||||
DateTime('krbprincipalexpiration?',
|
||||
cli_name='principal_expiration',
|
||||
label=_('Kerberos principal 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'),
|
||||
),
|
||||
Str('manager?',
|
||||
label=_('Manager'),
|
||||
),
|
||||
Str('carlicense*',
|
||||
label=_('Car License'),
|
||||
),
|
||||
Str('ipasshpubkey*', validate_sshpubkey,
|
||||
cli_name='sshpubkey',
|
||||
label=_('SSH public key'),
|
||||
normalizer=normalize_sshpubkey,
|
||||
csv=True,
|
||||
flags=['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'),
|
||||
csv=True,
|
||||
),
|
||||
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"',
|
||||
),
|
||||
)
|
||||
|
||||
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, 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, 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 m in xrange(len(manager)):
|
||||
if isinstance(manager[m], DN) and manager[m].endswith(container_dn):
|
||||
continue
|
||||
entry_attrs = self.backend.find_entry_by_attr(
|
||||
self.primary_key.name, manager[m], self.object_class, [''],
|
||||
container_dn
|
||||
)
|
||||
manager[m] = entry_attrs.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 baseuser_add(LDAPCreate):
|
||||
"""
|
||||
Prototype command plugin to be implemented by real plugin
|
||||
"""
|
||||
|
||||
class baseuser_del(LDAPDelete):
|
||||
"""
|
||||
Prototype command plugin to be implemented by real plugin
|
||||
"""
|
||||
|
||||
class baseuser_mod(LDAPUpdate):
|
||||
"""
|
||||
Prototype command plugin to be implemented by real plugin
|
||||
"""
|
||||
|
||||
class baseuser_find(LDAPSearch):
|
||||
"""
|
||||
Prototype command plugin to be implemented by real plugin
|
||||
"""
|
||||
|
||||
class baseuser_show(LDAPRetrieve):
|
||||
"""
|
||||
Prototype command plugin to be implemented by real plugin
|
||||
"""
|
277
ipalib/plugins/stageuser.py
Normal file
277
ipalib/plugins/stageuser.py
Normal file
@ -0,0 +1,277 @@
|
||||
# Authors:
|
||||
# Thierry Bordaz <tbordaz@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014 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/>.
|
||||
|
||||
from time import gmtime, strftime
|
||||
import string
|
||||
import posixpath
|
||||
import os
|
||||
|
||||
from ipalib import api, errors
|
||||
from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.plugins.baseldap import LDAPCreate, DN, entry_to_dict
|
||||
from ipalib.plugins import baseldap
|
||||
from ipalib.plugins.baseuser import baseuser, baseuser_add, baseuser_mod, baseuser_find, \
|
||||
NO_UPG_MAGIC, radius_dn2pk, \
|
||||
baseuser_pwdchars, fix_addressbook_permission_bindrule, normalize_principal, validate_principal, \
|
||||
baseuser_output_params, status_baseuser_output_params
|
||||
|
||||
from ipalib.request import context
|
||||
from ipalib import _, ngettext
|
||||
from ipalib import output
|
||||
from ipaplatform.paths import paths
|
||||
from ipapython.ipautil import ipa_generate_password
|
||||
from ipapython.ipavalidate import Email
|
||||
from ipalib.capabilities import client_has_capability
|
||||
from ipalib.util import (normalize_sshpubkey, validate_sshpubkey,
|
||||
convert_sshpubkey_post)
|
||||
if api.env.in_server and api.env.context in ['lite', 'server']:
|
||||
from ipaserver.plugins.ldap2 import ldap2
|
||||
|
||||
__doc__ = _("""
|
||||
Stageusers
|
||||
|
||||
Manage stage user entries.
|
||||
|
||||
Stage user entries are directly under the container: "cn=stage users,
|
||||
cn=accounts, cn=provisioning, SUFFIX".
|
||||
User can not authenticate with those entries (even if the entries
|
||||
contain credentials) and are candidate to become Active entries.
|
||||
|
||||
Active user entries are Posix users directly under the container: "cn=accounts, SUFFIX".
|
||||
User can authenticate with Active entries, at the condition they have
|
||||
credentials
|
||||
|
||||
Delete user enties are Posix users directly under the container: "cn=deleted users,
|
||||
cn=accounts, cn=provisioning, SUFFIX".
|
||||
User can not authenticate with those entries (even if the entries contain credentials)
|
||||
|
||||
The stage user container contains entries
|
||||
- created by 'stageuser-add' commands that are Posix users
|
||||
- created by external provisioning system
|
||||
|
||||
A valid stage user entry MUST:
|
||||
- entry RDN is 'uid'
|
||||
- ipaUniqueID is 'autogenerate'
|
||||
|
||||
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.
|
||||
|
||||
|
||||
EXAMPLES:
|
||||
|
||||
Add a new stageuser:
|
||||
ipa stageuser-add --first=Tim --last=User --password tuser1
|
||||
|
||||
Add a stageuser from the Delete container
|
||||
ipa stageuser-add --first=Tim --last=User --from-delete tuser1
|
||||
|
||||
""")
|
||||
|
||||
register = Registry()
|
||||
|
||||
|
||||
stageuser_output_params = baseuser_output_params
|
||||
|
||||
status_output_params = status_baseuser_output_params
|
||||
|
||||
@register()
|
||||
class stageuser(baseuser):
|
||||
"""
|
||||
Stage User object
|
||||
A Stage user is not an Active user and can not be used to bind with.
|
||||
Stage container is: cn=staged users,cn=accounts,cn=provisioning,SUFFIX
|
||||
Stage entry conforms the schema
|
||||
Stage entry RDN attribute is 'uid'
|
||||
Stage entry are disabled (nsAccountLock: True) through cos
|
||||
"""
|
||||
|
||||
container_dn = baseuser.stage_container_dn
|
||||
label = _('Stage Users')
|
||||
label_singular = _('Stage User')
|
||||
object_name = _('stage user')
|
||||
object_name_plural = _('stage users')
|
||||
managed_permissions = {}
|
||||
|
||||
@register()
|
||||
class stageuser_add(baseuser_add):
|
||||
__doc__ = _('Add a new stage user.')
|
||||
|
||||
msg_summary = _('Added stage user "%(value)s"')
|
||||
|
||||
has_output_params = baseuser_add.has_output_params + stageuser_output_params
|
||||
|
||||
takes_options = LDAPCreate.takes_options + (
|
||||
Flag('from_delete?',
|
||||
doc=_('Create Stage user in from a delete user'),
|
||||
cli_name='from_delete',
|
||||
default=False,
|
||||
),
|
||||
)
|
||||
|
||||
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
|
||||
assert isinstance(dn, DN)
|
||||
|
||||
if not options.get('from_delete'):
|
||||
# then givenname and sn are required attributes
|
||||
if 'givenname' not in entry_attrs:
|
||||
raise errors.RequirementError(name='givenname', error=_('givenname is required'))
|
||||
|
||||
if 'sn' not in entry_attrs:
|
||||
raise errors.RequirementError(name='sn', error=_('sn is required'))
|
||||
|
||||
# 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
|
||||
entry_attrs.setdefault('description', [])
|
||||
entry_attrs['description'].append(NO_UPG_MAGIC)
|
||||
|
||||
# uidNumber/gidNumber
|
||||
entry_attrs.setdefault('uidnumber', baseldap.DNA_MAGIC)
|
||||
entry_attrs.setdefault('gidnumber', baseldap.DNA_MAGIC)
|
||||
|
||||
if not client_has_capability(
|
||||
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".
|
||||
OLD_DNA_MAGIC = 999
|
||||
if entry_attrs.get('uidnumber') == OLD_DNA_MAGIC:
|
||||
entry_attrs['uidnumber'] = baseldap.DNA_MAGIC
|
||||
if entry_attrs.get('gidnumber') == OLD_DNA_MAGIC:
|
||||
entry_attrs['gidnumber'] = baseldap.DNA_MAGIC
|
||||
|
||||
|
||||
# Check the lenght of the RDN (uid) value
|
||||
config = ldap.get_ipa_config()
|
||||
if 'ipamaxusernamelength' in config:
|
||||
if len(keys[-1]) > int(config.get('ipamaxusernamelength')[0]):
|
||||
raise errors.ValidationError(
|
||||
name=self.obj.primary_key.cli_name,
|
||||
error=_('can be at most %(len)d characters') % dict(
|
||||
len = int(config.get('ipamaxusernamelength')[0])
|
||||
)
|
||||
)
|
||||
default_shell = config.get('ipadefaultloginshell', [paths.SH])[0]
|
||||
entry_attrs.setdefault('loginshell', default_shell)
|
||||
# hack so we can request separate first and last name in CLI
|
||||
full_name = '%s %s' % (entry_attrs['givenname'], entry_attrs['sn'])
|
||||
entry_attrs.setdefault('cn', full_name)
|
||||
|
||||
# Homedirectory
|
||||
# (order is : option, placeholder (TBD), CLI default value (here in config))
|
||||
if 'homedirectory' not in entry_attrs:
|
||||
# get home's root directory from config
|
||||
homes_root = config.get('ipahomesrootdir', [paths.HOME_DIR])[0]
|
||||
# build user's home directory based on his uid
|
||||
entry_attrs['homedirectory'] = posixpath.join(homes_root, keys[-1])
|
||||
|
||||
# Kerberos principal
|
||||
entry_attrs.setdefault('krbprincipalname', '%s@%s' % (entry_attrs['uid'], api.env.realm))
|
||||
|
||||
|
||||
# If requested, generate a userpassword
|
||||
if 'userpassword' not in entry_attrs and options.get('random'):
|
||||
entry_attrs['userpassword'] = ipa_generate_password(baseuser_pwdchars)
|
||||
# save the password so it can be displayed in post_callback
|
||||
setattr(context, 'randompassword', entry_attrs['userpassword'])
|
||||
|
||||
# Check the email or create it
|
||||
if 'mail' in entry_attrs:
|
||||
entry_attrs['mail'] = self.obj.normalize_and_validate_email(entry_attrs['mail'], config)
|
||||
else:
|
||||
# No e-mail passed in. If we have a default e-mail domain set
|
||||
# then we'll add it automatically.
|
||||
defaultdomain = config.get('ipadefaultemaildomain', [None])[0]
|
||||
if defaultdomain:
|
||||
entry_attrs['mail'] = self.obj.normalize_and_validate_email(keys[-1], config)
|
||||
|
||||
# If the manager is defined, check it is a ACTIVE user to validate it
|
||||
if 'manager' in entry_attrs:
|
||||
entry_attrs['manager'] = self.obj.normalize_manager(entry_attrs['manager'], self.obj.active_container_dn)
|
||||
|
||||
if ('objectclass' in entry_attrs
|
||||
and 'userclass' in entry_attrs
|
||||
and 'ipauser' not in entry_attrs['objectclass']):
|
||||
entry_attrs['objectclass'].append('ipauser')
|
||||
|
||||
if 'ipatokenradiusconfiglink' in entry_attrs:
|
||||
cl = entry_attrs['ipatokenradiusconfiglink']
|
||||
if cl:
|
||||
if 'objectclass' not in entry_attrs:
|
||||
_entry = ldap.get_entry(dn, ['objectclass'])
|
||||
entry_attrs['objectclass'] = _entry['objectclass']
|
||||
|
||||
if 'ipatokenradiusproxyuser' not in entry_attrs['objectclass']:
|
||||
entry_attrs['objectclass'].append('ipatokenradiusproxyuser')
|
||||
|
||||
answer = self.api.Object['radiusproxy'].get_dn_if_exists(cl)
|
||||
entry_attrs['ipatokenradiusconfiglink'] = answer
|
||||
|
||||
return dn
|
||||
|
||||
def execute(self, *keys, **options):
|
||||
'''
|
||||
A stage entry may be taken from the Delete container.
|
||||
In that case we rather do 'MODRDN' than 'ADD'.
|
||||
'''
|
||||
if options.get('from_delete'):
|
||||
ldap = self.obj.backend
|
||||
|
||||
staging_dn = self.obj.get_dn(*keys, **options)
|
||||
delete_dn = DN(staging_dn[0], self.obj.delete_container_dn, api.env.basedn)
|
||||
|
||||
# Check that this value is a Active user
|
||||
try:
|
||||
entry_attrs = self._exc_wrapper(keys, options, ldap.get_entry)(delete_dn, ['dn'])
|
||||
except errors.NotFound:
|
||||
raise
|
||||
self._exc_wrapper(keys, options, ldap.move_entry_newsuperior)(delete_dn, str(DN(self.obj.stage_container_dn, api.env.basedn)))
|
||||
|
||||
entry_attrs = entry_to_dict(entry_attrs, **options)
|
||||
entry_attrs['dn'] = delete_dn
|
||||
|
||||
if self.obj.primary_key and keys[-1] is not None:
|
||||
return dict(result=entry_attrs, value=keys[-1])
|
||||
return dict(result=entry_attrs, value=u'')
|
||||
else:
|
||||
return super(stageuser_add, self).execute(*keys, **options)
|
||||
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
assert isinstance(dn, DN)
|
||||
config = ldap.get_ipa_config()
|
||||
|
||||
# Fetch the entry again to update memberof, mep data, etc updated
|
||||
# at the end of the transaction.
|
||||
newentry = ldap.get_entry(dn, ['*'])
|
||||
entry_attrs.update(newentry)
|
||||
|
||||
if options.get('random', False):
|
||||
try:
|
||||
entry_attrs['randompassword'] = unicode(getattr(context, 'randompassword'))
|
||||
except AttributeError:
|
||||
# if both randompassword and userpassword options were used
|
||||
pass
|
||||
|
||||
self.obj.get_password_attributes(ldap, dn, entry_attrs)
|
||||
convert_sshpubkey_post(ldap, dn, entry_attrs)
|
||||
radius_dn2pk(self.api, entry_attrs)
|
||||
return dn
|
@ -25,6 +25,12 @@ import os
|
||||
|
||||
from ipalib import api, errors
|
||||
from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime
|
||||
from ipalib.plugins.baseuser import baseuser, baseuser_add, baseuser_del, \
|
||||
baseuser_mod, baseuser_find, baseuser_show, \
|
||||
NO_UPG_MAGIC, UPG_DEFINITION_DN, baseuser_output_params, \
|
||||
status_baseuser_output_params, baseuser_pwdchars, \
|
||||
validate_nsaccountlock, radius_dn2pk, convert_nsaccountlock, split_principal, validate_principal, \
|
||||
normalize_principal, fix_addressbook_permission_bindrule
|
||||
from ipalib.plugable import Registry
|
||||
from ipalib.plugins.baseldap import *
|
||||
from ipalib.plugins import baseldap
|
||||
@ -85,105 +91,10 @@ EXAMPLES:
|
||||
|
||||
register = Registry()
|
||||
|
||||
NO_UPG_MAGIC = '__no_upg__'
|
||||
|
||||
user_output_params = (
|
||||
Flag('has_keytab',
|
||||
label=_('Kerberos keys available'),
|
||||
),
|
||||
Str('sshpubkeyfp*',
|
||||
label=_('SSH public key fingerprint'),
|
||||
),
|
||||
)
|
||||
user_output_params = baseuser_output_params
|
||||
|
||||
status_output_params = (
|
||||
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'),
|
||||
),
|
||||
)
|
||||
|
||||
UPG_DEFINITION_DN = DN(('cn', 'UPG Definition'),
|
||||
('cn', 'Definitions'),
|
||||
('cn', 'Managed Entries'),
|
||||
('cn', 'etc'),
|
||||
api.env.basedn)
|
||||
|
||||
# characters to be used for generating random user passwords
|
||||
user_pwdchars = string.digits + string.ascii_letters + '_,.@+-='
|
||||
|
||||
def validate_nsaccountlock(entry_attrs):
|
||||
if 'nsaccountlock' in entry_attrs:
|
||||
nsaccountlock = entry_attrs['nsaccountlock']
|
||||
if not isinstance(nsaccountlock, (bool, Bool)):
|
||||
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'))
|
||||
|
||||
def radius_dn2pk(api, entry_attrs):
|
||||
cl = entry_attrs.get('ipatokenradiusconfiglink', None)
|
||||
if cl:
|
||||
pk = api.Object['radiusproxy'].get_primary_key_from_dn(cl[0])
|
||||
entry_attrs['ipatokenradiusconfiglink'] = [pk]
|
||||
|
||||
def convert_nsaccountlock(entry_attrs):
|
||||
if not 'nsaccountlock' in entry_attrs:
|
||||
entry_attrs['nsaccountlock'] = False
|
||||
else:
|
||||
nsaccountlock = Bool('temp')
|
||||
entry_attrs['nsaccountlock'] = nsaccountlock.convert(entry_attrs['nsaccountlock'][0])
|
||||
|
||||
def split_principal(principal):
|
||||
"""
|
||||
Split the principal into its components and do some basic validation.
|
||||
|
||||
Automatically append our realm if it wasn't provided.
|
||||
"""
|
||||
realm = None
|
||||
parts = principal.split('@')
|
||||
user = parts[0].lower()
|
||||
if len(parts) > 2:
|
||||
raise errors.MalformedUserPrincipal(principal=principal)
|
||||
|
||||
if len(parts) == 2:
|
||||
realm = parts[1].upper()
|
||||
# At some point we'll support multiple realms
|
||||
if realm != api.env.realm:
|
||||
raise errors.RealmMismatch()
|
||||
else:
|
||||
realm = api.env.realm
|
||||
|
||||
return (user, realm)
|
||||
|
||||
def validate_principal(ugettext, principal):
|
||||
"""
|
||||
All the real work is done in split_principal.
|
||||
"""
|
||||
(user, realm) = split_principal(principal)
|
||||
return None
|
||||
|
||||
def normalize_principal(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.
|
||||
"""
|
||||
(user, realm) = split_principal(principal)
|
||||
return unicode('%s@%s' % (user, realm))
|
||||
status_output_params = status_baseuser_output_params
|
||||
|
||||
|
||||
def check_protected_member(user, protected_group_name=u'admins'):
|
||||
@ -204,60 +115,17 @@ def check_protected_member(user, protected_group_name=u'admins'):
|
||||
raise errors.LastMemberError(key=user, label=_(u'group'),
|
||||
container=protected_group_name)
|
||||
|
||||
|
||||
def fix_addressbook_permission_bindrule(name, template, is_new,
|
||||
anonymous_read_aci,
|
||||
**other_options):
|
||||
"""Fix bind rule type for Read User Addressbook/IPA Attributes permission
|
||||
|
||||
When upgrading from an old IPA that had the global read ACI,
|
||||
or when installing the first replica with granular read permissions,
|
||||
we need to keep allowing anonymous access to many user attributes.
|
||||
This fixup_function changes the bind rule type accordingly.
|
||||
"""
|
||||
if is_new and anonymous_read_aci:
|
||||
template['ipapermbindruletype'] = 'anonymous'
|
||||
|
||||
|
||||
@register()
|
||||
class user(LDAPObject):
|
||||
class user(baseuser):
|
||||
"""
|
||||
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', 'ipauserauthtypeclass', 'ipauser',
|
||||
'ipatokenradiusproxyuser'
|
||||
]
|
||||
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'
|
||||
]
|
||||
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')]
|
||||
|
||||
container_dn = baseuser.active_container_dn
|
||||
label = _('Users')
|
||||
label_singular = _('User')
|
||||
object_name = _('user')
|
||||
object_name_plural = _('users')
|
||||
managed_permissions = {
|
||||
'System: Read User Standard Attributes': {
|
||||
'replaces_global_anonymous_aci': True,
|
||||
@ -460,259 +328,28 @@ class user(LDAPObject):
|
||||
},
|
||||
}
|
||||
|
||||
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'),
|
||||
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),
|
||||
),
|
||||
DateTime('krbprincipalexpiration?',
|
||||
cli_name='principal_expiration',
|
||||
label=_('Kerberos principal 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'),
|
||||
),
|
||||
Str('manager?',
|
||||
label=_('Manager'),
|
||||
),
|
||||
Str('carlicense*',
|
||||
label=_('Car License'),
|
||||
),
|
||||
takes_params = baseuser.takes_params + (
|
||||
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'],
|
||||
),
|
||||
StrEnum('ipauserauthtype*',
|
||||
cli_name='user_auth_type',
|
||||
label=_('User authentication types'),
|
||||
doc=_('Types of supported user authentication'),
|
||||
values=(u'password', u'radius', u'otp'),
|
||||
csv=True,
|
||||
),
|
||||
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"',
|
||||
),
|
||||
)
|
||||
|
||||
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, 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
|
||||
entry_attrs = self.backend.find_entry_by_attr(
|
||||
self.primary_key.name, manager[m], self.object_class, [''],
|
||||
container_dn
|
||||
)
|
||||
manager[m] = entry_attrs.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])
|
||||
return super(user, self).normalize_manager(manager, self.active_container_dn)
|
||||
|
||||
|
||||
@register()
|
||||
class user_add(LDAPCreate):
|
||||
class user_add(baseuser_add):
|
||||
__doc__ = _('Add a new user.')
|
||||
|
||||
msg_summary = _('Added user "%(value)s"')
|
||||
|
||||
has_output_params = LDAPCreate.has_output_params + user_output_params
|
||||
has_output_params = baseuser_add.has_output_params + user_output_params
|
||||
|
||||
takes_options = LDAPCreate.takes_options + (
|
||||
Flag('noprivate',
|
||||
@ -798,21 +435,21 @@ class user_add(LDAPCreate):
|
||||
entry_attrs['gidnumber'] = group_attrs['gidnumber']
|
||||
|
||||
if 'userpassword' not in entry_attrs and options.get('random'):
|
||||
entry_attrs['userpassword'] = ipa_generate_password(user_pwdchars)
|
||||
entry_attrs['userpassword'] = ipa_generate_password(baseuser_pwdchars)
|
||||
# save the password so it can be displayed in post_callback
|
||||
setattr(context, 'randompassword', entry_attrs['userpassword'])
|
||||
|
||||
if 'mail' in entry_attrs:
|
||||
entry_attrs['mail'] = self.obj._normalize_and_validate_email(entry_attrs['mail'], config)
|
||||
entry_attrs['mail'] = self.obj.normalize_and_validate_email(entry_attrs['mail'], config)
|
||||
else:
|
||||
# No e-mail passed in. If we have a default e-mail domain set
|
||||
# then we'll add it automatically.
|
||||
defaultdomain = config.get('ipadefaultemaildomain', [None])[0]
|
||||
if defaultdomain:
|
||||
entry_attrs['mail'] = self.obj._normalize_and_validate_email(keys[-1], config)
|
||||
entry_attrs['mail'] = self.obj.normalize_and_validate_email(keys[-1], config)
|
||||
|
||||
if 'manager' in entry_attrs:
|
||||
entry_attrs['manager'] = self.obj._normalize_manager(entry_attrs['manager'])
|
||||
entry_attrs['manager'] = self.obj.normalize_manager(entry_attrs['manager'], self.obj.active_container_dn)
|
||||
|
||||
if 'userclass' in entry_attrs and \
|
||||
'ipauser' not in entry_attrs['objectclass']:
|
||||
@ -847,7 +484,7 @@ class user_add(LDAPCreate):
|
||||
except errors.AlreadyGroupMember:
|
||||
pass
|
||||
|
||||
self.obj._convert_manager(entry_attrs, **options)
|
||||
self.obj.convert_manager(entry_attrs, **options)
|
||||
# delete description attribute NO_UPG_MAGIC if present
|
||||
if options.get('noprivate', False):
|
||||
if not options.get('all', False):
|
||||
@ -880,7 +517,7 @@ class user_add(LDAPCreate):
|
||||
|
||||
|
||||
@register()
|
||||
class user_del(LDAPDelete):
|
||||
class user_del(baseuser_del):
|
||||
__doc__ = _('Delete a user.')
|
||||
|
||||
msg_summary = _('Deleted user "%(value)s"')
|
||||
@ -905,12 +542,12 @@ class user_del(LDAPDelete):
|
||||
|
||||
|
||||
@register()
|
||||
class user_mod(LDAPUpdate):
|
||||
class user_mod(baseuser_mod):
|
||||
__doc__ = _('Modify a user.')
|
||||
|
||||
msg_summary = _('Modified user "%(value)s"')
|
||||
|
||||
has_output_params = LDAPUpdate.has_output_params + user_output_params
|
||||
has_output_params = baseuser_mod.has_output_params + user_output_params
|
||||
|
||||
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
|
||||
assert isinstance(dn, DN)
|
||||
@ -925,12 +562,12 @@ class user_mod(LDAPUpdate):
|
||||
)
|
||||
)
|
||||
if 'mail' in entry_attrs:
|
||||
entry_attrs['mail'] = self.obj._normalize_and_validate_email(entry_attrs['mail'])
|
||||
entry_attrs['mail'] = self.obj.normalize_and_validate_email(entry_attrs['mail'])
|
||||
if 'manager' in entry_attrs:
|
||||
entry_attrs['manager'] = self.obj._normalize_manager(entry_attrs['manager'])
|
||||
entry_attrs['manager'] = self.obj.normalize_manager(entry_attrs['manager'], self.obj.active_container_dn)
|
||||
validate_nsaccountlock(entry_attrs)
|
||||
if 'userpassword' not in entry_attrs and options.get('random'):
|
||||
entry_attrs['userpassword'] = ipa_generate_password(user_pwdchars)
|
||||
entry_attrs['userpassword'] = ipa_generate_password(baseuser_pwdchars)
|
||||
# save the password so it can be displayed in post_callback
|
||||
setattr(context, 'randompassword', entry_attrs['userpassword'])
|
||||
if ('ipasshpubkey' in entry_attrs or 'ipauserauthtype' in entry_attrs
|
||||
@ -970,7 +607,7 @@ class user_mod(LDAPUpdate):
|
||||
# if both randompassword and userpassword options were used
|
||||
pass
|
||||
convert_nsaccountlock(entry_attrs)
|
||||
self.obj._convert_manager(entry_attrs, **options)
|
||||
self.obj.convert_manager(entry_attrs, **options)
|
||||
self.obj.get_password_attributes(ldap, dn, entry_attrs)
|
||||
convert_sshpubkey_post(ldap, dn, entry_attrs)
|
||||
radius_dn2pk(self.api, entry_attrs)
|
||||
@ -978,11 +615,11 @@ class user_mod(LDAPUpdate):
|
||||
|
||||
|
||||
@register()
|
||||
class user_find(LDAPSearch):
|
||||
class user_find(baseuser_find):
|
||||
__doc__ = _('Search for users.')
|
||||
|
||||
member_attributes = ['memberof']
|
||||
has_output_params = LDAPSearch.has_output_params + user_output_params
|
||||
has_output_params = baseuser_find.has_output_params + user_output_params
|
||||
|
||||
takes_options = LDAPSearch.takes_options + (
|
||||
Flag('whoami',
|
||||
@ -995,7 +632,7 @@ class user_find(LDAPSearch):
|
||||
# assure the manager attr is a dn, not just a bare uid
|
||||
manager = options.get('manager')
|
||||
if manager is not None:
|
||||
options['manager'] = self.obj._normalize_manager(manager)
|
||||
options['manager'] = self.obj.normalize_manager(manager, self.obj.active_container_dn)
|
||||
|
||||
# Ensure that the RADIUS config link is a dn, not just the name
|
||||
cl = 'ipatokenradiusconfiglink'
|
||||
@ -1016,7 +653,7 @@ class user_find(LDAPSearch):
|
||||
if options.get('pkey_only', False):
|
||||
return truncated
|
||||
for attrs in entries:
|
||||
self.obj._convert_manager(attrs, **options)
|
||||
self.obj.convert_manager(attrs, **options)
|
||||
self.obj.get_password_attributes(ldap, attrs.dn, attrs)
|
||||
convert_nsaccountlock(attrs)
|
||||
convert_sshpubkey_post(ldap, attrs.dn, attrs)
|
||||
@ -1028,15 +665,15 @@ class user_find(LDAPSearch):
|
||||
|
||||
|
||||
@register()
|
||||
class user_show(LDAPRetrieve):
|
||||
class user_show(baseuser_show):
|
||||
__doc__ = _('Display information about a user.')
|
||||
|
||||
has_output_params = LDAPRetrieve.has_output_params + user_output_params
|
||||
has_output_params = baseuser_show.has_output_params + user_output_params
|
||||
|
||||
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
|
||||
assert isinstance(dn, DN)
|
||||
convert_nsaccountlock(entry_attrs)
|
||||
self.obj._convert_manager(entry_attrs, **options)
|
||||
self.obj.convert_manager(entry_attrs, **options)
|
||||
self.obj.get_password_attributes(ldap, dn, entry_attrs)
|
||||
convert_sshpubkey_post(ldap, dn, entry_attrs)
|
||||
radius_dn2pk(self.api, entry_attrs)
|
||||
|
Loading…
Reference in New Issue
Block a user