s/clientscope/client_scope/

This commit is contained in:
Felix Grzelka
2026-06-08 11:53:44 +00:00
parent 1794d4ff9b
commit 808d137e4c
8 changed files with 398 additions and 382 deletions

View File

@@ -71,7 +71,7 @@ All Keycloak administration modules from `community.general` are provided in thi
* `keycloak_client_rolemapping`: manage client role mappings for users and groups.
* `keycloak_client_rolescope`: manage client role scope mappings.
* `keycloak_client_scope`: manage client scopes and protocol mappers (replaces `community.general.keycloak_clientscope`).
* `keycloak_clientscope_type`: manage default and optional client scope assignments.
* `keycloak_client_scopeee_type`: manage default and optional client scope assignments.
* `keycloak_clientsecret_info`: retrieve client secret information.
* `keycloak_clientsecret_regenerate`: regenerate a client secret.
* `keycloak_clienttemplate`: manage legacy client templates.

View File

@@ -14,8 +14,8 @@ action_groups:
- keycloak_client_rolemapping
- keycloak_client_rolescope
- keycloak_client_scope
- keycloak_clientscope_type
- keycloak_clientscope_rolemappings
- keycloak_client_scope_type
- keycloak_client_scope_rolemappings
- keycloak_clientsecret_info
- keycloak_clientsecret_regenerate
- keycloak_clienttemplate
@@ -44,3 +44,19 @@ plugin_routing:
warning_text: >-
The module has been renamed to keycloak_client_scope for Keycloak 17+ (Quarkus).
Update playbooks to use middleware_automation.keycloak.keycloak_client_scope.
keycloak_clientscope_type:
redirect: middleware_automation.keycloak.keycloak_client_scope_type
deprecation:
removal_version: 5.0.0
warning_text: >-
The module has been renamed to keycloak_client_scope_type for Keycloak 17+ (Quarkus).
Update playbooks to use middleware_automation.keycloak.keycloak_client_scope_type.
keycloak_clientscope_rolemappings:
redirect: middleware_automation.keycloak.keycloak_client_scope_rolemappings
deprecation:
removal_version: 5.0.0
warning_text: >-
The module has been renamed to keycloak_client_scope_rolemappings for Keycloak 17+ (Quarkus).
Update playbooks to use middleware_automation.keycloak.keycloak_client_scope_rolemappings.

View File

@@ -37,8 +37,8 @@
- keycloak_client_rolemapping
- keycloak_client_rolescope
- keycloak_client_scope
- keycloak_clientscope_type
- keycloak_clientscope_rolemappings
- keycloak_client_scope_type
- keycloak_client_scope_rolemappings
- keycloak_clientsecret_info
- keycloak_clientsecret_regenerate
- keycloak_clienttemplate
@@ -265,10 +265,10 @@
- "'404' not in (clienttemplate_result.msg | default(''))"
- "'Not Found' not in (clienttemplate_result.msg | default(''))"
- name: keycloak_clientscope_type — attach scope as optional on realm
middleware_automation.keycloak.keycloak_clientscope_type:
- name: keycloak_client_scope_type — attach scope as optional on realm
middleware_automation.keycloak.keycloak_client_scope_type:
realm: "{{ target_realm }}"
optional_clientscopes:
optional_client_scopes:
- "{{ scope }}"
- name: keycloak_user_rolemapping — assign realm role to user
@@ -304,49 +304,49 @@
- "{{ role }}"
state: present
- name: keycloak_clientscope_rolemappings — map client roles to clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
- name: keycloak_client_scope_rolemappings — map client roles to client scope
middleware_automation.keycloak.keycloak_client_scope_rolemappings:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
clientscope_id: "{{ scope }}"
client_scope_id: "{{ scope }}"
role_names:
- "{{ client_role }}"
register: clientscope_rolemappings_result
register: client_scope_rolemappings_result
- name: Assert client scope role mappings were created
ansible.builtin.assert:
that:
- clientscope_rolemappings_result is changed
- clientscope_rolemappings_result.end_state | length == 1
- client_scope_rolemappings_result is changed
- client_scope_rolemappings_result.end_state | length == 1
- name: keycloak_clientscope_rolemappings — remap client role (idempotency)
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
- name: keycloak_client_scope_rolemappings — remap client role (idempotency)
middleware_automation.keycloak.keycloak_client_scope_rolemappings:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
clientscope_id: "{{ scope }}"
client_scope_id: "{{ scope }}"
role_names:
- "{{ client_role }}"
register: clientscope_rolemappings_idempotent_result
register: client_scope_rolemappings_idempotent_result
- name: Assert client scope role mappings are idempotent
ansible.builtin.assert:
that:
- clientscope_rolemappings_idempotent_result is not changed
- clientscope_rolemappings_idempotent_result.end_state | length == 1
- client_scope_rolemappings_idempotent_result is not changed
- client_scope_rolemappings_idempotent_result.end_state | length == 1
- name: keycloak_clientscope_rolemappings — map realm role to clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
- name: keycloak_client_scope_rolemappings — map realm role to client scope
middleware_automation.keycloak.keycloak_client_scope_rolemappings:
realm: "{{ target_realm }}"
clientscope_id: "{{ scope }}"
client_scope_id: "{{ scope }}"
role_names:
- "{{ role }}"
register: clientscope_realm_rolemappings_result
register: client_scope_realm_rolemappings_result
- name: Assert realm role was mapped to clientscope
- name: Assert realm role was mapped to client_scope
ansible.builtin.assert:
that:
- clientscope_realm_rolemappings_result is changed
- clientscope_realm_rolemappings_result.end_state | length == 1
- client_scope_realm_rolemappings_result is changed
- client_scope_realm_rolemappings_result.end_state | length == 1
- name: keycloak_user — set email_verified explicitly
middleware_automation.keycloak.keycloak_user:
@@ -517,19 +517,19 @@
name: "{{ authz_scope }}"
state: absent
- name: keycloak_clientscope_rolemappings — remove realm role from clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
- name: keycloak_client_scope_rolemappings — remove realm role from client scope
middleware_automation.keycloak.keycloak_client_scope_rolemappings:
realm: "{{ target_realm }}"
clientscope_id: "{{ scope }}"
client_scope_id: "{{ scope }}"
role_names:
- "{{ role }}"
state: absent
- name: keycloak_clientscope_rolemappings — remove client role from clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
- name: keycloak_client_scope_rolemappings — remove client role from client scope
middleware_automation.keycloak.keycloak_client_scope_rolemappings:
realm: "{{ target_realm }}"
client_id: "{{ client }}"
clientscope_id: "{{ scope }}"
client_scope_id: "{{ scope }}"
role_names:
- "{{ client_role }}"
state: absent

View File

