Skip to content

Commit

Permalink
ipauser: Add support for renaming users
Browse files Browse the repository at this point in the history
FreeIPA suports renaming user objects with the CLI parameter "rename",
and this parameter was missing in ansible-freeipa ipauser module.

This patch adds support for a new state 'renamed' and the 'rename'
parameter.

Tests were updated to cope with the changes.

Related to RHBZ#2234379, RHBZ#2234380

Fixes freeipa#1103
  • Loading branch information
rjeffman committed Nov 14, 2023
1 parent f1a6f44 commit 3e86874
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 26 deletions.
25 changes: 20 additions & 5 deletions README-user.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ Example playbook to disable a user:

This can also be done as an alternative with the `users` variable containing only names.


Example playbook to enable users:

```yaml
Expand All @@ -298,6 +297,22 @@ Example playbook to enable users:

This can also be done as an alternative with the `users` variable containing only names.

Example playbook to rename users:

```yaml
---
- name: Playbook to handle users
hosts: ipaserver
become: true
tasks:
# Rename user pinky to reddy
- ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
rename: reddy
state: enabled
```

Example playbook to unlock users:

Expand Down Expand Up @@ -401,7 +416,7 @@ Variable | Description | Required
`update_password` | Set password for a user in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
`preserve` | Delete a user, keeping the entry available for future use. (bool) | no
`action` | Work on user or member level. It can be on of `member` or `user` and defaults to `user`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `renamed`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes



Expand Down Expand Up @@ -458,10 +473,10 @@ Variable | Description | Required
`smb_profile_path:` \| `ipantprofilepath` | SMB profile path, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_dir` \| `ipanthomedirectory` | SMB Home Directory, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_drive` \| `ipanthomedirectorydrive` | SMB Home Directory Drive, a single upercase letter (A-Z) followed by a colon (:), for example "U:". Requires FreeIPA version 4.8.0+. | no
`rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
`nomembers` | Suppress processing of membership attributes. (bool) | no



Return Values
=============

Expand All @@ -477,5 +492,5 @@ Variable | Description | Returned When
Authors
=======

Thomas Woerner
Rafael Jeffman
- Thomas Woerner
- Rafael Jeffman
78 changes: 59 additions & 19 deletions plugins/modules/ipauser.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,11 @@
description: Suppress processing of membership attributes
required: false
type: bool
rename:
description: Rename the user object
required: false
type: str
aliases: ["new_name"]
required: false
first:
description: The first name. Required if user does not exist.
Expand Down Expand Up @@ -607,7 +612,8 @@
default: present
choices: ["present", "absent",
"enabled", "disabled",
"unlocked", "undeleted"]
"unlocked", "undeleted",
"renamed"]
author:
- Thomas Woerner (@t-woerner)
"""
Expand Down Expand Up @@ -694,6 +700,13 @@
smb_profile_path: \\\\server\\profiles\\some_profile
smb_home_dir: \\\\users\\home\\smbuser
smb_home_drive: "U:"
# Rename an existing user
- ipauser:
ipaadmin_password: SomeADMINpassword
name: someuser
rename: anotheruser
state: renamed
"""

