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] [email protected]:
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'[email protected]',
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]
[email protected]: 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'[email protected]',
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.
From 5e8eaf24abd6e2ff16f0ea7d57374b0ba8405a87 Mon Sep 17 00:00:00 2001
From: Martin Basti <[email protected]>
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
---
API.txt | 9 ++-
VERSION | 4 +-
ipalib/plugins/baseuser.py | 99 +++++++++++++++++++++++++++++++
ipalib/plugins/stageuser.py | 139 ++++----------------------------------------
ipalib/plugins/user.py | 42 +++++++++++++
5 files changed, 163 insertions(+), 130 deletions(-)
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..d9d99bb53efe6311f854c3259b9b1dd9fe700531 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'),
cli_name='from_delete',
default=False,
@@ -270,62 +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'))
-
- 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 +284,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 +299,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