Thank you for the patch, I checked it, I just changed permission name to have all first letters in uppercase as others.
Updated merged patch attached.

On 08/18/2015 05:34 PM, thierry bordaz wrote:
On 08/18/2015 04:13 PM, thierry bordaz wrote:
On 08/18/2015 04:04 PM, Martin Basti wrote:



On 08/18/2015 03:49 PM, thierry bordaz wrote:
On 08/18/2015 03:06 PM, Martin Basti wrote:


On 08/18/2015 11:32 AM, thierry bordaz wrote:
On 08/18/2015 10:02 AM, Martin Basti wrote:


On 08/18/2015 09:59 AM, thierry bordaz wrote:
On 08/18/2015 09:55 AM, Martin Basti wrote:


On 08/18/2015 09:50 AM, thierry bordaz wrote:
On 08/17/2015 08:33 PM, Martin Basti wrote:
Hello,

the 'user-stage' command replaces 'stageuser-add --from-delete' command.
https://fedorahosted.org/freeipa/ticket/5041

Thierry can you check If I don't break everything, it works for me, but the one never knows.

Honza can you please check the framework side? I use self.api.Object.stageuser.add.* in user command, I'm not sure if this is right way, but it works.

Patch attached. I created it in hurry, I'm expecting NACK :D


Just question at the end: should I implement way Active user -> stageuser? IMHO it would be implemented internally by calling 'user-del --preserve' inside 'user-stage'.



Hi Martin,

There is a small failure with VERSION (edewata pushed his patch first ;-) )

    git apply -v
    /tmp/freeipa-mbasti-0297-Add-user-stage-command.patch
    Checking patch API.txt...
    Checking patch VERSION...
    error: while searching for:
    # #
    ########################################################
    IPA_API_VERSION_MAJOR=2
    IPA_API_VERSION_MINOR=148
    # Last change: ftweedal - add --out option to user-show

    error: patch failed: VERSION:90
    error: VERSION: patch does not apply
    Checking patch ipalib/plugins/stageuser.py...
    Checking patch ipalib/plugins/user.py...


There is many pending patches that may change VERSION number, I will change it to right one before push.

Does code looks good for you?
Hi Martin,

Just a question, there is no additional permission. Did you test being 'admin' ?

thanks
theirry
No I didn't,.

I preserver all permission, the original permissions should work.

Martin
Hi Martin,

Running a test script, I have an issue with

    ipa stageuser-add --first=t --last=b tb1
    ipa: ERROR: an internal error has occurred


    [Tue Aug 18 11:16:56.440658 2015] [wsgi:error] [pid 10486]
    ipa: INFO: [jsonserver_kerb]
    stage...@abc.idm.lab.eng.brq.redhat.com:
    stageuser_add(u'tb1', givenname=u't', sn=u'b', cn=u't b',
    displayname=u't b', initials=u'tb', gecos=u't b',
    krbprincipalname=u't...@abc.idm.lab.eng.brq.redhat.com',
    random=False, all=False, raw=False, version=u'2.149',
    no_members=False): AttributeError
    [Tue Aug 18 11:21:25.198021 2015] [wsgi:error] [pid 10485]
    ipa: ERROR: non-public: AttributeError: 'DN' object has no
    attribute 'setdefault'
    [Tue Aug 18 11:21:25.198053 2015] [wsgi:error] [pid 10485]
    Traceback (most recent call last):
[Tue Aug 18 11:21:25.198058 2015] [wsgi:error] [pid 10485] File
    "/usr/lib/python2.7/site-packages/ipaserver/rpcserver.py",
    line 347, in wsgi_execute
    [Tue Aug 18 11:21:25.198062 2015] [wsgi:error] [pid
    10485]     result = self.Command[name](*args, **options)
[Tue Aug 18 11:21:25.198066 2015] [wsgi:error] [pid 10485] File "/usr/lib/python2.7/site-packages/ipalib/frontend.py",
    line 443, in __call__
    [Tue Aug 18 11:21:25.198070 2015] [wsgi:error] [pid
    10485]     ret = self.run(*args, **options)
[Tue Aug 18 11:21:25.198081 2015] [wsgi:error] [pid 10485] File "/usr/lib/python2.7/site-packages/ipalib/frontend.py",
    line 760, in run
    [Tue Aug 18 11:21:25.198133 2015] [wsgi:error] [pid
    10485]     return self.execute(*args, **options)
[Tue Aug 18 11:21:25.198139 2015] [wsgi:error] [pid 10485] File
    "/usr/lib/python2.7/site-packages/ipalib/plugins/baseldap.py", line
    1227, in execute
    [Tue Aug 18 11:21:25.198144 2015] [wsgi:error] [pid
    10485]     *keys, **options)
