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

@@ -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,16 +6,16 @@
from __future__ import annotations
DOCUMENTATION = r"""
module: keycloak_clientscope_rolemappings
module: keycloak_client_scope_rolemappings
short_description: Allows administration of Keycloak clientscope scope mappings to restrict the usage of certain roles to
specific clientscopes
short_description: Allows administration of Keycloak client scope scope mappings to restrict the usage of certain roles to
specific client scopes
# Originally added in community.general 13.1.0
version_added: "3.0.0"
description:
- This module allows you to add or remove Keycloak roles from clientscopes using the Keycloak REST API. It requires access
- This module allows you to add or remove Keycloak roles from client scopes using the Keycloak REST API. It requires access
to the REST API using OpenID Connect; the user connecting and the client being used must have the requisite access rights.
In a default Keycloak installation, C(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.
@@ -49,11 +49,11 @@ options:
- The Keycloak realm under which clients resides.
default: 'master'
clientscope_id:
client_scope_id:
required: true
type: str
description:
- Roles provided in O(role_names) will be added to this clientscope.
- Roles provided in O(role_names) will be added to this client scope.
client_id:
type: str
@@ -81,40 +81,40 @@ author:
"""
EXAMPLES = r"""
- name: Add roles to clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
- name: Add roles to client scope
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 clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
- name: Remove roles from client scope
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 clientscope
middleware_automation.keycloak.keycloak_clientscope_rolemappings:
- name: Add realm roles to client scope
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
@@ -122,7 +122,7 @@ EXAMPLES = r"""
RETURN = r"""
end_state:
description: Representation of clientscope scope mappings after module execution.
description: Representation of client scope scope mappings after module execution.
returned: on success
type: list
elements: dict
@@ -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,15 +6,15 @@
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"
description:
- This module allows you to set the type (optional, default) of clientscopes using the Keycloak REST API. It requires access
- This module allows you to set the type (optional, default) of client scopes using the Keycloak REST API. It requires access
to the REST API using 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.
@@ -36,18 +36,18 @@ options:
client_id:
description:
- The O(client_id) of the client. If not set the clientscope types are set as a default for the realm.
- The O(client_id) of the client. If not set the client scope types are set as a default for the realm.
aliases:
- 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)),
}
)