Skip to content
Commits on Source (21)
...@@ -133,6 +133,22 @@ Example playbook to enable a zone: ...@@ -133,6 +133,22 @@ Example playbook to enable a zone:
state: enabled state: enabled
``` ```
Example playbook to allow per-zone privilege delegation:
``` yaml
---
- name: Playbook to enable per-zone privilege delegation
hosts: ipaserver
become: true
tasks:
- name: Enable privilege delegation.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
permission: true
```
Example playbook to remove a zone: Example playbook to remove a zone:
```yaml ```yaml
...@@ -223,6 +239,7 @@ Variable | Description | Required ...@@ -223,6 +239,7 @@ Variable | Description | Required
`ttl`| Time to live for records at zone apex | no `ttl`| Time to live for records at zone apex | no
`default_ttl`| Time to live for records without explicit TTL definition | no `default_ttl`| Time to live for records without explicit TTL definition | no
`nsec3param_rec`| NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt | no `nsec3param_rec`| NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt | no
`permission` \| `managedby` | Set per-zone access delegation permission. | no
`skip_overlap_check`| Force DNS zone creation even if it will overlap with an existing zone | no `skip_overlap_check`| Force DNS zone creation even if it will overlap with an existing zone | no
`skip_nameserver_check` | Force DNS zone creation even if nameserver is not resolvable | no `skip_nameserver_check` | Force DNS zone creation even if nameserver is not resolvable | no
...@@ -238,4 +255,6 @@ Variable | Description | Returned When ...@@ -238,4 +255,6 @@ Variable | Description | Returned When
Authors Authors
======= =======
Sergio Oliveira Campos - Sergio Oliveira Campos
- Thomas Woerner
- Rafael Jeffman
...@@ -130,6 +130,45 @@ And ensure the presence of the groups with this example playbook: ...@@ -130,6 +130,45 @@ And ensure the presence of the groups with this example playbook:
groups: "{{ groups }}" groups: "{{ groups }}"
``` ```
Example playbook to rename a group:
```yaml
---
- name: Playbook to rename a single group
hosts: ipaserver
become: false
gather_facts: false
tasks:
- name: Rename group appops to webops
ipagroup:
ipaadmin_password: SomeADMINpassword
name: appops
rename: webops
state: renamed
```
Several groups can also be renamed with a single task, as in the example playbook:
```yaml
---
- name: Playbook to rename multiple groups
hosts: ipaserver
become: false
gather_facts: false
tasks:
- name Rename group1 to newgroup1 and group2 to newgroup2
ipagroup:
ipaadmin_password: SomeADMINpassword
groups:
- name: group1
rename: newgroup1
- name: group2
rename: newgroup2
state: renamed
```
Example playbook to add users to a group: Example playbook to add users to a group:
```yaml ```yaml
...@@ -262,11 +301,13 @@ Variable | Description | Required ...@@ -262,11 +301,13 @@ Variable | Description | Required
`membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no `membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`externalmember` \| `ipaexternalmember` \| `external_member`| List of members of a trusted domain in DOM\\name or name@domain form. | no `externalmember` \| `ipaexternalmember` \| `external_member`| List of members of a trusted domain in DOM\\name or name@domain form. | no
`idoverrideuser` | List of user ID overrides to manage. Only usable with IPA versions 4.8.7 and up.| no `idoverrideuser` | List of user ID overrides to manage. Only usable with IPA versions 4.8.7 and up.| no
`rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
`action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no `action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes `state` | The state to ensure. It can be one of `present`, `absent` or `renamed`, default: `present`. | yes
Authors Authors
======= =======
Thomas Woerner - Thomas Woerner
- Rafael Jeffman
...@@ -93,6 +93,26 @@ Example playbook to make sure sudocmds are not present in Sudo Rule: ...@@ -93,6 +93,26 @@ Example playbook to make sure sudocmds are not present in Sudo Rule:
state: absent state: absent
``` ```
Example playbook to ensure a Group of RunAs User is present in sudo rule:
```yaml
---
- name: Playbook to manage sudorule member
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member
```
Example playbook to make sure Sudo Rule is absent: Example playbook to make sure Sudo Rule is absent:
```yaml ```yaml
......
...@@ -279,7 +279,6 @@ Example playbook to disable a user: ...@@ -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. This can also be done as an alternative with the `users` variable containing only names.
Example playbook to enable users: Example playbook to enable users:
```yaml ```yaml
...@@ -298,6 +297,22 @@ Example playbook to enable users: ...@@ -298,6 +297,22 @@ Example playbook to enable users:
This can also be done as an alternative with the `users` variable containing only names. 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: Example playbook to unlock users:
...@@ -401,7 +416,7 @@ Variable | Description | Required ...@@ -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 `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 `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 `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
...@@ -458,10 +473,10 @@ Variable | Description | Required ...@@ -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_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_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 `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 `nomembers` | Suppress processing of membership attributes. (bool) | no
Return Values Return Values
============= =============
...@@ -477,5 +492,5 @@ Variable | Description | Returned When ...@@ -477,5 +492,5 @@ Variable | Description | Returned When
Authors Authors
======= =======
Thomas Woerner - Thomas Woerner
Rafael Jeffman - Rafael Jeffman
---
- name: Certificate manage example - name: Certificate manage example
hosts: ipaserver hosts: ipaserver
become: false become: false
......
---
- name: Playbook to manage sudorule member
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Ensure sudorule 'runasuser' do not have 'ipasuers' group as runas users.
ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member
state: absent
---
- name: Playbook to manage sudorule member
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member
...@@ -470,12 +470,11 @@ def _afm_convert(value): ...@@ -470,12 +470,11 @@ def _afm_convert(value):
return value return value
def module_params_get(module, name, allow_empty_string=False): def module_params_get(module, name, allow_empty_list_item=False):
value = _afm_convert(module.params.get(name)) value = _afm_convert(module.params.get(name))
# Fail on empty strings in the list or if allow_empty_string is True # Fail on empty strings in the list or if allow_empty_list_item is True
# if there is another entry in the list together with the empty # if there is another entry in the list together with the empty string.
# string.
# Due to an issue in Ansible it is possible to use the empty string # Due to an issue in Ansible it is possible to use the empty string
# "" for lists with choices, even if the empty list is not part of # "" for lists with choices, even if the empty list is not part of
# the choices. # the choices.
...@@ -483,7 +482,7 @@ def module_params_get(module, name, allow_empty_string=False): ...@@ -483,7 +482,7 @@ def module_params_get(module, name, allow_empty_string=False):
if isinstance(value, list): if isinstance(value, list):
for val in value: for val in value:
if isinstance(val, (str, unicode)) and not val: if isinstance(val, (str, unicode)) and not val:
if not allow_empty_string: if not allow_empty_list_item:
module.fail_json( module.fail_json(
msg="Parameter '%s' contains an empty string" % msg="Parameter '%s' contains an empty string" %
name) name)
...@@ -495,8 +494,8 @@ def module_params_get(module, name, allow_empty_string=False): ...@@ -495,8 +494,8 @@ def module_params_get(module, name, allow_empty_string=False):
return value return value
def module_params_get_lowercase(module, name, allow_empty_string=False): def module_params_get_lowercase(module, name, allow_empty_list_item=False):
value = module_params_get(module, name, allow_empty_string) value = module_params_get(module, name, allow_empty_list_item)
if isinstance(value, list): if isinstance(value, list):
value = [v.lower() for v in value] value = [v.lower() for v in value]
if isinstance(value, (str, unicode)): if isinstance(value, (str, unicode)):
...@@ -504,6 +503,48 @@ def module_params_get_lowercase(module, name, allow_empty_string=False): ...@@ -504,6 +503,48 @@ def module_params_get_lowercase(module, name, allow_empty_string=False):
return value return value
def module_params_get_with_type_cast(
module, name, datatype, allow_empty=False
):
"""
Retrieve value set for module parameter as a specific data type.
Parameters
----------
module: AnsibleModule
The module from where to get the parameter value from.
name: string
The name of the parameter to retrieve.
datatype: type
The type to convert the value to, if value is not empty.
allow_empty: bool
Allow an empty string for non list parameters or a list
containing (only) an empty string item. This is used for
resetting parameters to the default value.
"""
value = module_params_get(module, name, allow_empty)
if not allow_empty and value == "":
module.fail_json(
msg="Argument '%s' must not be an empty string" % (name,)
)
if value is not None and value != "":
try:
if datatype is bool:
# We let Ansible handle bool values
value = boolean(value)
else:
value = datatype(value)
except ValueError:
module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, name)
)
except TypeError as terr:
# If Ansible fails to parse a boolean, it will raise TypeError
module.fail_json(msg="Param '%s': %s" % (name, str(terr)))
return value
def api_get_domain(): def api_get_domain():
return api.env.domain return api.env.domain
...@@ -1051,7 +1092,7 @@ class IPAAnsibleModule(AnsibleModule): ...@@ -1051,7 +1092,7 @@ class IPAAnsibleModule(AnsibleModule):
finally: finally:
temp_kdestroy(ccache_dir, ccache_name) temp_kdestroy(ccache_dir, ccache_name)
def params_get(self, name, allow_empty_string=False): def params_get(self, name, allow_empty_list_item=False):
""" """
Retrieve value set for module parameter. Retrieve value set for module parameter.
...@@ -1059,13 +1100,13 @@ class IPAAnsibleModule(AnsibleModule): ...@@ -1059,13 +1100,13 @@ class IPAAnsibleModule(AnsibleModule):
---------- ----------
name: string name: string
The name of the parameter to retrieve. The name of the parameter to retrieve.
allow_empty_string: bool allow_empty_list_item: bool
The parameter allowes to have empty strings in a list The parameter allowes to have empty strings in a list
""" """
return module_params_get(self, name, allow_empty_string) return module_params_get(self, name, allow_empty_list_item)
def params_get_lowercase(self, name, allow_empty_string=False): def params_get_lowercase(self, name, allow_empty_list_item=False):
""" """
Retrieve value set for module parameter as lowercase, if not None. Retrieve value set for module parameter as lowercase, if not None.
...@@ -1073,11 +1114,34 @@ class IPAAnsibleModule(AnsibleModule): ...@@ -1073,11 +1114,34 @@ class IPAAnsibleModule(AnsibleModule):
---------- ----------
name: string name: string
The name of the parameter to retrieve. The name of the parameter to retrieve.
allow_empty_string: bool allow_empty_list_item: bool
The parameter allowes to have empty strings in a list The parameter allowes to have empty strings in a list
""" """
return module_params_get_lowercase(self, name, allow_empty_string) return module_params_get_lowercase(self, name, allow_empty_list_item)
def params_get_with_type_cast(
self, name, datatype, allow_empty=True
):
"""
Retrieve value set for module parameter as a specific data type.
Parameters
----------
name: string
The name of the parameter to retrieve.
datatype: type
The type to convert the value to, if not empty.
datatype: type
The type to convert the value to, if value is not empty.
allow_empty: bool
Allow an empty string for non list parameters or a list
containing (only) an empty string item. This is used for
resetting parameters to the default value.
"""
return module_params_get_with_type_cast(
self, name, datatype, allow_empty)
def params_fail_used_invalid(self, invalid_params, state, action=None): def params_fail_used_invalid(self, invalid_params, state, action=None):
""" """
......
...@@ -470,13 +470,13 @@ def main(): ...@@ -470,13 +470,13 @@ def main():
"netbios_name": "netbios_name", "netbios_name": "netbios_name",
"add_sids": "add_sids", "add_sids": "add_sids",
} }
allow_empty_string = ["pac_type", "user_auth_type", "configstring"]
reverse_field_map = {v: k for k, v in field_map.items()} reverse_field_map = {v: k for k, v in field_map.items()}
allow_empty_list_item = ["pac_type", "user_auth_type", "configstring"]
params = {} params = {}
for x in field_map: for x in field_map:
val = ansible_module.params_get( val = ansible_module.params_get(
x, allow_empty_string=x in allow_empty_string) x, allow_empty_list_item=x in allow_empty_list_item)
if val is not None: if val is not None:
params[field_map.get(x, x)] = val params[field_map.get(x, x)] = val
......
...@@ -142,6 +142,11 @@ options: ...@@ -142,6 +142,11 @@ options:
salt. salt.
required: false required: false
type: str type: str
permission:
description: Set per-zone access delegation permission.
required: false
type: bool
aliases: ["managedby"]
skip_overlap_check: skip_overlap_check:
description: | description: |
Force DNS zone creation even if it will overlap with an existing zone Force DNS zone creation even if it will overlap with an existing zone
...@@ -154,6 +159,7 @@ options: ...@@ -154,6 +159,7 @@ options:
author: author:
- Sergio Oliveira Campos (@seocam) - Sergio Oliveira Campos (@seocam)
- Thomas Woerner (@t-woerner) - Thomas Woerner (@t-woerner)
- Rafael Jeffman (@rjeffman)
""" # noqa: E501 """ # noqa: E501
EXAMPLES = """ EXAMPLES = """
...@@ -253,6 +259,9 @@ class DNSZoneModule(IPAAnsibleModule): ...@@ -253,6 +259,9 @@ class DNSZoneModule(IPAAnsibleModule):
"idnsallowdynupdate": "dynamic_update", "idnsallowdynupdate": "dynamic_update",
"idnssecinlinesigning": "dnssec", "idnssecinlinesigning": "dnssec",
"idnsupdatepolicy": "update_policy", "idnsupdatepolicy": "update_policy",
# FreeIPA uses 'managedby' for dnszone and dnsforwardzone
# to manage 'permissions'.
"managedby": "permission",
# Mapping by method # Mapping by method
"idnsforwarders": self.get_ipa_idnsforwarders, "idnsforwarders": self.get_ipa_idnsforwarders,
"idnsallowtransfer": self.get_ipa_idnsallowtransfer, "idnsallowtransfer": self.get_ipa_idnsallowtransfer,
...@@ -434,7 +443,7 @@ class DNSZoneModule(IPAAnsibleModule): ...@@ -434,7 +443,7 @@ class DNSZoneModule(IPAAnsibleModule):
is_zone_active = False is_zone_active = False
else: else:
zone = response["result"] zone = response["result"]
# FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean vaalues. # FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean values.
# See: https://github.com/freeipa/freeipa/pull/6294 # See: https://github.com/freeipa/freeipa/pull/6294
is_zone_active = ( is_zone_active = (
str(zone.get("idnszoneactive")[0]).upper() == "TRUE" str(zone.get("idnszoneactive")[0]).upper() == "TRUE"
...@@ -462,18 +471,24 @@ class DNSZoneModule(IPAAnsibleModule): ...@@ -462,18 +471,24 @@ class DNSZoneModule(IPAAnsibleModule):
self.fail_json( self.fail_json(
msg="Either `name` or `name_from_ip` must be provided." msg="Either `name` or `name_from_ip` must be provided."
) )
# check invalid parameters
invalid = []
if self.ipa_params.state != "present": if self.ipa_params.state != "present":
invalid = ["name_from_ip"] invalid .extend(["name_from_ip"])
if self.ipa_params.state == "absent":
self.params_fail_used_invalid(invalid, self.ipa_params.state) invalid.extend(["permission"])
self.params_fail_used_invalid(invalid, self.ipa_params.state)
def define_ipa_commands(self): def define_ipa_commands(self):
for zone_name in self.get_zone_names(): for zone_name in self.get_zone_names():
# Look for existing zone in IPA # Look for existing zone in IPA
zone, is_zone_active = self.get_zone(zone_name) zone, is_zone_active = self.get_zone(zone_name)
args = self.ipa_params.get_ipa_command_args(zone=zone)
if self.ipa_params.state in ["present", "enabled", "disabled"]: if self.ipa_params.state in ["present", "enabled", "disabled"]:
args = self.ipa_params.get_ipa_command_args(zone=zone)
# We'll handle "managedby" after dnszone add/mod.
args.pop("managedby", None)
if not zone: if not zone:
# Since the zone doesn't exist we just create it # Since the zone doesn't exist we just create it
# with given args # with given args
...@@ -487,6 +502,16 @@ class DNSZoneModule(IPAAnsibleModule): ...@@ -487,6 +502,16 @@ class DNSZoneModule(IPAAnsibleModule):
if not compare_args_ipa(self, args, zone): if not compare_args_ipa(self, args, zone):
self.commands.append((zone_name, "dnszone_mod", args)) self.commands.append((zone_name, "dnszone_mod", args))
# Permissions must be set on existing zones.
if self.ipa_params.permission is not None:
is_managed = zone.get("managedby")
if self.ipa_params.permission and not is_managed:
self.commands.append(
(zone_name, "dnszone_add_permission", {}))
if not self.ipa_params.permission and is_managed:
self.commands.append(
(zone_name, "dnszone_remove_permission", {}))
if self.ipa_params.state == "enabled" and not is_zone_active: if self.ipa_params.state == "enabled" and not is_zone_active:
self.commands.append((zone_name, "dnszone_enable", {})) self.commands.append((zone_name, "dnszone_enable", {}))
...@@ -555,6 +580,8 @@ def get_argument_spec(): ...@@ -555,6 +580,8 @@ def get_argument_spec():
ttl=dict(type="int", required=False, default=None), ttl=dict(type="int", required=False, default=None),
default_ttl=dict(type="int", required=False, default=None), default_ttl=dict(type="int", required=False, default=None),
nsec3param_rec=dict(type="str", required=False, default=None), nsec3param_rec=dict(type="str", required=False, default=None),
permission=dict(type="bool", required=False, default=None,
aliases=["managedby"]),
skip_nameserver_check=dict(type="bool", required=False, default=None), skip_nameserver_check=dict(type="bool", required=False, default=None),
skip_overlap_check=dict(type="bool", required=False, default=None), skip_overlap_check=dict(type="bool", required=False, default=None),
) )
......
...@@ -123,6 +123,11 @@ options: ...@@ -123,6 +123,11 @@ options:
required: false required: false
type: list type: list
elements: str elements: str
rename:
description: Rename the group object
required: false
type: str
aliases: ["new_name"]
description: description:
description: The group description description: The group description
type: str type: str
...@@ -198,11 +203,16 @@ options: ...@@ -198,11 +203,16 @@ options:
type: str type: str
default: group default: group
choices: ["member", "group"] choices: ["member", "group"]
rename:
description: Rename the group object
required: false
type: str
aliases: ["new_name"]
state: state:
description: State to ensure description: State to ensure
type: str type: str
default: present default: present
choices: ["present", "absent"] choices: ["present", "absent", "renamed"]
author: author:
- Thomas Woerner (@t-woerner) - Thomas Woerner (@t-woerner)
""" """
...@@ -267,6 +277,13 @@ EXAMPLES = """ ...@@ -267,6 +277,13 @@ EXAMPLES = """
group: group:
- group2 - group2
# Rename a group
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: oldname
rename: newestname
state: renamed
# Create a non-POSIX group # Create a non-POSIX group
- ipagroup: - ipagroup:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
...@@ -380,18 +397,20 @@ def gen_member_args(user, group, service, externalmember, idoverrideuser): ...@@ -380,18 +397,20 @@ def gen_member_args(user, group, service, externalmember, idoverrideuser):
def check_parameters(module, state, action): def check_parameters(module, state, action):
invalid = [] invalid = ["description", "gid", "posix", "nonposix", "external",
if state == "present": "nomembers"]
if action == "group":
if state == "present":
invalid = []
elif state == "absent":
invalid.extend(["user", "group", "service", "externalmember"])
if state == "renamed":
if action == "member": if action == "member":
invalid = ["description", "gid", "posix", "nonposix", "external", module.fail_json(
"nomembers"] msg="Action member can not be used with state: renamed.")
invalid.extend(["user", "group", "service", "externalmember"])
else: else:
invalid = ["description", "gid", "posix", "nonposix", "external", invalid.append("rename")
"nomembers"]
if action == "group":
invalid.extend(["user", "group", "service", "externalmember"])
module.params_fail_used_invalid(invalid, state, action) module.params_fail_used_invalid(invalid, state, action)
...@@ -448,7 +467,9 @@ def main(): ...@@ -448,7 +467,9 @@ def main():
aliases=[ aliases=[
"ipaexternalmember", "ipaexternalmember",
"external_member" "external_member"
]) ]),
rename=dict(type="str", required=False, default=None,
aliases=["new_name"]),
) )
ansible_module = IPAAnsibleModule( ansible_module = IPAAnsibleModule(
argument_spec=dict( argument_spec=dict(
...@@ -470,7 +491,7 @@ def main(): ...@@ -470,7 +491,7 @@ def main():
action=dict(type="str", default="group", action=dict(type="str", default="group",
choices=["member", "group"]), choices=["member", "group"]),
state=dict(type="str", default="present", state=dict(type="str", default="present",
choices=["present", "absent"]), choices=["present", "absent", "renamed"]),
# Add group specific parameters for simple use case # Add group specific parameters for simple use case
**group_spec **group_spec
...@@ -506,8 +527,10 @@ def main(): ...@@ -506,8 +527,10 @@ def main():
membermanager_user = ansible_module.params_get("membermanager_user") membermanager_user = ansible_module.params_get("membermanager_user")
membermanager_group = ansible_module.params_get("membermanager_group") membermanager_group = ansible_module.params_get("membermanager_group")
externalmember = ansible_module.params_get("externalmember") externalmember = ansible_module.params_get("externalmember")
# rename
rename = ansible_module.params_get("rename")
# state and action
action = ansible_module.params_get("action") action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state") state = ansible_module.params_get("state")
# Check parameters # Check parameters
...@@ -516,10 +539,11 @@ def main(): ...@@ -516,10 +539,11 @@ def main():
(groups is None or len(groups) < 1): (groups is None or len(groups) < 1):
ansible_module.fail_json(msg="At least one name or groups is required") ansible_module.fail_json(msg="At least one name or groups is required")
if state == "present": if state in ["present", "renamed"]:
if names is not None and len(names) != 1: if names is not None and len(names) != 1:
what = "renamed" if state == "renamed" else "added"
ansible_module.fail_json( ansible_module.fail_json(
msg="Only one group can be added at a time using 'name'.") msg="Only one group can be %s at a time using 'name'." % what)
check_parameters(ansible_module, state, action) check_parameters(ansible_module, state, action)
...@@ -633,6 +657,7 @@ def main(): ...@@ -633,6 +657,7 @@ def main():
membermanager_group = group_name.get("membermanager_group") membermanager_group = group_name.get("membermanager_group")
externalmember = group_name.get("externalmember") externalmember = group_name.get("externalmember")
nomembers = group_name.get("nomembers") nomembers = group_name.get("nomembers")
rename = group_name.get("rename")
check_parameters(ansible_module, state, action) check_parameters(ansible_module, state, action)
...@@ -794,6 +819,11 @@ def main(): ...@@ -794,6 +819,11 @@ def main():
membermanager_group, membermanager_group,
res_find.get("membermanager_group") res_find.get("membermanager_group")
) )
elif state == "renamed":
if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name)
elif rename != name:
commands.append([name, 'group_mod', {"rename": rename}])
else: else:
ansible_module.fail_json(msg="Unkown state '%s'" % state) ansible_module.fail_json(msg="Unkown state '%s'" % state)
......
...@@ -876,10 +876,11 @@ def main(): ...@@ -876,10 +876,11 @@ def main():
allow_retrieve_keytab_hostgroup = ansible_module.params_get( allow_retrieve_keytab_hostgroup = ansible_module.params_get(
"allow_retrieve_keytab_hostgroup") "allow_retrieve_keytab_hostgroup")
mac_address = ansible_module.params_get("mac_address") mac_address = ansible_module.params_get("mac_address")
sshpubkey = ansible_module.params_get("sshpubkey", sshpubkey = ansible_module.params_get(
allow_empty_string=True) "sshpubkey", allow_empty_list_item=True)
userclass = ansible_module.params_get("userclass") userclass = ansible_module.params_get("userclass")
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True) auth_ind = ansible_module.params_get(
"auth_ind", allow_empty_list_item=True)
requires_pre_auth = ansible_module.params_get("requires_pre_auth") requires_pre_auth = ansible_module.params_get("requires_pre_auth")
ok_as_delegate = ansible_module.params_get("ok_as_delegate") ok_as_delegate = ansible_module.params_get("ok_as_delegate")
ok_to_auth_as_delegate = ansible_module.params_get( ok_to_auth_as_delegate = ansible_module.params_get(
......
...@@ -243,7 +243,7 @@ def main(): ...@@ -243,7 +243,7 @@ def main():
# present # present
description = ansible_module.params_get("description") description = ansible_module.params_get("description")
name = ansible_module.params_get("name") name = ansible_module.params_get("name")
gid = ansible_module.params_get("gid") gid = ansible_module.params_get_with_type_cast("gid", int)
# runtime flags # runtime flags
fallback_to_ldap = ansible_module.params_get("fallback_to_ldap") fallback_to_ldap = ansible_module.params_get("fallback_to_ldap")
...@@ -271,19 +271,6 @@ def main(): ...@@ -271,19 +271,6 @@ def main():
ansible_module.params_fail_used_invalid(invalid, state) ansible_module.params_fail_used_invalid(invalid, state)
# Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
gid = int_or_empty_param(gid, "gid")
# Init # Init
changed = False changed = False
......
...@@ -439,9 +439,9 @@ def main(): ...@@ -439,9 +439,9 @@ def main():
# present # present
description = ansible_module.params_get("description") description = ansible_module.params_get("description")
name = ansible_module.params_get("name") name = ansible_module.params_get("name")
uid = ansible_module.params_get("uid") uid = ansible_module.params_get_with_type_cast("uid", int)
gecos = ansible_module.params_get("gecos") gecos = ansible_module.params_get("gecos")
gidnumber = ansible_module.params_get("gidnumber") gidnumber = ansible_module.params_get_with_type_cast("gidnumber", int)
homedir = ansible_module.params_get("homedir") homedir = ansible_module.params_get("homedir")
shell = ansible_module.params_get("shell") shell = ansible_module.params_get("shell")
sshpubkey = ansible_module.params_get("sshpubkey") sshpubkey = ansible_module.params_get("sshpubkey")
...@@ -479,20 +479,6 @@ def main(): ...@@ -479,20 +479,6 @@ def main():
ansible_module.params_fail_used_invalid(invalid, state, action) ansible_module.params_fail_used_invalid(invalid, state, action)
# Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
uid = int_or_empty_param(uid, "uid")
gidnumber = int_or_empty_param(gidnumber, "gidnumber")
if certificate is not None: if certificate is not None:
certificate = [cert.strip() for cert in certificate] certificate = [cert.strip() for cert in certificate]
......
...@@ -153,7 +153,7 @@ RETURN = """ ...@@ -153,7 +153,7 @@ RETURN = """
""" """
from ansible.module_utils.ansible_freeipa_module import \ from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, boolean IPAAnsibleModule, compare_args_ipa
def find_pwpolicy(module, name): def find_pwpolicy(module, name):
...@@ -294,20 +294,34 @@ def main(): ...@@ -294,20 +294,34 @@ def main():
names = ansible_module.params_get("name") names = ansible_module.params_get("name")
# present # present
maxlife = ansible_module.params_get("maxlife") maxlife = ansible_module.params_get_with_type_cast(
minlife = ansible_module.params_get("minlife") "maxlife", int, allow_empty=True)
history = ansible_module.params_get("history") minlife = ansible_module.params_get_with_type_cast(
minclasses = ansible_module.params_get("minclasses") "minlife", int, allow_empty=True)
minlength = ansible_module.params_get("minlength") history = ansible_module.params_get_with_type_cast(
priority = ansible_module.params_get("priority") "history", int, allow_empty=True)
maxfail = ansible_module.params_get("maxfail") minclasses = ansible_module.params_get_with_type_cast(
failinterval = ansible_module.params_get("failinterval") "minclasses", int, allow_empty=True)
lockouttime = ansible_module.params_get("lockouttime") minlength = ansible_module.params_get_with_type_cast(
maxrepeat = ansible_module.params_get("maxrepeat") "minlength", int, allow_empty=True)
maxsequence = ansible_module.params_get("maxsequence") priority = ansible_module.params_get_with_type_cast(
dictcheck = ansible_module.params_get("dictcheck") "priority", int, allow_empty=True)
usercheck = ansible_module.params_get("usercheck") maxfail = ansible_module.params_get_with_type_cast(
gracelimit = ansible_module.params_get("gracelimit") "maxfail", int, allow_empty=True)
failinterval = ansible_module.params_get_with_type_cast(
"failinterval", int, allow_empty=True)
lockouttime = ansible_module.params_get_with_type_cast(
"lockouttime", int, allow_empty=True)
maxrepeat = ansible_module.params_get_with_type_cast(
"maxrepeat", int, allow_empty=True)
maxsequence = ansible_module.params_get_with_type_cast(
"maxsequence", int, allow_empty=True)
dictcheck = ansible_module.params_get_with_type_cast(
"dictcheck", bool, allow_empty=True)
usercheck = ansible_module.params_get_with_type_cast(
"usercheck", bool, allow_empty=True)
gracelimit = ansible_module.params_get_with_type_cast(
"gracelimit", int, allow_empty=True)
# state # state
state = ansible_module.params_get("state") state = ansible_module.params_get("state")
...@@ -336,41 +350,6 @@ def main(): ...@@ -336,41 +350,6 @@ def main():
ansible_module.params_fail_used_invalid(invalid, state) ansible_module.params_fail_used_invalid(invalid, state)
# Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
maxlife = int_or_empty_param(maxlife, "maxlife")
minlife = int_or_empty_param(minlife, "minlife")
history = int_or_empty_param(history, "history")
minclasses = int_or_empty_param(minclasses, "minclasses")
minlength = int_or_empty_param(minlength, "minlength")
priority = int_or_empty_param(priority, "priority")
maxfail = int_or_empty_param(maxfail, "maxfail")
failinterval = int_or_empty_param(failinterval, "failinterval")
lockouttime = int_or_empty_param(lockouttime, "lockouttime")
maxrepeat = int_or_empty_param(maxrepeat, "maxrepeat")
maxsequence = int_or_empty_param(maxsequence, "maxsequence")
gracelimit = int_or_empty_param(gracelimit, "gracelimit")
def bool_or_empty_param(value, param): # pylint: disable=R1710
if value is None or value == "":
return value
try:
return boolean(value)
except TypeError as terr:
ansible_module.fail_json(msg="Param '%s': %s" % (param, str(terr)))
dictcheck = bool_or_empty_param(dictcheck, "dictcheck")
usercheck = bool_or_empty_param(usercheck, "usercheck")
# Ensure gracelimit has proper limit. # Ensure gracelimit has proper limit.
if gracelimit: if gracelimit:
if gracelimit < -1: if gracelimit < -1:
......
...@@ -607,8 +607,10 @@ def main(): ...@@ -607,8 +607,10 @@ def main():
# white space also. # white space also.
if certificate is not None: if certificate is not None:
certificate = [cert.strip() for cert in certificate] certificate = [cert.strip() for cert in certificate]
pac_type = ansible_module.params_get("pac_type", allow_empty_string=True) pac_type = ansible_module.params_get(
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True) "pac_type", allow_empty_list_item=True)
auth_ind = ansible_module.params_get(
"auth_ind", allow_empty_list_item=True)
skip_host_check = ansible_module.params_get("skip_host_check") skip_host_check = ansible_module.params_get("skip_host_check")
force = ansible_module.params_get("force") force = ansible_module.params_get("force")
requires_pre_auth = ansible_module.params_get("requires_pre_auth") requires_pre_auth = ansible_module.params_get("requires_pre_auth")
......
...@@ -138,6 +138,11 @@ options: ...@@ -138,6 +138,11 @@ options:
required: false required: false
type: list type: list
elements: str elements: str
runasuser_group:
description: List of groups for Sudo to execute as.
required: false
type: list
elements: str
runasgroup: runasgroup:
description: List of groups for Sudo to execute as. description: List of groups for Sudo to execute as.
required: false required: false
...@@ -214,6 +219,12 @@ EXAMPLES = """ ...@@ -214,6 +219,12 @@ EXAMPLES = """
hostmask: hostmask:
- 192.168.122.1/24 - 192.168.122.1/24
- 192.168.120.1/24 - 192.168.120.1/24
# Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
- ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member action: member
# Ensure Sudo Rule tesrule1 is absent # Ensure Sudo Rule tesrule1 is absent
...@@ -315,6 +326,8 @@ def main(): ...@@ -315,6 +326,8 @@ def main():
default=None), default=None),
runasgroup=dict(required=False, type="list", elements="str", runasgroup=dict(required=False, type="list", elements="str",
default=None), default=None),
runasuser_group=dict(required=False, type="list", elements="str",
default=None),
order=dict(type="int", required=False, aliases=['sudoorder']), order=dict(type="int", required=False, aliases=['sudoorder']),
sudooption=dict(required=False, type='list', elements="str", sudooption=dict(required=False, type='list', elements="str",
default=None, aliases=["options"]), default=None, aliases=["options"]),
...@@ -362,6 +375,7 @@ def main(): ...@@ -362,6 +375,7 @@ def main():
sudooption = ansible_module.params_get("sudooption") sudooption = ansible_module.params_get("sudooption")
order = ansible_module.params_get("order") order = ansible_module.params_get("order")
runasuser = ansible_module.params_get_lowercase("runasuser") runasuser = ansible_module.params_get_lowercase("runasuser")
runasuser_group = ansible_module.params_get_lowercase("runasuser_group")
runasgroup = ansible_module.params_get_lowercase("runasgroup") runasgroup = ansible_module.params_get_lowercase("runasgroup")
action = ansible_module.params_get("action") action = ansible_module.params_get("action")
...@@ -406,7 +420,8 @@ def main(): ...@@ -406,7 +420,8 @@ def main():
invalid.extend(["host", "hostgroup", "hostmask", "user", "group", invalid.extend(["host", "hostgroup", "hostmask", "user", "group",
"runasuser", "runasgroup", "allow_sudocmd", "runasuser", "runasgroup", "allow_sudocmd",
"allow_sudocmdgroup", "deny_sudocmd", "allow_sudocmdgroup", "deny_sudocmd",
"deny_sudocmdgroup", "sudooption"]) "deny_sudocmdgroup", "sudooption",
"runasuser_group"])
elif state in ["enabled", "disabled"]: elif state in ["enabled", "disabled"]:
if len(names) < 1: if len(names) < 1:
...@@ -420,7 +435,7 @@ def main(): ...@@ -420,7 +435,7 @@ def main():
"nomembers", "nomembers", "host", "hostgroup", "hostmask", "nomembers", "nomembers", "host", "hostgroup", "hostmask",
"user", "group", "allow_sudocmd", "allow_sudocmdgroup", "user", "group", "allow_sudocmd", "allow_sudocmdgroup",
"deny_sudocmd", "deny_sudocmdgroup", "runasuser", "deny_sudocmd", "deny_sudocmdgroup", "runasuser",
"runasgroup", "order", "sudooption"] "runasgroup", "order", "sudooption", "runasuser_group"]
else: else:
ansible_module.fail_json(msg="Invalid state '%s'" % state) ansible_module.fail_json(msg="Invalid state '%s'" % state)
...@@ -453,6 +468,7 @@ def main(): ...@@ -453,6 +468,7 @@ def main():
deny_cmdgroup_add, deny_cmdgroup_del = [], [] deny_cmdgroup_add, deny_cmdgroup_del = [], []
sudooption_add, sudooption_del = [], [] sudooption_add, sudooption_del = [], []
runasuser_add, runasuser_del = [], [] runasuser_add, runasuser_del = [], []
runasuser_group_add, runasuser_group_del = [], []
runasgroup_add, runasgroup_del = [], [] runasgroup_add, runasgroup_del = [], []
for name in names: for name in names:
...@@ -552,6 +568,12 @@ def main(): ...@@ -552,6 +568,12 @@ def main():
+ res_find.get('ipasudorunasextuser', []) + res_find.get('ipasudorunasextuser', [])
) )
) )
runasuser_group_add, runasuser_group_del = (
gen_add_del_lists(
runasuser_group,
res_find.get('ipasudorunas_group', [])
)
)
# runasgroup attribute can be used with both IPA and # runasgroup attribute can be used with both IPA and
# non-IPA (external) groups. IPA will handle the correct # non-IPA (external) groups. IPA will handle the correct
...@@ -623,6 +645,11 @@ def main(): ...@@ -623,6 +645,11 @@ def main():
(list(res_find.get('ipasudorunas_user', [])) (list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', []))) + list(res_find.get('ipasudorunasextuser', [])))
) )
if runasuser_group is not None:
runasuser_group_add = gen_add_list(
runasuser_group,
res_find.get('ipasudorunas_group', [])
)
# runasgroup attribute can be used with both IPA and # runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare # non-IPA (external) groups, so we need to compare
# the provided list against both users and external # the provided list against both users and external
...@@ -703,6 +730,11 @@ def main(): ...@@ -703,6 +730,11 @@ def main():
+ list(res_find.get('ipasudorunasextuser', [])) + list(res_find.get('ipasudorunasextuser', []))
) )
) )
if runasuser_group is not None:
runasuser_group_del = gen_intersection_list(
runasuser_group,
res_find.get('ipasudorunas_group', [])
)
# runasgroup attribute can be used with both IPA and # runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare # non-IPA (external) groups, so we need to compare
# the provided list against both groups and external # the provided list against both groups and external
...@@ -812,13 +844,19 @@ def main(): ...@@ -812,13 +844,19 @@ def main():
} }
]) ])
# Manage RunAS users # Manage RunAS users
if runasuser_add: if runasuser_add or runasuser_group_add:
commands.append([ # Can't use empty lists with command "sudorule_add_runasuser".
name, "sudorule_add_runasuser", {"user": runasuser_add} _args = {}
]) if runasuser_add:
if runasuser_del: _args["user"] = runasuser_add
if runasuser_group_add:
_args["group"] = runasuser_group_add
commands.append([name, "sudorule_add_runasuser", _args])
if runasuser_del or runasuser_group_del:
commands.append([ commands.append([
name, "sudorule_remove_runasuser", {"user": runasuser_del} name,
"sudorule_remove_runasuser",
{"user": runasuser_del, "group": runasuser_group_del}
]) ])
# Manage RunAS Groups # Manage RunAS Groups
......
...@@ -319,6 +319,11 @@ options: ...@@ -319,6 +319,11 @@ options:
description: Suppress processing of membership attributes description: Suppress processing of membership attributes
required: false required: false
type: bool type: bool
rename:
description: Rename the user object
required: false
type: str
aliases: ["new_name"]
required: false required: false
first: first:
description: The first name. Required if user does not exist. description: The first name. Required if user does not exist.
...@@ -586,6 +591,11 @@ options: ...@@ -586,6 +591,11 @@ options:
description: Suppress processing of membership attributes description: Suppress processing of membership attributes
required: false required: false
type: bool type: bool
rename:
description: Rename the user object
required: false
type: str
aliases: ["new_name"]
preserve: preserve:
description: Delete a user, keeping the entry available for future use description: Delete a user, keeping the entry available for future use
required: false required: false
...@@ -607,7 +617,8 @@ options: ...@@ -607,7 +617,8 @@ options:
default: present default: present
choices: ["present", "absent", choices: ["present", "absent",
"enabled", "disabled", "enabled", "disabled",
"unlocked", "undeleted"] "unlocked", "undeleted",
"renamed"]
author: author:
- Thomas Woerner (@t-woerner) - Thomas Woerner (@t-woerner)
""" """
...@@ -694,6 +705,13 @@ EXAMPLES = """ ...@@ -694,6 +705,13 @@ EXAMPLES = """
smb_profile_path: \\\\server\\profiles\\some_profile smb_profile_path: \\\\server\\profiles\\some_profile
smb_home_dir: \\\\users\\home\\smbuser smb_home_dir: \\\\users\\home\\smbuser
smb_home_drive: "U:" smb_home_drive: "U:"
# Rename an existing user
- ipauser:
ipaadmin_password: SomeADMINpassword
name: someuser
rename: anotheruser
state: renamed
""" """
RETURN = """ RETURN = """
...@@ -857,7 +875,7 @@ def check_parameters( # pylint: disable=unused-argument ...@@ -857,7 +875,7 @@ def check_parameters( # pylint: disable=unused-argument
employeenumber, employeetype, preferredlanguage, certificate, employeenumber, employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve, update_password, certmapdata, noprivate, nomembers, preserve, update_password,
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive, 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": if state == "present" and action == "user":
invalid = ["preserve"] invalid = ["preserve"]
...@@ -885,6 +903,19 @@ def check_parameters( # pylint: disable=unused-argument ...@@ -885,6 +903,19 @@ def check_parameters( # pylint: disable=unused-argument
module.fail_json( module.fail_json(
msg="Preserve is only possible for state=absent") 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) module.params_fail_used_invalid(invalid, state, action)
if certmapdata is not None: if certmapdata is not None:
...@@ -1097,6 +1128,8 @@ def main(): ...@@ -1097,6 +1128,8 @@ def main():
idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']), idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
idp_user_id=dict(type="str", default=None, idp_user_id=dict(type="str", default=None,
aliases=['ipaidpconfiglink']), aliases=['ipaidpconfiglink']),
rename=dict(type="str", required=False, default=None,
aliases=["new_name"]),
) )
ansible_module = IPAAnsibleModule( ansible_module = IPAAnsibleModule(
...@@ -1128,7 +1161,7 @@ def main(): ...@@ -1128,7 +1161,7 @@ def main():
choices=["member", "user"]), choices=["member", "user"]),
state=dict(type="str", default="present", state=dict(type="str", default="present",
choices=["present", "absent", "enabled", "disabled", choices=["present", "absent", "enabled", "disabled",
"unlocked", "undeleted"]), "unlocked", "undeleted", "renamed"]),
# Add user specific parameters for simple use case # Add user specific parameters for simple use case
**user_spec **user_spec
...@@ -1185,9 +1218,9 @@ def main(): ...@@ -1185,9 +1218,9 @@ def main():
manager = ansible_module.params_get("manager") manager = ansible_module.params_get("manager")
carlicense = ansible_module.params_get("carlicense") carlicense = ansible_module.params_get("carlicense")
sshpubkey = ansible_module.params_get("sshpubkey", sshpubkey = ansible_module.params_get("sshpubkey",
allow_empty_string=True) allow_empty_list_item=True)
userauthtype = ansible_module.params_get("userauthtype", userauthtype = ansible_module.params_get("userauthtype",
allow_empty_string=True) allow_empty_list_item=True)
userclass = ansible_module.params_get("userclass") userclass = ansible_module.params_get("userclass")
radius = ansible_module.params_get("radius") radius = ansible_module.params_get("radius")
radiususer = ansible_module.params_get("radiususer") radiususer = ansible_module.params_get("radiususer")
...@@ -1209,6 +1242,8 @@ def main(): ...@@ -1209,6 +1242,8 @@ def main():
preserve = ansible_module.params_get("preserve") preserve = ansible_module.params_get("preserve")
# mod # mod
update_password = ansible_module.params_get("update_password") update_password = ansible_module.params_get("update_password")
# rename
rename = ansible_module.params_get("rename")
# general # general
action = ansible_module.params_get("action") action = ansible_module.params_get("action")
state = ansible_module.params_get("state") state = ansible_module.params_get("state")
...@@ -1219,27 +1254,30 @@ def main(): ...@@ -1219,27 +1254,30 @@ def main():
(users is None or len(users) < 1): (users is None or len(users) < 1):
ansible_module.fail_json(msg="One of name and users is required") 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: if names is not None and len(names) != 1:
act = "renamed" if state == "renamed" else "added"
ansible_module.fail_json( ansible_module.fail_json(
msg="Only one user can be added at a time using name.") msg="Only one user can be %s at a time using name." % (act))
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)
# Use users if names is None # Use users if names is None
if users is not None: if users is not None:
names = users 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 # Init
...@@ -1330,6 +1368,7 @@ def main(): ...@@ -1330,6 +1368,7 @@ def main():
smb_home_drive = user.get("smb_home_drive") smb_home_drive = user.get("smb_home_drive")
idp = user.get("idp") idp = user.get("idp")
idp_user_id = user.get("idp_user_id") idp_user_id = user.get("idp_user_id")
rename = user.get("rename")
certificate = user.get("certificate") certificate = user.get("certificate")
certmapdata = user.get("certmapdata") certmapdata = user.get("certmapdata")
noprivate = user.get("noprivate") noprivate = user.get("noprivate")
...@@ -1346,7 +1385,8 @@ def main(): ...@@ -1346,7 +1385,8 @@ def main():
employeetype, preferredlanguage, certificate, employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve, certmapdata, noprivate, nomembers, preserve,
update_password, smb_logon_script, smb_profile_path, 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) certmapdata = convert_certmapdata(certmapdata)
# Check API specific parameters # Check API specific parameters
...@@ -1449,6 +1489,10 @@ def main(): ...@@ -1449,6 +1489,10 @@ def main():
del args["userpassword"] del args["userpassword"]
if "random" in args: if "random" in args:
del args["random"] del args["random"]
# if using "random:false" password should not be
# generated.
if not args.get("random", True):
del args["random"]
if "noprivate" in args: if "noprivate" in args:
del args["noprivate"] del args["noprivate"]
...@@ -1733,6 +1777,12 @@ def main(): ...@@ -1733,6 +1777,12 @@ def main():
else: else:
raise ValueError("No user '%s'" % name) 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: else:
ansible_module.fail_json(msg="Unkown state '%s'" % state) ansible_module.fail_json(msg="Unkown state '%s'" % state)
......
...@@ -201,6 +201,7 @@ Variable | Description | Required ...@@ -201,6 +201,7 @@ Variable | Description | Required
`ipasssd_preserve_sssd` | The bool value defines if the old SSSD configuration will be preserved if it is not possible to merge it with a new one. `ipasssd_preserve_sssd` defaults to `no`. | no `ipasssd_preserve_sssd` | The bool value defines if the old SSSD configuration will be preserved if it is not possible to merge it with a new one. `ipasssd_preserve_sssd` defaults to `no`. | no
`ipaclient_request_cert` | The bool value defines if the certificate for the machine wil be requested. The certificate will be stored in /etc/ipa/nssdb under the nickname "Local IPA host". . `ipaclient_request_cert` defaults to `no`. The option is deprecated and will be removed in a future release. | no `ipaclient_request_cert` | The bool value defines if the certificate for the machine wil be requested. The certificate will be stored in /etc/ipa/nssdb under the nickname "Local IPA host". . `ipaclient_request_cert` defaults to `no`. The option is deprecated and will be removed in a future release. | no
`ipaclient_keytab` | The string value contains the path on the node of a backup host keytab from a previous enrollment. | no `ipaclient_keytab` | The string value contains the path on the node of a backup host keytab from a previous enrollment. | no
`ipaclient_automount_location` | Automount location | no
Server Variables Server Variables
......
...@@ -398,7 +398,7 @@ ...@@ -398,7 +398,7 @@
ipaclient_setup_automount: ipaclient_setup_automount:
servers: "{{ result_ipaclient_test.servers }}" servers: "{{ result_ipaclient_test.servers }}"
sssd: "{{ result_ipaclient_test.sssd }}" sssd: "{{ result_ipaclient_test.sssd }}"
automount_location: "{{ ipaautomount_location | default(omit) }}" automount_location: "{{ ipaclient_automount_location | default(omit) }}"
- name: Install - Configure firefox - name: Install - Configure firefox
ipaclient_setup_firefox: ipaclient_setup_firefox:
......