[Tue Aug 18 11:21:25.198147 2015] [wsgi:error] [pid 10485] File
    "/usr/lib/python2.7/site-packages/ipalib/plugins/stageuser.py",
    line 373, in pre_callback
    [Tue Aug 18 11:21:25.198151 2015] [wsgi:error] [pid
    10485]     attrs_list, *keys, **options)
[Tue Aug 18 11:21:25.198155 2015] [wsgi:error] [pid 10485] File
    "/usr/lib/python2.7/site-packages/ipalib/plugins/stageuser.py",
    line 277, in set_default_values_pre_callback
    [Tue Aug 18 11:21:25.198159 2015] [wsgi:error] [pid 10485]
    entry_attrs.setdefault('description', [])
    [Tue Aug 18 11:21:25.198163 2015] [wsgi:error] [pid 10485]
    AttributeError: 'DN' object has no attribute 'setdefault'
    [Tue Aug 18 11:21:25.199276 2015] [wsgi:error] [pid 10485]
    ipa: INFO: [jsonserver_session]
    stage...@abc.idm.lab.eng.brq.redhat.com:
    stageuser_add(u'tb1', givenname=u't', sn=u'b', cn=u't b',
    displayname=u't b', initials=u'tb', gecos=u't b',
    krbprincipalname=u't...@abc.idm.lab.eng.brq.redhat.com',
    random=False, all=False, raw=False, version=u'2.149',
    no_members=False): AttributeError


The new set_default_values_pre_callback, can not use the set_default function. It is not clear why. entry_attrs is one of pre_callback parameter. Should set_default_values_pre_callback be a subfonction of pre_callback ?


thanks
thierry

Thank you,

updated patch attached.

So far, tests are ok.
Just one comment, the 'user-stage' command description is wrong, as it moves an active user into the staged area

user-stage Move deleted user into staged area
No, it's not doing that.

user-stage is replacement of stageuser-add --from-delete, it doesn't work for active users. The support to move active user to staged area is RFE, I did not implemented it yet, and I dont know if this will fit IPA 4.2 timeframe
Ok. thanks.
Sure user-stage (active->stage) will not fit into IPA 4.2 timeframe.

Running the tests being admin, there is no problem.
I have a permission issue, when running as 'Stage administrator'. The 'delete' entry being moved to 'stage' container, we need the a special permission for it.

Hello,

I tested this new permission to grant 'Stage user administrator' to do a 'user-stage'.
Is it ok to add it to your patch ?

thanks
thierry

[root@vm-141 ~]# ipa user-del ttest1 --preserve
---------------------
Deleted user "ttest1"
---------------------

[root@vm-141 ~]# ipa user-stage ttest1
ipa: ERROR: Insufficient access: Insufficient 'moddn' privilege to move an entry to 'cn=staged users,cn=accounts,cn=provisioning,dc=abc,dc=idm,dc=lab,dc=eng,dc=brq,dc=redhat,dc=com'.

[root@vm-141 ~]# klist
Ticket cache: KEYRING:persistent:0:krb_ccache_hw3P667
Default principal: stage...@abc.idm.lab.eng.brq.redhat.com

Valid starting       Expires              Service principal
08/18/2015 15:45:43 08/19/2015 15:45:42 ldap/vm-141.abc.idm.lab.eng.brq.redhat....@abc.idm.lab.eng.brq.redhat.com 08/18/2015 15:45:42 08/19/2015 15:45:42 krbtgt/abc.idm.lab.eng.brq.redhat....@abc.idm.lab.eng.brq.redhat.com

[root@vm-141 ~]# kinit admin
Password for ad...@abc.idm.lab.eng.brq.redhat.com:
[root@vm-141 ~]# ipa user-stage ttest1
----------------------------
Staged user account "ttest1"
----------------------------
[root@vm-141 ~]# ipa stageuser-find ttest1
--------------
1 user matched
--------------
  User login: ttest1
  First name: t
  Last name: test1
  Home directory: /home/ttest1
  Login shell: /bin/sh
  Email address: tte...@abc.idm.lab.eng.brq.redhat.com
  UID: 1814000011
  GID: 1814000011
  Password: False
  Kerberos keys available: False
----------------------------
Number of entries returned 1
----------------------------




From af5f98de9bda18acf8550ff4df8760ff169efec2 Mon Sep 17 00:00:00 2001
From: Martin Basti <mba...@redhat.com>
Date: Mon, 17 Aug 2015 20:11:21 +0200
Subject: [PATCH] Add user-stage command

This patch replaces 'stageuser-add --from-delete' with new command
user-stage.

