Skip to content

Commit

Permalink
Merge pull request freeipa#1174 from rjeffman/ipauser_rename
Browse files Browse the repository at this point in the history
ipauser: Add support for renaming users
  • Loading branch information
t-woerner authored Dec 20, 2023
2 parents 86e089f + 3eb86b2 commit 3a304e8
Show file tree
Hide file tree
Showing 4 changed files with 169 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
84 changes: 65 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 @@ -586,6 +591,11 @@
description: Suppress processing of membership attributes
required: false
type: bool
rename:
description: Rename the user object
required: false
type: str
aliases: ["new_name"]
preserve:
description: Delete a user, keeping the entry available for future use
required: false
Expand All @@ -607,7 +617,8 @@
default: present
choices: ["present", "absent",
"enabled", "disabled",
"unlocked", "undeleted"]
"unlocked", "undeleted",
"renamed"]
author:
- Thomas Woerner (@t-woerner)
"""
Expand Down Expand Up @@ -694,6 +705,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 +875,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 +903,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 +1128,8 @@ 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, default=None,
aliases=["new_name"]),
)

ansible_module = IPAAnsibleModule(
Expand Down Expand Up @@ -1128,7 +1161,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 +1242,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 +1254,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 +1368,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 +1385,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 @@ -1737,6 +1777,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 @@ -352,6 +352,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 3a304e8

Please sign in to comment.