Dne 18.5.2015 v 10:33 thierry bordaz napsal(a):
On 05/15/2015 04:44 PM, David Kupka wrote:
Hello Thierry,
thanks for the patch set. Overall functionality of ULC feature looks
good to
me and is definitely "alpha ready".

I found following issues but don't insist on fixing it right now:

1) When stageuser-activate fails due to already existent
active/deleted user.
DN is show instead of user name that's used in other commands (user-add,
$ ipa user-add tuser --first Test --last User
$ ipa stageuser-add tuser --first Test --last User
$ ipa stageuser-activate tuser
ipa: ERROR: Active user

already exists

Hi David, Jan,

Thanks you so much for all those tests and feedback. I agree, some minor
bugs can be fixed separatly from this main patches.

You are right, It should return the user ID not the DN.

2) According to the design there should be '--only-delete' and
options for user-find command instead there is '--preserved' option.
Honza proposed adding virtual boolean attribute 'deleted' to user
entry and
filter on it.
The 'deleted' attribute would be useful also in user-show where is no
way to
tell if the displayed user is active or deleted. (Except running with
and looking on the dn).

Yes a bit late to resynch the design.
The final option is 'preserved' for user-find and 'preserve' for
user-del. '--only-delete' or 'also-delete' are old name that I need to
replace in the design.

About the 'deleted' attribute, do you think adding a DS cos virtual
attribute ?

See the attached patch.

3) uidNumber and gidNumber can't be set back to '-1' once set to other
This would be useful when admin changes its mind and want IPA to
assign them.
IIUC, there should be no validation in cn=staged user container. All
validation should be done during stageuser-activate.

Yes that comes from user plugin that enforce the number to be >0.
That is a good point giving the ability to reset uidNumber/gidNumber.
I will check if it is possible, how (give a value or an option to
reset), and also if it would not create other issue.

4) Support for deleted -> stage workflow is still missing. But I'm
unsure if we
agreed to finish it now or later.

Yes thanks

5) Twice deleting user with '--preserve' deletes him permanently.
$ ipa user-add tuser --first Test --last User
$ ipa user-del tuser --preserve
$ ipa user-del tuser --preserve
$ ipa user-find --preserved
0 (delete) users matched
Number of entries returned 0

Deleting a deleted (preserved) entry, should permanently remove the entry.
Now if the second time the preserve option is present, it makes sense to
not delete it.

BTW: I might be stating the obvious here, but it would be better to use one boolean parameter rather than two mutually exclusive flags in user-del.


Jan Cholasta
>From 7151ebe30cac7877b31c3a682730ff3c63561e9f Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 20 May 2015 08:12:07 +0000
Subject: [PATCH] User life cycle: provide preserved user virtual attribute

 API.txt                |  2 +-
 VERSION                |  4 +--
 ipalib/plugins/user.py | 74 ++++++++++++++++++++++++++++++--------------------
 3 files changed, 47 insertions(+), 33 deletions(-)

diff --git a/API.txt b/API.txt
index 0808f3c..37eba3f 100644
--- a/API.txt
+++ b/API.txt
@@ -4611,7 +4611,7 @@ option: Str('pager', attribute=True, autofill=False, cli_name='pager', multivalu
 option: Flag('pkey_only?', autofill=True, default=False)
 option: Str('postalcode', attribute=True, autofill=False, cli_name='postalcode', multivalue=False, query=True, required=False)
 option: Str('preferredlanguage', attribute=True, autofill=False, 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})?)))?)*)|(\\*))$', query=True, required=False)
