summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--API.txt49
-rw-r--r--VERSION4
-rw-r--r--install/updates/30-provisioning.update29
-rw-r--r--ipalib/constants.py2
-rw-r--r--ipalib/plugins/baseuser.py471
-rw-r--r--ipalib/plugins/stageuser.py277
-rw-r--r--ipalib/plugins/user.py439
7 files changed, 856 insertions, 415 deletions
diff --git a/API.txt b/API.txt
index 0c7eda9f5..f747765d7 100644
--- a/API.txt
+++ b/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)
diff --git a/VERSION b/VERSION
index 13c9760c8..b584eb458 100644
--- a/VERSION
+++ b/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"
diff --git a/install/updates/30-provisioning.update b/install/updates/30-provisioning.update
index ef6d01a44..11e01df74 100644
--- a/install/updates/30-provisioning.update
+++ b/install/updates/30-provisioning.update
@@ -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";)'
diff --git a/ipalib/constants.py b/ipalib/constants.py
index 50a2b1f7a..f1e14702f 100644
--- a/ipalib/constants.py
+++ b/ipalib/constants.py
@@ -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'))),
diff --git a/ipalib/plugins/baseuser.py b/ipalib/plugins/baseuser.py
new file mode 100644
index 000000000..16c7b2a88
--- /dev/null
+++ b/ipalib/plugins/baseuser.py
@@ -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
+ """
diff --git a/ipalib/plugins/stageuser.py b/ipalib/plugins/stageuser.py
new file mode 100644
index 000000000..2a9a7f413
--- /dev/null
+++ b/ipalib/plugins/stageuser.py
@@ -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
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index abe5ee26b..dea946e35 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -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'),
- ),
- )
-
-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
+user_output_params = baseuser_output_params
-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)