From 0acf576d99405cdbc05be194afb2f42aadff3514 Mon Sep 17 00:00:00 2001
From: Thomas Woerner <twoerner@redhat.com>
Date: Mon, 8 Jun 2020 16:01:04 +0200
Subject: [PATCH] ipagroup: Add support for group membership management

A group membership manager is a user or a group that can add members to
a group or remove members from a group.

This is related to https://pagure.io/freeipa/issue/8114

New parameters have been added to the module:
- `membermanager_user`: List of member manager users assigned to this
  group. Only usable with IPA versions 4.8.4 and up.
- `membermanager_group`: List of member manager groups assigned to this
  group. Only usable with IPA versions 4.8.4 and up.

These parameters behave like member parameters.

A new test has been added:
- tests/group/test_group_membermanager.yml
---
 README-group.md                          |   2 +
 plugins/modules/ipagroup.py              |  88 +++++++++-
 tests/group/test_group_membermanager.yml | 194 +++++++++++++++++++++++
 3 files changed, 283 insertions(+), 1 deletion(-)
 create mode 100644 tests/group/test_group_membermanager.yml

diff --git a/README-group.md b/README-group.md
index 4a278560..4ffdb291 100644
--- a/README-group.md
+++ b/README-group.md
@@ -143,6 +143,8 @@ Variable | Description | Required
 `user` | List of user name strings assigned to this group. | no
 `group` | List of group name strings assigned to this group. | no
 `service` | List of service name strings assigned to this group. Only usable with IPA versions 4.7 and up. | no
