mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[PR #5814/7d3e6d1b backport][stable-6] keycloak_group: support keycloak subgroups (#6089)
keycloak_group: support keycloak subgroups (#5814)
* feat(module/keycloak_group): add support for ...
... handling subgroups
* added changelog fragment and fixing sanity ...
... test issues
* more sanity fixes
* fix missing version and review issues
* added missing licence header
* fix docu
* fix line beeing too long
* replaced suboptimal string type prefixing ...
... with better subdict based approach
* fix sanity issues
* more sanity fixing
* fixed more review issues
* fix argument list too long
* why is it failing? something wrong with the docu?
* is it this line then?
* undid group attribute removing, it does not ...
... belong into this PR
* fix version_added for parents parameter
---------
Co-authored-by: Mirko Wilhelmi <Mirko.Wilhelmi@sma.de>
(cherry picked from commit 7d3e6d1bb7
)
Co-authored-by: morco <thegreatwiper@web.de>
This commit is contained in:
parent
139fcdba88
commit
1462ed0b4a
7 changed files with 796 additions and 9 deletions
2
changelogs/fragments/5814-support-keycloak-subgroups.yml
Normal file
2
changelogs/fragments/5814-support-keycloak-subgroups.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- keycloak_group - add new optional module parameter ``parents`` to properly handle keycloak subgroups (https://github.com/ansible-collections/community.general/pull/5814).
|
|
@ -42,6 +42,7 @@ URL_CLIENTTEMPLATE = "{url}/admin/realms/{realm}/client-templates/{id}"
|
|||
URL_CLIENTTEMPLATES = "{url}/admin/realms/{realm}/client-templates"
|
||||
URL_GROUPS = "{url}/admin/realms/{realm}/groups"
|
||||
URL_GROUP = "{url}/admin/realms/{realm}/groups/{groupid}"
|
||||
URL_GROUP_CHILDREN = "{url}/admin/realms/{realm}/groups/{groupid}/children"
|
||||
|
||||
URL_CLIENTSCOPES = "{url}/admin/realms/{realm}/client-scopes"
|
||||
URL_CLIENTSCOPE = "{url}/admin/realms/{realm}/client-scopes/{id}"
|
||||
|
@ -1249,7 +1250,7 @@ class KeycloakAPI(object):
|
|||
self.module.fail_json(msg="Could not fetch group %s in realm %s: %s"
|
||||
% (gid, realm, str(e)))
|
||||
|
||||
def get_group_by_name(self, name, realm="master"):
|
||||
def get_group_by_name(self, name, realm="master", parents=None):
|
||||
""" Fetch a keycloak group within a realm based on its name.
|
||||
|
||||
The Keycloak API does not allow filtering of the Groups resource by name.
|
||||
|
@ -1259,9 +1260,18 @@ class KeycloakAPI(object):
|
|||
If the group does not exist, None is returned.
|
||||
:param name: Name of the group to fetch.
|
||||
:param realm: Realm in which the group resides; default 'master'
|
||||
:param parents: Optional list of parents when group to look for is a subgroup
|
||||
"""
|
||||
groups_url = URL_GROUPS.format(url=self.baseurl, realm=realm)
|
||||
try:
|
||||
if parents:
|
||||
parent = self.get_subgroup_direct_parent(parents, realm)
|
||||
|
||||
if not parent:
|
||||
return None
|
||||
|
||||
all_groups = parent['subGroups']
|
||||
else:
|
||||
all_groups = self.get_groups(realm=realm)
|
||||
|
||||
for group in all_groups:
|
||||
|
@ -1274,6 +1284,102 @@ class KeycloakAPI(object):
|
|||
self.module.fail_json(msg="Could not fetch group %s in realm %s: %s"
|
||||
% (name, realm, str(e)))
|
||||
|
||||
def _get_normed_group_parent(self, parent):
|
||||
""" Converts parent dict information into a more easy to use form.
|
||||
|
||||
:param parent: parent describing dict
|
||||
"""
|
||||
if parent['id']:
|
||||
return (parent['id'], True)
|
||||
|
||||
return (parent['name'], False)
|
||||
|
||||
def get_subgroup_by_chain(self, name_chain, realm="master"):
|
||||
""" Access a subgroup API object by walking down a given name/id chain.
|
||||
|
||||
Groups can be given either as by name or by ID, the first element
|
||||
must either be a toplvl group or given as ID, all parents must exist.
|
||||
|
||||
If the group cannot be found, None is returned.
|
||||
:param name_chain: Topdown ordered list of subgroup parent (ids or names) + its own name at the end
|
||||
:param realm: Realm in which the group resides; default 'master'
|
||||
"""
|
||||
cp = name_chain[0]
|
||||
|
||||
# for 1st parent in chain we must query the server
|
||||
cp, is_id = self._get_normed_group_parent(cp)
|
||||
|
||||
if is_id:
|
||||
tmp = self.get_group_by_groupid(cp, realm=realm)
|
||||
else:
|
||||
# given as name, assume toplvl group
|
||||
tmp = self.get_group_by_name(cp, realm=realm)
|
||||
|
||||
if not tmp:
|
||||
return None
|
||||
|
||||
for p in name_chain[1:]:
|
||||
for sg in tmp['subGroups']:
|
||||
pv, is_id = self._get_normed_group_parent(p)
|
||||
|
||||
if is_id:
|
||||
cmpkey = "id"
|
||||
else:
|
||||
cmpkey = "name"
|
||||
|
||||
if pv == sg[cmpkey]:
|
||||
tmp = sg
|
||||
break
|
||||
|
||||
if not tmp:
|
||||
return None
|
||||
|
||||
return tmp
|
||||
|
||||
def get_subgroup_direct_parent(self, parents, realm="master", children_to_resolve=None):
|
||||
""" Get keycloak direct parent group API object for a given chain of parents.
|
||||
|
||||
To succesfully work the API for subgroups we actually dont need
|
||||
to "walk the whole tree" for nested groups but only need to know
|
||||
the ID for the direct predecessor of current subgroup. This
|
||||
method will guarantee us this information getting there with
|
||||
as minimal work as possible.
|
||||
|
||||
Note that given parent list can and might be incomplete at the
|
||||
upper levels as long as it starts with an ID instead of a name
|
||||
|
||||
If the group does not exist, None is returned.
|
||||
:param parents: Topdown ordered list of subgroup parents
|
||||
:param realm: Realm in which the group resides; default 'master'
|
||||
"""
|
||||
if children_to_resolve is None:
|
||||
# start recursion by reversing parents (in optimal cases
|
||||
# we dont need to walk the whole tree upwarts)
|
||||
parents = list(reversed(parents))
|
||||
children_to_resolve = []
|
||||
|
||||
if not parents:
|
||||
# walk complete parents list to the top, all names, no id's,
|
||||
# try to resolve it assuming list is complete and 1st
|
||||
# element is a toplvl group
|
||||
return self.get_subgroup_by_chain(list(reversed(children_to_resolve)), realm=realm)
|
||||
|
||||
cp = parents[0]
|
||||
unused, is_id = self._get_normed_group_parent(cp)
|
||||
|
||||
if is_id:
|
||||
# current parent is given as ID, we can stop walking
|
||||
# upwards searching for an entry point
|
||||
return self.get_subgroup_by_chain([cp] + list(reversed(children_to_resolve)), realm=realm)
|
||||
else:
|
||||
# current parent is given as name, it must be resolved
|
||||
# later, try next parent (recurse)
|
||||
children_to_resolve.append(cp)
|
||||
return self.get_subgroup_direct_parent(
|
||||
parents[1:],
|
||||
realm=realm, children_to_resolve=children_to_resolve
|
||||
)
|
||||
|
||||
def create_group(self, grouprep, realm="master"):
|
||||
""" Create a Keycloak group.
|
||||
|
||||
|
@ -1288,6 +1394,34 @@ class KeycloakAPI(object):
|
|||
self.module.fail_json(msg="Could not create group %s in realm %s: %s"
|
||||
% (grouprep['name'], realm, str(e)))
|
||||
|
||||
def create_subgroup(self, parents, grouprep, realm="master"):
|
||||
""" Create a Keycloak subgroup.
|
||||
|
||||
:param parents: list of one or more parent groups
|
||||
:param grouprep: a GroupRepresentation of the group to be created. Must contain at minimum the field name.
|
||||
:return: HTTPResponse object on success
|
||||
"""
|
||||
parent_id = "---UNDETERMINED---"
|
||||
try:
|
||||
parent_id = self.get_subgroup_direct_parent(parents, realm)
|
||||
|
||||
if not parent_id:
|
||||
raise Exception(
|
||||
"Could not determine subgroup parent ID for given"
|
||||
" parent chain {0}. Assure that all parents exist"
|
||||
" already and the list is complete and properly"
|
||||
" ordered, starts with an ID or starts at the"
|
||||
" top level".format(parents)
|
||||
)
|
||||
|
||||
parent_id = parent_id["id"]
|
||||
url = URL_GROUP_CHILDREN.format(url=self.baseurl, realm=realm, groupid=parent_id)
|
||||
return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||
data=json.dumps(grouprep), validate_certs=self.validate_certs)
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg="Could not create subgroup %s for parent group %s in realm %s: %s"
|
||||
% (grouprep['name'], parent_id, realm, str(e)))
|
||||
|
||||
def update_group(self, grouprep, realm="master"):
|
||||
""" Update an existing group.
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ description:
|
|||
to your needs and a user having the expected roles.
|
||||
|
||||
- The names of module options are snake_cased versions of the camelCase ones found in the
|
||||
Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/8.0/rest-api/index.html).
|
||||
Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/20.0.2/rest-api/index.html).
|
||||
|
||||
- Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and will
|
||||
be returned that way by this module. You may pass single values for attributes when calling the module,
|
||||
|
@ -42,7 +42,9 @@ options:
|
|||
description:
|
||||
- State of the group.
|
||||
- On C(present), the group will be created if it does not yet exist, or updated with the parameters you provide.
|
||||
- On C(absent), the group will be removed if it exists.
|
||||
- >-
|
||||
On C(absent), the group will be removed if it exists. Be aware that absenting
|
||||
a group with subgroups will automatically delete all its subgroups too.
|
||||
default: 'present'
|
||||
type: str
|
||||
choices:
|
||||
|
@ -74,6 +76,38 @@ options:
|
|||
- A dict of key/value pairs to set as custom attributes for the group.
|
||||
- Values may be single values (e.g. a string) or a list of strings.
|
||||
|
||||
parents:
|
||||
version_added: "6.4.0"
|
||||
type: list
|
||||
description:
|
||||
- List of parent groups for the group to handle sorted top to bottom.
|
||||
- >-
|
||||
Set this to create a group as a subgroup of another group or groups (parents) or
|
||||
when accessing an existing subgroup by name.
|
||||
- >-
|
||||
Not necessary to set when accessing an existing subgroup by its C(ID) because in
|
||||
that case the group can be directly queried without necessarily knowing its parent(s).
|
||||
elements: dict
|
||||
suboptions:
|
||||
id:
|
||||
type: str
|
||||
description:
|
||||
- Identify parent by ID.
|
||||
- Needs less API calls than using I(name).
|
||||
- A deep parent chain can be started at any point when first given parent is given as ID.
|
||||
- Note that in principle both ID and name can be specified at the same time
|
||||
but current implementation only always use just one of them, with ID
|
||||
being preferred.
|
||||
name:
|
||||
type: str
|
||||
description:
|
||||
- Identify parent by name.
|
||||
- Needs more internal API calls than using I(id) to map names to ID's under the hood.
|
||||
- When giving a parent chain with only names it must be complete up to the top.
|
||||
- Note that in principle both ID and name can be specified at the same time
|
||||
but current implementation only always use just one of them, with ID
|
||||
being preferred.
|
||||
|
||||
notes:
|
||||
- Presently, the I(realmRoles), I(clientRoles) and I(access) attributes returned by the Keycloak API
|
||||
are read-only for groups. This limitation will be removed in a later version of this module.
|
||||
|
@ -97,6 +131,7 @@ EXAMPLES = '''
|
|||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
register: result_new_kcgrp
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create a Keycloak group, authentication with token
|
||||
|
@ -162,6 +197,64 @@ EXAMPLES = '''
|
|||
- list
|
||||
- items
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create a Keycloak subgroup of a base group (using parent name)
|
||||
community.general.keycloak_group:
|
||||
name: my-new-kc-group-sub
|
||||
realm: MyCustomRealm
|
||||
state: present
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
parents:
|
||||
- name: my-new-kc-group
|
||||
register: result_new_kcgrp_sub
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create a Keycloak subgroup of a base group (using parent id)
|
||||
community.general.keycloak_group:
|
||||
name: my-new-kc-group-sub2
|
||||
realm: MyCustomRealm
|
||||
state: present
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
parents:
|
||||
- id: "{{ result_new_kcgrp.end_state.id }}"
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create a Keycloak subgroup of a subgroup (using parent names)
|
||||
community.general.keycloak_group:
|
||||
name: my-new-kc-group-sub-sub
|
||||
realm: MyCustomRealm
|
||||
state: present
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
parents:
|
||||
- name: my-new-kc-group
|
||||
- name: my-new-kc-group-sub
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Create a Keycloak subgroup of a subgroup (using direct parent id)
|
||||
community.general.keycloak_group:
|
||||
name: my-new-kc-group-sub-sub
|
||||
realm: MyCustomRealm
|
||||
state: present
|
||||
auth_client_id: admin-cli
|
||||
auth_keycloak_url: https://auth.example.com/auth
|
||||
auth_realm: master
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
parents:
|
||||
- id: "{{ result_new_kcgrp_sub.end_state.id }}"
|
||||
delegate_to: localhost
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
@ -240,6 +333,13 @@ def main():
|
|||
id=dict(type='str'),
|
||||
name=dict(type='str'),
|
||||
attributes=dict(type='dict'),
|
||||
parents=dict(
|
||||
type='list', elements='dict',
|
||||
options=dict(
|
||||
id=dict(type='str'),
|
||||
name=dict(type='str')
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
argument_spec.update(meta_args)
|
||||
|
@ -266,6 +366,8 @@ def main():
|
|||
name = module.params.get('name')
|
||||
attributes = module.params.get('attributes')
|
||||
|
||||
parents = module.params.get('parents')
|
||||
|
||||
# attributes in Keycloak have their values returned as lists
|
||||
# via the API. attributes is a dict, so we'll transparently convert
|
||||
# the values to lists.
|
||||
|
@ -275,12 +377,12 @@ def main():
|
|||
|
||||
# Filter and map the parameters names that apply to the group
|
||||
group_params = [x for x in module.params
|
||||
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm'] and
|
||||
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'parents'] and
|
||||
module.params.get(x) is not None]
|
||||
|
||||
# See if it already exists in Keycloak
|
||||
if gid is None:
|
||||
before_group = kc.get_group_by_name(name, realm=realm)
|
||||
before_group = kc.get_group_by_name(name, realm=realm, parents=parents)
|
||||
else:
|
||||
before_group = kc.get_group_by_groupid(gid, realm=realm)
|
||||
|
||||
|
@ -323,9 +425,15 @@ def main():
|
|||
if module.check_mode:
|
||||
module.exit_json(**result)
|
||||
|
||||
# create it
|
||||
# create it ...
|
||||
if parents:
|
||||
# ... as subgroup of another parent group
|
||||
kc.create_subgroup(parents, desired_group, realm=realm)
|
||||
else:
|
||||
# ... as toplvl base group
|
||||
kc.create_group(desired_group, realm=realm)
|
||||
after_group = kc.get_group_by_name(name, realm)
|
||||
|
||||
after_group = kc.get_group_by_name(name, realm, parents=parents)
|
||||
|
||||
result['end_state'] = after_group
|
||||
|
||||
|
|
5
tests/integration/targets/keycloak_group/aliases
Normal file
5
tests/integration/targets/keycloak_group/aliases
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Copyright (c) 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
|
||||
|
||||
unsupported
|
27
tests/integration/targets/keycloak_group/readme.adoc
Normal file
27
tests/integration/targets/keycloak_group/readme.adoc
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 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
|
||||
|
||||
To be able to run these integration tests a keycloak server must be
|
||||
reachable under a specific url with a specific admin user and password.
|
||||
The exact values expected for these parameters can be found in
|
||||
'vars/main.yml' file. A simple way to do this is to use the official
|
||||
keycloak docker images like this:
|
||||
|
||||
----
|
||||
docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=<url-path> -e KEYCLOAK_ADMIN=<admin_user> -e KEYCLOAK_ADMIN_PASSWORD=<admin_password> quay.io/keycloak/keycloak:20.0.2 start-dev
|
||||
----
|
||||
|
||||
Example with concrete values inserted:
|
||||
|
||||
----
|
||||
docker run --name mykeycloak -p 8080:8080 -e KC_HTTP_RELATIVE_PATH=/auth -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:20.0.2 start-dev
|
||||
----
|
||||
|
||||
This test suite can run against a fresh unconfigured server instance
|
||||
(no preconfiguration required) and cleans up after itself (undoes all
|
||||
its config changes) as long as it runs through completly. While its active
|
||||
it changes the server configuration in the following ways:
|
||||
|
||||
* creating, modifying and deleting some keycloak groups
|
||||
|
501
tests/integration/targets/keycloak_group/tasks/main.yml
Normal file
501
tests/integration/targets/keycloak_group/tasks/main.yml
Normal file
|
@ -0,0 +1,501 @@
|
|||
---
|
||||
# Copyright (c) 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
|
||||
|
||||
- name: Create a keycloak group
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: test-group
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: Assert group was created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "test-group"
|
||||
- result.end_state.path == "/test-group"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- set_fact:
|
||||
test_group_id: "{{ result.end_state.id }}"
|
||||
|
||||
- name: Group creation rerun (test for idempotency)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: test-group
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "test-group"
|
||||
- result.end_state.path == "/test-group"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- name: Update the name of a keycloak group
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
id: "{{ test_group_id }}"
|
||||
name: new-test-group
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- name: Assert that group name was updated
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "new-test-group"
|
||||
- result.end_state.path == "/new-test-group"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- name: Delete a keycloak group by id
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
id: "{{ test_group_id }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that group was deleted
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Redo group deletion (check for idempotency)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
id: "{{ test_group_id }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Create a keycloak group with some custom attributes
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: my-new_group
|
||||
attributes:
|
||||
attrib1: value1
|
||||
attrib2: value2
|
||||
attrib3:
|
||||
- item1
|
||||
- item2
|
||||
register: result
|
||||
|
||||
- name: Assert that group was correctly created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "my-new_group"
|
||||
- result.end_state.path == "/my-new_group"
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
- result.end_state.attributes != {}
|
||||
- result.end_state.attributes.attrib1 == ["value1"]
|
||||
- result.end_state.attributes.attrib2 == ["value2"]
|
||||
- result.end_state.attributes.attrib3 == ["item1", "item2"]
|
||||
|
||||
- name: Delete a keycloak group based on name
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: my-new_group
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that group was deleted
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state == {}
|
||||
|
||||
## subgroup tests
|
||||
## we already testet this so no asserts for this
|
||||
- name: Create a new base group for subgroup testing (test setup)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: rootgrp
|
||||
register: subgrp_basegrp_result
|
||||
|
||||
- name: Create a subgroup using parent id
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: subgrp1
|
||||
parents:
|
||||
- id: "{{ subgrp_basegrp_result.end_state.id }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that subgroup was correctly created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "subgrp1"
|
||||
- result.end_state.path == "/rootgrp/subgrp1"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- name: Recreate a subgroup using parent id (test idempotency)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: subgrp1
|
||||
parents:
|
||||
- id: "{{ subgrp_basegrp_result.end_state.id }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "subgrp1"
|
||||
- result.end_state.path == "/rootgrp/subgrp1"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- name: Changing name of existing group
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
id: "{{ result.end_state.id }}"
|
||||
name: new-subgrp1
|
||||
parents:
|
||||
- id: "{{ subgrp_basegrp_result.end_state.id }}"
|
||||
register: result
|
||||
|
||||
- name: Assert that subgroup name has changed correctly
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "new-subgrp1"
|
||||
- result.end_state.path == "/rootgrp/new-subgrp1"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- name: Create a subgroup using parent name
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: subgrp2
|
||||
parents:
|
||||
- name: rootgrp
|
||||
register: result
|
||||
|
||||
- name: Assert that subgroup was correctly created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "subgrp2"
|
||||
- result.end_state.path == "/rootgrp/subgrp2"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- name: Recreate a subgroup using parent name (test idempotency)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: subgrp2
|
||||
parents:
|
||||
- name: rootgrp
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "subgrp2"
|
||||
- result.end_state.path == "/rootgrp/subgrp2"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
## subgroup of subgroup tests
|
||||
- name: Create a subgroup of a subgroup using parent names (complete parent chain)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: subsubgrp
|
||||
parents:
|
||||
- name: rootgrp
|
||||
- name: subgrp2
|
||||
register: result
|
||||
|
||||
- name: Assert subgroup of subgroup was created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "subsubgrp"
|
||||
- result.end_state.path == "/rootgrp/subgrp2/subsubgrp"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- name: ReCreate a subgroup of a subgroup using parent names (test idempotency)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: subsubgrp
|
||||
parents:
|
||||
- name: rootgrp
|
||||
- name: subgrp2
|
||||
register: result_subsubgrp
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result_subsubgrp is not changed
|
||||
- result_subsubgrp.end_state != {}
|
||||
- result_subsubgrp.end_state.name == "subsubgrp"
|
||||
- result_subsubgrp.end_state.path == "/rootgrp/subgrp2/subsubgrp"
|
||||
- result_subsubgrp.end_state.attributes == {}
|
||||
- result_subsubgrp.end_state.clientRoles == {}
|
||||
- result_subsubgrp.end_state.realmRoles == []
|
||||
- result_subsubgrp.end_state.subGroups == []
|
||||
|
||||
- name: Create a subgroup of a subgroup using direct parent id (incomplete parent chain)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: subsubsubgrp
|
||||
parents:
|
||||
- id: "{{ result_subsubgrp.end_state.id }}"
|
||||
register: result
|
||||
|
||||
- name: Assert subgroup of subgroup was created
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state != {}
|
||||
- result.end_state.name == "subsubsubgrp"
|
||||
- result.end_state.path == "/rootgrp/subgrp2/subsubgrp/subsubsubgrp"
|
||||
- result.end_state.attributes == {}
|
||||
- result.end_state.clientRoles == {}
|
||||
- result.end_state.realmRoles == []
|
||||
- result.end_state.subGroups == []
|
||||
|
||||
- name: ReCreate a subgroup of a subgroup using direct parent id (test idempotency)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: subsubsubgrp
|
||||
parents:
|
||||
- id: "{{ result_subsubgrp.end_state.id }}"
|
||||
register: result_subsubsubgrp
|
||||
|
||||
- name: Assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- result_subsubsubgrp is not changed
|
||||
- result_subsubsubgrp.end_state != {}
|
||||
- result_subsubsubgrp.end_state.name == "subsubsubgrp"
|
||||
- result_subsubsubgrp.end_state.path == "/rootgrp/subgrp2/subsubgrp/subsubsubgrp"
|
||||
- result_subsubsubgrp.end_state.attributes == {}
|
||||
- result_subsubsubgrp.end_state.clientRoles == {}
|
||||
- result_subsubsubgrp.end_state.realmRoles == []
|
||||
- result_subsubsubgrp.end_state.subGroups == []
|
||||
|
||||
## subgroup deletion tests
|
||||
## note: in principle we already have tested group deletion in general
|
||||
## enough already, but what makes it interesting here again is to
|
||||
## see it works also properly for subgroups and groups with subgroups
|
||||
- name: Deleting a subgroup by id (no parents needed)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
id: "{{ result_subsubsubgrp.end_state.id }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that subgroup was deleted
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Redo subgroup deletion (idempotency test)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
id: "{{ result_subsubsubgrp.end_state.id }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Deleting a subgroup by name
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: new-subgrp1
|
||||
parents:
|
||||
- name: rootgrp
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that subgroup was deleted
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Redo deleting a subgroup by name (idempotency test)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: new-subgrp1
|
||||
parents:
|
||||
- name: rootgrp
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that nothing has changed
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Delete keycloak group which has subgroups
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: rootgrp
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that group was deleted
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
- result.end_state == {}
|
||||
|
||||
- name: Redo delete keycloak group which has subgroups (idempotency test)
|
||||
community.general.keycloak_group:
|
||||
auth_keycloak_url: "{{ url }}"
|
||||
auth_realm: "{{ admin_realm }}"
|
||||
auth_username: "{{ admin_user }}"
|
||||
auth_password: "{{ admin_password }}"
|
||||
realm: "{{ realm }}"
|
||||
name: rootgrp
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Assert that group was deleted
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
- result.end_state == {}
|
10
tests/integration/targets/keycloak_group/vars/main.yml
Normal file
10
tests/integration/targets/keycloak_group/vars/main.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
# Copyright (c) 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
|
||||
|
||||
url: http://localhost:8080/auth
|
||||
admin_realm: master
|
||||
admin_user: admin
|
||||
admin_password: password
|
||||
realm: master
|
Loading…
Reference in a new issue