community.general/tests/integration/targets/keycloak_client/tasks/main.yml
desand01 f34842b7b2
Some checks failed
EOL CI / EOL Sanity (Ⓐ2.16) (push) Has been cancelled
EOL CI / EOL Units (Ⓐ2.16+py2.7) (push) Has been cancelled
EOL CI / EOL Units (Ⓐ2.16+py3.11) (push) Has been cancelled
EOL CI / EOL Units (Ⓐ2.16+py3.6) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/1/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/2/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+alpine3+py:azp/posix/3/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/1/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/2/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+fedora38+py:azp/posix/3/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/1/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/2/) (push) Has been cancelled
EOL CI / EOL I (Ⓐ2.16+opensuse15+py:azp/posix/3/) (push) Has been cancelled
nox / Run extra sanity tests (push) Has been cancelled
Keycloak client scope support (#10842)
* first commit

* sanity

* fixe test

* trailing white space

* sanity

* Fragment

* test sanity

* Update changelogs/fragments/10842-keycloak-client-scope-support.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update plugins/modules/keycloak_client.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* add client_scopes_behavior

* Sanity

* Sanity

* Update plugins/modules/keycloak_client.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Fix typo.

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* Update plugins/modules/keycloak_client.py

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* Update plugins/modules/keycloak_client.py

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* Update plugins/modules/keycloak_client.py

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

* Update plugins/modules/keycloak_client.py

Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>

---------

Co-authored-by: Andre Desrosiers <andre.desrosiers@ssss.gouv.qc.ca>
Co-authored-by: Felix Fontein <felix@fontein.de>
Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
2025-10-06 18:16:27 +02:00

459 lines
No EOL
17 KiB
YAML

---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Install required packages
pip:
name:
- jmespath
register: result
until: result is success
- name: Wait for Keycloak
uri:
url: "{{ url }}/admin/"
status_code: 200
validate_certs: false
register: result
until: result.status == 200
retries: 10
delay: 10
- name: Delete realm
community.general.keycloak_realm:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
id: "{{ realm }}"
realm: "{{ realm }}"
state: absent
- name: Create realm
community.general.keycloak_realm:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
id: "{{ realm }}"
realm: "{{ realm }}"
state: present
- name: Desire client
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers1}}'
register: desire_client_not_present
- name: Desire client again with same props
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers1}}'
register: desire_client_when_present_and_same
- name: Check client again with same props
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers2_unordered}}'
authorization_services_enabled: false
check_mode: true
register: check_client_when_present_and_same
- name: Assert changes not detected in last two tasks (desire when same, and check)
assert:
that:
- desire_client_when_present_and_same is not changed
- check_client_when_present_and_same is not changed
- name: Check client again with changed props
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers1}}'
authorization_services_enabled: false
service_accounts_enabled: true
check_mode: true
register: check_client_when_present_and_changed
- name: Assert changes detected in last tasks
assert:
that:
- check_client_when_present_and_changed is changed
- name: Check client with modified protocol_mappers idempotence
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers3_modifed}}'
authorization_services_enabled: false
service_accounts_enabled: true
register: check_client_protocol_mappers_idempotence
- name: Assert idempotence changes to protocol_mappers
assert:
that:
- check_client_protocol_mappers_idempotence is changed
- end_state.protocolMappers | length == 3
- end_state.protocolMappers | community.general.json_query("[?name == 'email_verified']") | length == 0
- end_state.protocolMappers | community.general.json_query("[?name == 'address']") | length == 1
- end_state.protocolMappers | community.general.json_query("[?name == 'email']") | length == 1
- end_state.protocolMappers | community.general.json_query("[?name == 'family_name']") | length == 1
- email.config is defined
- email.config['access.token.claim'] == "false"
vars:
end_state: "{{ check_client_protocol_mappers_idempotence.end_state }}"
email: "{{ end_state.protocolMappers | community.general.json_query('[?name == `email`]') | first | d({}) }}"
- name: Desire client with flow binding overrides
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers1}}'
authentication_flow_binding_overrides:
browser_name: browser
direct_grant_name: direct grant
register: desire_client_with_flow_binding_overrides
- name: Assert flows are set
assert:
that:
- desire_client_with_flow_binding_overrides is changed
- "'authenticationFlowBindingOverrides' in desire_client_with_flow_binding_overrides.end_state"
- desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides.browser | length > 0
- desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides.direct_grant | length > 0
- name: Backup flow UUIDs
set_fact:
flow_browser_uuid: "{{ desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides.browser }}"
flow_direct_grant_uuid: "{{ desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides.direct_grant }}"
- name: Desire client with flow binding overrides remove direct_grant_name
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers1}}'
authentication_flow_binding_overrides:
browser_name: browser
register: desire_client_with_flow_binding_overrides
- name: Assert flows are updated
assert:
that:
- desire_client_with_flow_binding_overrides is changed
- "'authenticationFlowBindingOverrides' in desire_client_with_flow_binding_overrides.end_state"
- desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides.browser | length > 0
- "'direct_grant' not in desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides"
- name: Desire client with flow binding overrides remove browser add direct_grant
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers1}}'
authentication_flow_binding_overrides:
direct_grant_name: direct grant
register: desire_client_with_flow_binding_overrides
- name: Assert flows are updated
assert:
that:
- desire_client_with_flow_binding_overrides is changed
- "'authenticationFlowBindingOverrides' in desire_client_with_flow_binding_overrides.end_state"
- "'browser' not in desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides"
- desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides.direct_grant | length > 0
- name: Desire client with flow binding overrides with UUIDs
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers1}}'
authentication_flow_binding_overrides:
browser: "{{ flow_browser_uuid }}"
direct_grant: "{{ flow_direct_grant_uuid }}"
register: desire_client_with_flow_binding_overrides
- name: Assert flows are updated
assert:
that:
- desire_client_with_flow_binding_overrides is changed
- "'authenticationFlowBindingOverrides' in desire_client_with_flow_binding_overrides.end_state"
- desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides.browser == flow_browser_uuid
- desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides.direct_grant == flow_direct_grant_uuid
- name: Unset flow binding overrides
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id }}"
state: present
redirect_uris: '{{redirect_uris1}}'
attributes: '{{client_attributes1}}'
protocol_mappers: '{{protocol_mappers1}}'
authentication_flow_binding_overrides:
browser: "{{ None }}"
direct_grant: null
register: desire_client_with_flow_binding_overrides
- name: Assert flows are removed
assert:
that:
- desire_client_with_flow_binding_overrides is changed
- "'authenticationFlowBindingOverrides' in desire_client_with_flow_binding_overrides.end_state"
- "'browser' not in desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides"
- "'direct_grant' not in desire_client_with_flow_binding_overrides.end_state.authenticationFlowBindingOverrides"
- name: Create a scope1 client scope
community.general.keycloak_clientscope:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
name: scope1
description: "test 1"
protocol: openid-connect
- name: Create a scope2 client scope
community.general.keycloak_clientscope:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
name: scope2
description: "test 2"
protocol: openid-connect
- name: Create a scope3 client scope
community.general.keycloak_clientscope:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
name: scope3
description: "test 3"
protocol: openid-connect
- name: Create Keycloak client with default_client_scopes (idempotent behavior)
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_scopes_behavior: idempotent
default_client_scopes: ['scope1']
optional_client_scopes: ['scope2']
client_id: testSD-bug
state: present
register: desire_client_with_default_client_scopes
- name: Assert default_client_scopes and optional_client_scopes are set correctly
assert:
that:
- desire_client_with_default_client_scopes is changed
- '"scope1" in end_state.defaultClientScopes'
- '"scope2" in end_state.optionalClientScopes'
- end_state.defaultClientScopes | length == 1
- end_state.optionalClientScopes | length == 1
vars:
end_state: "{{ desire_client_with_default_client_scopes.end_state }}"
- name: Update Keycloak client with new scopes (ignore behavior, check mode)
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
default_client_scopes: ['scope3']
optional_client_scopes: ['scope3']
client_id: testSD-bug
state: present
check_mode: true
register: desire_client_with_default_client_scopes
- name: Assert client scopes remain unchanged with ignore behavior
assert:
that:
- desire_client_with_default_client_scopes is not changed
- end_state.defaultClientScopes | length == 1
- end_state.optionalClientScopes | length == 1
- '"scope1" in end_state.defaultClientScopes'
- '"scope2" in end_state.optionalClientScopes'
vars:
end_state: "{{ desire_client_with_default_client_scopes.end_state }}"
- name: Update Keycloak client with conflicting scopes (patch behavior, should fail)
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_scopes_behavior: patch
default_client_scopes: ['scope3']
optional_client_scopes: ['scope1', 'scope2', 'scope3']
client_id: testSD-bug
state: present
ignore_errors: true
register: desire_client_with_default_client_scopes
- name: Assert patch behavior fails when scope is both default and optional
assert:
that:
- desire_client_with_default_client_scopes is failed
- "'scope3' in desire_client_with_default_client_scopes.msg"
- name: Update Keycloak client with new scopes (patch behavior)
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_scopes_behavior: patch
default_client_scopes: ['scope1', 'scope3']
optional_client_scopes: []
client_id: testSD-bug
state: present
register: desire_client_with_default_client_scopes
- name: Assert client scopes are patched correctly
assert:
that:
- desire_client_with_default_client_scopes is changed
- end_state.defaultClientScopes | length == 2
- end_state.optionalClientScopes | length == 1
- '"scope1" in end_state.defaultClientScopes'
- '"scope3" in end_state.defaultClientScopes'
- '"scope2" in end_state.optionalClientScopes'
vars:
end_state: "{{ desire_client_with_default_client_scopes.end_state }}"
- name: Update Keycloak client with empty default_client_scopes (idempotent behavior)
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_scopes_behavior: idempotent
default_client_scopes: []
optional_client_scopes: ['scope3']
client_id: testSD-bug
state: present
register: desire_client_with_default_client_scopes
- name: Assert idempotent behavior with empty default_client_scopes
assert:
that:
- desire_client_with_default_client_scopes is changed
- end_state.defaultClientScopes | length == 0
- end_state.optionalClientScopes | length == 1
- '"scope3" in end_state.optionalClientScopes'
vars:
end_state: "{{ desire_client_with_default_client_scopes.end_state }}"
- name: Create client with initial attributes
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id_2 }}"
state: present
attributes: '{{ client_attributes1 }}'
register: check_client_when_present_and_attributes_modified
- name: Update client attributes
community.general.keycloak_client:
auth_keycloak_url: "{{ url }}"
auth_realm: "{{ admin_realm }}"
auth_username: "{{ admin_user }}"
auth_password: "{{ admin_password }}"
realm: "{{ realm }}"
client_id: "{{ client_id_2 }}"
state: present
attributes: '{{ client_attributes2 }}'
register: check_client_when_present_and_attributes_modified
- name: Assert client attributes are updated
assert:
that:
- check_client_when_present_and_attributes_modified is changed
- end_state.attributes["backchannel.logout.revoke.offline.tokens"] == 'false'
- end_state.attributes["backchannel.logout.session.required"] == 'false'
- end_state.attributes["oauth2.device.authorization.grant.enabled"] == 'false'
vars:
end_state: "{{ check_client_when_present_and_attributes_modified.end_state }}"