RETURN = """
Expand Down Expand Up @@ -857,7 +870,7 @@ def check_parameters( # pylint: disable=unused-argument
employeenumber, employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve, update_password,
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
idp, ipa_user_id,
idp, ipa_user_id, rename
):
if state == "present" and action == "user":
invalid = ["preserve"]
Expand Down Expand Up @@ -885,6 +898,19 @@ def check_parameters( # pylint: disable=unused-argument
module.fail_json(
msg="Preserve is only possible for state=absent")

if state != "renamed":
invalid.append("rename")
else:
invalid.extend([
"preserve", "principal", "manager", "certificate", "certmapdata",
])
if not rename:
module.fail_json(
msg="A value for attribute 'rename' must be provided.")
if action == "member":
module.fail_json(
msg="Action member can not be used with state: renamed.")

module.params_fail_used_invalid(invalid, state, action)

if certmapdata is not None:
Expand Down Expand Up @@ -1097,6 +1123,7 @@ def main():
idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
idp_user_id=dict(type="str", default=None,
aliases=['ipaidpconfiglink']),
rename=dict(type="str", required=False, aliases=["new_name"]),
)

ansible_module = IPAAnsibleModule(
Expand Down Expand Up @@ -1128,7 +1155,7 @@ def main():
choices=["member", "user"]),
state=dict(type="str", default="present",
choices=["present", "absent", "enabled", "disabled",
"unlocked", "undeleted"]),
"unlocked", "undeleted", "renamed"]),

# Add user specific parameters for simple use case
**user_spec
Expand Down Expand Up @@ -1209,6 +1236,8 @@ def main():
preserve = ansible_module.params_get("preserve")
# mod
update_password = ansible_module.params_get("update_password")
# rename
rename = ansible_module.params_get("rename")
# general
action = ansible_module.params_get("action")
state = ansible_module.params_get("state")
Expand All @@ -1219,27 +1248,30 @@ def main():
(users is None or len(users) < 1):
ansible_module.fail_json(msg="One of name and users is required")

if state == "present":
if state in ["present", "renamed"]:
if names is not None and len(names) != 1:
act = "renamed" if state == "renamed" else "added"
ansible_module.fail_json(
msg="Only one user can be added at a time using name.")

check_parameters(
ansible_module, state, action,
first, last, fullname, displayname, initials, homedir, gecos, shell,
email,
principal, principalexpiration, passwordexpiration, password, random,
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
preserve, update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id)
certmapdata = convert_certmapdata(certmapdata)
msg="Only one user can be %s at a time using name." % (act))

# Use users if names is None
if users is not None:
names = users
else:
check_parameters(
ansible_module, state, action,
first, last, fullname, displayname, initials, homedir, gecos,
shell, email,
principal, principalexpiration, passwordexpiration, password,
random,
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
preserve, update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
)
certmapdata = convert_certmapdata(certmapdata)

# Init

Expand Down Expand Up @@ -1330,6 +1362,7 @@ def main():
smb_home_drive = user.get("smb_home_drive")
idp = user.get("idp")
idp_user_id = user.get("idp_user_id")
rename = user.get("rename")
certificate = user.get("certificate")
certmapdata = user.get("certmapdata")
noprivate = user.get("noprivate")
Expand All @@ -1346,7 +1379,8 @@ def main():
employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve,
update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id)
smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
)
certmapdata = convert_certmapdata(certmapdata)

# Check API specific parameters
Expand Down Expand Up @@ -1733,6 +1767,12 @@ def main():
else:
raise ValueError("No user '%s'" % name)

elif state == "renamed":
if res_find is None:
ansible_module.fail_json(msg="No user '%s'" % name)
else:
if rename != name:
commands.append([name, 'user_mod', {"rename": rename}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)

Expand Down
42 changes: 41 additions & 1 deletion tests/user/test_user.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: manager1,manager2,manager3,pinky,pinky2,igagarin
name: manager1,manager2,manager3,pinky,pinky2,igagarin,reddy
state: absent

- name: User manager1 present
Expand Down Expand Up @@ -341,6 +341,46 @@
register: result
failed_when: result.changed or result.failed

- name: Rename user pinky to reddy
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: pinky
rename: reddy
state: renamed
register: result
failed_when: not result.changed or result.failed

- name: Rename user pinky to reddy, again
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: pinky
rename: reddy
state: renamed
register: result
failed_when: not result.failed or "No user 'pinky'" not in result.msg

- name: Rename user reddy to reddy
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: reddy
rename: reddy
state: renamed
register: result
failed_when: result.changed or result.failed

- name: Rename user reddy back to pinky
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: reddy
rename: pinky
state: renamed
register: result
failed_when: not result.changed or result.failed

- name: User pinky absent and preserved for future exclusion.
ipauser:
ipaadmin_password: SomeADMINpassword
Expand Down
44 changes: 43 additions & 1 deletion tests/user/test_users.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
gather_facts: false

tasks:
- name: Remove test users
ipauser:
ipaadmin_password: SomeADMINpassword
name: manager1,manager2,manager3,pinky,pinky2,mod1,mod2
state: absent

- name: Remove test users
ipauser:
ipaadmin_password: SomeADMINpassword
Expand Down Expand Up @@ -48,7 +54,7 @@
register: result
failed_when: not result.changed or result.failed

- name: Users user1..10 present
- name: Users user1..10 present, again
ipauser:
ipaadmin_password: SomeADMINpassword
users:
Expand Down Expand Up @@ -85,6 +91,42 @@
register: result
failed_when: result.changed or result.failed

- name: Rename users user1 and user2 to mod1 and mod1
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: user1
rename: mod1
- name: user2
rename: mod2
state: renamed
register: result
failed_when: not result.changed or result.failed

- name: Rename users mod1 and mod2 to the same name
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: mod1
rename: mod1
- name: mod2
rename: mod2
state: renamed
register: result
failed_when: result.changed or result.failed

- name: Rename users mod1 and mod2 back to user1 and user2
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: mod1
rename: user1
- name: mod2
rename: user2
state: renamed
register: result
failed_when: not result.changed or result.failed

# failed_when: not result.failed has been added as this test needs to
# fail because two users with the same name should be added in the same
# task.
Expand Down

0 comments on commit 3e86874

Please sign in to comment.