Skip to content

Commit 8b1a193

Browse files
[PR #9987/a8b97732 backport][stable-9] Fix Keycloak authentication flow configuration issues (#10017)
Fix Keycloak authentication flow configuration issues (#9987) * Add delete_authentication_config method and integrate it into create_or_update_executions * typo * Sanity * Add integration tests for keycloak_authentication module with README, tasks, and variables * Add copyright and license information to access_token.yml * Sanity * Refactor Keycloak integration tests: streamline README, update access token task, and enhance variable management * Maj changelogs fragments --------- Co-authored-by: Andre Desrosiers <[email protected]> (cherry picked from commit a8b9773) Co-authored-by: desand01 <[email protected]>
1 parent 69bb442 commit 8b1a193

File tree

8 files changed

+262
-0
lines changed

8 files changed

+262
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
bugfixes:
2+
- keycloak_authentication - fix authentification config duplication for Keycloak < 26.2.0 (https://github.com/ansible-collections/community.general/pull/9987).

plugins/module_utils/identity/keycloak/keycloak.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2228,6 +2228,23 @@ def add_authenticationConfig_to_execution(self, executionId, authenticationConfi
22282228
except Exception as e:
22292229
self.fail_open_url(e, msg="Unable to add authenticationConfig %s: %s" % (executionId, str(e)))
22302230

2231+
def delete_authentication_config(self, configId, realm='master'):
2232+
""" Delete authenticator config
2233+
2234+
:param configId: id of authentication config
2235+
:param realm: realm of authentication config to be deleted
2236+
"""
2237+
try:
2238+
# Send a DELETE request to remove the specified authentication config from the Keycloak server.
2239+
self._request(
2240+
URL_AUTHENTICATION_CONFIG.format(
2241+
url=self.baseurl,
2242+
realm=realm,
2243+
id=configId),
2244+
method='DELETE')
2245+
except Exception as e:
2246+
self.fail_request(e, msg="Unable to delete authentication config %s: %s" % (configId, str(e)))
2247+
22312248
def create_subflow(self, subflowName, flowAlias, realm='master', flowType='basic-flow'):
22322249
""" Create new sublow on the flow
22332250

plugins/modules/keycloak_authentication.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ def create_or_update_executions(kc, config, realm='master'):
308308
}
309309
# add the execution configuration
310310
if new_exec["authenticationConfig"] is not None:
311+
if "authenticationConfig" in execution and "id" in execution["authenticationConfig"]:
312+
kc.delete_authentication_config(execution["authenticationConfig"]["id"], realm=realm)
311313
kc.add_authenticationConfig_to_execution(updated_exec["id"], new_exec["authenticationConfig"], realm=realm)
312314
for key in new_exec:
313315
# remove unwanted key for the next API call
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!--
2+
Copyright (c) Ansible Project
3+
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
4+
SPDX-License-Identifier: GPL-3.0-or-later
5+
-->
6+
# Running keycloak_authentication module integration test
7+
8+
Run integration tests:
9+
10+
ansible-test integration -v keycloak_authentication --allow-unsupported --docker fedora35 --docker-network host
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Copyright (c) Ansible Project
2+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
5+
unsupported
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright (c) Ansible Project
2+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
3+
# SPDX-License-Identifier: GPL-3.0-or-later
4+
---
5+
- name: Get access token
6+
ansible.builtin.uri:
7+
url: "{{ url }}/realms/{{ admin_realm }}/protocol/openid-connect/token"
8+
method: POST
9+
status_code: 200
10+
headers:
11+
Accept: application/json
12+
User-agent: Ansible
13+
body_format: form-urlencoded
14+
body:
15+
grant_type: "password"
16+
client_id: "admin-cli"
17+
username: "{{ admin_user }}"
18+
password: "{{ admin_password }}"
19+
register: token_response
20+
no_log: true
21+
22+
- name: Extract access token
23+
ansible.builtin.set_fact:
24+
access_token: "{{ token_response.json['access_token'] }}"
25+
no_log: true
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
---
2+
# Copyright (c) Ansible Project
3+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
4+
# SPDX-License-Identifier: GPL-3.0-or-later
5+
- name: Install required packages
6+
pip:
7+
name:
8+
- jmespath
9+
- requests
10+
register: result
11+
until: result is success
12+
13+
- name: Start container
14+
community.docker.docker_container:
15+
name: mykeycloak
16+
image: "quay.io/keycloak/keycloak:{{ keycloak_version }}"
17+
command: start-dev
18+
env:
19+
KC_HTTP_RELATIVE_PATH: /auth
20+
KEYCLOAK_ADMIN: admin
21+
KEYCLOAK_ADMIN_PASSWORD: password
22+
ports:
23+
- "{{ keycloak_port }}:8080"
24+
detach: true
25+
auto_remove: true
26+
memory: 2200M
27+
28+
- name: Wait for Keycloak
29+
uri:
30+
url: "{{ url }}/admin/"
31+
status_code: 200
32+
validate_certs: no
33+
register: result
34+
until: result.status == 200
35+
retries: 10
36+
delay: 10
37+
38+
- name: Delete realm if exists
39+
community.general.keycloak_realm:
40+
auth_keycloak_url: "{{ url }}"
41+
auth_realm: "{{ admin_realm }}"
42+
auth_username: "{{ admin_user }}"
43+
auth_password: "{{ admin_password }}"
44+
realm: "{{ realm }}"
45+
state: absent
46+
47+
- name: Create realm
48+
community.general.keycloak_realm:
49+
auth_keycloak_url: "{{ url }}"
50+
auth_realm: "{{ admin_realm }}"
51+
auth_username: "{{ admin_user }}"
52+
auth_password: "{{ admin_password }}"
53+
id: "{{ realm }}"
54+
realm: "{{ realm }}"
55+
state: present
56+
57+
- name: Create an authentication flow from first broker login and add an execution to it.
58+
community.general.keycloak_authentication:
59+
auth_keycloak_url: "{{ url }}"
60+
auth_realm: "{{ admin_realm }}"
61+
auth_username: "{{ admin_user }}"
62+
auth_password: "{{ admin_password }}"
63+
realm: "{{ realm }}"
64+
alias: "Test first broker login"
65+
copyFrom: "first broker login"
66+
authenticationExecutions:
67+
- providerId: "idp-review-profile"
68+
requirement: "REQUIRED"
69+
authenticationConfig:
70+
alias: "Test review profile config"
71+
config:
72+
update.profile.on.first.login: "missing"
73+
74+
- name: Create auth flow
75+
community.general.keycloak_authentication:
76+
auth_keycloak_url: "{{ url }}"
77+
auth_realm: "{{ admin_realm }}"
78+
auth_username: "{{ admin_user }}"
79+
auth_password: "{{ admin_password }}"
80+
realm: "{{ realm }}"
81+
alias: "My conditionnal browser otp"
82+
description: "browser based authentication with otp"
83+
providerId: "basic-flow"
84+
authenticationExecutions:
85+
- displayName: Cookie
86+
providerId: auth-cookie
87+
requirement: ALTERNATIVE
88+
- displayName: Kerberos
89+
providerId: auth-spnego
90+
requirement: DISABLED
91+
- displayName: Identity Provider Redirector
92+
providerId: identity-provider-redirector
93+
requirement: ALTERNATIVE
94+
- displayName: My browser otp forms
95+
requirement: ALTERNATIVE
96+
- displayName: Username Password Form
97+
flowAlias: My browser otp forms
98+
providerId: auth-username-password-form
99+
requirement: REQUIRED
100+
- displayName: My browser otp Browser - Conditional OTP
101+
flowAlias: My browser otp forms
102+
requirement: REQUIRED
103+
providerId: "auth-conditional-otp-form"
104+
authenticationConfig:
105+
alias: my-conditional-otp-config
106+
config:
107+
defaultOtpOutcome: "force"
108+
noOtpRequiredForHeaderPattern: "{{ keycloak_no_otp_required_pattern_orinale }}"
109+
state: present
110+
111+
- name: Modified auth flow with new config
112+
community.general.keycloak_authentication:
113+
auth_keycloak_url: "{{ url }}"
114+
auth_realm: "{{ admin_realm }}"
115+
auth_username: "{{ admin_user }}"
116+
auth_password: "{{ admin_password }}"
117+
realm: "{{ realm }}"
118+
alias: "My conditionnal browser otp"
119+
description: "browser based authentication with otp"
120+
providerId: "basic-flow"
121+
authenticationExecutions:
122+
- displayName: Cookie
123+
providerId: auth-cookie
124+
requirement: ALTERNATIVE
125+
- displayName: Kerberos
126+
providerId: auth-spnego
127+
requirement: DISABLED
128+
- displayName: Identity Provider Redirector
129+
providerId: identity-provider-redirector
130+
requirement: ALTERNATIVE
131+
- displayName: My browser otp forms
132+
requirement: ALTERNATIVE
133+
- displayName: Username Password Form
134+
flowAlias: My browser otp forms
135+
providerId: auth-username-password-form
136+
requirement: REQUIRED
137+
- displayName: My browser otp Browser - Conditional OTP
138+
flowAlias: My browser otp forms
139+
requirement: REQUIRED
140+
providerId: "auth-conditional-otp-form"
141+
authenticationConfig:
142+
alias: my-conditional-otp-config
143+
config:
144+
defaultOtpOutcome: "force"
145+
noOtpRequiredForHeaderPattern: "{{ keycloak_no_otp_required_pattern_modifed }}"
146+
state: present
147+
register: result
148+
149+
- name: Retrive access
150+
ansible.builtin.include_tasks:
151+
file: access_token.yml
152+
153+
- name: Export realm
154+
ansible.builtin.uri:
155+
url: "{{ url }}/admin/realms/{{ realm }}/partial-export?exportClients=false&exportGroupsAndRoles=false"
156+
method: POST
157+
headers:
158+
Accept: application/json
159+
User-agent: Ansible
160+
Authorization: "Bearer {{ access_token }}"
161+
body_format: form-urlencoded
162+
body: {}
163+
register: exported_realm
164+
no_log: true
165+
166+
- name: Assert `my-conditional-otp-config` exists only once
167+
ansible.builtin.assert:
168+
that:
169+
- exported_realm.json | community.general.json_query('authenticatorConfig[?alias==`my-conditional-otp-config`]') | length == 1
170+
171+
- name: Delete auth flow
172+
community.general.keycloak_authentication:
173+
auth_keycloak_url: "{{ url }}"
174+
auth_realm: "{{ admin_realm }}"
175+
auth_username: "{{ admin_user }}"
176+
auth_password: "{{ admin_password }}"
177+
realm: "{{ realm }}"
178+
alias: "My conditionnal browser otp"
179+
state: absent
180+
register: result
181+
182+
- name: Remove container
183+
community.docker.docker_container:
184+
name: mykeycloak
185+
state: absent
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
# Copyright (c) Ansible Project
3+
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
4+
# SPDX-License-Identifier: GPL-3.0-or-later
5+
keycloak_version: latest
6+
keycloak_port: 8080
7+
8+
url: "http://localhost:{{ keycloak_port }}/auth"
9+
admin_realm: master
10+
admin_user: admin
11+
admin_password: password
12+
realm: myrealm
13+
14+
15+
keycloak_no_otp_required_pattern_orinale: "X-Forwarded-For: 10\\.[0-9\\.:]+"
16+
keycloak_no_otp_required_pattern_modifed: "X-Original-Forwarded-For: 10\\.[0-9\\.:]+"

0 commit comments

Comments
 (0)