@@ -57,23 +57,23 @@ 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}"
URL_CLIENTSCOPE_SCOPE_MAPPINGS = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings"
URL_CLIENTSCOPE_SCOPE_MAPPINGS_REALM = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings/realm"
URL_CLIENTSCOPE_SCOPE_MAPPINGS_CLIENT = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings/clients/{client}"
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_CLIENT_SCOPES = "{url}/admin/realms/{realm}/client-scopes"
URL_CLIENT_SCOPE = "{url}/admin/realms/{realm}/client-scopes/{id}"
URL_CLIENT_SCOPE_SCOPE_MAPPINGS = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings"
URL_CLIENT_SCOPE_SCOPE_MAPPINGS_REALM = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings/realm"
URL_CLIENT_SCOPE_SCOPE_MAPPINGS_CLIENT = "{url}/admin/realms/{realm}/client-scopes/{id}/scope-mappings/clients/{client}"
URL_CLIENT_SCOPE_PROTOCOLMAPPERS = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models"
URL_CLIENT_SCOPE_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_DEFAULT_CLIENT_SCOPES = "{url}/admin/realms/{realm}/default-default-client-scopes"
URL_DEFAULT_CLIENT_SCOPE = "{url}/admin/realms/{realm}/default-default-client-scopes/{id}"
URL_OPTIONAL_CLIENT_SCOPES = "{url}/admin/realms/{realm}/default-optional-client-scopes"
URL_OPTIONAL_CLIENT_SCOPE = "{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_DEFAULT_CLIENT_SCOPES = "{url}/admin/realms/{realm}/clients/{cid}/default-client-scopes"
URL_CLIENT_DEFAULT_CLIENT_SCOPE = "{url}/admin/realms/{realm}/clients/{cid}/default-client-scopes/{id}"
URL_CLIENT_OPTIONAL_CLIENT_SCOPES = "{url}/admin/realms/{realm}/clients/{cid}/optional-client-scopes"
URL_CLIENT_OPTIONAL_CLIENT_SCOPE = "{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_AVAILABLE = (
@@ -1289,22 +1289,22 @@ class KeycloakAPI:
except Exception as e:
self.fail_request(e, msg=f"Could not delete client template {id} in realm {realm}: {e}")
def get_clientscopes(self, realm: str = "master"):
def get_client_scopes(self, realm: str = "master"):
"""Fetch the name and ID of all client scopes on the Keycloak server.
To fetch the full data of the group, make a subsequent call to
get_clientscope_by_clientscopeid, passing in the ID of the group you wish to return.
get_client_scope_by_client_scopeid, passing in the ID of the group you wish to return.
:param realm: Realm in which the client scope resides; default 'master'.
:return The client scopes of this realm (default "master")
"""
clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm)
client_scopes_url = URL_CLIENT_SCOPES.format(url=self.baseurl, realm=realm)
try:
return self._request_and_deserialize(clientscopes_url, method="GET")
return self._request_and_deserialize(client_scopes_url, method="GET")
except Exception as e:
self.fail_request(e, msg=f"Could not fetch list of client scopes in realm {realm}: {e}")
def get_clientscope_by_clientscopeid(self, cid, realm: str = "master"):
def get_client_scope_by_client_scopeid(self, cid, realm: str = "master"):
"""Fetch a keycloak client scope from the provided realm using the client scope's unique ID.
If the client scope does not exist, None is returned.
@@ -1313,9 +1313,9 @@ class KeycloakAPI:
:param cid: UUID of the client scope to be returned
:param realm: Realm in which the client scope resides; default 'master'.
"""
clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=cid)
client_scope_url = URL_CLIENT_SCOPE.format(url=self.baseurl, realm=realm, id=cid)
try:
return self._request_and_deserialize(clientscope_url, method="GET")
return self._request_and_deserialize(client_scope_url, method="GET")
except HTTPError as e:
if e.code == HTTPStatus.NOT_FOUND:
@@ -1325,7 +1325,7 @@ class KeycloakAPI:
except Exception as e:
self.module.fail_json(msg=f"Could not client scope group {cid} in realm {realm}: {e}")
def get_clientscope_by_name(self, name, realm: str = "master"):
def get_client_scope_by_name(self, name, realm: str = "master"):
"""Fetch a keycloak client scope within a realm based on its name.
The Keycloak API does not allow filtering of the client scopes resource by name.
@@ -1337,44 +1337,44 @@ class KeycloakAPI:
:param realm: Realm in which the client scope resides; default 'master'
"""
try:
all_clientscopes = self.get_clientscopes(realm=realm)
all_client_scopes = self.get_client_scopes(realm=realm)
for clientscope in all_clientscopes:
if clientscope["name"] == name:
return self.get_clientscope_by_clientscopeid(clientscope["id"], realm=realm)
for client_scope in all_client_scopes:
if client_scope["name"] == name:
return self.get_client_scope_by_client_scopeid(client_scope["id"], realm=realm)
return None
except Exception as e:
self.module.fail_json(msg=f"Could not fetch client scope {name} in realm {realm}: {e}")
def create_clientscope(self, clientscoperep, realm: str = "master"):
def create_client_scope(self, client_scoperep, realm: str = "master"):
"""Create a Keycloak client scope.
:param clientscoperep: a ClientScopeRepresentation of the clientscope to be created. Must contain at minimum the field name.
:param client_scoperep: a ClientScopeRepresentation of the client scope to be created. Must contain at minimum the field name.
:return: HTTPResponse object on success
"""
clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm)
client_scopes_url = URL_CLIENT_SCOPES.format(url=self.baseurl, realm=realm)
try:
return self._request(clientscopes_url, method="POST", data=json.dumps(clientscoperep))
return self._request(client_scopes_url, method="POST", data=json.dumps(client_scoperep))
except Exception as e:
self.fail_request(e, msg=f"Could not create clientscope {clientscoperep['name']} in realm {realm}: {e}")
self.fail_request(e, msg=f"Could not create client scope {client_scoperep['name']} in realm {realm}: {e}")
def update_clientscope(self, clientscoperep, realm: str = "master"):
def update_client_scope(self, client_scoperep, realm: str = "master"):
"""Update an existing client scope.
:param grouprep: A GroupRepresentation of the updated group.
:return HTTPResponse object on success
"""
clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=clientscoperep["id"])
client_scope_url = URL_CLIENT_SCOPE.format(url=self.baseurl, realm=realm, id=client_scoperep["id"])
try:
return self._request(clientscope_url, method="PUT", data=json.dumps(clientscoperep))
return self._request(client_scope_url, method="PUT", data=json.dumps(client_scoperep))
except Exception as e:
self.fail_request(e, msg=f"Could not update clientscope {clientscoperep['name']} in realm {realm}: {e}")
self.fail_request(e, msg=f"Could not update client scope {client_scoperep['name']} in realm {realm}: {e}")
def delete_clientscope(self, name=None, cid=None, realm: str = "master"):
def delete_client_scope(self, name=None, cid=None, realm: str = "master"):
"""Delete a client scope. One of name or cid must be provided.
Providing the client scope ID is preferred as it avoids a second lookup to
@@ -1393,9 +1393,9 @@ class KeycloakAPI:
# in the case that both are provided, prefer the ID, since it is one
# less lookup.
if cid is None and name is not None:
for clientscope in self.get_clientscopes(realm=realm):
if clientscope["name"] == name:
cid = clientscope["id"]
for client_scope in self.get_client_scopes(realm=realm):
if client_scope["name"] == name:
cid = client_scope["id"]
break
# if the group doesn't exist - no problem, nothing to delete.
@@ -1403,30 +1403,30 @@ class KeycloakAPI:
return None
# should have a good cid by here.
clientscope_url = URL_CLIENTSCOPE.format(realm=realm, id=cid, url=self.baseurl)
client_scope_url = URL_CLIENT_SCOPE.format(realm=realm, id=cid, url=self.baseurl)
try:
return self._request(clientscope_url, method="DELETE")
return self._request(client_scope_url, method="DELETE")
except Exception as e:
self.fail_request(e, msg=f"Unable to delete client scope {cid}: {e}")
def get_clientscope_protocolmappers(self, cid, realm: str = "master"):
def get_client_scope_protocolmappers(self, cid, realm: str = "master"):
"""Fetch the name and ID of all client scopes on the Keycloak server.
To fetch the full data of the group, make a subsequent call to
get_clientscope_by_clientscopeid, passing in the ID of the group you wish to return.
get_client_scope_by_client_scopeid, passing in the ID of the group you wish to return.
:param cid: id of client scope (not name).
:param realm: Realm in which the clientscope resides; default 'master'.
:param realm: Realm in which the client_scope resides; default 'master'.
:return The protocolmappers of this realm (default "master")
"""
protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(id=cid, url=self.baseurl, realm=realm)
protocolmappers_url = URL_CLIENT_SCOPE_PROTOCOLMAPPERS.format(id=cid, url=self.baseurl, realm=realm)
try:
return self._request_and_deserialize(protocolmappers_url, method="GET")
except Exception as e:
self.fail_request(e, msg=f"Could not fetch list of protocolmappers in realm {realm}: {e}")
def get_clientscope_protocolmapper_by_protocolmapperid(self, pid, cid, realm: str = "master"):
def get_client_scope_protocolmapper_by_protocolmapperid(self, pid, cid, realm: str = "master"):
"""Fetch a keycloak client scope from the provided realm using the client scope's unique ID.
If the client scope does not exist, None is returned.
@@ -1437,7 +1437,7 @@ class KeycloakAPI:
:param cid: UUID of the client scope to be returned
:param realm: Realm in which the client scope resides; default 'master'.
"""
protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=pid)
protocolmapper_url = URL_CLIENT_SCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=pid)
try:
return self._request_and_deserialize(protocolmapper_url, method="GET")
@@ -1449,7 +1449,7 @@ class KeycloakAPI:
except Exception as e:
self.module.fail_json(msg=f"Could not fetch protocolmapper {cid} in realm {realm}: {e}")
def get_clientscope_protocolmapper_by_name(self, cid, name, realm: str = "master"):
def get_client_scope_protocolmapper_by_name(self, cid, name, realm: str = "master"):
"""Fetch a keycloak client scope within a realm based on its name.
The Keycloak API does not allow filtering of the client scopes resource by name.
@@ -1462,11 +1462,11 @@ class KeycloakAPI:
:param realm: Realm in which the client scope resides; default 'master'
"""
try:
all_protocolmappers = self.get_clientscope_protocolmappers(cid, realm=realm)
all_protocolmappers = self.get_client_scope_protocolmappers(cid, realm=realm)
for protocolmapper in all_protocolmappers:
if protocolmapper["name"] == name:
return self.get_clientscope_protocolmapper_by_protocolmapperid(
return self.get_client_scope_protocolmapper_by_protocolmapperid(
protocolmapper["id"], cid, realm=realm
)
@@ -1475,27 +1475,27 @@ class KeycloakAPI:
except Exception as e:
self.module.fail_json(msg=f"Could not fetch protocolmapper {name} in realm {realm}: {e}")
def create_clientscope_protocolmapper(self, cid, mapper_rep, realm: str = "master"):
def create_client_scope_protocolmapper(self, cid, mapper_rep, realm: str = "master"):
"""Create a Keycloak client scope protocolmapper.
:param cid: Id of the client scope.
:param mapper_rep: a ProtocolMapperRepresentation of the protocolmapper to be created. Must contain at minimum the field name.
:return: HTTPResponse object on success
"""
protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(url=self.baseurl, id=cid, realm=realm)
protocolmappers_url = URL_CLIENT_SCOPE_PROTOCOLMAPPERS.format(url=self.baseurl, id=cid, realm=realm)
try:
return self._request(protocolmappers_url, method="POST", data=json.dumps(mapper_rep))
except Exception as e:
self.fail_request(e, msg=f"Could not create protocolmapper {mapper_rep['name']} in realm {realm}: {e}")
def update_clientscope_protocolmappers(self, cid, mapper_rep, realm: str = "master"):
def update_client_scope_protocolmappers(self, cid, mapper_rep, realm: str = "master"):
"""Update an existing client scope.
:param cid: Id of the client scope.
:param mapper_rep: A ProtocolMapperRepresentation of the updated protocolmapper.
:return HTTPResponse object on success
"""
protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(
protocolmapper_url = URL_CLIENT_SCOPE_PROTOCOLMAPPER.format(
url=self.baseurl, realm=realm, id=cid, mapper_id=mapper_rep["id"]
)
@@ -1507,37 +1507,37 @@ class KeycloakAPI:
e, msg=f"Could not update protocolmappers for client scope {mapper_rep} in realm {realm}: {e}"
)
def get_default_clientscopes(self, realm, client_id=None):
def get_default_client_scopes(self, realm, client_id=None):
"""Fetch the name and ID of all client scopes 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.
get_client_scope_by_client_scopeid, passing in the ID of the client scope you wish to return.
:param realm: Realm in which the client scope resides.
:param client_id: The client in which the client scope resides.
:return The default client scopes 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)
url = URL_DEFAULT_CLIENT_SCOPES if client_id is None else URL_CLIENT_DEFAULT_CLIENT_SCOPES
return self._get_client_scopes_of_type(realm, url, "default", client_id)
def get_optional_clientscopes(self, realm, client_id=None):
def get_optional_client_scopes(self, realm, client_id=None):
"""Fetch the name and ID of all client scopes 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.
get_client_scope_by_client_scopeid, passing in the ID of the client scope you wish to return.
:param realm: Realm in which the client scope resides.
:param client_id: The client in which the client scope resides.
:return The optional client scopes 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)
url = URL_OPTIONAL_CLIENT_SCOPES if client_id is None else URL_CLIENT_OPTIONAL_CLIENT_SCOPES
return self._get_client_scopes_of_type(realm, url, "optional", client_id)
def _get_clientscopes_of_type(self, realm, url_template, scope_type, client_id=None):
def _get_client_scopes_of_type(self, realm, url_template, scope_type, client_id=None):
"""Fetch the name and ID of all client scopes 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.
get_client_scope_by_client_scopeid, passing in the ID of the client scope you wish to return.
:param realm: Realm in which the client scope resides.
:param url_template the template for the right type
@@ -1546,75 +1546,75 @@ class KeycloakAPI:
:return The client scopes of the specified type of this realm
"""
if client_id is None:
clientscopes_url = url_template.format(url=self.baseurl, realm=realm)
client_scopes_url = url_template.format(url=self.baseurl, realm=realm)
try:
return self._request_and_deserialize(clientscopes_url, method="GET")
return self._request_and_deserialize(client_scopes_url, method="GET")
except Exception as e:
self.fail_request(e, msg=f"Could not fetch list of {scope_type} client scopes in realm {realm}: {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)
client_scopes_url = url_template.format(url=self.baseurl, realm=realm, cid=cid)
try:
return self._request_and_deserialize(clientscopes_url, method="GET")
return self._request_and_deserialize(client_scopes_url, method="GET")
except Exception as e:
self.fail_request(
e,
msg=f"Could not fetch list of {scope_type} clientscopes in client {client_id}: {clientscopes_url}",
msg=f"Could not fetch list of {scope_type} client scopes in client {client_id}: {client_scopes_url}",
)
def _decide_url_type_clientscope(self, client_id=None, scope_type="default"):
def _decide_url_type_client_scope(self, client_id=None, scope_type="default"):
"""Decides which url to use.
:param scope_type this can be either optional or default
:param client_id: The client in which the client scope resides.
"""
if client_id is None:
if scope_type == "default":
return URL_DEFAULT_CLIENTSCOPE
return URL_DEFAULT_CLIENT_SCOPE
if scope_type == "optional":
return URL_OPTIONAL_CLIENTSCOPE
return URL_OPTIONAL_CLIENT_SCOPE
else:
if scope_type == "default":
return URL_CLIENT_DEFAULT_CLIENTSCOPE
return URL_CLIENT_DEFAULT_CLIENT_SCOPE
if scope_type == "optional":
return URL_CLIENT_OPTIONAL_CLIENTSCOPE
return URL_CLIENT_OPTIONAL_CLIENT_SCOPE
def add_default_clientscope(self, id, realm: str = "master", client_id=None):
def add_default_client_scope(self, id, realm: str = "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 client scope resides.
:param client_id: The client in which the client scope resides.
"""
self._action_type_clientscope(id, client_id, "default", realm, "add")
self._action_type_client_scope(id, client_id, "default", realm, "add")
def add_optional_clientscope(self, id, realm: str = "master", client_id=None):
def add_optional_client_scope(self, id, realm: str = "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 client scope resides.
:param client_id: The client in which the client scope resides.
"""
self._action_type_clientscope(id, client_id, "optional", realm, "add")
self._action_type_client_scope(id, client_id, "optional", realm, "add")
def delete_default_clientscope(self, id, realm: str = "master", client_id=None):
def delete_default_client_scope(self, id, realm: str = "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 client scope resides.
:param client_id: The client in which the client scope resides.
"""
self._action_type_clientscope(id, client_id, "default", realm, "delete")
self._action_type_client_scope(id, client_id, "default", realm, "delete")
def delete_optional_clientscope(self, id, realm: str = "master", client_id=None):
def delete_optional_client_scope(self, id, realm: str = "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 client scope resides.
:param client_id: The client in which the client scope resides.
"""
self._action_type_clientscope(id, client_id, "optional", realm, "delete")
self._action_type_client_scope(id, client_id, "optional", realm, "delete")
def _action_type_clientscope(
def _action_type_client_scope(
self, id=None, client_id=None, scope_type="default", realm: str = "master", action="add"
):
"""Delete or add a client scope of type.
@@ -1625,16 +1625,16 @@ class KeycloakAPI:
"""
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(
client_scope_type_url = self._decide_url_type_client_scope(client_id, scope_type).format(
realm=realm, id=id, cid=cid, url=self.baseurl
)
try:
method = "PUT" if action == "add" else "DELETE"
return self._request(clientscope_type_url, method=method)
return self._request(client_scope_type_url, method=method)
except Exception as e:
place = "realm" if client_id is None else f"client {client_id}"
self.fail_request(e, msg=f"Unable to {action} {scope_type} clientscope {id} @ {place} : {e}")
self.fail_request(e, msg=f"Unable to {action} {scope_type} client_scope {id} @ {place} : {e}")
def create_clientsecret(self, id, realm: str = "master"):
"""Generate a new client secret by id
@@ -3259,77 +3259,77 @@ class KeycloakAPI:
except Exception:
return False
def get_all_clientscope_scope_mappings(self, clientscope_id, realm: str = "master"):
def get_all_client_scope_scope_mappings(self, client_scope_id, realm: str = "master"):
"""Fetch all (realm and client) roles (scope-mappings) associated with the client scope for a specific client scope on the Keycloak server.
:param clientscope_id: ID of the clientscope from which to obtain the associated roles.
:param client_scope_id: ID of the client scope from which to obtain the associated roles.
:param realm: Realm from which to obtain the scope.
:return: The client scope scope-mappings.
"""
client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS.format(url=self.baseurl, realm=realm, id=clientscope_id)
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS.format(url=self.baseurl, realm=realm, id=client_scope_id)
try:
return self._request_and_deserialize(client_role_scope_url, method="GET")
except Exception as e:
self.fail_request(e, msg=f"Could not fetch roles for client-scope {clientscope_id} in realm {realm}: {e}")
self.fail_request(e, msg=f"Could not fetch roles for client-scope {client_scope_id} in realm {realm}: {e}")
def get_clientscope_scope_mappings_realm(self, clientscope_id, realm: str = "master"):
def get_client_scope_scope_mappings_realm(self, client_scope_id, realm: str = "master"):
"""Fetch the realm roles (scope-mappings) associated with the client scope for a specific client scope on the Keycloak server.
:param clientscope_id: ID of the clientscope from which to obtain the associated roles.
:param client_scope_id: ID of the client scope from which to obtain the associated roles.
:param realm: Realm from which to obtain the scope.
:return: The client scope realm scope-mappings.
"""
client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=clientscope_id
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=client_scope_id
)
try:
return self._request_and_deserialize(client_role_scope_url, method="GET")
except Exception as e:
self.fail_request(
e, msg=f"Could not fetch realm roles for client-scope {clientscope_id} in realm {realm}: {e}"
e, msg=f"Could not fetch realm roles for client-scope {client_scope_id} in realm {realm}: {e}"
)
def get_clientscope_scope_mappings_client(self, clientscope_id, client_id, realm: str = "master"):
def get_client_scope_scope_mappings_client(self, client_scope_id, client_id, realm: str = "master"):
"""Fetch the client roles (scope-mappings) associated with the client scope for a specific client scope and client on the Keycloak server.
:param clientscope_id: ID of the clientscope from which to obtain the associated roles.
:param client_scope_id: ID of the client scope from which to obtain the associated roles.
:param clientid: ID of the client from which to obtain the associated roles.
:param realm: Realm from which to obtain the scope.
:return: The client scope client scope-mappings.
"""
client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=clientscope_id, client=client_id
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=client_scope_id, client=client_id
)
try:
return self._request_and_deserialize(client_role_scope_url, method="GET")
except Exception as e:
self.fail_request(
e,
msg=f"Could not fetch client roles from client {client_id} for client-scope {clientscope_id} in realm {realm}: {e}",
msg=f"Could not fetch client roles from client {client_id} for client-scope {client_scope_id} in realm {realm}: {e}",
)
def get_client_role_scope_from_client(self, clientid, clientscopeid, realm: str = "master"):
def get_client_role_scope_from_client(self, clientid, client_scopeid, realm: str = "master"):
"""Fetch the roles associated with the client's scope for a specific client on the Keycloak server.
:param clientid: ID of the client from which to obtain the associated roles.
:param clientscopeid: ID of the client who owns the roles.
:param client_scopeid: ID of the client who owns the roles.
:param realm: Realm from which to obtain the scope.
:return: The client scope of roles from specified client.
"""
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(
url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid
url=self.baseurl, realm=realm, id=clientid, scopeid=client_scopeid
)
try:
return self._request_and_deserialize(client_role_scope_url, method="GET")
except Exception as e:
self.fail_request(e, msg=f"Could not fetch roles scope for client {clientid} in realm {realm}: {e}")
def update_client_role_scope_from_client(self, payload, clientid, clientscopeid, realm: str = "master"):
def update_client_role_scope_from_client(self, payload, clientid, client_scopeid, realm: str = "master"):
"""Update and fetch the roles associated with the client's scope on the Keycloak server.
:param payload: List of roles to be added to the scope.
:param clientid: ID of the client to update scope.
:param clientscopeid: ID of the client who owns the roles.
:param client_scopeid: ID of the client who owns the roles.
:param realm: Realm from which to obtain the clients.
:return: The client scope of roles from specified client.
"""
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(
url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid
url=self.baseurl, realm=realm, id=clientid, scopeid=client_scopeid
)
try:
self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
@@ -3337,18 +3337,18 @@ class KeycloakAPI:
except Exception as e:
self.fail_request(e, msg=f"Could not update roles scope for client {clientid} in realm {realm}: {e}")
return self.get_client_role_scope_from_client(clientid, clientscopeid, realm)
return self.get_client_role_scope_from_client(clientid, client_scopeid, realm)
def delete_client_role_scope_from_client(self, payload, clientid, clientscopeid, realm: str = "master"):
def delete_client_role_scope_from_client(self, payload, clientid, client_scopeid, realm: str = "master"):
"""Delete the roles contains in the payload from the client's scope on the Keycloak server.
:param payload: List of roles to be deleted.
:param clientid: ID of the client to delete roles from scope.
:param clientscopeid: ID of the client who owns the roles.
:param client_scopeid: ID of the client who owns the roles.
:param realm: Realm from which to obtain the clients.
:return: The client scope of roles from specified client.
"""
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(
url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid
url=self.baseurl, realm=realm, id=clientid, scopeid=client_scopeid
)
try:
self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
@@ -3356,20 +3356,20 @@ class KeycloakAPI:
except Exception as e:
self.fail_request(e, msg=f"Could not delete roles scope for client {clientid} in realm {realm}: {e}")
return self.get_client_role_scope_from_client(clientid, clientscopeid, realm)
return self.get_client_role_scope_from_client(clientid, client_scopeid, realm)
def update_clientscope_scope_mappings_client(
self, payload: list[dict], clientscope_id: str, client_id: str, realm: str = "master"
def update_client_scope_scope_mappings_client(
self, payload: list[dict], client_scope_id: str, client_id: str, realm: str = "master"
):
"""Update and fetch the client roles (scope-mappings) associated with the client scope on the Keycloak server.
:param payload: List of client roles to be added to the scope.
:param clientscope_id: ID of the clientscope to update scope-mappings.
:param client_scope_id: ID of the client scope to update scope-mappings.
:param clientid: ID of the client from which to obtain the associated roles.
:param realm: Realm from which to obtain the client.
:return: The client scope client scope-mappings.
"""
client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=clientscope_id, client=client_id
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=client_scope_id, client=client_id
)
try:
self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
@@ -3377,43 +3377,43 @@ class KeycloakAPI:
except Exception as e:
self.fail_request(
e,
msg=f"Could not update scope mappings for client-scope {client_id}.{clientscope_id} in realm {realm}: {e}",
msg=f"Could not update scope mappings for client-scope {client_id}.{client_scope_id} in realm {realm}: {e}",
)
return self.get_clientscope_scope_mappings_client(clientscope_id, client_id, realm)
return self.get_client_scope_scope_mappings_client(client_scope_id, client_id, realm)
def update_clientscope_scope_mappings_realm(self, payload: list[dict], clientscope_id: str, realm: str = "master"):
def update_client_scope_scope_mappings_realm(self, payload: list[dict], client_scope_id: str, realm: str = "master"):
"""Update and fetch the realm roles (scope-mappings) associated with the client scope on the Keycloak server.
:param payload: List of realm roles to be added to the scope.
:param clientscope_id: ID of the clientscope to update scope-mappings.
:param client_scope_id: ID of the client scope to update scope-mappings.
:param realm: Realm from which to obtain the roles.
:return: The client scope realm scope-mappings.
"""
client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=clientscope_id
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=client_scope_id
)
try:
self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
except Exception as e:
self.fail_request(
e, msg=f"Could not update scope mappings for client-scope {clientscope_id} in realm {realm}: {e}"
e, msg=f"Could not update scope mappings for client scope {client_scope_id} in realm {realm}: {e}"
)
return self.get_clientscope_scope_mappings_realm(clientscope_id, realm)
return self.get_client_scope_scope_mappings_realm(client_scope_id, realm)
def delete_clientscope_scope_mappings_client(
self, payload: list[dict], clientscope_id: str, client_id: str, realm: str = "master"
def delete_client_scope_scope_mappings_client(
self, payload: list[dict], client_scope_id: str, client_id: str, realm: str = "master"
):
"""Delete the client roles (scope_mappings) contained in the payload from the client scope on the Keycloak server.
:param payload: List of roles to be deleted.
:param clientscope_id: ID of the clientscope to delete roles from scope-mappings.
:param client_scope_id: ID of the client scope to delete roles from scope-mappings.
:param clientid: ID of the client who owns the roles.
:param realm: Realm from which to obtain the client.
:return: The client scope client scope-mappings.
"""
client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=clientscope_id, client=client_id
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_CLIENT.format(
url=self.baseurl, realm=realm, id=client_scope_id, client=client_id
)
try:
self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
@@ -3421,30 +3421,30 @@ class KeycloakAPI:
except Exception as e:
self.fail_request(
e,
msg=f"Could not delete scope mappings for client-scope {client_id}.{clientscope_id} in realm {realm}: {e}",
msg=f"Could not delete scope mappings for client scope {client_id}.{client_scope_id} in realm {realm}: {e}",
)
return self.get_clientscope_scope_mappings_client(clientscope_id, client_id, realm)
return self.get_client_scope_scope_mappings_client(client_scope_id, client_id, realm)
def delete_clientscope_scope_mappings_realm(self, payload: list[dict], clientscope_id: str, realm: str = "master"):
def delete_client_scope_scope_mappings_realm(self, payload: list[dict], client_scope_id: str, realm: str = "master"):
"""Delete the realm roles (scope_mappings) contained in the payload from the client scope on the Keycloak server.
:param payload: List of roles to be deleted.
:param clientscope_id: ID of the clientscope to delete roles from scope-mappings.
:param client_scope_id: ID of the client scope to delete roles from scope-mappings.
:param realm: Realm from which to obtain the roles.
:return: The client scope realm scope-mappings.
"""
client_role_scope_url = URL_CLIENTSCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=clientscope_id
client_role_scope_url = URL_CLIENT_SCOPE_SCOPE_MAPPINGS_REALM.format(
url=self.baseurl, realm=realm, id=client_scope_id
)
try:
self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
except Exception as e:
self.fail_request(
e, msg=f"Could not delete scope mappings for client-scope {clientscope_id} in realm {realm}: {e}"
e, msg=f"Could not delete scope mappings for client-scope {client_scope_id} in realm {realm}: {e}"
)
return self.get_clientscope_scope_mappings_realm(clientscope_id, realm)
return self.get_client_scope_scope_mappings_realm(client_scope_id, realm)
def get_client_role_scope_from_realm(self, clientid, realm: str = "master"):
"""Fetch the realm roles from the client's scope on the Keycloak server.

View File

@@ -1100,11 +1100,11 @@ def add_default_client_scopes(desired_client, before_client, realm, kc):
missing_scopes = [item for item in desired_default_scope if item not in before_client["defaultClientScopes"]]
if not missing_scopes:
return
client_scopes = kc.get_clientscopes(realm)
client_scopes = kc.get_client_scopes(realm)
for name in missing_scopes:
scope = find_match(client_scopes, "name", name)
if scope:
kc.add_default_clientscope(scope["id"], realm, desired_client["clientId"])
kc.add_default_client_scope(scope["id"], realm, desired_client["clientId"])
def add_optional_client_scopes(desired_client, before_client, realm, kc):
@@ -1139,11 +1139,11 @@ def add_optional_client_scopes(desired_client, before_client, realm, kc):
missing_scopes = [item for item in desired_optional_scope if item not in before_client["optionalClientScopes"]]
if not missing_scopes:
return
client_scopes = kc.get_clientscopes(realm)
client_scopes = kc.get_client_scopes(realm)
for name in missing_scopes:
scope = find_match(client_scopes, "name", name)
if scope:
kc.add_optional_clientscope(scope["id"], realm, desired_client["clientId"])
kc.add_optional_client_scope(scope["id"], realm, desired_client["clientId"])
def remove_default_client_scopes(desired_client, before_client, realm, kc):
@@ -1178,11 +1178,11 @@ def remove_default_client_scopes(desired_client, before_client, realm, kc):
missing_scopes = [item for item in before_default_scope if item not in desired_client["defaultClientScopes"]]
if not missing_scopes:
return
client_scopes = kc.get_default_clientscopes(realm, desired_client["clientId"])
client_scopes = kc.get_default_client_scopes(realm, desired_client["clientId"])
for name in missing_scopes:
scope = find_match(client_scopes, "name", name)
if scope:
kc.delete_default_clientscope(scope["id"], realm, desired_client["clientId"])
kc.delete_default_client_scope(scope["id"], realm, desired_client["clientId"])
def remove_optional_client_scopes(desired_client, before_client, realm, kc):
@@ -1217,11 +1217,11 @@ def remove_optional_client_scopes(desired_client, before_client, realm, kc):
missing_scopes = [item for item in before_optional_scope if item not in desired_client["optionalClientScopes"]]
if not missing_scopes:
return
client_scopes = kc.get_optional_clientscopes(realm, desired_client["clientId"])
client_scopes = kc.get_optional_client_scopes(realm, desired_client["clientId"])
for name in missing_scopes:
scope = find_match(client_scopes, "name", name)
if scope:
kc.delete_optional_clientscope(scope["id"], realm, desired_client["clientId"])
kc.delete_optional_client_scope(scope["id"], realm, desired_client["clientId"])
def main():

View File

@@ -230,7 +230,7 @@ def main():
attributes = module.params.get('attributes')
protocol_mappers = module.params.get('protocol_mappers')
before_scope = kc.get_clientscope_by_name(name, realm=realm)
before_scope = kc.get_client_scope_by_name(name, realm=realm)
if state == 'absent':
if before_scope:
@@ -239,7 +239,7 @@ def main():
result['diff'] = dict(before=before_scope, after='')
if module.check_mode:
module.exit_json(**result)
kc.delete_clientscope(cid=before_scope['id'], realm=realm)
kc.delete_client_scope(cid=before_scope['id'], realm=realm)
result['msg'] = "Client scope {name} has been deleted".format(name=name)
else:
result['msg'] = "Client scope {name} does not exist, doing nothing".format(name=name)
@@ -261,8 +261,8 @@ def main():
if module.check_mode:
module.exit_json(**result)
kc.create_clientscope(scope_rep, realm=realm)
after_scope = kc.get_clientscope_by_name(name, realm=realm)
kc.create_client_scope(scope_rep, realm=realm)
after_scope = kc.get_client_scope_by_name(name, realm=realm)
if protocol_mappers:
for mapper in protocol_mappers:
@@ -272,8 +272,8 @@ def main():
'protocolMapper': mapper['protocolMapper'],
'config': mapper['config'],
}
kc.create_clientscope_protocolmapper(after_scope['id'], mapper_rep, realm=realm)
after_scope = kc.get_clientscope_by_name(name, realm=realm)
kc.create_client_scope_protocolmapper(after_scope['id'], mapper_rep, realm=realm)
after_scope = kc.get_client_scope_by_name(name, realm=realm)
result['end_state'] = after_scope
result['msg'] = "Client scope {name} has been created".format(name=name)
@@ -296,10 +296,10 @@ def main():
result['diff'] = dict(before=before_scope, after=scope_rep)
if module.check_mode:
module.exit_json(**result)
kc.update_clientscope(scope_rep, realm=realm)
kc.update_client_scope(scope_rep, realm=realm)
if protocol_mappers:
existing_mappers = kc.get_clientscope_protocolmappers(before_scope['id'], realm=realm)
existing_mappers = kc.get_client_scope_protocolmappers(before_scope['id'], realm=realm)
existing_mapper_names = {m['name'] for m in existing_mappers}
for mapper in protocol_mappers:
@@ -312,9 +312,9 @@ def main():
'protocolMapper': mapper['protocolMapper'],
'config': mapper['config'],
}
kc.create_clientscope_protocolmapper(before_scope['id'], mapper_rep, realm=realm)
kc.create_client_scope_protocolmapper(before_scope['id'], mapper_rep, realm=realm)
after_scope = kc.get_clientscope_by_name(name, realm=realm)
after_scope = kc.get_client_scope_by_name(name, realm=realm)
result['end_state'] = after_scope
if result['changed']:

View File

@@ -6,7 +6,7 @@
from __future__ import annotations
DOCUMENTATION = r"""
module: keycloak_clientscope_rolemappings
module: keycloak_client_scope_rolemappings
short_description: Allows administration of Keycloak client scope scope mappings to restrict the usage of certain roles to
specific client scopes
@@ -49,7 +49,7 @@ options:
- The Keycloak realm under which clients resides.
default: 'master'
clientscope_id:
client_scope_id:
required: true
type: str
description:
@@ -82,39 +82,39 @@ author:
EXAMPLES = r"""
- name: Add roles to client scope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
middleware_automation.keycloak.keycloak_client_scope_rolemappings:
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
realm: MyCustomRealm
client_id: frontend-client-public
clientscope_id: frontend-clientscope
client_scope_id: frontend-client-scope
role_names:
- backend-role-admin
- backend-role-user
- name: Remove roles from client scope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
middleware_automation.keycloak.keycloak_client_scope_rolemappings:
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
realm: MyCustomRealm
client_id: frontend-client-public
clientscope_id: frontend-clientscope
client_scope_id: frontend-client-scope
role_names:
- backend-role-admin
state: absent
- name: Add realm roles to client scope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
middleware_automation.keycloak.keycloak_client_scope_rolemappings:
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
realm: MyCustomRealm
clientscope_id: frontend-clientscope
client_scope_id: frontend-client-scope
role_names:
- realm-role-admin
- realm-role-user
@@ -164,7 +164,7 @@ def main():
meta_args = dict(
client_id=dict(type="str"),
clientscope_id=dict(type="str", required=True),
client_scope_id=dict(type="str", required=True),
realm=dict(type="str", default="master"),
role_names=dict(type="list", elements="str", required=True),
state=dict(type="str", default="present", choices=["present", "absent"]),
@@ -186,7 +186,7 @@ def main():
realm = module.params["realm"]
client_id = module.params["client_id"]
clientscope_id = module.params["clientscope_id"]
client_scope_id = module.params["client_scope_id"]
role_names = module.params["role_names"]
state = module.params["state"]
@@ -194,9 +194,9 @@ def main():
if not realm_object:
module.fail_json(msg=f"Failed to retrieve realm '{realm}'")
clientscope_object = kc.get_clientscope_by_name(clientscope_id, realm)
if not clientscope_object:
module.fail_json(msg=f"Failed to retrieve client-scope '{clientscope_id}'")
client_scope_object = kc.get_client_scope_by_name(client_scope_id, realm)
if not client_scope_object:
module.fail_json(msg=f"Failed to retrieve client-scope '{client_scope_id}'")
if client_id:
# add client role
@@ -206,11 +206,11 @@ def main():
if client_object["fullScopeAllowed"] and state == "present":
module.fail_json(msg=f"FullScopeAllowed is active for Client '{realm}.{client_id}'")
before_roles = kc.get_clientscope_scope_mappings_client(clientscope_object["id"], client_object["id"], realm)
before_roles = kc.get_client_scope_scope_mappings_client(client_scope_object["id"], client_object["id"], realm)
available_roles_by_name = kc.get_client_roles_by_id(client_object["id"], realm)
else:
# add realm role
before_roles = kc.get_clientscope_scope_mappings_realm(clientscope_object["id"], realm)
before_roles = kc.get_client_scope_scope_mappings_realm(client_scope_object["id"], realm)
available_roles_by_name = kc.get_realm_roles(realm)
# convert to indexed Dict by name
@@ -248,33 +248,33 @@ def main():
if not result["changed"]:
# no changes
result["end_state"] = before_roles
result["msg"] = f"No changes required for clientscope {clientscope_id}."
result["msg"] = f"No changes required for client scope {client_scope_id}."
elif state == "present":
# doing update
if module.check_mode:
result["end_state"] = desired_role_mapping
elif client_id:
result["end_state"] = kc.update_clientscope_scope_mappings_client(
changed_roles, clientscope_object["id"], client_object["id"], realm
result["end_state"] = kc.update_client_scope_scope_mappings_client(
changed_roles, client_scope_object["id"], client_object["id"], realm
)
else:
result["end_state"] = kc.update_clientscope_scope_mappings_realm(
changed_roles, clientscope_object["id"], realm
result["end_state"] = kc.update_client_scope_scope_mappings_realm(
changed_roles, client_scope_object["id"], realm
)
result["msg"] = f"Clientscope scope mappings for {clientscope_id} have been updated"
result["msg"] = f"Clientscope scope mappings for {client_scope_id} have been updated"
else:
# doing delete
if module.check_mode:
result["end_state"] = desired_role_mapping
elif client_id:
result["end_state"] = kc.delete_clientscope_scope_mappings_client(
changed_roles, clientscope_object["id"], client_object["id"], realm
result["end_state"] = kc.delete_client_scope_scope_mappings_client(
changed_roles, client_scope_object["id"], client_object["id"], realm
)
else:
result["end_state"] = kc.delete_clientscope_scope_mappings_realm(
changed_roles, clientscope_object["id"], realm
result["end_state"] = kc.delete_client_scope_scope_mappings_realm(
changed_roles, client_scope_object["id"], realm
)
result["msg"] = f"Clientscope scope mappings for {clientscope_id} have been deleted"
result["msg"] = f"Clientscope scope mappings for {client_scope_id} have been deleted"
module.exit_json(**result)

View File

@@ -6,9 +6,9 @@
from __future__ import annotations
DOCUMENTATION = r"""
module: keycloak_clientscope_type
module: keycloak_client_scope_type
short_description: Set the type of aclientscope in realm or client using Keycloak API
short_description: Set the type of a client scope in a realm or client using the Keycloak API
# Originally added in community.general 6.6.0
version_added: "3.0.0"
@@ -41,13 +41,13 @@ options:
- clientId
type: str
default_clientscopes:
default_client_scopes:
description:
- Client scopes that should be of type default.
type: list
elements: str
optional_clientscopes:
optional_client_scopes:
description:
- Client scopes that should be of type optional.
type: list
@@ -64,26 +64,26 @@ author:
EXAMPLES = r"""
- name: Set default client scopes on realm level
middleware_automation.keycloak.keycloak_clientscope_type:
middleware_automation.keycloak.keycloak_client_scope_type:
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com
auth_realm: master
auth_username: USERNAME
auth_password: PASSWORD
realm: "MyCustomRealm"
default_clientscopes: ['profile', 'roles']
default_client_scopes: ['profile', 'roles']
delegate_to: localhost
- name: Set default and optional client scopes on client level with token auth
middleware_automation.keycloak.keycloak_clientscope_type:
middleware_automation.keycloak.keycloak_client_scope_type:
auth_client_id: admin-cli
auth_keycloak_url: https://auth.example.com
token: TOKEN
realm: "MyCustomRealm"
client_id: "MyCustomClient"
default_clientscopes: ['profile', 'roles']
optional_clientscopes: ['phone']
default_client_scopes: ['profile', 'roles']
optional_client_scopes: ['phone']
delegate_to: localhost
"""
@@ -99,11 +99,11 @@ proposed:
type: dict
sample:
{
"default_clientscopes": [
"default_client_scopes": [
"profile",
"role"
],
"optional_clientscopes": []
"optional_client_scopes": []
}
existing:
description:
@@ -112,11 +112,11 @@ existing:
type: dict
sample:
{
"default_clientscopes": [
"default_client_scopes": [
"profile",
"role"
],
"optional_clientscopes": [
"optional_client_scopes": [
"phone"
]
}
@@ -128,11 +128,11 @@ end_state:
type: dict
sample:
{
"default_clientscopes": [
"default_client_scopes": [
"profile",
"role"
],
"optional_clientscopes": []
"optional_client_scopes": []
}
"""
@@ -146,7 +146,7 @@ from ansible_collections.middleware_automation.keycloak.plugins.module_utils.ide
)
def keycloak_clientscope_type_module():
def keycloak_client_scope_type_module():
"""
Returns an AnsibleModule definition.
@@ -157,8 +157,8 @@ def keycloak_clientscope_type_module():
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"),
default_client_scopes=dict(type="list", elements="str"),
optional_client_scopes=dict(type="list", elements="str"),
)
argument_spec.update(meta_args)
@@ -169,7 +169,7 @@ def keycloak_clientscope_type_module():
required_one_of=(
[
["token", "auth_realm", "auth_username", "auth_password", "auth_client_id", "auth_client_secret"],
["default_clientscopes", "optional_clientscopes"],
["default_client_scopes", "optional_client_scopes"],
]
),
required_together=([["auth_username", "auth_password"]]),
@@ -180,21 +180,21 @@ def keycloak_clientscope_type_module():
return module
def clientscopes_to_add(existing, proposed):
def client_scopes_to_add(existing, proposed):
to_add = []
existing_clientscope_ids = extract_field(existing, "id")
for clientscope in proposed:
if clientscope["id"] not in existing_clientscope_ids:
to_add.append(clientscope)
existing_client_scope_ids = extract_field(existing, "id")
for client_scope in proposed:
if client_scope["id"] not in existing_client_scope_ids:
to_add.append(client_scope)
return to_add
def clientscopes_to_delete(existing, proposed):
def client_scopes_to_delete(existing, proposed):
to_delete = []
proposed_clientscope_ids = extract_field(proposed, "id")
for clientscope in existing:
if clientscope["id"] not in proposed_clientscope_ids:
to_delete.append(clientscope)
proposed_client_scope_ids = extract_field(proposed, "id")
for client_scope in existing:
if client_scope["id"] not in proposed_client_scope_ids:
to_delete.append(client_scope)
return to_delete
@@ -204,21 +204,21 @@ def extract_field(dictionary, field="name"):
def normalize_scopes(scopes):
scopes_copy = scopes.copy()
if isinstance(scopes_copy.get("default_clientscopes"), list):
scopes_copy["default_clientscopes"] = sorted(scopes_copy["default_clientscopes"])
if isinstance(scopes_copy.get("optional_clientscopes"), list):
scopes_copy["optional_clientscopes"] = sorted(scopes_copy["optional_clientscopes"])
if isinstance(scopes_copy.get("default_client_scopes"), list):
scopes_copy["default_client_scopes"] = sorted(scopes_copy["default_client_scopes"])
if isinstance(scopes_copy.get("optional_client_scopes"), list):
scopes_copy["optional_client_scopes"] = sorted(scopes_copy["optional_client_scopes"])
return scopes_copy
def main():
"""
Module keycloak_clientscope_type
Module keycloak_client_scope_type
:return:
"""
module = keycloak_clientscope_type_module()
module = keycloak_client_scope_type_module()
# Obtain access token, initialize API
try:
@@ -230,81 +230,81 @@ def main():
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")
default_client_scopes = module.params.get("default_client_scopes")
optional_client_scopes = module.params.get("optional_client_scopes")
result = dict(changed=False, msg="", proposed={}, existing={}, end_state={})
all_clientscopes = kc.get_clientscopes(realm)
default_clientscopes_real = []
optional_clientscopes_real = []
all_client_scopes = kc.get_client_scopes(realm)
default_client_scopes_real = []
optional_client_scopes_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)
for client_scope in all_client_scopes:
if default_client_scopes is not None and client_scope["name"] in default_client_scopes:
default_client_scopes_real.append(client_scope)
if optional_client_scopes is not None and client_scope["name"] in optional_client_scopes:
optional_client_scopes_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 default_client_scopes is not None and len(default_client_scopes_real) != len(default_client_scopes):
module.fail_json(msg="At least one of the default_client_scopes 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!")
if optional_client_scopes is not None and len(optional_client_scopes_real) != len(optional_client_scopes):
module.fail_json(msg="At least one of the optional_client_scopes 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_client_scopes": "no-change" if default_client_scopes is None else default_client_scopes,
"optional_client_scopes": "no-change" if optional_client_scopes is None else optional_client_scopes,
}
)
default_clientscopes_existing = kc.get_default_clientscopes(realm, client_id)
optional_clientscopes_existing = kc.get_optional_clientscopes(realm, client_id)
default_client_scopes_existing = kc.get_default_client_scopes(realm, client_id)
optional_client_scopes_existing = kc.get_optional_client_scopes(realm, client_id)
result["existing"].update(
{
"default_clientscopes": extract_field(default_clientscopes_existing),
"optional_clientscopes": extract_field(optional_clientscopes_existing),
"default_client_scopes": extract_field(default_client_scopes_existing),
"optional_client_scopes": extract_field(optional_client_scopes_existing),
}
)
if module._diff:
result["diff"] = dict(before=normalize_scopes(result["existing"]), after=normalize_scopes(result["proposed"]))
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_client_scopes_add = client_scopes_to_add(default_client_scopees_existing, defaultclient_scopees_real)
optional_client_scopes_add = client_scopes_to_add(optional_client_scopes_existing, optional_client_scopes_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)
default_client_scopes_delete = client_scopes_to_delete(default_client_scopes_existing, default_client_scopes_real)
optional_client_scopes_delete = client_scopes_to_delete(optional_client_scopes_existing, optional_client_scopes_real)
result["changed"] = any(
len(x) > 0
for x in [
default_clientscopes_add,
optional_clientscopes_add,
default_clientscopes_delete,
optional_clientscopes_delete,
default_client_scopes_add,
optional_client_scopes_add,
default_client_scopes_delete,
optional_client_scopes_delete,
]
)
if module.check_mode:
module.exit_json(**result)
# 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)
# first delete so client_scopes can change type
for client_scope in default_client_scopes_delete:
kc.delete_default_client_scope(client_scope["id"], realm, client_id)
for client_scope in optional_client_scopes_delete:
kc.delete_optional_client_scope(client_scope["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)
for client_scope in default_client_scopes_add:
kc.add_default_client_scope(client_scope["id"], realm, client_id)
for client_scope in optional_client_scopes_add:
kc.add_optional_client_scope(client_scope["id"], realm, client_id)
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)),
"default_client_scopes": extract_field(kc.get_default_client_scopes(realm, client_id)),
"optional_client_scopes": extract_field(kc.get_optional_client_scopes(realm, client_id)),
}
)