summaryrefslogtreecommitdiffstats
path: root/ipalib/plugins/stageuser.py
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib/plugins/stageuser.py')
-rw-r--r--ipalib/plugins/stageuser.py277
1 files changed, 277 insertions, 0 deletions
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