Skip to content

Commit

Permalink
[DRAFT]: Add support for state:fetched to user module
Browse files Browse the repository at this point in the history
  • Loading branch information
t-woerner committed Mar 14, 2022
1 parent 9981e5f commit cedfc2a
Show file tree
Hide file tree
Showing 3 changed files with 328 additions and 9 deletions.
72 changes: 72 additions & 0 deletions plugins/module_utils/ansible_freeipa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,78 @@ def exception_handler(module, ex, exit_args, one_name):

return changed

def execute_fetched(self, names, prefix, name_ipa_param,
fetch_param, fetch_command, fetch_param_mapping):
"""
Execute fetched state.
Parameters
----------
names: The main items to return
It names is not None and not an empty list then all items
found with "item_find" are returned, else the items in names.
prefix: The prefix for use with several main items
The prefix is "users" for the "user" module. It is used
if only the list of main items (example: users) is returned.
name_ipa_param: The IPA param name of the name parameter
This is for example "uid" that is used for the user name in
the user module.
fetch_param: The parameters to return
The parameters that should be returned. If fetch_params is
["all"], all parameters in ipa_pram_names will be returned.
fetch_param_mapping: IPA param mapping
This is the mapping of the default module paramter name to
IPA option name.
Example: "uid" for user name of the user commands.
fetch_command: The Fetch function
This is a module function that returns the structure(s) from
the show or find command.
"""

def store_params(exit_args, name, prefix, name_ipa_param, result,
params):
if params is None:
exit_args.setdefault(prefix, []).append(
result[name_ipa_param])
return
for field in params:
if fetch_param_mapping[field] is True:
ipa_field = field
else:
ipa_field = fetch_param_mapping[field]

if ipa_field in result:
value = result[ipa_field]
if name is None:
exit_args[field] = value
else:
exit_args.setdefault(name, {})[field] = value

# Create exit_args
exit_args = {}

if fetch_param == ["all"]:
fetch_param = fetch_param_mapping.keys()

if names and isinstance(names, list):
with_name = len(names) > 1
for name in names:
result = fetch_command(self, name)
if result:
store_params(exit_args, name if with_name else None,
prefix, name_ipa_param, result,
fetch_param)
else:
results = fetch_command(self, None)
if results is not None:
for result in results:
name = result[name_ipa_param]
store_params(exit_args, name, prefix, name_ipa_param,
result, fetch_param)

return exit_args

class FreeIPABaseModule(IPAAnsibleModule):
"""
Base class for FreeIPA Ansible modules.
Expand Down
157 changes: 148 additions & 9 deletions plugins/modules/ipauser.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@
default: "always"
choices: ["always", "on_create"]
required: false
fetch_param:
description: The fields to fetch with state=fetched
required: false
action:
description: Work on user or member level
default: "user"
Expand All @@ -393,7 +396,8 @@
default: present
choices: ["present", "absent",
"enabled", "disabled",
"unlocked", "undeleted"]
"unlocked", "undeleted",
"fetched"]
author:
- Thomas Woerner
"""
Expand Down Expand Up @@ -481,7 +485,7 @@
unicode = str


def find_user(module, name):
def user_show(module, name):
_args = {
"all": True,
}
Expand All @@ -501,6 +505,50 @@ def find_user(module, name):
return _result


def convert_result(res):
_res = {}
for key in res:
if key in ["manager", "krbprincipalname", "ipacertmapdata"]:
_res[key] = [to_text(x) for x in (res.get(key) or [])]
elif key == "usercertificate":
_res[key] = [encode_certificate(x) for x in (res.get(key) or [])]
elif isinstance(res[key], list) and len(res[key]) == 1:
# All single value parameters should not be lists
# This does not apply to manager, krbprincipalname,
# usercertificate and ipacertmapdata
_res[key] = to_text(res[key][0])
elif key in ["uidNumber", "gidNumber"]:
_res[key] = int(res[key])
else:
_res[key] = to_text(res[key])
return _res


def fetch_command(module, name, sizelimit=None, timelimit=None):
_args = {"all": True}

if sizelimit is not None:
_args["sizelimit"] = sizelimit
if timelimit is not None:
_args["timelimit"] = timelimit

try:
if name:
_args["uid"] = name
_result = module.ipa_command_no_name("user_find", _args).get("result")
if _result:
if name:
_result = convert_result(_result[0])
else:
_result = [convert_result(res)
for res in _result]

except ipalib_errors.NotFound:
return None
else:
return _result


def gen_args(first, last, fullname, displayname, initials, homedir, shell,
email, principalexpiration, passwordexpiration, password,
random, uid, gid, city, userstate, postalcode, phone, mobile,
Expand Down Expand Up @@ -618,6 +666,15 @@ def check_parameters( # pylint: disable=unused-argument
"certificate", "certmapdata",
])

