From c6189bfc51f4e40650d5e376c3093e77c9a9a360 Mon Sep 17 00:00:00 2001 From: pamenon Date: Thu, 23 Apr 2026 12:49:32 +0100 Subject: [PATCH 1/2] Add keycloak_client_scope and keycloak_authentication_flow modules with example playbooks The collection was missing modules for managing client scopes and authentication flows, forcing users to write raw uri calls against the Keycloak Admin REST API. This adds two new modules that leverage the existing KeycloakAPI helper methods: - keycloak_client_scope: create/update/delete client scopes with protocol mappers (supports check_mode and diff) - keycloak_authentication_flow: create/delete authentication flows with execution steps, or copy existing flows (supports check_mode and diff) Also adds three example playbooks using the new modules: - keycloak_client_scope.yml - keycloak_authentication_flow.yml - keycloak_realm_client.yml Made-with: Cursor --- playbooks/keycloak_authentication_flow.yml | 27 ++ playbooks/keycloak_client_scope.yml | 48 +++ playbooks/keycloak_realm_client.yml | 39 +++ .../modules/keycloak_authentication_flow.py | 296 ++++++++++++++++ plugins/modules/keycloak_client_scope.py | 324 ++++++++++++++++++ 5 files changed, 734 insertions(+) create mode 100644 playbooks/keycloak_authentication_flow.yml create mode 100644 playbooks/keycloak_client_scope.yml create mode 100644 playbooks/keycloak_realm_client.yml create mode 100644 plugins/modules/keycloak_authentication_flow.py create mode 100644 plugins/modules/keycloak_client_scope.py diff --git a/playbooks/keycloak_authentication_flow.yml b/playbooks/keycloak_authentication_flow.yml new file mode 100644 index 0000000..38878b5 --- /dev/null +++ b/playbooks/keycloak_authentication_flow.yml @@ -0,0 +1,27 @@ +--- +- name: Playbook for Keycloak Authentication Flow Configuration + hosts: all + vars: + keycloak_admin_user: admin + keycloak_admin_password: "remembertochangeme" + keycloak_url: "http://localhost:8080" + keycloak_realm: TestRealm + tasks: + - name: Create authentication flow with executions + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: "{{ keycloak_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + alias: my-browser-flow + description: "Custom browser authentication flow" + provider_id: basic-flow + executions: + - provider_id: auth-cookie + requirement: ALTERNATIVE + - provider_id: auth-password + requirement: REQUIRED + - provider_id: auth-otp-form + requirement: ALTERNATIVE + state: present diff --git a/playbooks/keycloak_client_scope.yml b/playbooks/keycloak_client_scope.yml new file mode 100644 index 0000000..aa5ed3d --- /dev/null +++ b/playbooks/keycloak_client_scope.yml @@ -0,0 +1,48 @@ +--- +- name: Playbook for Keycloak Client Scope Configuration + hosts: all + vars: + keycloak_admin_user: admin + keycloak_admin_password: "remembertochangeme" + keycloak_url: "http://localhost:8080" + keycloak_realm: TestRealm + tasks: + - name: Create client scope with protocol mappers + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: "{{ keycloak_url }}" + auth_realm: master + auth_username: "{{ keycloak_admin_user }}" + auth_password: "{{ keycloak_admin_password }}" + realm: "{{ keycloak_realm }}" + name: TestClientScope + description: "Client scope created via Ansible" + protocol: openid-connect + protocol_mappers: + - name: email + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: email + claim.name: email + jsonType.label: String + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + - name: firstName + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: firstName + claim.name: given_name + jsonType.label: String + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + - name: username + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: username + claim.name: preferred_username + jsonType.label: String + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + state: present diff --git a/playbooks/keycloak_realm_client.yml b/playbooks/keycloak_realm_client.yml new file mode 100644 index 0000000..ddefd88 --- /dev/null +++ b/playbooks/keycloak_realm_client.yml @@ -0,0 +1,39 @@ +--- +- name: Playbook for Keycloak Realm and Client Configuration + hosts: all + tasks: + - name: Keycloak Realm Role + ansible.builtin.include_role: + name: middleware_automation.keycloak.keycloak_realm + vars: + keycloak_admin_password: "remembertochangeme" + keycloak_realm: TestRealm + keycloak_client_default_roles: + - TestRoleAdmin + - TestRoleUser + keycloak_client_users: + - username: TestUser + password: password + client_roles: + - client: TestClient1 + role: TestRoleUser + realm: TestRealm + - username: TestAdmin + password: password + client_roles: + - client: TestClient1 + role: TestRoleUser + realm: TestRealm + - client: TestClient1 + role: TestRoleAdmin + realm: TestRealm + keycloak_clients: + - name: TestClient1 + client_id: TestClient1 + roles: "{{ keycloak_client_default_roles }}" + realm: TestRealm + public_client: true + web_origins: + - http://testclient1origin/application + - http://testclient1origin/other + users: "{{ keycloak_client_users }}" diff --git a/plugins/modules/keycloak_authentication_flow.py b/plugins/modules/keycloak_authentication_flow.py new file mode 100644 index 0000000..c6ae5e3 --- /dev/null +++ b/plugins/modules/keycloak_authentication_flow.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2024, Contributors to the middleware_automation.keycloak collection +# 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_authentication_flow + +short_description: Allows administration of Keycloak authentication flows via Keycloak API + +description: + - This module allows you to add, remove or modify Keycloak authentication flows 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. + + - This module supports creating new top-level authentication flows, copying existing flows, + and adding execution steps to a flow. + +attributes: + check_mode: + support: full + diff_mode: + support: full + +options: + state: + description: + - State of the authentication flow. + - On V(present), the flow will be created if it does not yet exist. + - On V(absent), the flow will be removed if it exists. + default: 'present' + type: str + choices: + - present + - absent + + alias: + type: str + required: true + description: + - Alias (name) of the authentication flow. + + description: + type: str + description: + - Description of the authentication flow. + default: '' + + realm: + type: str + description: + - The Keycloak realm under which this authentication flow resides. + default: 'master' + + provider_id: + type: str + description: + - The provider ID for the flow. + default: 'basic-flow' + aliases: + - providerId + + copy_from: + type: str + description: + - If set, the new flow is created as a copy of the flow with this alias. + - Cannot be used together with O(executions). + aliases: + - copyFrom + + executions: + type: list + elements: dict + description: + - A list of executions (authenticator steps) to add to the flow. + - Each execution is a dict with keys C(provider_id) (or C(providerId)) and C(requirement). + - Executions are only added when the flow is first created. + default: [] + suboptions: + provider_id: + type: str + required: true + description: + - The authenticator provider ID (e.g. V(auth-cookie), V(auth-password), V(auth-otp-form)). + aliases: + - providerId + requirement: + type: str + required: true + description: + - The requirement level for this execution. + choices: + - REQUIRED + - ALTERNATIVE + - DISABLED + - CONDITIONAL + +extends_documentation_fragment: + - middleware_automation.keycloak.keycloak + - middleware_automation.keycloak.attributes + +author: + - Paulo Menon (@paulomenon) +''' + +EXAMPLES = ''' +- name: Create an authentication flow with executions + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + alias: my-browser-flow + description: "Custom browser flow" + provider_id: basic-flow + executions: + - provider_id: auth-cookie + requirement: ALTERNATIVE + - provider_id: auth-password + requirement: REQUIRED + - provider_id: auth-otp-form + requirement: ALTERNATIVE + state: present + delegate_to: localhost + +- name: Create an authentication flow by copying an existing one + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + alias: my-copy-of-browser + copy_from: browser + state: present + delegate_to: localhost + +- name: Create a flow using token authentication + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + token: MY_TOKEN + realm: TestRealm + alias: my-flow + state: present + delegate_to: localhost + +- name: Delete an authentication flow + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + alias: my-browser-flow + state: absent + delegate_to: localhost +''' + +RETURN = ''' +msg: + description: Message as to what action was taken. + returned: always + type: str + sample: "Authentication flow my-browser-flow has been created" + +end_state: + description: Representation of the authentication flow after module execution. + returned: on success + type: dict + sample: { + "id": "uuid-here", + "alias": "my-browser-flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": false + } +''' + +from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \ + keycloak_argument_spec, get_token, KeycloakError +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = keycloak_argument_spec() + + execution_spec = dict( + provider_id=dict(type='str', required=True, aliases=['providerId']), + requirement=dict(type='str', required=True, choices=['REQUIRED', 'ALTERNATIVE', 'DISABLED', 'CONDITIONAL']), + ) + + meta_args = dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + alias=dict(type='str', required=True), + description=dict(type='str', default=''), + realm=dict(type='str', default='master'), + provider_id=dict(type='str', default='basic-flow', aliases=['providerId']), + copy_from=dict(type='str', aliases=['copyFrom']), + executions=dict(type='list', default=[], options=execution_spec, elements='dict'), + ) + + 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']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), + mutually_exclusive=[['copy_from', 'executions']]) + + result = dict(changed=False, msg='', diff={}, end_state={}) + + 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') + alias = module.params.get('alias') + state = module.params.get('state') + description = module.params.get('description') + provider_id = module.params.get('provider_id') + copy_from = module.params.get('copy_from') + executions = module.params.get('executions') + + before_flow = kc.get_authentication_flow_by_alias(alias, realm=realm) + flow_exists = bool(before_flow) + + if state == 'absent': + if flow_exists: + result['changed'] = True + if module._diff: + result['diff'] = dict(before=before_flow, after='') + if module.check_mode: + module.exit_json(**result) + kc.delete_authentication_flow_by_id(before_flow['id'], realm=realm) + result['msg'] = "Authentication flow {alias} has been deleted".format(alias=alias) + else: + result['msg'] = "Authentication flow {alias} does not exist, doing nothing".format(alias=alias) + result['end_state'] = {} + module.exit_json(**result) + + if flow_exists: + result['changed'] = False + result['end_state'] = before_flow + result['msg'] = "Authentication flow {alias} already exists".format(alias=alias) + module.exit_json(**result) + + result['changed'] = True + + flow_config = { + 'alias': alias, + 'description': description, + 'providerId': provider_id, + } + + if module._diff: + result['diff'] = dict(before='', after=flow_config) + + if module.check_mode: + module.exit_json(**result) + + if copy_from: + flow_config['copyFrom'] = copy_from + after_flow = kc.copy_auth_flow(flow_config, realm=realm) + result['msg'] = "Authentication flow {alias} has been created (copied from {src})".format(alias=alias, src=copy_from) + else: + after_flow = kc.create_empty_auth_flow(flow_config, realm=realm) + + if executions: + for execution in executions: + exec_rep = { + 'providerId': execution['provider_id'], + 'requirement': execution['requirement'], + } + kc.create_execution(exec_rep, alias, realm=realm) + + result['msg'] = "Authentication flow {alias} has been created".format(alias=alias) + + after_flow = kc.get_authentication_flow_by_alias(alias, realm=realm) + result['end_state'] = after_flow + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/keycloak_client_scope.py b/plugins/modules/keycloak_client_scope.py new file mode 100644 index 0000000..cf5f6ab --- /dev/null +++ b/plugins/modules/keycloak_client_scope.py @@ -0,0 +1,324 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2024, Contributors to the middleware_automation.keycloak collection +# 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_client_scope + +short_description: Allows administration of Keycloak client scopes via Keycloak API + +description: + - This module allows you to add, remove or modify Keycloak client scopes 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. + + - This module also supports managing protocol mappers within a client scope. + +attributes: + check_mode: + support: full + diff_mode: + support: full + +options: + state: + description: + - State of the client scope. + - On V(present), the client scope will be created if it does not yet exist, or updated with the parameters you provide. + - On V(absent), the client scope will be removed if it exists. + default: 'present' + type: str + choices: + - present + - absent + + name: + type: str + required: true + description: + - Name of the client scope. + + description: + type: str + description: + - Description of the client scope. + + realm: + type: str + description: + - The Keycloak realm under which this client scope resides. + default: 'master' + + protocol: + type: str + description: + - The protocol associated with the client scope. + default: 'openid-connect' + choices: + - openid-connect + - saml + + attributes: + type: dict + description: + - A dict of key/value pairs to set as attributes for the client scope. + + protocol_mappers: + type: list + elements: dict + description: + - A list of protocol mappers to associate with the client scope. + - Each mapper is a dict with the keys C(name), C(protocol), C(protocolMapper), and C(config). + default: [] + suboptions: + name: + type: str + required: true + description: + - Name of the protocol mapper. + protocol: + type: str + description: + - Protocol for the mapper. + default: 'openid-connect' + protocolMapper: + type: str + required: true + description: + - The mapper type (e.g. V(oidc-usermodel-attribute-mapper), V(oidc-audience-mapper)). + aliases: + - protocol_mapper_type + config: + type: dict + required: true + description: + - Configuration for the protocol mapper. + +extends_documentation_fragment: + - middleware_automation.keycloak.keycloak + - middleware_automation.keycloak.attributes + +author: + - Paulo Menon (@paulomenon) +''' + +EXAMPLES = ''' +- name: Create a client scope with protocol mappers + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + name: my-client-scope + description: "A custom client scope" + protocol: openid-connect + protocol_mappers: + - name: email + protocol: openid-connect + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: email + claim.name: email + jsonType.label: String + id.token.claim: "true" + access.token.claim: "true" + userinfo.token.claim: "true" + state: present + delegate_to: localhost + +- name: Create a client scope using token authentication + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: http://localhost:8080 + token: MY_TOKEN + realm: TestRealm + name: my-scope + state: present + delegate_to: localhost + +- name: Delete a client scope + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: password + realm: TestRealm + name: my-client-scope + state: absent + delegate_to: localhost +''' + +RETURN = ''' +msg: + description: Message as to what action was taken. + returned: always + type: str + sample: "Client scope my-scope has been created" + +end_state: + description: Representation of the client scope after module execution. + returned: on success + type: dict + sample: { + "id": "uuid-here", + "name": "my-scope", + "protocol": "openid-connect", + "description": "A custom scope" + } +''' + +from ansible_collections.middleware_automation.keycloak.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, \ + keycloak_argument_spec, get_token, KeycloakError +from ansible.module_utils.basic import AnsibleModule +import copy + + +def main(): + argument_spec = keycloak_argument_spec() + + mapper_spec = dict( + name=dict(type='str', required=True), + protocol=dict(type='str', default='openid-connect'), + protocolMapper=dict(type='str', required=True, aliases=['protocol_mapper_type']), + config=dict(type='dict', required=True), + ) + + meta_args = dict( + state=dict(type='str', default='present', choices=['present', 'absent']), + name=dict(type='str', required=True), + description=dict(type='str', default=''), + realm=dict(type='str', default='master'), + protocol=dict(type='str', default='openid-connect', choices=['openid-connect', 'saml']), + attributes=dict(type='dict'), + protocol_mappers=dict(type='list', default=[], options=mapper_spec, elements='dict'), + ) + + 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']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']])) + + result = dict(changed=False, msg='', diff={}, end_state={}) + + 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') + name = module.params.get('name') + state = module.params.get('state') + protocol = module.params.get('protocol') + description = module.params.get('description') + attributes = module.params.get('attributes') + protocol_mappers = module.params.get('protocol_mappers') + + before_scope = kc.get_clientscope_by_name(name, realm=realm) + + if state == 'absent': + if before_scope: + result['changed'] = True + if module._diff: + result['diff'] = dict(before=before_scope, after='') + if module.check_mode: + module.exit_json(**result) + kc.delete_clientscope(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) + result['end_state'] = {} + module.exit_json(**result) + + scope_rep = { + 'name': name, + 'protocol': protocol, + 'description': description, + } + if attributes: + scope_rep['attributes'] = attributes + + if not before_scope: + result['changed'] = True + if module._diff: + result['diff'] = dict(before='', after=scope_rep) + 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) + + if protocol_mappers: + for mapper in protocol_mappers: + mapper_rep = { + 'name': mapper['name'], + 'protocol': mapper.get('protocol', protocol), + '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) + + result['end_state'] = after_scope + result['msg'] = "Client scope {name} has been created".format(name=name) + module.exit_json(**result) + + else: + changed = False + for key in ('protocol', 'description'): + if scope_rep.get(key) and scope_rep[key] != before_scope.get(key): + changed = True + break + + if attributes and attributes != before_scope.get('attributes', {}): + changed = True + + if changed: + result['changed'] = True + scope_rep['id'] = before_scope['id'] + if module._diff: + result['diff'] = dict(before=before_scope, after=scope_rep) + if module.check_mode: + module.exit_json(**result) + kc.update_clientscope(scope_rep, realm=realm) + + if protocol_mappers: + existing_mappers = kc.get_clientscope_protocolmappers(before_scope['id'], realm=realm) + existing_mapper_names = {m['name'] for m in existing_mappers} + + for mapper in protocol_mappers: + if mapper['name'] not in existing_mapper_names: + result['changed'] = True + if not module.check_mode: + mapper_rep = { + 'name': mapper['name'], + 'protocol': mapper.get('protocol', protocol), + 'protocolMapper': mapper['protocolMapper'], + 'config': mapper['config'], + } + kc.create_clientscope_protocolmapper(before_scope['id'], mapper_rep, realm=realm) + + after_scope = kc.get_clientscope_by_name(name, realm=realm) + result['end_state'] = after_scope + + if result['changed']: + result['msg'] = "Client scope {name} has been updated".format(name=name) + else: + result['msg'] = "No changes required to client scope {name}".format(name=name) + module.exit_json(**result) + + +if __name__ == '__main__': + main() From 06e096ac500c733a2f328c6de4bad085296002e4 Mon Sep 17 00:00:00 2001 From: pamenon Date: Thu, 23 Apr 2026 12:54:22 +0100 Subject: [PATCH 2/2] Add module documentation to collection and role READMEs Document all six modules (including the two new ones) in the main collection README under a new 'Included modules' section. Add the three new example playbooks to the Config Playbooks section. Update the keycloak_realm role README with a 'Related Modules' table and inline examples for keycloak_client_scope and keycloak_authentication_flow usage. Made-with: Cursor --- README.md | 16 ++++++++-- roles/keycloak_realm/README.md | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 11c55f9..5afa582 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,15 @@ A requirement file is provided to install: +### Included modules + +* `keycloak_realm`: module for managing Keycloak realms (create/update/delete). +* `keycloak_client`: module for managing Keycloak clients (create/update/delete). +* `keycloak_role`: module for managing Keycloak roles — realm roles and client roles (create/update/delete). +* `keycloak_user_federation`: module for managing user federations such as LDAP/AD (create/update/delete). +* `keycloak_client_scope`: module for managing client scopes and protocol mappers (create/update/delete). +* `keycloak_authentication_flow`: module for managing authentication flows and execution steps (create/delete, copy existing flows). + ## Usage @@ -109,10 +118,13 @@ Note: when deploying clustered configurations, all hosts belonging to the cluste ## Configuration -### Config Playbook +### Config Playbooks -[`playbooks/keycloak_realm.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm.yml) creates or updates provided realm, user federation(s), client(s), client role(s) and client user(s). +* [`playbooks/keycloak_realm.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm.yml) creates or updates provided realm, user federation(s), client(s), client role(s) and client user(s). +* [`playbooks/keycloak_realm_client.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_realm_client.yml) creates a realm with clients, roles and users using the `keycloak_realm` role. +* [`playbooks/keycloak_client_scope.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_client_scope.yml) creates a client scope with protocol mappers using the `keycloak_client_scope` module. +* [`playbooks/keycloak_authentication_flow.yml`](https://github.com/ansible-middleware/keycloak/blob/main/playbooks/keycloak_authentication_flow.yml) creates a custom authentication flow with execution steps using the `keycloak_authentication_flow` module. ### Example configuration command diff --git a/roles/keycloak_realm/README.md b/roles/keycloak_realm/README.md index e01c72f..d217285 100644 --- a/roles/keycloak_realm/README.md +++ b/roles/keycloak_realm/README.md @@ -107,6 +107,20 @@ Refer to [docs](https://docs.ansible.com/ansible/latest/collections/community/ge For a comprehensive example, refer to the [playbook](../../playbooks/keycloak_realm.yml). +Related Modules +--------------- + +For features not covered by this role, the collection provides dedicated modules: + +| Module | What It Manages | +|:-------|:----------------| +| `keycloak_client_scope` | Client scopes and protocol mappers — see [example playbook](../../playbooks/keycloak_client_scope.yml) | +| `keycloak_authentication_flow` | Authentication flows and execution steps — see [example playbook](../../playbooks/keycloak_authentication_flow.yml) | +| `keycloak_client` | Clients (also used internally by this role) | +| `keycloak_role` | Realm and client roles | +| `keycloak_user_federation` | User federations such as LDAP (also used internally by this role) | + + Example Playbook ---------------- @@ -127,6 +141,47 @@ The following is an example playbook that makes use of the role to create a real keycloak_clients: [...] ``` +The following example uses the `keycloak_client_scope` module to create a client scope with protocol mappers: + +```yaml +- name: Create client scope + middleware_automation.keycloak.keycloak_client_scope: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: changeme + realm: TestRealm + name: my-scope + protocol_mappers: + - name: email + protocolMapper: oidc-usermodel-attribute-mapper + config: + user.attribute: email + claim.name: email + id.token.claim: "true" + access.token.claim: "true" + state: present +``` + +The following example uses the `keycloak_authentication_flow` module to create a custom authentication flow: + +```yaml +- name: Create authentication flow + middleware_automation.keycloak.keycloak_authentication_flow: + auth_keycloak_url: http://localhost:8080 + auth_realm: master + auth_username: admin + auth_password: changeme + realm: TestRealm + alias: my-browser-flow + executions: + - provider_id: auth-cookie + requirement: ALTERNATIVE + - provider_id: auth-password + requirement: REQUIRED + state: present +``` + License -------