mirror of
https://github.com/ansible-middleware/keycloak.git
synced 2026-06-13 12:05:54 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f172e019b4 | ||
|
|
e764cfd6f1 | ||
|
|
01b00dfb2e | ||
|
|
1908794569 | ||
|
|
c8bcff39ef | ||
|
|
0fad56294b | ||
|
|
b2b52ddfb5 | ||
|
|
7369a5724c | ||
|
|
942f0ae896 | ||
|
|
92d6dddd49 | ||
|
|
a1bb84ea5b | ||
|
|
1ec94b961f | ||
|
|
ba3f716e5c | ||
|
|
808d137e4c | ||
|
|
1794d4ff9b | ||
|
|
e898a2511a | ||
|
|
dd2cfaa87d | ||
|
|
b114c7b252 | ||
|
|
9920dc93c9 | ||
|
|
5cb555d6c2 |
@@ -1,4 +1,6 @@
|
||||
# .ansible-lint
|
||||
profile: production
|
||||
|
||||
exclude_paths:
|
||||
- .cache/
|
||||
- .github/
|
||||
|
||||
@@ -6,6 +6,17 @@ middleware\_automation.keycloak Release Notes
|
||||
|
||||
This changelog describes changes after version 0.2.6.
|
||||
|
||||
v3.0.9
|
||||
======
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- AMW-551 Providing correct rhbk version `#344 <https://github.com/ansible-middleware/keycloak/pull/344>`_
|
||||
|
||||
v3.0.8
|
||||
======
|
||||
|
||||
v3.0.7
|
||||
======
|
||||
|
||||
|
||||
10
README.md
10
README.md
@@ -49,9 +49,9 @@ A requirement file is provided to install:
|
||||
<!--start roles_paths -->
|
||||
### Included roles
|
||||
|
||||
* `keycloak_quarkus`: role for installing keycloak (>= 19.0.0, quarkus based).
|
||||
* `keycloak_realm`: role for configuring a realm, user federation(s), clients and users, in an installed service.
|
||||
* `keycloak`: role for installing legacy keycloak (<= 19.0, wildfly based).
|
||||
* [`keycloak_quarkus`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_quarkus/README.md): role for installing keycloak (>= 19.0.0, quarkus based).
|
||||
* [`keycloak_realm`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak_realm/README.md): role for configuring a realm, user federation(s), clients and users, in an installed service.
|
||||
* [`keycloak`](https://github.com/ansible-middleware/keycloak/blob/main/roles/keycloak/README.md): role for installing legacy keycloak (<= 19.0, wildfly based).
|
||||
|
||||
<!--end roles_paths -->
|
||||
|
||||
@@ -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_scope_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.
|
||||
@@ -220,5 +220,5 @@ For details on changes between versions, please see the [CHANGELOG](https://gith
|
||||
|
||||
Apache License v2.0 or later
|
||||
<!--start license -->
|
||||
See [LICENSE](LICENSE) to view the full text.
|
||||
See [LICENSE](https://github.com/ansible-middleware/keycloak/blob/main/LICENSE) to view the full text.
|
||||
<!--end license -->
|
||||
|
||||
@@ -6,4 +6,5 @@ python3-netaddr [platform:rpm platform:dpkg]
|
||||
python3-lxml [platform:rpm platform:dpkg]
|
||||
python3-jmespath [platform:rpm platform:dpkg]
|
||||
python3-requests [platform:rpm platform:dpkg]
|
||||
podman [platform:rpm platform:dpkg]
|
||||
|
||||
|
||||
@@ -825,3 +825,14 @@ releases:
|
||||
- 341.yaml
|
||||
- 343.yaml
|
||||
release_date: '2026-06-01'
|
||||
3.0.8:
|
||||
release_date: '2026-06-09'
|
||||
3.0.9:
|
||||
changes:
|
||||
bugfixes:
|
||||
- 'AMW-551 Providing correct rhbk version `#344 <https://github.com/ansible-middleware/keycloak/pull/344>`_
|
||||
|
||||
'
|
||||
fragments:
|
||||
- 344.yaml
|
||||
release_date: '2026-06-11'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
namespace: middleware_automation
|
||||
name: keycloak
|
||||
version: "3.0.7"
|
||||
version: "3.0.9"
|
||||
readme: README.md
|
||||
authors:
|
||||
- Romain Pelisse <rpelisse@redhat.com>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
driver:
|
||||
name: podman
|
||||
name: docker
|
||||
platforms:
|
||||
- name: instance
|
||||
image: registry.access.redhat.com/ubi9/ubi-init:latest
|
||||
|
||||
@@ -20,8 +20,50 @@
|
||||
|
||||
- name: Download keycloak archive to controller directory
|
||||
ansible.builtin.get_url: # noqa risky-file-permissions delegated, uses controller host user
|
||||
url: https://github.com/keycloak/keycloak/releases/download/26.4.7/keycloak-26.4.7.zip
|
||||
url: https://github.com/keycloak/keycloak/releases/download/26.6.2/keycloak-26.6.2.zip
|
||||
dest: /tmp/keycloak
|
||||
mode: '0640'
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
ignore_errors: true
|
||||
|
||||
- name: Attempt RHBK download using redhat.runtimes_common collection
|
||||
when:
|
||||
- rhn_username is defined
|
||||
- rhn_username | length > 0
|
||||
block:
|
||||
- name: Retrieve RHBK product download using Unified Downloads API
|
||||
middleware_automation.common.product_search:
|
||||
client_id: "{{ rhn_username }}"
|
||||
client_secret: "{{ rhn_password }}"
|
||||
product_type: DISTRIBUTION
|
||||
product_version: "{{ keycloak_quarkus_version | default('26.6.2') }}"
|
||||
product_category: "RHBK"
|
||||
register: rhn_products
|
||||
no_log: "{{ omit_rhn_output | default(true) }}"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
ignore_errors: true
|
||||
|
||||
- name: Determine install zipfile from search results
|
||||
ansible.builtin.set_fact:
|
||||
rhn_matched_products: "{{ rhn_products.results | selectattr('file_name', 'match', '.*keycloak-' + (keycloak_quarkus_version | default('26.6.2')) + '.zip$') }}"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
when:
|
||||
- rhn_products is defined
|
||||
- rhn_products.results is defined
|
||||
|
||||
- name: Download Red Hat Build of Keycloak
|
||||
middleware_automation.common.product_download:
|
||||
client_id: "{{ rhn_username }}"
|
||||
client_secret: "{{ rhn_password }}"
|
||||
product_id: "{{ (rhn_matched_products | first).id }}"
|
||||
dest: "/tmp/keycloak/keycloak-{{ keycloak_quarkus_version | default('26.6.2') }}.zip"
|
||||
no_log: "{{ omit_rhn_output | default(true) }}"
|
||||
delegate_to: localhost
|
||||
run_once: true
|
||||
when:
|
||||
- rhn_matched_products is defined
|
||||
- rhn_matched_products | length > 0
|
||||
ignore_errors: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
driver:
|
||||
name: podman
|
||||
name: docker
|
||||
platforms:
|
||||
- name: instance
|
||||
image: registry.access.redhat.com/ubi9/ubi-init:latest
|
||||
|
||||
@@ -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
|
||||
@@ -299,54 +299,54 @@
|
||||
- name: keycloak_client_rolescope — restrict realm role on client
|
||||
middleware_automation.keycloak.keycloak_client_rolescope:
|
||||
realm: "{{ target_realm }}"
|
||||
client_id: "{{ client }}"
|
||||
target_client_id: "{{ client }}"
|
||||
role_names:
|
||||
- "{{ 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 clientscope role mappings were created
|
||||
- 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 clientscope role mappings are idempotent
|
||||
- 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
|
||||
@@ -537,7 +537,7 @@
|
||||
- name: keycloak_client_rolescope — remove role scope mapping
|
||||
middleware_automation.keycloak.keycloak_client_rolescope:
|
||||
realm: "{{ target_realm }}"
|
||||
client_id: "{{ client }}"
|
||||
target_client_id: "{{ client }}"
|
||||
role_names:
|
||||
- "{{ role }}"
|
||||
state: absent
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
vars_files:
|
||||
- ../group_vars/all/vars.yml
|
||||
vars:
|
||||
rhn_username: "{{ lookup('env', 'rhn_username') | default('4278e994-7f90-46eb-b99c-90f2815b845f', true) }}"
|
||||
rhn_password: "{{ lookup('env', 'rhn_password') | default('AHOLJo08ursGdWVm0F66iDR5Owk0CwpL', true) }}"
|
||||
keycloak_quarkus_bootstrap_admin_password: "remembertochangeme"
|
||||
keycloak_admin_password: "remembertochangeme"
|
||||
keycloak_quarkus_hostname: "http://instance:8080"
|
||||
keycloak_config_override_template: custom.xml.j2
|
||||
keycloak_http_port: 8081
|
||||
keycloak_management_http_port: 19990
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
keycloak_quarkus_systemd_wait_for_delay: 2
|
||||
keycloak_quarkus_systemd_wait_for_log: true
|
||||
keycloak_quarkus_restart_health_check: false # would fail because of self-signed cert
|
||||
keycloak_quarkus_version: 26.4.7
|
||||
keycloak_quarkus_version: 26.6.2
|
||||
keycloak_quarkus_java_heap_opts: "-Xms1024m -Xmx1024m"
|
||||
keycloak_quarkus_additional_env_vars:
|
||||
- key: KC_FEATURES_DISABLED
|
||||
@@ -39,16 +39,16 @@
|
||||
- key: default-connection-pool-size
|
||||
value: 10
|
||||
- id: spid-saml
|
||||
url: https://github.com/italia/spid-keycloak-provider/releases/download/24.0.2/spid-provider.jar
|
||||
url: https://github.com/italia/spid-keycloak-provider/releases/download/26.5.6/spid-provider.jar
|
||||
- id: spid-saml-w-checksum
|
||||
url: https://github.com/italia/spid-keycloak-provider/releases/download/24.0.2/spid-provider.jar
|
||||
checksum: sha256:fbb50e73739d7a6d35b5bff611b1c01668b29adf6f6259624b95e466a305f377
|
||||
url: https://github.com/italia/spid-keycloak-provider/releases/download/26.5.6/spid-provider.jar
|
||||
checksum: sha256:2ddafc389a5f017d8665bfdfa2f72b3784fc74b9f3a482e796fa89a5ba5cc95b
|
||||
- id: keycloak-kerberos-federation
|
||||
maven:
|
||||
repository_url: https://repo1.maven.org/maven2/ # https://mvnrepository.com/artifact/org.keycloak/keycloak-kerberos-federation/24.0.4
|
||||
group_id: org.keycloak
|
||||
artifact_id: keycloak-kerberos-federation
|
||||
version: 26.4.7 # optional
|
||||
version: 26.6.3 # optional
|
||||
# username: myUser # optional
|
||||
# password: myPAT # optional
|
||||
# - id: my-static-theme
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
driver:
|
||||
name: podman
|
||||
name: docker
|
||||
platforms:
|
||||
- name: instance
|
||||
image: registry.access.redhat.com/ubi9/ubi-init:latest
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
keycloak_quarkus_additional_env_vars:
|
||||
- key: KC_FEATURES_DISABLED
|
||||
value: ciba,device-flow,impersonation,kerberos,docker
|
||||
keycloak_quarkus_version: 26.0.7
|
||||
keycloak_quarkus_version: 26.6.2
|
||||
roles:
|
||||
- role: keycloak_quarkus
|
||||
|
||||
@@ -4,7 +4,7 @@ dependency:
|
||||
options:
|
||||
requirements-file: molecule/requirements.yml
|
||||
driver:
|
||||
name: podman
|
||||
name: docker
|
||||
platforms:
|
||||
- name: instance
|
||||
image: registry.access.redhat.com/ubi9/ubi-init:latest
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
- vars.yml
|
||||
vars:
|
||||
sudo_pkg_name: sudo
|
||||
keycloak_quarkus_version: 26.0.4
|
||||
keycloak_quarkus_version: 26.6.1
|
||||
keycloak_quarkus_additional_env_vars:
|
||||
- key: KC_FEATURES_DISABLED
|
||||
value: impersonation,kerberos
|
||||
|
||||
@@ -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 = (
|
||||
@@ -154,18 +154,6 @@ URL_AUTHZ_CUSTOM_POLICY = "{url}/admin/realms/{realm}/clients/{client_id}/authz/
|
||||
URL_AUTHZ_CUSTOM_POLICIES = "{url}/admin/realms/{realm}/clients/{client_id}/authz/resource-server/policy"
|
||||
|
||||
|
||||
def normalize_keycloak_url(url: str) -> str:
|
||||
"""Normalize Keycloak base URL for Admin REST API access.
|
||||
|
||||
Keycloak 17+ (Quarkus) exposes the API at the server root without an /auth prefix.
|
||||
WildFly-based Keycloak used /auth as the context path. Trailing slashes are removed.
|
||||
"""
|
||||
url = url.rstrip("/")
|
||||
if url.endswith("/auth"):
|
||||
return url[:-5]
|
||||
return url
|
||||
|
||||
|
||||
def keycloak_argument_spec() -> dict[str, t.Any]:
|
||||
"""
|
||||
Returns argument_spec of options common to keycloak_*-modules
|
||||
@@ -215,7 +203,7 @@ def _token_request(module_params: dict[str, t.Any], payload: dict[str, t.Any]) -
|
||||
'refresh_token' for type 'refresh_token'.
|
||||
:return: access token
|
||||
"""
|
||||
base_url = normalize_keycloak_url(module_params["auth_keycloak_url"])
|
||||
base_url = module_params["auth_keycloak_url"]
|
||||
if not base_url.lower().startswith(("http", "https")):
|
||||
raise KeycloakError(f"auth_url '{base_url}' should either start with 'http' or 'https'.")
|
||||
auth_realm = module_params.get("auth_realm")
|
||||
@@ -403,7 +391,7 @@ class KeycloakAPI:
|
||||
|
||||
def __init__(self, module: AnsibleModule, connection_header: dict[str, str]) -> None:
|
||||
self.module = module
|
||||
self.baseurl = normalize_keycloak_url(self.module.params.get("auth_keycloak_url"))
|
||||
self.baseurl = self.module.params.get("auth_keycloak_url")
|
||||
self.validate_certs = self.module.params.get("validate_certs")
|
||||
self.connection_timeout = self.module.params.get("connection_timeout")
|
||||
self.restheaders = connection_header
|
||||
@@ -713,7 +701,7 @@ class KeycloakAPI:
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg=f"Could not obtain list of clients for realm {realm}: {e}")
|
||||
|
||||
def get_client_by_clientid(self, client_id, realm: str = "master"):
|
||||
def get_client_by_client_id(self, client_id, realm: str = "master"):
|
||||
"""Get client representation by clientId
|
||||
:param client_id: The clientId to be queried
|
||||
:param realm: realm from which to obtain the client representation
|
||||
@@ -756,7 +744,7 @@ class KeycloakAPI:
|
||||
:param realm: client template from this realm
|
||||
:return: id of client (usually a UUID)
|
||||
"""
|
||||
result = self.get_client_by_clientid(client_id, realm)
|
||||
result = self.get_client_by_client_id(client_id, realm)
|
||||
if isinstance(result, dict) and "id" in result:
|
||||
return result["id"]
|
||||
else:
|
||||
@@ -1301,99 +1289,99 @@ 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"):
|
||||
"""Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||
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_scope_id, passing in the ID of the group you wish to return.
|
||||
|
||||
:param realm: Realm in which the clientscope resides; default 'master'.
|
||||
:return The clientscopes of this realm (default "master")
|
||||
: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 clientscopes in realm {realm}: {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"):
|
||||
"""Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID.
|
||||
def get_client_scope_by_client_scope_id(self, cid, realm: str = "master"):
|
||||
"""Fetch a keycloak client scope from the provided realm using the client scope's unique ID.
|
||||
|
||||
If the clientscope does not exist, None is returned.
|
||||
If the client scope does not exist, None is returned.
|
||||
|
||||
gid is a UUID provided by the Keycloak API
|
||||
:param cid: UUID of the clientscope to be returned
|
||||
:param realm: Realm in which the clientscope resides; default 'master'.
|
||||
: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:
|
||||
return None
|
||||
else:
|
||||
self.fail_request(e, msg=f"Could not fetch clientscope {cid} in realm {realm}: {e}")
|
||||
self.fail_request(e, msg=f"Could not fetch client scope {cid} in realm {realm}: {e}")
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg=f"Could not clientscope group {cid} in realm {realm}: {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"):
|
||||
"""Fetch a keycloak clientscope within a realm based on its name.
|
||||
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 clientscopes resource by name.
|
||||
As a result, this method first retrieves the entire list of clientscopes - name and ID -
|
||||
The Keycloak API does not allow filtering of the client scopes resource by name.
|
||||
As a result, this method first retrieves the entire list of client scopes - name and ID -
|
||||
then performs a second query to fetch the group.
|
||||
|
||||
If the clientscope does not exist, None is returned.
|
||||
:param name: Name of the clientscope to fetch.
|
||||
:param realm: Realm in which the clientscope resides; default 'master'
|
||||
If the client scope does not exist, None is returned.
|
||||
:param name: Name of the client scope to fetch.
|
||||
: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_scope_id(client_scope["id"], realm=realm)
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg=f"Could not fetch clientscope {name} in realm {realm}: {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"):
|
||||
"""Create a Keycloak clientscope.
|
||||
def create_client_scope(self, client_scope_rep, 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_scope_rep: 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_scope_rep))
|
||||
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_scope_rep['name']} in realm {realm}: {e}")
|
||||
|
||||
def update_clientscope(self, clientscoperep, realm: str = "master"):
|
||||
"""Update an existing clientscope.
|
||||
def update_client_scope(self, client_scope_rep, 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_scope_rep["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_scope_rep))
|
||||
|
||||
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_scope_rep['name']} in realm {realm}: {e}")
|
||||
|
||||
def delete_clientscope(self, name=None, cid=None, realm: str = "master"):
|
||||
"""Delete a clientscope. One of name or cid must be provided.
|
||||
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 clientscope ID is preferred as it avoids a second lookup to
|
||||
convert a clientscope name to an ID.
|
||||
Providing the client scope ID is preferred as it avoids a second lookup to
|
||||
convert a client scope name to an ID.
|
||||
|
||||
:param name: The name of the clientscope. A lookup will be performed to retrieve the clientscope ID.
|
||||
:param cid: The ID of the clientscope (preferred to name).
|
||||
:param name: The name of the client scope. A lookup will be performed to retrieve the client scope ID.
|
||||
:param cid: The ID of the client scope (preferred to name).
|
||||
:param realm: The realm in which this group resides, default "master".
|
||||
"""
|
||||
|
||||
@@ -1405,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.
|
||||
@@ -1415,41 +1403,41 @@ 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 clientscope {cid}: {e}")
|
||||
self.fail_request(e, msg=f"Unable to delete client scope {cid}: {e}")
|
||||
|
||||
def get_clientscope_protocolmappers(self, cid, realm: str = "master"):
|
||||
"""Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||
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_scope_id, passing in the ID of the group you wish to return.
|
||||
|
||||
:param cid: id of clientscope (not name).
|
||||
:param realm: Realm in which the clientscope resides; default 'master'.
|
||||
:param cid: id of client scope (not name).
|
||||
: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"):
|
||||
"""Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID.
|
||||
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 clientscope does not exist, None is returned.
|
||||
If the client scope does not exist, None is returned.
|
||||
|
||||
gid is a UUID provided by the Keycloak API
|
||||
|
||||
:param cid: UUID of the protocolmapper to be returned
|
||||
:param cid: UUID of the clientscope to be returned
|
||||
:param realm: Realm in which the clientscope resides; default 'master'.
|
||||
: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")
|
||||
|
||||
@@ -1461,24 +1449,24 @@ 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"):
|
||||
"""Fetch a keycloak clientscope within a realm based on its name.
|
||||
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 clientscopes resource by name.
|
||||
As a result, this method first retrieves the entire list of clientscopes - name and ID -
|
||||
The Keycloak API does not allow filtering of the client scopes resource by name.
|
||||
As a result, this method first retrieves the entire list of client scopes - name and ID -
|
||||
then performs a second query to fetch the group.
|
||||
|
||||
If the clientscope does not exist, None is returned.
|
||||
:param cid: Id of the clientscope (not name).
|
||||
If the client scope does not exist, None is returned.
|
||||
:param cid: Id of the client scope (not name).
|
||||
:param name: Name of the protocolmapper to fetch.
|
||||
:param realm: Realm in which the clientscope resides; default 'master'
|
||||
: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
|
||||
)
|
||||
|
||||
@@ -1487,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"):
|
||||
"""Create a Keycloak clientscope protocolmapper.
|
||||
def create_client_scope_protocolmapper(self, cid, mapper_rep, realm: str = "master"):
|
||||
"""Create a Keycloak client scope protocolmapper.
|
||||
|
||||
:param cid: Id of the clientscope.
|
||||
: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"):
|
||||
"""Update an existing clientscope.
|
||||
def update_client_scope_protocolmappers(self, cid, mapper_rep, realm: str = "master"):
|
||||
"""Update an existing client scope.
|
||||
|
||||
:param cid: Id of the clientscope.
|
||||
: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"]
|
||||
)
|
||||
|
||||
@@ -1516,137 +1504,137 @@ class KeycloakAPI:
|
||||
|
||||
except Exception as e:
|
||||
self.fail_request(
|
||||
e, msg=f"Could not update protocolmappers for clientscope {mapper_rep} in realm {realm}: {e}"
|
||||
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):
|
||||
"""Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||
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_scope_id, passing in the ID of the client scope you wish to return.
|
||||
|
||||
:param realm: Realm in which the clientscope resides.
|
||||
:param client_id: The client in which the clientscope resides.
|
||||
:return The default clientscopes of this realm or client
|
||||
: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):
|
||||
"""Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||
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_scope_id, passing in the ID of the client scope you wish to return.
|
||||
|
||||
:param realm: Realm in which the clientscope resides.
|
||||
:param client_id: The client in which the clientscope resides.
|
||||
:return The optional clientscopes of this realm or client
|
||||
: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):
|
||||
"""Fetch the name and ID of all clientscopes on the Keycloak server.
|
||||
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_scope_id, passing in the ID of the client scope you wish to return.
|
||||
|
||||
:param realm: Realm in which the clientscope resides.
|
||||
:param realm: Realm in which the client scope resides.
|
||||
:param url_template the template for the right type
|
||||
:param scope_type this can be either optional or default
|
||||
:param client_id: The client in which the clientscope resides.
|
||||
:return The clientscopes of the specified type of this realm
|
||||
:param client_id: The client in which the client scope resides.
|
||||
: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} clientscopes in realm {realm}: {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 clientscope resides.
|
||||
: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 clientscope resides.
|
||||
:param client_id: The client in which the clientscope resides.
|
||||
: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 clientscope resides.
|
||||
:param client_id: The client in which the clientscope resides.
|
||||
: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 clientscope resides.
|
||||
:param client_id: The client in which the clientscope resides.
|
||||
: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 clientscope resides.
|
||||
:param client_id: The client in which the clientscope resides.
|
||||
: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 clientscope of type.
|
||||
:param name: The name of the clientscope. A lookup will be performed to retrieve the clientscope ID.
|
||||
:param client_id: The ID of the clientscope (preferred to name).
|
||||
"""Delete or add a client scope of type.
|
||||
:param name: The name of the client scope. A lookup will be performed to retrieve the client scope ID.
|
||||
:param client_id: The ID of the client scope (preferred to name).
|
||||
:param scope_type 'default' or 'optional'
|
||||
:param realm: The realm in which this group resides, default "master".
|
||||
"""
|
||||
cid = None if client_id is None else self.get_client_id(client_id=client_id, realm=realm)
|
||||
# should have a good cid by here.
|
||||
clientscope_type_url = self._decide_url_type_clientscope(client_id, scope_type).format(
|
||||
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
|
||||
@@ -2032,7 +2020,7 @@ class KeycloakAPI:
|
||||
composite_url = ""
|
||||
try:
|
||||
if clientid is not None:
|
||||
client = self.get_client_by_clientid(client_id=clientid, realm=realm)
|
||||
client = self.get_client_by_client_id(client_id=clientid, realm=realm)
|
||||
cid = client["id"]
|
||||
composite_url = URL_CLIENT_ROLE_COMPOSITES.format(
|
||||
url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="")
|
||||
@@ -2050,7 +2038,7 @@ class KeycloakAPI:
|
||||
composite_url = ""
|
||||
try:
|
||||
if clientid is not None:
|
||||
client = self.get_client_by_clientid(client_id=clientid, realm=realm)
|
||||
client = self.get_client_by_client_id(client_id=clientid, realm=realm)
|
||||
cid = client["id"]
|
||||
composite_url = URL_CLIENT_ROLE_COMPOSITES.format(
|
||||
url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="")
|
||||
@@ -2069,7 +2057,7 @@ class KeycloakAPI:
|
||||
composite_url = ""
|
||||
try:
|
||||
if clientid is not None:
|
||||
client = self.get_client_by_clientid(client_id=clientid, realm=realm)
|
||||
client = self.get_client_by_client_id(client_id=clientid, realm=realm)
|
||||
cid = client["id"]
|
||||
composite_url = URL_CLIENT_ROLE_COMPOSITES.format(
|
||||
url=self.baseurl, realm=realm, id=cid, name=quote(rolerep["name"], safe="")
|
||||
@@ -3271,192 +3259,192 @@ class KeycloakAPI:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_all_clientscope_scope_mappings(self, clientscope_id, realm: str = "master"):
|
||||
"""Fetch all (realm and client) roles (scope-mappings) associated with the clientscope for a specific clientscope on the Keycloak server.
|
||||
:param clientscope_id: ID of the clientscope from which to obtain the associated roles.
|
||||
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 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 clientscope scope-mappings.
|
||||
: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"):
|
||||
"""Fetch the realm roles (scope-mappings) associated with the clientscope for a specific clientscope on the Keycloak server.
|
||||
:param clientscope_id: ID of the clientscope from which to obtain the associated roles.
|
||||
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 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 clientscope realm scope-mappings.
|
||||
: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"):
|
||||
"""Fetch the client roles (scope-mappings) associated with the clientscope for a specific clientscope and client on the Keycloak server.
|
||||
:param clientscope_id: ID of the clientscope from which to obtain the associated roles.
|
||||
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 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 clientscope client scope-mappings.
|
||||
: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, target_client_id, role_owner_client_id, 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 target_client_id: ID of the client from which to obtain the associated roles.
|
||||
:param role_owner_client_id: 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=target_client_id, scopeid=role_owner_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 roles scope for client {clientid} in realm {realm}: {e}")
|
||||
self.fail_request(e, msg=f"Could not fetch roles scope for client {target_client_id} 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, roles, target_client_id, role_owner_client_id, 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 roles: List of roles to be added to the scope.
|
||||
:param target_client_id: ID of the client to update scope.
|
||||
:param role_owner_client_id: 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=target_client_id, scopeid=role_owner_client_id
|
||||
)
|
||||
try:
|
||||
self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
|
||||
self._request(client_role_scope_url, method="POST", data=json.dumps(roles))
|
||||
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg=f"Could not update roles scope for client {clientid} in realm {realm}: {e}")
|
||||
self.fail_request(e, msg=f"Could not update roles scope for client {target_client_id} in realm {realm}: {e}")
|
||||
|
||||
return self.get_client_role_scope_from_client(clientid, clientscopeid, realm)
|
||||
return self.get_client_role_scope_from_client(target_client_id, role_owner_client_id, realm)
|
||||
|
||||
def delete_client_role_scope_from_client(self, payload, clientid, clientscopeid, 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.
|
||||
def delete_client_role_scope_from_client(self, roles, target_client_id, role_owner_client_id, realm: str = "master"):
|
||||
"""Delete the roles from the client's scope on the Keycloak server.
|
||||
:param roles: List of roles to be deleted.
|
||||
:param target_client_id: ID of the client to delete roles from scope.
|
||||
:param role_owner_client_id: 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=target_client_id, scopeid=role_owner_client_id
|
||||
)
|
||||
try:
|
||||
self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
|
||||
self._request(client_role_scope_url, method="DELETE", data=json.dumps(roles))
|
||||
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg=f"Could not delete roles scope for client {clientid} in realm {realm}: {e}")
|
||||
self.fail_request(e, msg=f"Could not delete roles from scope for client {target_client_id} in realm {realm}: {e}")
|
||||
|
||||
return self.get_client_role_scope_from_client(clientid, clientscopeid, realm)
|
||||
return self.get_client_role_scope_from_client(target_client_id, role_owner_client_id, 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, roles: list[dict], client_scope_id: str, role_owner_client_id: str, realm: str = "master"
|
||||
):
|
||||
"""Update and fetch the client roles (scope-mappings) associated with the clientscope 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 clientid: ID of the client from which to obtain the associated roles.
|
||||
"""Update and fetch the client roles (scope-mappings) associated with the client scope on the Keycloak server.
|
||||
:param roles: List of client roles to be added to the scope.
|
||||
:param client_scope_id: ID of the client scope to update scope-mappings.
|
||||
:param role_owner_client_id: ID of the client from which to obtain the associated roles.
|
||||
:param realm: Realm from which to obtain the client.
|
||||
:return: The clientscope client scope-mappings.
|
||||
: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=role_owner_client_id
|
||||
)
|
||||
try:
|
||||
self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
|
||||
self._request(client_role_scope_url, method="POST", data=json.dumps(roles))
|
||||
|
||||
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 {role_owner_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, role_owner_client_id, realm)
|
||||
|
||||
def update_clientscope_scope_mappings_realm(self, payload: list[dict], clientscope_id: str, realm: str = "master"):
|
||||
"""Update and fetch the realm roles (scope-mappings) associated with the clientscope 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.
|
||||
def update_client_scope_scope_mappings_realm(self, roles: 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 roles: List of realm roles to be added to the scope.
|
||||
:param client_scope_id: ID of the client scope to update scope-mappings.
|
||||
:param realm: Realm from which to obtain the roles.
|
||||
:return: The clientscope realm scope-mappings.
|
||||
: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))
|
||||
self._request(client_role_scope_url, method="POST", data=json.dumps(roles))
|
||||
|
||||
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, roles: list[dict], client_scope_id: str, role_owner_client_id: str, realm: str = "master"
|
||||
):
|
||||
"""Delete the client roles (scope_mappings) contained in the payload from the clientscope 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 clientid: ID of the client who owns the roles.
|
||||
"""Delete the client roles (scope_mappings) from the client scope on the Keycloak server.
|
||||
:param roles: List of roles to be deleted.
|
||||
:param client_scope_id: ID of the client scope to delete roles from scope-mappings.
|
||||
:param role_owner_client_id: ID of the client who owns the roles.
|
||||
:param realm: Realm from which to obtain the client.
|
||||
:return: The clientscope client scope-mappings.
|
||||
: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=role_owner_client_id
|
||||
)
|
||||
try:
|
||||
self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
|
||||
self._request(client_role_scope_url, method="DELETE", data=json.dumps(roles))
|
||||
|
||||
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 {role_owner_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, role_owner_client_id, realm)
|
||||
|
||||
def delete_clientscope_scope_mappings_realm(self, payload: list[dict], clientscope_id: str, realm: str = "master"):
|
||||
"""Delete the realm roles (scope_mappings) contained in the payload from the clientscope 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.
|
||||
def delete_client_scope_scope_mappings_realm(self, roles: list[dict], client_scope_id: str, realm: str = "master"):
|
||||
"""Delete the realm roles (scope_mappings) contained in the roles from the client scope on the Keycloak server.
|
||||
:param roles: List of roles to be deleted.
|
||||
: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 clientscope realm scope-mappings.
|
||||
: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))
|
||||
self._request(client_role_scope_url, method="DELETE", data=json.dumps(roles))
|
||||
|
||||
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.
|
||||
@@ -3470,32 +3458,32 @@ class KeycloakAPI:
|
||||
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_realm(self, payload, clientid, realm: str = "master"):
|
||||
def update_client_role_scope_from_realm(self, roles, clientid, realm: str = "master"):
|
||||
"""Update and fetch the realm roles from the client's scope on the Keycloak server.
|
||||
:param payload: List of realm roles to add.
|
||||
:param roles: List of realm roles to add.
|
||||
:param clientid: ID of the client to update scope.
|
||||
:param realm: Realm from which to obtain the clients.
|
||||
:return: The client realm roles scope.
|
||||
"""
|
||||
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid)
|
||||
try:
|
||||
self._request(client_role_scope_url, method="POST", data=json.dumps(payload))
|
||||
self._request(client_role_scope_url, method="POST", data=json.dumps(roles))
|
||||
|
||||
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_realm(clientid, realm)
|
||||
|
||||
def delete_client_role_scope_from_realm(self, payload, clientid, realm: str = "master"):
|
||||
"""Delete the realm roles contains in the payload from the client's scope on the Keycloak server.
|
||||
:param payload: List of realm roles to delete.
|
||||
def delete_client_role_scope_from_realm(self, roles, clientid, realm: str = "master"):
|
||||
"""Delete the realm roles from the client's scope on the Keycloak server.
|
||||
:param roles: List of realm roles to delete.
|
||||
:param clientid: ID of the client to delete roles from scope.
|
||||
:param realm: Realm from which to obtain the clients.
|
||||
:return: The client realm roles scope.
|
||||
"""
|
||||
client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid)
|
||||
try:
|
||||
self._request(client_role_scope_url, method="DELETE", data=json.dumps(payload))
|
||||
self._request(client_role_scope_url, method="DELETE", data=json.dumps(roles))
|
||||
|
||||
except Exception as e:
|
||||
self.fail_request(e, msg=f"Could not delete roles scope for client {clientid} in realm {realm}: {e}")
|
||||
|
||||
@@ -66,7 +66,7 @@ def keycloak_clientsecret_module_resolve_params(module: AnsibleModule, kc: Keycl
|
||||
# less lookup.
|
||||
if id is None:
|
||||
# Due to the required_one_of spec, client_id is guaranteed to not be None
|
||||
client = kc.get_client_by_clientid(client_id, realm=realm)
|
||||
client = kc.get_client_by_client_id(client_id, realm=realm)
|
||||
|
||||
if client is None:
|
||||
module.fail_json(msg=f"Client does not exist {client_id}")
|
||||
|
||||
@@ -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():
|
||||
@@ -1346,7 +1346,7 @@ def main():
|
||||
|
||||
# See if it already exists in Keycloak
|
||||
if cid is None:
|
||||
before_client = kc.get_client_by_clientid(module.params.get("client_id"), realm=realm)
|
||||
before_client = kc.get_client_by_client_id(module.params.get("client_id"), realm=realm)
|
||||
if before_client is not None:
|
||||
cid = before_client["id"]
|
||||
else:
|
||||
@@ -1440,7 +1440,7 @@ def main():
|
||||
|
||||
# create it
|
||||
kc.create_client(desired_client, realm=realm)
|
||||
after_client = kc.get_client_by_clientid(desired_client["clientId"], realm=realm)
|
||||
after_client = kc.get_client_by_client_id(desired_client["clientId"], realm=realm)
|
||||
|
||||
result["end_state"] = sanitize_cr(after_client)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ description:
|
||||
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.
|
||||
- Client O(client_id) must have O(middleware_automation.keycloak.keycloak_client#module:full_scope_allowed) set to V(false).
|
||||
- Client O(target_client_id) must have O(middleware_automation.keycloak.keycloak_client#module:full_scope_allowed) set to V(false).
|
||||
- Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and are returned that way
|
||||
by this module. You may pass single values for attributes when calling the module, and this is translated into a list
|
||||
suitable for the API.
|
||||
@@ -50,12 +50,12 @@ options:
|
||||
- The Keycloak realm under which clients resides.
|
||||
default: 'master'
|
||||
|
||||
client_id:
|
||||
target_client_id:
|
||||
type: str
|
||||
required: true
|
||||
description:
|
||||
- Roles provided in O(role_names) while be added to this client scope.
|
||||
client_scope_id:
|
||||
role_owner_client_id:
|
||||
type: str
|
||||
description:
|
||||
- If the O(role_names) are client role, the client ID under which it resides.
|
||||
@@ -66,8 +66,8 @@ options:
|
||||
elements: str
|
||||
description:
|
||||
- Names of roles to manipulate.
|
||||
- If O(client_scope_id) is present, all roles must be under this client.
|
||||
- If O(client_scope_id) is absent, all roles must be under the realm.
|
||||
- If O(role_owner_client_id) is present, all roles must be under this client.
|
||||
- If O(role_owner_client_id) is absent, all roles must be under the realm.
|
||||
extends_documentation_fragment:
|
||||
- middleware_automation.keycloak.keycloak
|
||||
- middleware_automation.keycloak.actiongroup_keycloak
|
||||
@@ -85,8 +85,8 @@ EXAMPLES = r"""
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
realm: MyCustomRealm
|
||||
client_id: frontend-client-public
|
||||
client_scope_id: backend-client-private
|
||||
target_client_id: frontend-client-public
|
||||
role_owner_client_id: backend-client-private
|
||||
role_names:
|
||||
- backend-role-admin
|
||||
- backend-role-user
|
||||
@@ -98,8 +98,8 @@ EXAMPLES = r"""
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
realm: MyCustomRealm
|
||||
client_id: frontend-client-public
|
||||
client_scope_id: backend-client-private
|
||||
target_client_id: frontend-client-public
|
||||
role_owner_client_id: backend-client-private
|
||||
role_names:
|
||||
- backend-role-admin
|
||||
state: absent
|
||||
@@ -111,7 +111,7 @@ EXAMPLES = r"""
|
||||
auth_username: USERNAME
|
||||
auth_password: PASSWORD
|
||||
realm: MyCustomRealm
|
||||
client_id: frontend-client-public
|
||||
target_client_id: frontend-client-public
|
||||
role_names:
|
||||
- realm-role-admin
|
||||
- realm-role-user
|
||||
@@ -167,8 +167,8 @@ def main():
|
||||
argument_spec = keycloak_argument_spec()
|
||||
|
||||
meta_args = dict(
|
||||
client_id=dict(type="str", required=True),
|
||||
client_scope_id=dict(type="str"),
|
||||
target_client_id=dict(type="str", required=True),
|
||||
role_owner_client_id=dict(type="str"),
|
||||
realm=dict(type="str", default="master"),
|
||||
role_names=dict(type="list", elements="str", required=True),
|
||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
||||
@@ -189,8 +189,8 @@ def main():
|
||||
kc = KeycloakAPI(module, connection_header)
|
||||
|
||||
realm = module.params.get("realm")
|
||||
clientid = module.params.get("client_id")
|
||||
client_scope_id = module.params.get("client_scope_id")
|
||||
target_client_id = module.params.get("target_client_id")
|
||||
role_owner_client_id = module.params.get("role_owner_client_id")
|
||||
role_names = module.params.get("role_names")
|
||||
state = module.params.get("state")
|
||||
|
||||
@@ -198,23 +198,23 @@ def main():
|
||||
if not objRealm:
|
||||
module.fail_json(msg=f"Failed to retrive realm '{realm}'")
|
||||
|
||||
objClient = kc.get_client_by_clientid(clientid, realm)
|
||||
objClient = kc.get_client_by_client_id(target_client_id, realm)
|
||||
if not objClient:
|
||||
module.fail_json(msg=f"Failed to retrive client '{realm}.{clientid}'")
|
||||
module.fail_json(msg=f"Failed to retrive client '{realm}.{target_client_id}'")
|
||||
if objClient["fullScopeAllowed"] and state == "present":
|
||||
module.fail_json(msg=f"FullScopeAllowed is active for Client '{realm}.{clientid}'")
|
||||
module.fail_json(msg=f"FullScopeAllowed is active for Client '{realm}.{target_client_id}'")
|
||||
|
||||
if client_scope_id:
|
||||
objClientScope = kc.get_client_by_clientid(client_scope_id, realm)
|
||||
if not objClientScope:
|
||||
module.fail_json(msg=f"Failed to retrive client '{realm}.{client_scope_id}'")
|
||||
before_role_mapping = kc.get_client_role_scope_from_client(objClient["id"], objClientScope["id"], realm)
|
||||
if role_owner_client_id:
|
||||
role_owner_client = kc.get_client_by_client_id(role_owner_client_id, realm)
|
||||
if not role_owner_client:
|
||||
module.fail_json(msg=f"Failed to retrive client '{realm}.{role_owner_client_id}'")
|
||||
before_role_mapping = kc.get_client_role_scope_from_client(objClient["id"], role_owner_client["id"], realm)
|
||||
else:
|
||||
before_role_mapping = kc.get_client_role_scope_from_realm(objClient["id"], realm)
|
||||
|
||||
if client_scope_id:
|
||||
if role_owner_client_id:
|
||||
# retrive all role from client_scope
|
||||
client_scope_roles_by_name = kc.get_client_roles_by_id(objClientScope["id"], realm)
|
||||
client_scope_roles_by_name = kc.get_client_roles_by_id(role_owner_client["id"], realm)
|
||||
else:
|
||||
# retrive all role from realm
|
||||
client_scope_roles_by_name = kc.get_realm_roles(realm)
|
||||
@@ -228,8 +228,8 @@ def main():
|
||||
# update desired
|
||||
for role_name in role_names:
|
||||
if role_name not in client_scope_roles_by_name:
|
||||
if client_scope_id:
|
||||
module.fail_json(msg=f"Failed to retrive role '{realm}.{client_scope_id}.{role_name}'")
|
||||
if role_owner_client_id:
|
||||
module.fail_json(msg=f"Failed to retrive role '{realm}.{role_owner_client_id}.{role_name}'")
|
||||
else:
|
||||
module.fail_json(msg=f"Failed to retrive role '{realm}.{role_name}'")
|
||||
if role_name not in role_mapping_by_name:
|
||||
@@ -253,33 +253,33 @@ def main():
|
||||
if not result["changed"]:
|
||||
# no changes
|
||||
result["end_state"] = before_role_mapping
|
||||
result["msg"] = f"No changes required for client role scope {clientid}."
|
||||
result["msg"] = f"No changes required for client role scope {target_client_id}."
|
||||
elif state == "present":
|
||||
# doing update
|
||||
if module.check_mode:
|
||||
result["end_state"] = desired_role_mapping
|
||||
elif client_scope_id:
|
||||
elif role_owner_client_id:
|
||||
result["end_state"] = kc.update_client_role_scope_from_client(
|
||||
role_mapping_to_manipulate, objClient["id"], objClientScope["id"], realm
|
||||
role_mapping_to_manipulate, objClient["id"], role_owner_client["id"], realm
|
||||
)
|
||||
else:
|
||||
result["end_state"] = kc.update_client_role_scope_from_realm(
|
||||
role_mapping_to_manipulate, objClient["id"], realm
|
||||
)
|
||||
result["msg"] = f"Client role scope for {clientid} has been updated"
|
||||
result["msg"] = f"Client role scope for {target_client_id} has been updated"
|
||||
else:
|
||||
# doing delete
|
||||
if module.check_mode:
|
||||
result["end_state"] = desired_role_mapping
|
||||
elif client_scope_id:
|
||||
elif role_owner_client_id:
|
||||
result["end_state"] = kc.delete_client_role_scope_from_client(
|
||||
role_mapping_to_manipulate, objClient["id"], objClientScope["id"], realm
|
||||
role_mapping_to_manipulate, objClient["id"], role_owner_client["id"], realm
|
||||
)
|
||||
else:
|
||||
result["end_state"] = kc.delete_client_role_scope_from_realm(
|
||||
role_mapping_to_manipulate, objClient["id"], realm
|
||||
)
|
||||
result["msg"] = f"Client role scope for {clientid} has been deleted"
|
||||
result["msg"] = f"Client role scope for {target_client_id} has been deleted"
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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,23 +194,23 @@ 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
|
||||
client_object = kc.get_client_by_clientid(client_id, realm)
|
||||
client_object = kc.get_client_by_client_id(client_id, realm)
|
||||
if not client_object:
|
||||
module.fail_json(msg=f"Failed to retrieve client '{realm}.{client_id}'")
|
||||
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -94,16 +94,16 @@ msg:
|
||||
type: str
|
||||
sample: ""
|
||||
proposed:
|
||||
description: Representation of proposed client-scope types mapping.
|
||||
description: Representation of proposed client scope types mapping.
|
||||
returned: always
|
||||
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_scopes_existing, default_client_scopes_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)),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2,4 +2,14 @@
|
||||
collections:
|
||||
- name: middleware_automation.common
|
||||
version: ">=1.2.4"
|
||||
- name: middleware_automation.infinispan
|
||||
- name: community.general
|
||||
- name: ansible.posix
|
||||
- name: community.docker
|
||||
version: ">=3.8.0"
|
||||
- name: containers.podman
|
||||
version: ">=1.8.1"
|
||||
|
||||
roles:
|
||||
- name: elan.simple_nginx_reverse_proxy
|
||||
version: "0.2.1"
|
||||
|
||||
@@ -33,7 +33,7 @@ Role Defaults
|
||||
|
||||
| Variable | Description | Default |
|
||||
|:---------|:------------|:--------|
|
||||
|`keycloak_quarkus_version`| keycloak.org package version | `26.4.7` |
|
||||
|`keycloak_quarkus_version`| keycloak.org package version | `26.6.2` |
|
||||
|`keycloak_quarkus_offline_install` | Perform an offline install | `False`|
|
||||
|`keycloak_quarkus_dest`| Installation root path | `/opt/keycloak` |
|
||||
|`keycloak_quarkus_download_url` | Download URL for keycloak | `https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}` |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
### Configuration specific to keycloak
|
||||
keycloak_quarkus_version: 26.4.7
|
||||
keycloak_quarkus_version: 26.6.2
|
||||
keycloak_quarkus_archive: "keycloak-{{ keycloak_quarkus_version }}.zip"
|
||||
keycloak_quarkus_download_url: "https://github.com/keycloak/keycloak/releases/download/{{ keycloak_quarkus_version }}/{{ keycloak_quarkus_archive }}"
|
||||
keycloak_quarkus_installdir: "{{ keycloak_quarkus_dest }}/keycloak-{{ keycloak_quarkus_version }}"
|
||||
|
||||
@@ -2,7 +2,7 @@ argument_specs:
|
||||
main:
|
||||
options:
|
||||
keycloak_quarkus_version:
|
||||
default: "26.4.7"
|
||||
default: "26.6.2"
|
||||
description: "keycloak.org package version"
|
||||
type: "str"
|
||||
keycloak_quarkus_archive:
|
||||
@@ -519,7 +519,7 @@ argument_specs:
|
||||
downstream:
|
||||
options:
|
||||
rhbk_version:
|
||||
default: "26.4.7"
|
||||
default: "26.4.11"
|
||||
description: "Red Hat Build of Keycloak version"
|
||||
type: "str"
|
||||
rhbk_archive:
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
- name: Validate admin console password
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- keycloak_quarkus_bootstrap_admin_password is defined
|
||||
- keycloak_quarkus_bootstrap_admin_password is not none
|
||||
- keycloak_quarkus_bootstrap_admin_password | length > 12
|
||||
quiet: true
|
||||
fail_msg: "The console administrator password is empty or invalid. Please set the keycloak_quarkus_bootstrap_admin_password to a 12+ char long string"
|
||||
|
||||
15
roles/keycloak_quarkus/templates/rhbk-sysconfig.j2
Normal file
15
roles/keycloak_quarkus/templates/rhbk-sysconfig.j2
Normal file
@@ -0,0 +1,15 @@
|
||||
{{ ansible_managed | comment }}
|
||||
{% if not ansible_local.keycloak.general.bootstrapped | default(false) | bool %}
|
||||
KC_BOOTSTRAP_ADMIN_USERNAME={{ keycloak_quarkus_bootstrap_admin_user }}
|
||||
KC_BOOTSTRAP_ADMIN_PASSWORD='{{ keycloak_quarkus_bootstrap_admin_password }}'
|
||||
{% else %}
|
||||
{{ keycloak.bootstrap_mnemonic }}
|
||||
{% endif %}
|
||||
PATH="{{ keycloak_quarkus_java_home | default(keycloak_sys_pkg_java_home, true) }}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
JAVA_HOME="{{ keycloak_quarkus_java_home | default(keycloak_sys_pkg_java_home, true) }}"
|
||||
JAVA_OPTS="{{ keycloak_quarkus_java_opts }}"
|
||||
|
||||
# Custom ENV variables
|
||||
{% for env in keycloak_quarkus_additional_env_vars %}
|
||||
{{ env.key }}={{ env.value }}
|
||||
{% endfor %}
|
||||
110
roles/keycloak_quarkus/templates/rhbk.conf.j2
Normal file
110
roles/keycloak_quarkus/templates/rhbk.conf.j2
Normal file
@@ -0,0 +1,110 @@
|
||||
{{ ansible_managed | comment }}
|
||||
|
||||
{% if keycloak_quarkus_db_enabled %}
|
||||
# Database
|
||||
db={{ keycloak_quarkus_db_engine }}
|
||||
db-url={{ keycloak_quarkus_db_url }}
|
||||
db-username={{ keycloak_quarkus_db_user }}
|
||||
{% if not keycloak.config_key_store_enabled %}
|
||||
db-password={{ keycloak_quarkus_db_pass }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if keycloak.config_key_store_enabled %}
|
||||
# Config store
|
||||
config-keystore={{ keycloak_quarkus_config_key_store_file }}
|
||||
config-keystore-password={{ keycloak_quarkus_config_key_store_password }}
|
||||
{% endif %}
|
||||
|
||||
# Observability
|
||||
metrics-enabled={{ keycloak_quarkus_metrics_enabled | lower }}
|
||||
health-enabled={{ keycloak_quarkus_health_enabled | lower }}
|
||||
|
||||
# HTTP
|
||||
http-enabled={{ keycloak_quarkus_http_enabled | lower }}
|
||||
{% if keycloak_quarkus_http_enabled %}
|
||||
http-port={{ keycloak_quarkus_http_port }}
|
||||
{% endif %}
|
||||
http-relative-path={{ keycloak_quarkus_http_relative_path }}
|
||||
http-host={{ keycloak_quarkus_http_host }}
|
||||
|
||||
# Management
|
||||
http-management-port={{ keycloak_quarkus_http_management_port }}
|
||||
{% if keycloak_quarkus_http_management_relative_path is defined and keycloak_quarkus_http_management_relative_path | length > 0 %}
|
||||
http-management-relative-path={{ keycloak_quarkus_http_management_relative_path }}
|
||||
{% endif %}
|
||||
|
||||
# HTTPS
|
||||
https-port={{ keycloak_quarkus_https_port }}
|
||||
{% if keycloak_quarkus_https_key_file_enabled %}
|
||||
https-certificate-file={{ keycloak_quarkus_cert_file}}
|
||||
https-certificate-key-file={{ keycloak_quarkus_key_file }}
|
||||
{% endif %}
|
||||
{% if keycloak_quarkus_https_key_store_enabled %}
|
||||
https-key-store-file={{ keycloak_quarkus_https_key_store_file }}
|
||||
https-key-store-password={{ keycloak_quarkus_https_key_store_password }}
|
||||
{% endif %}
|
||||
{% if keycloak_quarkus_https_trust_store_enabled %}
|
||||
https-trust-store-file={{ keycloak_quarkus_https_trust_store_file }}
|
||||
https-trust-store-password={{ keycloak_quarkus_https_trust_store_password }}
|
||||
{% endif %}
|
||||
|
||||
# Client URL configuration
|
||||
hostname={{ keycloak_quarkus_hostname }}
|
||||
hostname-admin={{ keycloak_quarkus_hostname_admin }}
|
||||
hostname-strict={{ keycloak_quarkus_hostname_strict | lower }}
|
||||
hostname-backchannel-dynamic={{ keycloak_quarkus_hostname_backchannel_dynamic | lower }}
|
||||
|
||||
# Cluster
|
||||
{% if keycloak_quarkus_ha_enabled %}
|
||||
cache=ispn
|
||||
{% if keycloak_quarkus_cache_managed_infinispan_config %}
|
||||
cache-config-file=cache-ispn.xml
|
||||
{% endif %}
|
||||
{% if keycloak_quarkus_cache_remote %}
|
||||
cache-remote-username={{ keycloak_quarkus_cache_remote_username }}
|
||||
cache-remote-password={{ keycloak_quarkus_cache_remote_password }}
|
||||
cache-remote-host={{ keycloak_quarkus_cache_remote_host }}
|
||||
cache-remote-port={{ keycloak_quarkus_cache_remote_port }}
|
||||
cache-remote-tls-enabled={{ keycloak_quarkus_cache_remote_tls_enabled | lower }}
|
||||
{% endif %}
|
||||
{{ keycloak_quarkus_cache_embedded_properties }}
|
||||
{% endif %}
|
||||
|
||||
{% if keycloak_quarkus_proxy_headers | length > 0 %}
|
||||
proxy-headers={{ keycloak_quarkus_proxy_headers | lower }}
|
||||
{% elif keycloak_quarkus_proxy_mode is defined and keycloak_quarkus_proxy_mode != "none" %}
|
||||
# Deprecated Proxy configuration
|
||||
proxy={{ keycloak_quarkus_proxy_mode }}
|
||||
{% endif %}
|
||||
|
||||
spi-sticky-session-encoder-infinispan-should-attach-route={{ keycloak_quarkus_spi_sticky_session_encoder_infinispan_should_attach_route | d(true) | lower }}
|
||||
|
||||
# Transaction
|
||||
transaction-xa-enabled={{ keycloak_quarkus_transaction_xa_enabled | lower }}
|
||||
|
||||
# Logging
|
||||
#log-format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n
|
||||
log={{ keycloak_quarkus_log }}
|
||||
log-level={{ keycloak.log.level }}
|
||||
log-file={{ keycloak.log.file }}
|
||||
log-file-format={{ keycloak.log.format }}
|
||||
|
||||
# Vault
|
||||
{% if keycloak_quarkus_ks_vault_enabled %}
|
||||
vault=keystore
|
||||
vault-file={{ keycloak_quarkus_ks_vault_file }}
|
||||
vault-type={{ keycloak_quarkus_ks_vault_type }}
|
||||
vault-pass={{ keycloak_quarkus_ks_vault_pass }}
|
||||
{% endif %}
|
||||
|
||||
|
||||
# Providers
|
||||
{% for provider in keycloak_quarkus_providers %}
|
||||
{% if provider.default is defined and provider.default %}
|
||||
spi-{{ provider.spi }}-provider={{ provider.id }}
|
||||
{% endif %}
|
||||
{% if provider.properties is defined %}{% for property in provider.properties %}
|
||||
spi-{{ provider.spi }}-{{ provider.id }}-{{ property.key }}={{ property.value }}
|
||||
{% endfor %}{% endif %}
|
||||
{% endfor %}
|
||||
2
roles/keycloak_quarkus/templates/rhbk.fact.j2
Normal file
2
roles/keycloak_quarkus/templates/rhbk.fact.j2
Normal file
@@ -0,0 +1,2 @@
|
||||
[general]
|
||||
bootstrapped={{ bootstrapped | lower }}
|
||||
33
roles/keycloak_quarkus/templates/rhbk.service.j2
Normal file
33
roles/keycloak_quarkus/templates/rhbk.service.j2
Normal file
@@ -0,0 +1,33 @@
|
||||
{{ ansible_managed | comment }}
|
||||
[Unit]
|
||||
Description=Keycloak Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=-{{ keycloak_quarkus_sysconf_file }}
|
||||
{% if keycloak_quarkus_start_dev %}
|
||||
ExecStart={{ keycloak.home }}/bin/kc.sh start-dev
|
||||
{% else %}
|
||||
ExecStart={{ keycloak.home }}/bin/kc.sh start --optimized
|
||||
{% endif %}
|
||||
User={{ keycloak.service_user }}
|
||||
Group={{ keycloak.service_group }}
|
||||
SuccessExitStatus=0 143
|
||||
{% if keycloak_quarkus_service_restart_always %}
|
||||
Restart=always
|
||||
{% elif keycloak_quarkus_service_restart_on_failure %}
|
||||
Restart=on-failure
|
||||
{% endif %}
|
||||
RestartSec={{ keycloak_quarkus_service_restartsec }}
|
||||
{% if keycloak_quarkus_http_port | int < 1024 or keycloak_quarkus_https_port | int < 1024 %}
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
{% endif %}
|
||||
{% if keycloak_quarkus_systemd_wait_for_port %}
|
||||
ExecStartPost=/usr/bin/timeout {{ keycloak_quarkus_systemd_wait_for_timeout }} sh -c 'while ! ss -H -t -l -n sport = :{{ keycloak_quarkus_systemd_wait_for_port_number }} | grep -q "^LISTEN.*:{{ keycloak_quarkus_systemd_wait_for_port_number }}"; do sleep 1; done && /bin/sleep {{ keycloak_quarkus_systemd_wait_for_delay }}'
|
||||
{% endif %}
|
||||
{% if keycloak_quarkus_systemd_wait_for_log %}
|
||||
ExecStartPost=/usr/bin/timeout {{ keycloak_quarkus_systemd_wait_for_timeout }} sh -c 'cat {{ keycloak.log.file }} | sed "/Profile.*activated/ q" && /bin/sleep {{ keycloak_quarkus_systemd_wait_for_delay }}'
|
||||
{% endif %}
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user