if state == "fetched":
invalid.append("users")

if action == "member":
module.fail_json(
msg="Fetched is not possible with action=member")
else:
invalid.append("fetch_param")

if state != "absent" and preserve is not None:
module.fail_json(
msg="Preserve is only possible for state=absent")
Expand Down Expand Up @@ -742,6 +799,70 @@ def exception_handler(module, ex, errors, exit_args, one_name):
return False


fetch_param_base = [
"login", "first", "last", "shell", "principal", "uid", "gid", "disabled"
]


fetch_param_mapping = {
# password, randompassword and krbprincipalkey may not be in the returned
# information even in server context.

"objectclass": True,
"ipauniqueid": True,

"login": "uid",
"first": "givenname",
"last": "sn",
"fullname": "cn",
"displayname": True,
"initials": True,
"homedir": "homedirectory",
"shell": "loginshell",
"email": "mail",
"principalexpiration": "krbprincipalexpiration",
"passwordexpiration": "krbpasswordexpiration",
"uid": "uidnumber",
"gid": "gidnumber",
"city": "l",
"userstate": "st",
"postalcode": "postalcode",
"phone": "telephonenumber",
"mobile": "mobile",
"pager": "pager",
"fax": "facsimiletelephonenumber",
"orgunit": "ou",
"title": True,
"carlicense": True,
"sshpubkey": "ipasshpubkey",
"userauthtype": "ipauserauthtype",
"userclass": True,
"radius": "ipatokenradiusconfiglink",
"radiususer": "ipatokenradiususername",
"departmentnumber": True,
"employeenumber": True,
"employeetype": True,
"preferredlanguage": "preferredlanguage",
"manager": True,
"principal": "krbprincipalname",
"certificate": "usercertificate",
"certmapdata": "ipacertmapdata",

"gecos": True,
"krblastpwdchange": True,
"krblastadminunlock": True,
"krbextradata": True,
"krbticketflags": True,
"krbloginfailedcount": True,
"krblastsuccessfulauth": True,
"has_password": True,
"has_keytab": True,
"preserved": True,
"memberof_group": True,
"disabled": "nsaccountock"
}


def main():
user_spec = dict(
# present
Expand Down Expand Up @@ -833,18 +954,25 @@ def main():
update_password=dict(type='str', default=None, no_log=False,
choices=['always', 'on_create']),

# fetched
fetch_param=dict(type="list", default=None,
choices=["all"].extend(
fetch_param_mapping.keys()),
required=False),

# general
action=dict(type="str", default="user",
choices=["member", "user"]),
state=dict(type="str", default="present",
choices=["present", "absent", "enabled", "disabled",
"unlocked", "undeleted"]),
"unlocked", "undeleted", "fetched"]),

# Add user specific parameters for simple use case
**user_spec
),
mutually_exclusive=[["name", "users"]],
required_one_of=[["name", "users"]],
# Required one of [["name", "users"]] has been removed as there is
# an extra test below and it is not working with state=fetched
supports_check_mode=True,
)

Expand Down Expand Up @@ -909,15 +1037,19 @@ def main():
preserve = ansible_module.params_get("preserve")
# mod
update_password = ansible_module.params_get("update_password")
# fetched
fetch_param = ansible_module.params_get("fetch_param")

# general
action = ansible_module.params_get("action")
state = ansible_module.params_get("state")

# Check parameters

if (names is None or len(names) < 1) and \
(users is None or len(users) < 1):
ansible_module.fail_json(msg="One of name and users is required")
if state != "fetched":
if (names is None or len(names) < 1) and \
(users is None or len(users) < 1):
ansible_module.fail_json(msg="One of name and users is required")

if state == "present":
if names is not None and len(names) != 1:
Expand Down Expand Up @@ -965,6 +1097,13 @@ def main():
commands = []
user_set = set()

if state == "fetched":
exit_args = ansible_module.execute_fetched(
names, "users", "uid", fetch_param, fetch_command,
fetch_param_mapping)

ansible_module.exit_json(changed=False, user=exit_args)

for user in names:
if isinstance(user, dict):
name = user.get("name")
Expand Down Expand Up @@ -1074,7 +1213,7 @@ def main():
"your IPA version")

# Make sure user exists
res_find = find_user(ansible_module, name)
res_find = user_show(ansible_module, name)

# Create command
if state == "present":
Expand Down Expand Up @@ -1141,7 +1280,7 @@ def main():
principal_add, principal_del = gen_add_del_lists(
principal, res_find.get("krbprincipalname"))
# Principals are not returned as utf8 for IPA using
# python2 using user_find, therefore we need to
# python2 using user_show, therefore we need to
# convert the principals that we should remove.
principal_del = [to_text(x) for x in principal_del]

Expand Down
Loading

0 comments on commit cedfc2a

Please sign in to comment.