-option: Flag('preserved?', autofill=True, cli_name='preserved', default=False)
+option: Bool('preserved', attribute=False, autofill=False, cli_name='preserved', default=False, multivalue=False, query=True, required=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: Int('sizelimit?', autofill=False, minvalue=0)
 option: Str('sn', attribute=True, autofill=False, cli_name='last', multivalue=False, query=True, required=False)
diff --git a/VERSION b/VERSION
index c207558..40aa3a6 100644
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
-# Last change: tbordaz - Add stageuser_find, stageuser_mod, stageuser_del, stageuser_show
+# Last change: jcholast - User life cycle: provide preserved user virtual attribute
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index 54d47bb..34c503e 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -333,6 +333,11 @@ class user(baseuser):
             label=_('Account disabled'),
+        Bool('preserved?',
+            label=_('Preserved deleted user'),
+            default=False,
+            flags=['virtual_attribute', 'no_create', 'no_update'],
+        ),
     def get_dn(self, *keys, **options):
@@ -369,6 +374,11 @@ class user(baseuser):
         return super(user, self).normalize_manager(manager, self.active_container_dn)
+    def get_preserved_attribute(self, entry):
+        delete_container_dn = DN(self.delete_container_dn, api.env.basedn)
+        if entry.dn.endswith(delete_container_dn):
+            entry['preserved'] = True
 class user_add(baseuser_add):
@@ -540,6 +550,7 @@ class user_add(baseuser_add):
         self.obj.get_password_attributes(ldap, dn, entry_attrs)
         convert_sshpubkey_post(ldap, dn, entry_attrs)
         radius_dn2pk(self.api, entry_attrs)
+        self.obj.get_preserved_attribute(entry_attrs)
         return dn
@@ -665,6 +676,7 @@ class user_mod(baseuser_mod):
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         self.post_common_callback(ldap, dn, entry_attrs, **options)
+        self.obj.get_preserved_attribute(entry_attrs)
         return dn
@@ -675,56 +687,56 @@ class user_find(baseuser_find):
     member_attributes = ['memberof']
     has_output_params = baseuser_find.has_output_params + user_output_params
+    msg_summary = ngettext(
+        '%(count)d user matched', '%(count)d users matched', 0
+    )
     takes_options = LDAPSearch.takes_options + (
             doc=_('Display user record for current Kerberos principal'),
-        Flag('preserved?',
-            doc=_('Display preserved deleted user'),
-            cli_name='preserved',
-            default=False,
-        ),
-    def execute(self, *args, **options):
-        if self.original_msg_summary:
-            object.__setattr__(self, 'msg_summary', self.original_msg_summary)
-        newoptions = {}
-        self.common_enhance_options(newoptions, **options)
-        options.update(newoptions)
-        for arg in args:
-            self.log.debug("user-find- exec arg %r" % (arg))
-        if options['preserved']:
-            self.obj.container_dn = baseuser.delete_container_dn
-            self.msg_summary = ngettext('%(count)d (delete) user matched', '%(count)d (delete) users matched', 0)
-            ret = super(user_find, self).execute(self, *args, **options)
-            self.obj.container_dn = baseuser.active_container_dn
-            return ret
-        else:
-            return super(user_find, self).execute(self, *args, **options)
     def pre_callback(self, ldap, filter, attrs_list, base_dn, scope, *keys, **options):
         assert isinstance(base_dn, DN)
         if options.get('whoami'):
             return ("(&(objectclass=posixaccount)(krbprincipalname=%s))"%\
                         getattr(context, 'principal'), base_dn, scope)
+        newoptions = {}
+        self.common_enhance_options(newoptions, **options)
+        options.update(newoptions)
+        preserved = options.get('preserved', False)
+        if preserved is None:
+            base_dn = self.api.env.basedn
+            scope = ldap.SCOPE_SUBTREE
+        elif preserved:
+            base_dn = DN(self.obj.delete_container_dn, self.api.env.basedn)
+        else:
+            base_dn = DN(self.obj.active_container_dn, self.api.env.basedn)
         return (filter, base_dn, scope)
     def post_callback(self, ldap, entries, truncated, *args, **options):
         if options.get('pkey_only', False):
             return truncated
+        if options.get('preserved', False) is None:
+            base_dns = (
+                DN(self.obj.active_container_dn, self.api.env.basedn),
+                DN(self.obj.delete_container_dn, self.api.env.basedn),
+            )
+            entries[:] = [e for e in entries
+                          if any(e.dn.endswith(bd) for bd in base_dns)]
         self.post_common_callback(ldap, entries, lockout=False, **options)
-        return truncated
+        for entry in entries:
+            self.obj.get_preserved_attribute(entry)
-    msg_summary = ngettext(
-        '%(count)d user matched', '%(count)d users matched', 0
-    )
-    original_msg_summary = msg_summary
+        return truncated
@@ -736,6 +748,7 @@ class user_show(baseuser_show):
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         self.post_common_callback(ldap, dn, entry_attrs, **options)
+        self.obj.get_preserved_attribute(entry_attrs)
         return dn
@@ -944,6 +957,7 @@ class user_status(LDAPQuery):
                 if 'nsaccountlock' in entry:
                     disabled = entry['nsaccountlock']
+                self.obj.get_preserved_attribute(entry)
                 count += 1
             except errors.NotFound:

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

Reply via email to