+`membermanager_user` | List of member manager users 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
 `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
 
diff --git a/plugins/modules/ipagroup.py b/plugins/modules/ipagroup.py
index 1e6522c6..915bc499 100644
--- a/plugins/modules/ipagroup.py
+++ b/plugins/modules/ipagroup.py
@@ -75,6 +75,18 @@ options:
     - Only usable with IPA versions 4.7 and up.
     required: false
     type: list
+  membermanager_user:
+    description:
+    - List of member manager users assigned to this group.
+    - Only usable with IPA versions 4.8.4 and up.
+    required: false
+    type: list
+  membermanager_group:
+    description:
+    - List of member manager groups assigned to this group.
+    - Only usable with IPA versions 4.8.4 and up.
+    required: false
+    type: list
   action:
     description: Work on group or member level
     default: group
@@ -141,7 +153,7 @@ RETURN = """
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.ansible_freeipa_module import temp_kinit, \
     temp_kdestroy, valid_creds, api_connect, api_command, compare_args_ipa, \
-    api_check_param, module_params_get, gen_add_del_lists
+    api_check_param, module_params_get, gen_add_del_lists, api_check_command
 
 
 def find_group(module, name):
@@ -207,6 +219,9 @@ def main():
             user=dict(required=False, type='list', default=None),
             group=dict(required=False, type='list', default=None),
             service=dict(required=False, type='list', default=None),
+            membermanager_user=dict(required=False, type='list', default=None),
+            membermanager_group=dict(required=False, type='list',
+                                     default=None),
             action=dict(type="str", default="group",
                         choices=["member", "group"]),
             # state
@@ -237,6 +252,10 @@ def main():
     user = module_params_get(ansible_module, "user")
     group = module_params_get(ansible_module, "group")
     service = module_params_get(ansible_module, "service")
+    membermanager_user = module_params_get(ansible_module,
+                                           "membermanager_user")
+    membermanager_group = module_params_get(ansible_module,
+                                            "membermanager_group")
     action = module_params_get(ansible_module, "action")
     # state
     state = module_params_get(ansible_module, "state")
@@ -287,6 +306,14 @@ def main():
                 msg="Managing a service as part of a group is not supported "
                 "by your IPA version")
 
+        has_add_membermanager = api_check_command("group_add_member_manager")
+        if ((membermanager_user is not None or
+             membermanager_group is not None) and not has_add_membermanager):
+            ansible_module.fail_json(
+                msg="Managing a membermanager user or group is not supported "
+                "by your IPA version"
+            )
+
         commands = []
 
         for name in names:
@@ -360,6 +387,41 @@ def main():
                                                      "user": user_del,
                                                      "group": group_del,
                                                  }])
+
+                    membermanager_user_add, membermanager_user_del = \
+                        gen_add_del_lists(
+                            membermanager_user,
+                            res_find.get("membermanager_user")
+                        )
+
+                    membermanager_group_add, membermanager_group_del = \
+                        gen_add_del_lists(
+                            membermanager_group,
+                            res_find.get("membermanager_group")
+                        )
+
+                    if has_add_membermanager:
+                        # Add membermanager users and groups
+                        if len(membermanager_user_add) > 0 or \
+                           len(membermanager_group_add) > 0:
+                            commands.append(
+                                [name, "group_add_member_manager",
+                                 {
+                                     "user": membermanager_user_add,
+                                     "group": membermanager_group_add,
+                                 }]
+                            )
+                        # Remove member manager
+                        if len(membermanager_user_del) > 0 or \
+                           len(membermanager_group_del) > 0:
+                            commands.append(
+                                [name, "group_remove_member_manager",
+                                 {
+                                     "user": membermanager_user_del,
+                                     "group": membermanager_group_del,
+                                 }]
+                            )
+
                 elif action == "member":
                     if res_find is None:
                         ansible_module.fail_json(msg="No group '%s'" % name)
@@ -377,6 +439,18 @@ def main():
                                              "group": group,
                                          }])
 
+                    if has_add_membermanager:
+                        # Add membermanager users and groups
+                        if membermanager_user is not None or \
+                           membermanager_group is not None:
+                            commands.append(
+                                [name, "group_add_member_manager",
+                                 {
+                                     "user": membermanager_user,
+                                     "group": membermanager_group,
+                                 }]
+                            )
+
             elif state == "absent":
                 if action == "group":
                     if res_find is not None:
@@ -400,6 +474,18 @@ def main():
                                              "group": group,
                                          }])
 
+                    if has_add_membermanager:
+                        # Remove membermanager users and groups
+                        if membermanager_user is not None or \
+                           membermanager_group is not None:
+                            commands.append(
+                                [name, "group_remove_member_manager",
+                                 {
+                                     "user": membermanager_user,
+                                     "group": membermanager_group,
+                                 }]
+                            )
+
             else:
                 ansible_module.fail_json(msg="Unkown state '%s'" % state)
 
diff --git a/tests/group/test_group_membermanager.yml b/tests/group/test_group_membermanager.yml
new file mode 100644
index 00000000..1d38654f
--- /dev/null
+++ b/tests/group/test_group_membermanager.yml
@@ -0,0 +1,194 @@
+---
+- name: Test group membermanagers
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  - name: Ensure user manangeruser1 and manageruser2 is absent
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name: manageruser1,manageruser2
+      state: absent
+
+  - name: Ensure group testgroup, managergroup1 and managergroup2 are absent
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup,managergroup1,managergroup2
+      state: absent
+
+  - name: Ensure user manageruser1 and manageruser2 are present
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      users:
+      - name: manageruser1
+        first: manageruser1
+        last: Last1
+      - name: manageruser2
+        first: manageruser2
+        last: Last2
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure testgroup is present
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure managergroup1 is present
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: managergroup1
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure managergroup2 is present
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: managergroup2
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure membermanager user1 is present for testgroup
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure membermanager user1 is present for testgroup again
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure membermanager group1 is present for testgroup
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_group: managergroup1
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure membermanager group1 is present for testgroup again
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_group: managergroup1
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure membermanager user2 and group2 members are present for testgroup
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser2
+      membermanager_group: managergroup2
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure membermanager user2 and group2 members are present for testgroup again
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser2
+      membermanager_group: managergroup2
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure membermanager user and group members are present for testgroup again
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1,manageruser2
+      membermanager_group: managergroup1,managergroup2
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure membermanager user1 and group1 members are absent for testgroup
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1
+      membermanager_group: managergroup1
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure membermanager user1 and group1 members are absent for testgroup again
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1
+      membermanager_group: managergroup1
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure membermanager user1 and group1 members are present for testgroup
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1
+      membermanager_group: managergroup1
+      action: member
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure membermanager user1 and group1 members are present for testgroup again
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1
+      membermanager_group: managergroup1
+      action: member
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure membermanager user and group members are absent for testgroup
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1,manageruser2
+      membermanager_group: managergroup1,managergroup2
+      action: member
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure membermanager user and group members are absent for testgroup again
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup
+      membermanager_user: manageruser1,manageruser2
+      membermanager_group: managergroup1,managergroup2
+      action: member
+      state: absent
+    register: result
+    failed_when: result.changed
+
+  - name: Ensure user manangeruser1 and manageruser2 is absent
+    ipauser:
+      ipaadmin_password: SomeADMINpassword
+      name: manageruser1,manageruser2
+      state: absent
+    register: result
+    failed_when: not result.changed
+
+  - name: Ensure group testgroup, managergroup1 and managergroup2 are absent
+    ipagroup:
+      ipaadmin_password: SomeADMINpassword
+      name: testgroup,managergroup1,managergroup2
+      state: absent
+    register: result
+    failed_when: not result.changed
-- 
GitLab