Original way always required  to specify first and last name, and
overall combination of options was hard to manage. The new command
requires only login of deleted user (user-del --preserve).

https://fedorahosted.org/freeipa/ticket/5041
---
 ACI.txt                     |   2 +
 API.txt                     |   9 ++-
 VERSION                     |   4 +-
 ipalib/plugins/baseuser.py  |  99 +++++++++++++++++++++++++++++
 ipalib/plugins/stageuser.py | 150 +++++++-------------------------------------
 ipalib/plugins/user.py      |  42 +++++++++++++
 6 files changed, 176 insertions(+), 130 deletions(-)

diff --git a/ACI.txt b/ACI.txt
index 99099275e1383f16aca122e05e34b2330f4d06a3..55e0ce8368f8735f152203e4d4513e89769ce3fb 100644
--- a/ACI.txt
+++ b/ACI.txt
@@ -269,6 +269,8 @@ aci: (targetattr = "*")(target = "ldap:///uid=*,cn=deleted users,cn=accounts,cn=
 dn: cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example
 aci: (targetattr = "krblastpwdchange || krbpasswordexpiration || krbprincipalkey || userpassword")(target = "ldap:///uid=*,cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=posixaccount)")(version 3.0;acl "permission:System: Reset Preserved User password";allow (read,search,write) groupdn = "ldap:///cn=System: Reset Preserved User password,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: dc=ipa,dc=example
+aci: (target_to = "ldap:///cn=staged users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(target_from = "ldap:///cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=nsContainer)")(version 3.0;acl "permission:System: Stage Preserved User";allow (moddn) groupdn = "ldap:///cn=System: Stage Preserved User,cn=permissions,cn=pbac,dc=ipa,dc=example";)
+dn: dc=ipa,dc=example
 aci: (target_to = "ldap:///cn=users,cn=accounts,dc=ipa,dc=example";)(target_from = "ldap:///cn=deleted users,cn=accounts,cn=provisioning,dc=ipa,dc=example")(targetfilter = "(objectclass=nsContainer)")(version 3.0;acl "permission:System: Undelete User";allow (moddn) groupdn = "ldap:///cn=System: Undelete User,cn=permissions,cn=pbac,dc=ipa,dc=example";)
 dn: cn=sudocmds,cn=sudo,dc=ipa,dc=example
 aci: (targetfilter = "(objectclass=ipasudocmd)")(version 3.0;acl "permission:System: Add Sudo Command";allow (add) groupdn = "ldap:///cn=System: Add Sudo Command,cn=permissions,cn=pbac,dc=ipa,dc=example";)
diff --git a/API.txt b/API.txt
index 9dbf86aedf2a1b62dabab21fb30bbceb2f0f237b..28c79526e7e0f2600685c20973755e59e9020593 100644
--- a/API.txt
+++ b/API.txt
@@ -4211,7 +4211,7 @@ option: Str('displayname', attribute=True, autofill=True, cli_name='displayname'
 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: DeprecatedParam('from_delete?', 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)
@@ -5371,6 +5371,13 @@ 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: user_stage
+args: 1,1,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, query=True, required=True)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'bool'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: PrimaryKey('value', None, None)
 command: user_status
 args: 1,4,4
 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, query=True, required=True)
diff --git a/VERSION b/VERSION
index c42bea06522dae55e1a89ff94ae394594086b467..c0fbd094f0c3b248b6f2715203e4f7ef12a0d3ca 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=149
-# Last change: edewata - Added CLI param and ACL for vault service operations
+IPA_API_VERSION_MINOR=150
+# Last change: mbasti - add 'user-stage' command
diff --git a/ipalib/plugins/baseuser.py b/ipalib/plugins/baseuser.py
index 5eede7a98e7e6d9bf31a6d553b0ce60c7cf3527c..112631110acee2fc170100a3c63f17fe5c53a2b9 100644
--- a/ipalib/plugins/baseuser.py
+++ b/ipalib/plugins/baseuser.py
@@ -475,6 +475,105 @@ class baseuser(LDAPObject):
             entry_attrs['usercertificate'] = entry_attrs.pop(
                 'usercertificate;binary')
 
+    def stage_pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
+                           **options):
+
+        # 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.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))
+
+        # Check the email or create it
+        if 'mail' in entry_attrs:
+            entry_attrs['mail'] = self.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.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.normalize_manager(
+                entry_attrs['manager'], self.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 stage_post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        assert isinstance(dn, DN)
+
+        # 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)
+
+        self.get_password_attributes(ldap, dn, entry_attrs)
+        convert_sshpubkey_post(ldap, dn, entry_attrs)
+        radius_dn2pk(self.api, entry_attrs)
+        return dn
+
+
 class baseuser_add(LDAPCreate):
     """
     Prototype command plugin to be implemented by real plugin
diff --git a/ipalib/plugins/stageuser.py b/ipalib/plugins/stageuser.py
index 29739d5d434b1f384d2f9a0c15cddd2bf887e7b7..90c9040dec2ceff550dd74bd40704b71c7ba9fc6 100644
--- a/ipalib/plugins/stageuser.py
+++ b/ipalib/plugins/stageuser.py
@@ -23,7 +23,8 @@ import posixpath
 import os
 from copy import deepcopy
 from ipalib import api, errors
-from ipalib import Flag, Int, Password, Str, Bool, StrEnum, DateTime
+from ipalib import (Flag, Int, Password, Str, Bool, StrEnum, DateTime,
+                    DeprecatedParam)
 from ipalib.plugable import Registry
 from ipalib.plugins.baseldap import LDAPCreate, LDAPQuery, LDAPSearch, DN, entry_to_dict, pkey_to_value
 from ipalib.plugins import baseldap
@@ -249,6 +250,17 @@ class stageuser(baseuser):
             'ipapermright': {'moddn'},
             'default_privileges': {'Stage User Administrators'},
         },
+        # Allow to move preserved user to stage container (user-stage)
+        # Note: targetfilter is the target parent container
+        'System: Stage Preserved User': {
+            'ipapermlocation': DN(api.env.basedn),
+            'ipapermbindruletype': 'permission',
+            'ipapermtargetfrom': DN(baseuser.delete_container_dn, api.env.basedn),
+            'ipapermtargetto': DN(baseuser.stage_container_dn, api.env.basedn),
+            'ipapermtargetfilter': {'(objectclass=nsContainer)'},
+            'ipapermright': {'moddn'},
+            'default_privileges': {'Stage User Administrators'},
+        },
      }
 
 @register()
@@ -260,7 +272,7 @@ class stageuser_add(baseuser_add):
     has_output_params = baseuser_add.has_output_params + stageuser_output_params
 
     takes_options = LDAPCreate.takes_options + (
-        Flag('from_delete?',
+        DeprecatedParam('from_delete?',
             doc=_('Create Stage user in from a delete user'),
             cli_name='from_delete',
             default=False,
@@ -270,62 +282,12 @@ class stageuser_add(baseuser_add):
     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 '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'))
 
         # If requested, generate a userpassword
         if 'userpassword' not in entry_attrs and options.get('random'):
@@ -333,76 +295,13 @@ class stageuser_add(baseuser_add):
             # 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
-
+        dn = self.obj.stage_pre_callback(ldap, dn, entry_attrs, attrs_list,
+                                         *keys, **options)
         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)
-            new_dn = DN(staging_dn[0], self.obj.stage_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:
-                self.obj.handle_not_found(*keys)
-
-            self._exc_wrapper(keys, options, ldap.move_entry)(
-                delete_dn, new_dn)
-            entry_attrs = entry_to_dict(entry_attrs, **options)
-            entry_attrs['dn'] = new_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)
+        dn = self.obj.stage_post_callback(ldap, dn, entry_attrs, *keys,
+                                          **options)
 
         if options.get('random', False):
             try:
@@ -411,9 +310,6 @@ class stageuser_add(baseuser_add):
                 # 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
 
 @register()
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index 4de33b9ff80799f5e499e05ef38cfc444e69a316..8eab065efecaddc46a5de411f535f21350ee4e95 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -861,6 +861,48 @@ class user_undel(LDAPQuery):
             value=pkey_to_value(keys[0], options),
         )
 
+
+@register()
+class user_stage(LDAPQuery):
+    __doc__ = _('Move deleted user into staged area')
+
+    has_output = output.standard_value
+    msg_summary = _('Staged user account "%(value)s"')
+
+    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
+                     **options):
+        return self.obj.stage_pre_callback(
+            ldap, dn, entry_attrs, attrs_list, *keys, **options)
+
+    def execute(self, *keys, **options):
+        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)
+        new_dn = DN(staging_dn[0], self.obj.stage_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:
+            self.obj.handle_not_found(*keys)
+
+        self._exc_wrapper(keys, options, ldap.move_entry)(
+            delete_dn, new_dn)
+        entry_attrs = entry_to_dict(entry_attrs, **options)
+        entry_attrs['dn'] = new_dn
+
+        return dict(
+            result=True,
+            value=pkey_to_value(keys[0], options),
+        )
+
+    def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+        return self.obj.stage_post_callback(
+            ldap, dn, entry_attrs, *keys, **options)
+
+
 @register()
 class user_disable(LDAPQuery):
     __doc__ = _('Disable a user account.')
-- 
2.4.3

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to