On 19.8.2015 10:57, Jan Cholasta wrote:
On 19.8.2015 10:47, thierry bordaz wrote:
On 08/19/2015 10:34 AM, Jan Cholasta wrote:
On 19.8.2015 09:39, thierry bordaz wrote:

It worked like a charm.
I had a problem to commit it because of the VERSION stuff that changed.

Except that (changing VERSION), the fix looks good to me

On 08/18/2015 07:21 PM, Martin Basti wrote:
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:

the 'user-stage' command replaces 'stageuser-add
--from-delete' command.

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

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
    Checking patch API.txt...
    Checking patch VERSION...
    error: while searching for:
    # #
    # 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' ?

No I didn't,.

I preserver all permission, the original permissions should

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]
    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',
    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]
    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]
    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]
    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]
    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]
    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',
    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 ?


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
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.


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


[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

[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

08/18/2015 15:45:42  08/19/2015 15:45:42

[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


1) Use ADD+DEL instead of MODRDN as we agreed before:


I have a slight preference doing MODRDN than ADD+DEL but I think it is
for corner case.
Before preserving a user, the user was active and could be updated. If
the user gets updated on a replica (e.g. change its phonenumer) but for
some reason the update is not immediately replicated, then a later
'user-del --preserve' + 'user-stage' will stage the user without the
updated phonenumber.

In addition, doing 2 ops rather than one costs more and is not atomic
(more complex to handle failure).

The same problem exists for stageuser_activate, and unless you want to
change it to use MODRDN as well, user_stage must use ADD+DEL.

This was already discussed quite thoroughly and we reached the decision
to use ADD+DEL, because it is consistent with the rest of the user code.
I don't see a point in discussing this further and rehashing what was
already said.


2) You can't use the entry preparation code from stageuser-add in
user-stage - it is supposed to normalize user input, not already
normalized data from LDAP, and could lead to subtle and hard to track


I have updated Martin's patch with fixes for the above. See attachment.

Jan Cholasta
From 8caeda38d49aff277a16f6dea32cf7e534c54caa 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

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).

 API.txt                     | 10 ++++++++-
 VERSION                     |  4 ++--
 ipalib/plugins/stageuser.py | 44 +++++++-------------------------------
 ipalib/plugins/user.py      | 51 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 70 insertions(+), 39 deletions(-)

diff --git a/API.txt b/API.txt
index dd6bcc3..1944a6f 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,14 @@ 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,2,3
+arg: Str('uid', attribute=True, cli_name='login', maxlength=255, multivalue=True, pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$', primary_key=True, query=True, required=True)
+option: Flag('continue', autofill=True, cli_name='continue', default=False)
+option: Str('version?', exclude='webui')
+output: Output('result', <type 'dict'>, None)
+output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
+output: ListOfPrimaryKeys('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 0ccfd09..e8387a1 100644
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
-# Last change: cheimes - Add flag to list all service and user vaults
+# Last change: mbasti - add 'user-stage' command
diff --git a/ipalib/plugins/stageuser.py b/ipalib/plugins/stageuser.py
index 29739d5..f378853 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
@@ -260,7 +261,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'),
@@ -270,13 +271,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'))
+        # 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'))
+        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
@@ -367,34 +367,6 @@ class stageuser_add(baseuser_add):
         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()
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index 1d6073b..fb4e994 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -859,6 +859,57 @@ class user_undel(LDAPQuery):
             value=pkey_to_value(keys[0], options),
+class user_stage(LDAPMultiQuery):
+    __doc__ = _('Move deleted user into staged area')
+    has_output = output.standard_multi_delete
+    msg_summary = _('Staged user account "%(value)s"')
+    def execute(self, *keys, **options):
+        staged = []
+        failed = []
+        for key in keys[-1]:
+            single_keys = keys[:-1] + (key,)
+            multi_keys = keys[:-1] + ((key,),)
+            user = self.api.Command.user_show(*single_keys, all=True)['result']
+            new_options = {}
+            for param in self.api.Command.stageuser_add.options():
+                try:
+                    value = user[param.name]
+                except KeyError:
+                    continue
+                if param.multivalue and not isinstance(value, (list, tuple)):
+                    value = [value]
+                elif not param.multivalue and isinstance(value, (list, tuple)):
+                    value = value[0]
+                new_options[param.name] = value
+            try:
+                self.api.Command.stageuser_add(*single_keys, **new_options)
+                try:
+                    self.api.Command.user_del(*multi_keys, preserve=False)
+                except errors.ExecutionError:
+                    self.api.Command.stageuser_del(*multi_keys)
+                    raise
+            except errors.ExecutionError:
+                if not options['continue']:
+                    raise
+                failed.append(key)
+            else:
+                staged.append(key)
+        return dict(
+            result=dict(
+                failed=pkey_to_value(failed, options),
+            ),
+            value=pkey_to_value(staged, options),
+        )
 class user_disable(LDAPQuery):
     __doc__ = _('Disable a user account.')

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

Reply via email to