mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[PR #6322/1f2c7b17 backport][stable-6] Add a module to set the keycloak client scope type (#6392)
Add a module to set the keycloak client scope type (#6322)
The module keycloak_clientscope_type allows to set the client scope
types (optional/default) either on realm or client level.
(cherry picked from commit 1f2c7b1731
)
Co-authored-by: Simon Pahl <simpahl@gmail.com>
This commit is contained in:
parent
fd91e94279
commit
29671cb54c
7 changed files with 629 additions and 0 deletions
2
.github/BOTMETA.yml
vendored
2
.github/BOTMETA.yml
vendored
|
@ -684,6 +684,8 @@ files:
|
||||||
maintainers: Gaetan2907
|
maintainers: Gaetan2907
|
||||||
$modules/keycloak_clientscope.py:
|
$modules/keycloak_clientscope.py:
|
||||||
maintainers: Gaetan2907
|
maintainers: Gaetan2907
|
||||||
|
$modules/keycloak_clientscope_type.py:
|
||||||
|
maintainers: simonpahl
|
||||||
$modules/keycloak_clientsecret_info.py:
|
$modules/keycloak_clientsecret_info.py:
|
||||||
maintainers: fynncfchen johncant
|
maintainers: fynncfchen johncant
|
||||||
$modules/keycloak_clientsecret_regenerate.py:
|
$modules/keycloak_clientsecret_regenerate.py:
|
||||||
|
|
|
@ -49,6 +49,16 @@ URL_CLIENTSCOPE = "{url}/admin/realms/{realm}/client-scopes/{id}"
|
||||||
URL_CLIENTSCOPE_PROTOCOLMAPPERS = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models"
|
URL_CLIENTSCOPE_PROTOCOLMAPPERS = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models"
|
||||||
URL_CLIENTSCOPE_PROTOCOLMAPPER = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models/{mapper_id}"
|
URL_CLIENTSCOPE_PROTOCOLMAPPER = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models/{mapper_id}"
|
||||||
|
|
||||||
|
URL_DEFAULT_CLIENTSCOPES = "{url}/admin/realms/{realm}/default-default-client-scopes"
|
||||||
|
URL_DEFAULT_CLIENTSCOPE = "{url}/admin/realms/{realm}/default-default-client-scopes/{id}"
|
||||||
|
URL_OPTIONAL_CLIENTSCOPES = "{url}/admin/realms/{realm}/default-optional-client-scopes"
|
||||||
|
URL_OPTIONAL_CLIENTSCOPE = "{url}/admin/realms/{realm}/default-optional-client-scopes/{id}"
|
||||||
|
|
||||||
|
URL_CLIENT_DEFAULT_CLIENTSCOPES = "{url}/admin/realms/{realm}/clients/{cid}/default-client-scopes"
|
||||||
|
URL_CLIENT_DEFAULT_CLIENTSCOPE = "{url}/admin/realms/{realm}/clients/{cid}/default-client-scopes/{id}"
|
||||||
|
URL_CLIENT_OPTIONAL_CLIENTSCOPES = "{url}/admin/realms/{realm}/clients/{cid}/optional-client-scopes"
|
||||||
|
URL_CLIENT_OPTIONAL_CLIENTSCOPE = "{url}/admin/realms/{realm}/clients/{cid}/optional-client-scopes/{id}"
|
||||||
|
|
||||||
URL_CLIENT_GROUP_ROLEMAPPINGS = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}"
|
URL_CLIENT_GROUP_ROLEMAPPINGS = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}"
|
||||||
URL_CLIENT_GROUP_ROLEMAPPINGS_AVAILABLE = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}/available"
|
URL_CLIENT_GROUP_ROLEMAPPINGS_AVAILABLE = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}/available"
|
||||||
URL_CLIENT_GROUP_ROLEMAPPINGS_COMPOSITE = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}/composite"
|
URL_CLIENT_GROUP_ROLEMAPPINGS_COMPOSITE = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}/composite"
|
||||||
|
@ -1163,6 +1173,131 @@ class KeycloakAPI(object):
|
||||||
self.module.fail_json(msg='Could not update protocolmappers for clientscope %s in realm %s: %s'
|
self.module.fail_json(msg='Could not update protocolmappers for clientscope %s in realm %s: %s'
|
||||||
% (mapper_rep, realm, str(e)))
|
% (mapper_rep, realm, str(e)))
|
||||||
|
|
||||||
|
def get_default_clientscopes(self, realm, client_id=None):
|
||||||
|
"""Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||||
|
|
||||||
|
To fetch the full data of the client scope, make a subsequent call to
|
||||||
|
get_clientscope_by_clientscopeid, passing in the ID of the client scope you wish to return.
|
||||||
|
|
||||||
|
:param realm: Realm in which the clientscope resides.
|
||||||
|
:param client_id: The client in which the clientscope resides.
|
||||||
|
:return The default clientscopes of this realm or client
|
||||||
|
"""
|
||||||
|
url = URL_DEFAULT_CLIENTSCOPES if client_id is None else URL_CLIENT_DEFAULT_CLIENTSCOPES
|
||||||
|
return self._get_clientscopes_of_type(realm, url, 'default', client_id)
|
||||||
|
|
||||||
|
def get_optional_clientscopes(self, realm, client_id=None):
|
||||||
|
"""Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||||
|
|
||||||
|
To fetch the full data of the client scope, make a subsequent call to
|
||||||
|
get_clientscope_by_clientscopeid, passing in the ID of the client scope you wish to return.
|
||||||
|
|
||||||
|
:param realm: Realm in which the clientscope resides.
|
||||||
|
:param client_id: The client in which the clientscope resides.
|
||||||
|
:return The optinal clientscopes of this realm or client
|
||||||
|
"""
|
||||||
|
url = URL_OPTIONAL_CLIENTSCOPES if client_id is None else URL_CLIENT_OPTIONAL_CLIENTSCOPES
|
||||||
|
return self._get_clientscopes_of_type(realm, url, 'optional', client_id)
|
||||||
|
|
||||||
|
def _get_clientscopes_of_type(self, realm, url_template, scope_type, client_id=None):
|
||||||
|
"""Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||||
|
|
||||||
|
To fetch the full data of the client scope, make a subsequent call to
|
||||||
|
get_clientscope_by_clientscopeid, passing in the ID of the client scope you wish to return.
|
||||||
|
|
||||||
|
:param realm: Realm in which the clientscope resides.
|
||||||
|
:param url_template the template for the right type
|
||||||
|
:param scope_type this can be either optinal or default
|
||||||
|
:param client_id: The client in which the clientscope resides.
|
||||||
|
:return The clientscopes of the specified type of this realm
|
||||||
|
"""
|
||||||
|
if client_id is None:
|
||||||
|
clientscopes_url = url_template.format(url=self.baseurl, realm=realm)
|
||||||
|
try:
|
||||||
|
return json.loads(to_native(open_url(clientscopes_url, method="GET", http_agent=self.http_agent, headers=self.restheaders,
|
||||||
|
timeout=self.connection_timeout, validate_certs=self.validate_certs).read()))
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg="Could not fetch list of %s clientscopes in realm %s: %s" % (scope_type, realm, str(e)))
|
||||||
|
else:
|
||||||
|
cid = self.get_client_id(client_id=client_id, realm=realm)
|
||||||
|
clientscopes_url = url_template.format(url=self.baseurl, realm=realm, cid=cid)
|
||||||
|
try:
|
||||||
|
return json.loads(to_native(open_url(clientscopes_url, method="GET", http_agent=self.http_agent, headers=self.restheaders,
|
||||||
|
timeout=self.connection_timeout, validate_certs=self.validate_certs).read()))
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg="Could not fetch list of %s clientscopes in client %s: %s" % (scope_type, client_id, clientscopes_url))
|
||||||
|
|
||||||
|
def _decide_url_type_clientscope(self, client_id=None, scope_type="default"):
|
||||||
|
"""Decides which url to use.
|
||||||
|
:param scope_type this can be either optinal or default
|
||||||
|
:param client_id: The client in which the clientscope resides.
|
||||||
|
"""
|
||||||
|
if client_id is None:
|
||||||
|
if scope_type == "default":
|
||||||
|
return URL_DEFAULT_CLIENTSCOPE
|
||||||
|
if scope_type == "optional":
|
||||||
|
return URL_OPTIONAL_CLIENTSCOPE
|
||||||
|
else:
|
||||||
|
if scope_type == "default":
|
||||||
|
return URL_CLIENT_DEFAULT_CLIENTSCOPE
|
||||||
|
if scope_type == "optional":
|
||||||
|
return URL_CLIENT_OPTIONAL_CLIENTSCOPE
|
||||||
|
|
||||||
|
def add_default_clientscope(self, id, realm="master", client_id=None):
|
||||||
|
"""Add a client scope as default either on realm or client level.
|
||||||
|
|
||||||
|
:param id: Client scope Id.
|
||||||
|
:param realm: Realm in which the clientscope resides.
|
||||||
|
:param client_id: The client in which the clientscope resides.
|
||||||
|
"""
|
||||||
|
self._action_type_clientscope(id, client_id, "default", realm, 'add')
|
||||||
|
|
||||||
|
def add_optional_clientscope(self, id, realm="master", client_id=None):
|
||||||
|
"""Add a client scope as optional either on realm or client level.
|
||||||
|
|
||||||
|
:param id: Client scope Id.
|
||||||
|
:param realm: Realm in which the clientscope resides.
|
||||||
|
:param client_id: The client in which the clientscope resides.
|
||||||
|
"""
|
||||||
|
self._action_type_clientscope(id, client_id, "optional", realm, 'add')
|
||||||
|
|
||||||
|
def delete_default_clientscope(self, id, realm="master", client_id=None):
|
||||||
|
"""Remove a client scope as default either on realm or client level.
|
||||||
|
|
||||||
|
:param id: Client scope Id.
|
||||||
|
:param realm: Realm in which the clientscope resides.
|
||||||
|
:param client_id: The client in which the clientscope resides.
|
||||||
|
"""
|
||||||
|
self._action_type_clientscope(id, client_id, "default", realm, 'delete')
|
||||||
|
|
||||||
|
def delete_optional_clientscope(self, id, realm="master", client_id=None):
|
||||||
|
"""Remove a client scope as optional either on realm or client level.
|
||||||
|
|
||||||
|
:param id: Client scope Id.
|
||||||
|
:param realm: Realm in which the clientscope resides.
|
||||||
|
:param client_id: The client in which the clientscope resides.
|
||||||
|
"""
|
||||||
|
self._action_type_clientscope(id, client_id, "optional", realm, 'delete')
|
||||||
|
|
||||||
|
def _action_type_clientscope(self, id=None, client_id=None, scope_type="default", realm="master", action='add'):
|
||||||
|
""" Delete or add a clientscope of type.
|
||||||
|
:param name: The name of the clientscope. A lookup will be performed to retrieve the clientscope ID.
|
||||||
|
:param client_id: The ID of the clientscope (preferred to name).
|
||||||
|
:param scope_type 'default' or 'optional'
|
||||||
|
:param realm: The realm in which this group resides, default "master".
|
||||||
|
"""
|
||||||
|
cid = None if client_id is None else self.get_client_id(client_id=client_id, realm=realm)
|
||||||
|
# should have a good cid by here.
|
||||||
|
clientscope_type_url = self._decide_url_type_clientscope(client_id, scope_type).format(realm=realm, id=id, cid=cid, url=self.baseurl)
|
||||||
|
try:
|
||||||
|
method = 'PUT' if action == "add" else 'DELETE'
|
||||||
|
return open_url(clientscope_type_url, method=method, http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout,
|
||||||
|
validate_certs=self.validate_certs)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
place = 'realm' if client_id is None else 'client ' + client_id
|
||||||
|
self.module.fail_json(msg="Unable to %s %s clientscope %s @ %s : %s" % (action, scope_type, id, place, str(e)))
|
||||||
|
|
||||||
def create_clientsecret(self, id, realm="master"):
|
def create_clientsecret(self, id, realm="master"):
|
||||||
""" Generate a new client secret by id
|
""" Generate a new client secret by id
|
||||||
|
|
||||||
|
|
285
plugins/modules/keycloak_clientscope_type.py
Normal file
285
plugins/modules/keycloak_clientscope_type.py
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: keycloak_clientscope_type
|
||||||
|
|
||||||
|
short_description: Set the type of aclientscope in realm or client via Keycloak API
|
||||||
|
|
||||||
|
version_added: 6.6.0
|
||||||
|
|
||||||
|
description:
|
||||||
|
- This module allows you to set the type (optional, default) of clientscopes
|
||||||
|
via the Keycloak REST API. It requires access to the REST API via OpenID
|
||||||
|
Connect; the user connecting and the client being used must have the
|
||||||
|
requisite access rights. In a default Keycloak installation, admin-cli and
|
||||||
|
an admin user would work, as would a separate client definition with the
|
||||||
|
scope tailored to your needs and a user having the expected roles.
|
||||||
|
|
||||||
|
attributes:
|
||||||
|
check_mode:
|
||||||
|
support: full
|
||||||
|
diff_mode:
|
||||||
|
support: full
|
||||||
|
|
||||||
|
options:
|
||||||
|
realm:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- The Keycloak realm.
|
||||||
|
default: 'master'
|
||||||
|
|
||||||
|
client_id:
|
||||||
|
description:
|
||||||
|
- The I(client_id) of the client. If not set the clientscop types are set as a default for the realm.
|
||||||
|
aliases:
|
||||||
|
- clientId
|
||||||
|
type: str
|
||||||
|
|
||||||
|
default_clientscopes:
|
||||||
|
description:
|
||||||
|
- Client scopes that should be of type default.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
|
||||||
|
optional_clientscopes:
|
||||||
|
description:
|
||||||
|
- Client scopes that should be of type optional.
|
||||||
|
type: list
|
||||||
|
elements: str
|
||||||
|
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.keycloak
|
||||||
|
- community.general.attributes
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Simon Pahl (@simonpahl)
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Set default client scopes on realm level
|
||||||
|
community.general.keycloak_clientsecret_info:
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: USERNAME
|
||||||
|
auth_password: PASSWORD
|
||||||
|
realm: "MyCustomRealm"
|
||||||
|
default_clientscopes: ['profile', 'roles']
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|
||||||
|
- name: Set default and optional client scopes on client level with token auth
|
||||||
|
community.general.keycloak_clientsecret_info:
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
token: TOKEN
|
||||||
|
realm: "MyCustomRealm"
|
||||||
|
client_id: "MyCustomClient"
|
||||||
|
default_clientscopes: ['profile', 'roles']
|
||||||
|
optional_clientscopes: ['phone']
|
||||||
|
delegate_to: localhost
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
msg:
|
||||||
|
description: Message as to what action was taken.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
sample: ""
|
||||||
|
proposed:
|
||||||
|
description: Representation of proposed client-scope types mapping.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
default_clientscopes: ["profile", "role"],
|
||||||
|
optional_clientscopes: []
|
||||||
|
}
|
||||||
|
existing:
|
||||||
|
description:
|
||||||
|
- Representation of client scopes before module execution.
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
default_clientscopes: ["profile", "role"],
|
||||||
|
optional_clientscopes: ["phone"]
|
||||||
|
}
|
||||||
|
end_state:
|
||||||
|
description:
|
||||||
|
- Representation of client scopes after module execution.
|
||||||
|
- The sample is truncated.
|
||||||
|
returned: on success
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
default_clientscopes: ["profile", "role"],
|
||||||
|
optional_clientscopes: []
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import (
|
||||||
|
KeycloakAPI, KeycloakError, get_token)
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import \
|
||||||
|
keycloak_argument_spec
|
||||||
|
|
||||||
|
|
||||||
|
def keycloak_clientscope_type_module():
|
||||||
|
"""
|
||||||
|
Returns an AnsibleModule definition.
|
||||||
|
|
||||||
|
:return: argument_spec dict
|
||||||
|
"""
|
||||||
|
argument_spec = keycloak_argument_spec()
|
||||||
|
|
||||||
|
meta_args = dict(
|
||||||
|
realm=dict(default='master'),
|
||||||
|
client_id=dict(type='str', aliases=['clientId']),
|
||||||
|
default_clientscopes=dict(type='list', elements='str'),
|
||||||
|
optional_clientscopes=dict(type='list', elements='str'),
|
||||||
|
)
|
||||||
|
|
||||||
|
argument_spec.update(meta_args)
|
||||||
|
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=([
|
||||||
|
['token', 'auth_realm', 'auth_username', 'auth_password'],
|
||||||
|
['default_clientscopes', 'optional_clientscopes']
|
||||||
|
]),
|
||||||
|
required_together=([['auth_realm', 'auth_username', 'auth_password']]),
|
||||||
|
mutually_exclusive=[
|
||||||
|
['token', 'auth_realm'],
|
||||||
|
['token', 'auth_username'],
|
||||||
|
['token', 'auth_password']
|
||||||
|
])
|
||||||
|
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
|
def clientscopes_to_add(existing, proposed):
|
||||||
|
to_add = []
|
||||||
|
existing_clientscope_ids = extract_field(existing, 'id')
|
||||||
|
for clientscope in proposed:
|
||||||
|
if not clientscope['id'] in existing_clientscope_ids:
|
||||||
|
to_add.append(clientscope)
|
||||||
|
return to_add
|
||||||
|
|
||||||
|
|
||||||
|
def clientscopes_to_delete(existing, proposed):
|
||||||
|
to_delete = []
|
||||||
|
proposed_clientscope_ids = extract_field(proposed, 'id')
|
||||||
|
for clientscope in existing:
|
||||||
|
if not clientscope['id'] in proposed_clientscope_ids:
|
||||||
|
to_delete.append(clientscope)
|
||||||
|
return to_delete
|
||||||
|
|
||||||
|
|
||||||
|
def extract_field(dictionary, field='name'):
|
||||||
|
return [cs[field] for cs in dictionary]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Module keycloak_clientscope_type
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
module = keycloak_clientscope_type_module()
|
||||||
|
|
||||||
|
# Obtain access token, initialize API
|
||||||
|
try:
|
||||||
|
connection_header = get_token(module.params)
|
||||||
|
except KeycloakError as e:
|
||||||
|
module.fail_json(msg=str(e))
|
||||||
|
|
||||||
|
kc = KeycloakAPI(module, connection_header)
|
||||||
|
|
||||||
|
realm = module.params.get('realm')
|
||||||
|
client_id = module.params.get('client_id')
|
||||||
|
default_clientscopes = module.params.get('default_clientscopes')
|
||||||
|
optional_clientscopes = module.params.get('optional_clientscopes')
|
||||||
|
|
||||||
|
result = dict(changed=False, msg='', proposed={}, existing={}, end_state={})
|
||||||
|
|
||||||
|
all_clientscopes = kc.get_clientscopes(realm)
|
||||||
|
default_clientscopes_real = []
|
||||||
|
optional_clientscopes_real = []
|
||||||
|
|
||||||
|
for client_scope in all_clientscopes:
|
||||||
|
if default_clientscopes is not None and client_scope["name"] in default_clientscopes:
|
||||||
|
default_clientscopes_real.append(client_scope)
|
||||||
|
if optional_clientscopes is not None and client_scope["name"] in optional_clientscopes:
|
||||||
|
optional_clientscopes_real.append(client_scope)
|
||||||
|
|
||||||
|
if default_clientscopes is not None and len(default_clientscopes_real) != len(default_clientscopes):
|
||||||
|
module.fail_json(msg='At least one of the default_clientscopes does not exist!')
|
||||||
|
|
||||||
|
if optional_clientscopes is not None and len(optional_clientscopes_real) != len(optional_clientscopes):
|
||||||
|
module.fail_json(msg='At least one of the optional_clientscopes does not exist!')
|
||||||
|
|
||||||
|
result['proposed'].update({
|
||||||
|
'default_clientscopes': 'no-change' if default_clientscopes is None else default_clientscopes,
|
||||||
|
'optional_clientscopes': 'no-change' if optional_clientscopes is None else optional_clientscopes
|
||||||
|
})
|
||||||
|
|
||||||
|
default_clientscopes_existing = kc.get_default_clientscopes(realm, client_id)
|
||||||
|
optional_clientscopes_existing = kc.get_optional_clientscopes(realm, client_id)
|
||||||
|
|
||||||
|
result['existing'].update({
|
||||||
|
'default_clientscopes': extract_field(default_clientscopes_existing),
|
||||||
|
'optional_clientscopes': extract_field(optional_clientscopes_existing)
|
||||||
|
})
|
||||||
|
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before=result['existing'], after=result['proposed'])
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
default_clientscopes_add = clientscopes_to_add(default_clientscopes_existing, default_clientscopes_real)
|
||||||
|
optional_clientscopes_add = clientscopes_to_add(optional_clientscopes_existing, optional_clientscopes_real)
|
||||||
|
|
||||||
|
default_clientscopes_delete = clientscopes_to_delete(default_clientscopes_existing, default_clientscopes_real)
|
||||||
|
optional_clientscopes_delete = clientscopes_to_delete(optional_clientscopes_existing, optional_clientscopes_real)
|
||||||
|
|
||||||
|
# first delete so clientscopes can change type
|
||||||
|
for clientscope in default_clientscopes_delete:
|
||||||
|
kc.delete_default_clientscope(clientscope['id'], realm, client_id)
|
||||||
|
for clientscope in optional_clientscopes_delete:
|
||||||
|
kc.delete_optional_clientscope(clientscope['id'], realm, client_id)
|
||||||
|
|
||||||
|
for clientscope in default_clientscopes_add:
|
||||||
|
kc.add_default_clientscope(clientscope['id'], realm, client_id)
|
||||||
|
for clientscope in optional_clientscopes_add:
|
||||||
|
kc.add_optional_clientscope(clientscope['id'], realm, client_id)
|
||||||
|
|
||||||
|
result["changed"] = (
|
||||||
|
len(default_clientscopes_add) > 0
|
||||||
|
or len(optional_clientscopes_add) > 0
|
||||||
|
or len(default_clientscopes_delete) > 0
|
||||||
|
or len(optional_clientscopes_delete) > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
result['end_state'].update({
|
||||||
|
'default_clientscopes': extract_field(kc.get_default_clientscopes(realm, client_id)),
|
||||||
|
'optional_clientscopes': extract_field(kc.get_optional_clientscopes(realm, client_id))
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!--
|
||||||
|
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
|
||||||
|
-->
|
||||||
|
|
||||||
|
The integration test can be performed as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
# 1. Start docker-compose:
|
||||||
|
docker-compose -f tests/integration/targets/keycloak_clientscope_type/docker-compose.yml down
|
||||||
|
docker-compose -f tests/integration/targets/keycloak_clientscope_type/docker-compose.yml up -d
|
||||||
|
|
||||||
|
# 2. Run the integration tests:
|
||||||
|
ansible-test integration keycloak_clientscope_type --allow-unsupported -v
|
||||||
|
```
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
# 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
|
||||||
|
|
||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:21.0.2
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
environment:
|
||||||
|
KEYCLOAK_ADMIN: admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD: password
|
||||||
|
command: start-dev
|
|
@ -0,0 +1,164 @@
|
||||||
|
---
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
# Fixtures
|
||||||
|
- name: Create keycloak realm
|
||||||
|
community.general.keycloak_realm:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
id: ""
|
||||||
|
state: present
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- name: Create keycloak client
|
||||||
|
community.general.keycloak_client:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
state: present
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
- name: Create a scope1 client scope
|
||||||
|
community.general.keycloak_clientscope:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
name: scope1
|
||||||
|
description: "test 1"
|
||||||
|
protocol: openid-connect
|
||||||
|
|
||||||
|
- name: Create a scope2 client scope
|
||||||
|
community.general.keycloak_clientscope:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
name: scope2
|
||||||
|
description: "test 2"
|
||||||
|
protocol: openid-connect
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
### Realm
|
||||||
|
- name: adjust client-scope types in realm
|
||||||
|
community.general.keycloak_clientscope_type:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
default_clientscopes: ['scope1', 'scope2']
|
||||||
|
optional_clientscopes: []
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Assert that client scope types are set
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.end_state != {}
|
||||||
|
- '"scope1" in result.end_state.default_clientscopes'
|
||||||
|
- '"scope2" in result.end_state.default_clientscopes'
|
||||||
|
- result.end_state.default_clientscopes|length == 2
|
||||||
|
- result.end_state.optional_clientscopes|length == 0
|
||||||
|
|
||||||
|
- name: adjust client-scope types in realm again
|
||||||
|
community.general.keycloak_clientscope_type:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
default_clientscopes: ['scope1', 'scope2']
|
||||||
|
optional_clientscopes: []
|
||||||
|
register: result
|
||||||
|
failed_when: result is changed
|
||||||
|
|
||||||
|
- name: adjust client-scope types in realm move scope 2 to optional
|
||||||
|
community.general.keycloak_clientscope_type:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
default_clientscopes: ['scope1']
|
||||||
|
optional_clientscopes: ['scope2']
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Assert that client scope types are set
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.end_state != {}
|
||||||
|
- '"scope1" in result.end_state.default_clientscopes'
|
||||||
|
- '"scope2" in result.end_state.optional_clientscopes'
|
||||||
|
- result.end_state.default_clientscopes|length == 1
|
||||||
|
- result.end_state.optional_clientscopes|length == 1
|
||||||
|
|
||||||
|
### Client
|
||||||
|
- name: adjust client-scope types in client
|
||||||
|
community.general.keycloak_clientscope_type:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
default_clientscopes: ['scope1', 'scope2']
|
||||||
|
optional_clientscopes: []
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Assert that client scope types are set
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.end_state != {}
|
||||||
|
- '"scope1" in result.end_state.default_clientscopes'
|
||||||
|
- '"scope2" in result.end_state.default_clientscopes'
|
||||||
|
- result.end_state.default_clientscopes|length == 2
|
||||||
|
- result.end_state.optional_clientscopes|length == 0
|
||||||
|
|
||||||
|
- name: adjust client-scope types in client again
|
||||||
|
community.general.keycloak_clientscope_type:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
default_clientscopes: ['scope1', 'scope2']
|
||||||
|
optional_clientscopes: []
|
||||||
|
register: result
|
||||||
|
failed_when: result is changed
|
||||||
|
|
||||||
|
- name: adjust client-scope types in client move scope 2 to optional
|
||||||
|
community.general.keycloak_clientscope_type:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
default_clientscopes: ['scope1']
|
||||||
|
optional_clientscopes: ['scope2']
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Assert that client scope types are set
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.end_state != {}
|
||||||
|
- '"scope1" in result.end_state.default_clientscopes'
|
||||||
|
- '"scope2" in result.end_state.optional_clientscopes'
|
||||||
|
- result.end_state.default_clientscopes|length == 1
|
||||||
|
- result.end_state.optional_clientscopes|length == 1
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
# 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
|
||||||
|
admin_realm: master
|
||||||
|
admin_user: admin
|
||||||
|
admin_password: password
|
||||||
|
realm: clientscope-type-realm
|
||||||
|
client_id: clientscope-type-client
|
Loading…
Reference in a new issue