mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Keycloak set client authentification flows by name (#8428)
* first commit * Add change logs * fix sanity * Sanity 2 * Test unset flows * 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: Felix Fontein <felix@fontein.de> * Update changelogs/fragments/8428-assign-auth-flow-by-name-keycloak-client.yaml Co-authored-by: Felix Fontein <felix@fontein.de> * Remove double traitement from "alias" * Update plugins/modules/keycloak_client.py Co-authored-by: Felix Fontein <felix@fontein.de> * Update plugins/modules/keycloak_client.py Co-authored-by: Felix Fontein <felix@fontein.de> --------- Co-authored-by: Andre Desrosiers <andre.desrosiers@ssss.gouv.qc.ca> Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com> Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
df7fe19bbe
commit
b11da288d2
3 changed files with 240 additions and 1 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- keycloak_client - assign auth flow by name (https://github.com/ansible-collections/community.general/pull/8428).
|
|
@ -340,6 +340,42 @@ options:
|
||||||
description:
|
description:
|
||||||
- Override realm authentication flow bindings.
|
- Override realm authentication flow bindings.
|
||||||
type: dict
|
type: dict
|
||||||
|
suboptions:
|
||||||
|
browser:
|
||||||
|
description:
|
||||||
|
- Flow ID of the browser authentication flow.
|
||||||
|
- O(authentication_flow_binding_overrides.browser)
|
||||||
|
and O(authentication_flow_binding_overrides.browser_name) are mutually exclusive.
|
||||||
|
type: str
|
||||||
|
|
||||||
|
browser_name:
|
||||||
|
description:
|
||||||
|
- Flow name of the browser authentication flow.
|
||||||
|
- O(authentication_flow_binding_overrides.browser)
|
||||||
|
and O(authentication_flow_binding_overrides.browser_name) are mutually exclusive.
|
||||||
|
aliases:
|
||||||
|
- browserName
|
||||||
|
type: str
|
||||||
|
version_added: 9.1.0
|
||||||
|
|
||||||
|
direct_grant:
|
||||||
|
description:
|
||||||
|
- Flow ID of the direct grant authentication flow.
|
||||||
|
- O(authentication_flow_binding_overrides.direct_grant)
|
||||||
|
and O(authentication_flow_binding_overrides.direct_grant_name) are mutually exclusive.
|
||||||
|
aliases:
|
||||||
|
- directGrant
|
||||||
|
type: str
|
||||||
|
|
||||||
|
direct_grant_name:
|
||||||
|
description:
|
||||||
|
- Flow name of the direct grant authentication flow.
|
||||||
|
- O(authentication_flow_binding_overrides.direct_grant)
|
||||||
|
and O(authentication_flow_binding_overrides.direct_grant_name) are mutually exclusive.
|
||||||
|
aliases:
|
||||||
|
- directGrantName
|
||||||
|
type: str
|
||||||
|
version_added: 9.1.0
|
||||||
aliases:
|
aliases:
|
||||||
- authenticationFlowBindingOverrides
|
- authenticationFlowBindingOverrides
|
||||||
version_added: 3.4.0
|
version_added: 3.4.0
|
||||||
|
@ -781,6 +817,64 @@ def sanitize_cr(clientrep):
|
||||||
return normalise_cr(result)
|
return normalise_cr(result)
|
||||||
|
|
||||||
|
|
||||||
|
def get_authentication_flow_id(flow_name, realm, kc):
|
||||||
|
""" Get the authentication flow ID based on the flow name, realm, and Keycloak client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flow_name (str): The name of the authentication flow.
|
||||||
|
realm (str): The name of the realm.
|
||||||
|
kc (KeycloakClient): The Keycloak client instance.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The ID of the authentication flow.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeycloakAPIException: If the authentication flow with the given name is not found in the realm.
|
||||||
|
"""
|
||||||
|
flow = kc.get_authentication_flow_by_alias(flow_name, realm)
|
||||||
|
if flow:
|
||||||
|
return flow["id"]
|
||||||
|
kc.module.fail_json(msg='Authentification flow %s not found in realm %s' % (flow_name, realm))
|
||||||
|
|
||||||
|
|
||||||
|
def flow_binding_from_dict_to_model(newClientFlowBinding, realm, kc):
|
||||||
|
""" Convert a dictionary representing client flow bindings to a model representation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
newClientFlowBinding (dict): A dictionary containing client flow bindings.
|
||||||
|
realm (str): The name of the realm.
|
||||||
|
kc (KeycloakClient): An instance of the KeycloakClient class.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary representing the model flow bindings. The dictionary has two keys:
|
||||||
|
- "browser" (str or None): The ID of the browser authentication flow binding, or None if not provided.
|
||||||
|
- "direct_grant" (str or None): The ID of the direct grant authentication flow binding, or None if not provided.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
KeycloakAPIException: If the authentication flow with the given name is not found in the realm.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
modelFlow = {
|
||||||
|
"browser": None,
|
||||||
|
"direct_grant": None
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v in newClientFlowBinding.items():
|
||||||
|
if not v:
|
||||||
|
continue
|
||||||
|
if k == "browser":
|
||||||
|
modelFlow["browser"] = v
|
||||||
|
elif k == "browser_name":
|
||||||
|
modelFlow["browser"] = get_authentication_flow_id(v, realm, kc)
|
||||||
|
elif k == "direct_grant":
|
||||||
|
modelFlow["direct_grant"] = v
|
||||||
|
elif k == "direct_grant_name":
|
||||||
|
modelFlow["direct_grant"] = get_authentication_flow_id(v, realm, kc)
|
||||||
|
|
||||||
|
return modelFlow
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""
|
"""
|
||||||
Module execution
|
Module execution
|
||||||
|
@ -799,6 +893,13 @@ def main():
|
||||||
config=dict(type='dict'),
|
config=dict(type='dict'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
authentication_flow_spec = dict(
|
||||||
|
browser=dict(type='str'),
|
||||||
|
browser_name=dict(type='str', aliases=['browserName']),
|
||||||
|
direct_grant=dict(type='str', aliases=['directGrant']),
|
||||||
|
direct_grant_name=dict(type='str', aliases=['directGrantName']),
|
||||||
|
)
|
||||||
|
|
||||||
meta_args = dict(
|
meta_args = dict(
|
||||||
state=dict(default='present', choices=['present', 'absent']),
|
state=dict(default='present', choices=['present', 'absent']),
|
||||||
realm=dict(type='str', default='master'),
|
realm=dict(type='str', default='master'),
|
||||||
|
@ -838,7 +939,13 @@ def main():
|
||||||
use_template_scope=dict(type='bool', aliases=['useTemplateScope']),
|
use_template_scope=dict(type='bool', aliases=['useTemplateScope']),
|
||||||
use_template_mappers=dict(type='bool', aliases=['useTemplateMappers']),
|
use_template_mappers=dict(type='bool', aliases=['useTemplateMappers']),
|
||||||
always_display_in_console=dict(type='bool', aliases=['alwaysDisplayInConsole']),
|
always_display_in_console=dict(type='bool', aliases=['alwaysDisplayInConsole']),
|
||||||
authentication_flow_binding_overrides=dict(type='dict', aliases=['authenticationFlowBindingOverrides']),
|
authentication_flow_binding_overrides=dict(
|
||||||
|
type='dict',
|
||||||
|
aliases=['authenticationFlowBindingOverrides'],
|
||||||
|
options=authentication_flow_spec,
|
||||||
|
required_one_of=[['browser', 'direct_grant', 'browser_name', 'direct_grant_name']],
|
||||||
|
mutually_exclusive=[['browser', 'browser_name'], ['direct_grant', 'direct_grant_name']],
|
||||||
|
),
|
||||||
protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']),
|
protocol_mappers=dict(type='list', elements='dict', options=protmapper_spec, aliases=['protocolMappers']),
|
||||||
authorization_settings=dict(type='dict', aliases=['authorizationSettings']),
|
authorization_settings=dict(type='dict', aliases=['authorizationSettings']),
|
||||||
default_client_scopes=dict(type='list', elements='str', aliases=['defaultClientScopes']),
|
default_client_scopes=dict(type='list', elements='str', aliases=['defaultClientScopes']),
|
||||||
|
@ -900,6 +1007,8 @@ def main():
|
||||||
# they are not specified
|
# they are not specified
|
||||||
if client_param == 'protocol_mappers':
|
if client_param == 'protocol_mappers':
|
||||||
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value]
|
new_param_value = [dict((k, v) for k, v in x.items() if x[k] is not None) for x in new_param_value]
|
||||||
|
elif client_param == 'authentication_flow_binding_overrides':
|
||||||
|
new_param_value = flow_binding_from_dict_to_model(new_param_value, realm, kc)
|
||||||
|
|
||||||
changeset[camel(client_param)] = new_param_value
|
changeset[camel(client_param)] = new_param_value
|
||||||
|
|
||||||
|
|
|
@ -103,3 +103,131 @@
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- check_client_when_present_and_changed is changed
|
- check_client_when_present_and_changed is changed
|
||||||
|
|
||||||
|
- 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"
|
Loading…
Reference in a new issue