#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2017, Ansible Project # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = r''' --- module: ipa_group author: Thomas Krahn (@Nosmoht) short_description: Manage FreeIPA group description: - Add, modify and delete group within IPA server attributes: check_mode: support: full diff_mode: support: none options: append: description: - If C(true), add the listed I(user) and I(group) to the group members. - If C(false), only the listed I(user) and I(group) will be group members, removing any other members. default: false type: bool version_added: 4.0.0 cn: description: - Canonical name. - Can not be changed as it is the unique identifier. required: true aliases: ['name'] type: str description: description: - Description of the group. type: str external: description: - Allow adding external non-IPA members from trusted domains. type: bool gidnumber: description: - GID (use this option to set it manually). aliases: ['gid'] type: str group: description: - List of group names assigned to this group. - If I(append=false) and an empty list is passed all groups will be removed from this group. - Groups that are already assigned but not passed will be removed. - If I(append=true) the listed groups will be assigned without removing other groups. - If option is omitted assigned groups will not be checked or changed. type: list elements: str nonposix: description: - Create as a non-POSIX group. type: bool user: description: - List of user names assigned to this group. - If I(append=false) and an empty list is passed all users will be removed from this group. - Users that are already assigned but not passed will be removed. - If I(append=true) the listed users will be assigned without removing other users. - If option is omitted assigned users will not be checked or changed. type: list elements: str external_user: description: - List of external users assigned to this group. - Behaves identically to I(user) with respect to I(append) attribute. - List entries can be in C(DOMAIN\\username) or SID format. - Unless SIDs are provided, the module will always attempt to make changes even if the group already has all the users. This is because only SIDs are returned by IPA query. - I(external=true) is needed for this option to work. type: list elements: str version_added: 6.3.0 state: description: - State to ensure default: "present" choices: ["absent", "present"] type: str extends_documentation_fragment: - community.general.ipa.documentation - community.general.attributes ''' EXAMPLES = r''' - name: Ensure group is present community.general.ipa_group: name: oinstall gidnumber: '54321' state: present ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Ensure that groups sysops and appops are assigned to ops but no other group community.general.ipa_group: name: ops group: - sysops - appops ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Ensure that users linus and larry are assign to the group, but no other user community.general.ipa_group: name: sysops user: - linus - larry ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Ensure that new starter named john is member of the group, without removing other members community.general.ipa_group: name: developers user: - john append: true state: present ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Add external user to a group community.general.ipa_group: name: developers external: true append: true external_user: - S-1-5-21-123-1234-12345-63421 ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Add a user from MYDOMAIN community.general.ipa_group: name: developers external: true append: true external_user: - MYDOMAIN\\john ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret - name: Ensure group is absent community.general.ipa_group: name: sysops state: absent ipa_host: ipa.example.com ipa_user: admin ipa_pass: topsecret ''' RETURN = r''' group: description: Group as returned by IPA API returned: always type: dict ''' import traceback from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec from ansible.module_utils.common.text.converters import to_native class GroupIPAClient(IPAClient): def __init__(self, module, host, port, protocol): super(GroupIPAClient, self).__init__(module, host, port, protocol) def group_find(self, name): return self._post_json(method='group_find', name=None, item={'all': True, 'cn': name}) def group_add(self, name, item): return self._post_json(method='group_add', name=name, item=item) def group_mod(self, name, item): return self._post_json(method='group_mod', name=name, item=item) def group_del(self, name): return self._post_json(method='group_del', name=name) def group_add_member(self, name, item): return self._post_json(method='group_add_member', name=name, item=item) def group_add_member_group(self, name, item): return self.group_add_member(name=name, item={'group': item}) def group_add_member_user(self, name, item): return self.group_add_member(name=name, item={'user': item}) def group_add_member_externaluser(self, name, item): return self.group_add_member(name=name, item={'ipaexternalmember': item}) def group_remove_member(self, name, item): return self._post_json(method='group_remove_member', name=name, item=item) def group_remove_member_group(self, name, item): return self.group_remove_member(name=name, item={'group': item}) def group_remove_member_user(self, name, item): return self.group_remove_member(name=name, item={'user': item}) def group_remove_member_externaluser(self, name, item): return self.group_remove_member(name=name, item={'ipaexternalmember': item}) def get_group_dict(description=None, external=None, gid=None, nonposix=None): group = {} if description is not None: group['description'] = description if external is not None: group['external'] = external if gid is not None: group['gidnumber'] = gid if nonposix is not None: group['nonposix'] = nonposix return group def get_group_diff(client, ipa_group, module_group): data = [] # With group_add attribute nonposix is passed, whereas with group_mod only posix can be passed. if 'nonposix' in module_group: # Only non-posix groups can be changed to posix if not module_group['nonposix'] and ipa_group.get('nonposix'): module_group['posix'] = True del module_group['nonposix'] if 'external' in module_group: if module_group['external'] and 'ipaexternalgroup' in ipa_group.get('objectclass'): del module_group['external'] return client.get_diff(ipa_data=ipa_group, module_data=module_group) def ensure(module, client): state = module.params['state'] name = module.params['cn'] group = module.params['group'] user = module.params['user'] external = module.params['external'] external_user = module.params['external_user'] append = module.params['append'] module_group = get_group_dict(description=module.params['description'], external=external, gid=module.params['gidnumber'], nonposix=module.params['nonposix']) ipa_group = client.group_find(name=name) if (not (external or external_user is None)): module.fail_json("external_user can only be set if external = True") changed = False if state == 'present': if not ipa_group: changed = True if not module.check_mode: ipa_group = client.group_add(name, item=module_group) else: diff = get_group_diff(client, ipa_group, module_group) if len(diff) > 0: changed = True if not module.check_mode: data = {} for key in diff: data[key] = module_group.get(key) client.group_mod(name=name, item=data) if group is not None: changed = client.modify_if_diff(name, ipa_group.get('member_group', []), group, client.group_add_member_group, client.group_remove_member_group, append=append) or changed if user is not None: changed = client.modify_if_diff(name, ipa_group.get('member_user', []), user, client.group_add_member_user, client.group_remove_member_user, append=append) or changed if external_user is not None: changed = client.modify_if_diff(name, ipa_group.get('ipaexternalmember', []), external_user, client.group_add_member_externaluser, client.group_remove_member_externaluser, append=append) or changed else: if ipa_group: changed = True if not module.check_mode: client.group_del(name) return changed, client.group_find(name=name) def main(): argument_spec = ipa_argument_spec() argument_spec.update(cn=dict(type='str', required=True, aliases=['name']), description=dict(type='str'), external=dict(type='bool'), external_user=dict(type='list', elements='str'), gidnumber=dict(type='str', aliases=['gid']), group=dict(type='list', elements='str'), nonposix=dict(type='bool'), state=dict(type='str', default='present', choices=['present', 'absent']), user=dict(type='list', elements='str'), append=dict(type='bool', default=False)) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, ) client = GroupIPAClient(module=module, host=module.params['ipa_host'], port=module.params['ipa_port'], protocol=module.params['ipa_prot']) try: client.login(username=module.params['ipa_user'], password=module.params['ipa_pass']) changed, group = ensure(module, client) module.exit_json(changed=changed, group=group) except Exception as e: module.fail_json(msg=to_native(e), exception=traceback.format_exc()) if __name__ == '__main__': main()