From 31e3791533db42c62f2b4e057996f3e39c415ff7 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Thu, 12 Sep 2024 23:38:00 -0400 Subject: [PATCH 01/30] feat: begin refactor to support refresh token in keycloak modules --- .../identity/keycloak/keycloak.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 128b0fee134..300695d3621 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -22,6 +22,7 @@ URL_REALM_KEYS_METADATA = "{url}/admin/realms/{realm}/keys" URL_TOKEN = "{url}/realms/{realm}/protocol/openid-connect/token" +URL_USERINFO = "{url}/realms/{realm}/protocol/openid-connect/userinfo" URL_CLIENT = "{url}/admin/realms/{realm}/clients/{id}" URL_CLIENTS = "{url}/admin/realms/{realm}/clients" @@ -161,22 +162,36 @@ def get_token(module_params): :return: connection header """ token = module_params.get('token') + refresh_token = module_params.get('refresh_token') base_url = module_params.get('auth_keycloak_url') http_agent = module_params.get('http_agent') if not base_url.lower().startswith(('http', 'https')): raise KeycloakError("auth_url '%s' should either start with 'http' or 'https'." % base_url) - if token is None: - base_url = module_params.get('auth_keycloak_url') - validate_certs = module_params.get('validate_certs') - auth_realm = module_params.get('auth_realm') - client_id = module_params.get('auth_client_id') - auth_username = module_params.get('auth_username') - auth_password = module_params.get('auth_password') - client_secret = module_params.get('auth_client_secret') - connection_timeout = module_params.get('connection_timeout') - auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) + base_url = module_params.get('auth_keycloak_url') + validate_certs = module_params.get('validate_certs') + auth_realm = module_params.get('auth_realm') + client_id = module_params.get('auth_client_id') + auth_username = module_params.get('auth_username') + auth_password = module_params.get('auth_password') + client_secret = module_params.get('auth_client_secret') + connection_timeout = module_params.get('connection_timeout') + auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) + + if token is not None: + # Check token is valid via userinfo route + userinfo_url = URL_USERINFO(url=base_url, realm=auth_realm) + try: + r = json.loads(to_native(open_url(userinfo_url, method='GET', + validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout,))) + except Exception as e: + raise KeycloakError('Could not check access token from %s: %s' + % (userinfo_url, str(e))) + + # if r.StatusCode == 401: + # Fall back to refresh token and then re-authentication with username/password + else: temp_payload = { 'grant_type': 'password', 'client_id': client_id, From 3fe444a8f7e3d10438aa224396760967199a27f8 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Fri, 13 Sep 2024 22:32:51 -0400 Subject: [PATCH 02/30] chore: add start of tests for shared token usage --- .../targets/keycloak_role/tasks/main.yml | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/tests/integration/targets/keycloak_role/tasks/main.yml b/tests/integration/targets/keycloak_role/tasks/main.yml index c649b868089..d2584964d4b 100644 --- a/tests/integration/targets/keycloak_role/tasks/main.yml +++ b/tests/integration/targets/keycloak_role/tasks/main.yml @@ -3,6 +3,29 @@ # 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 +# Placeholder tests for shared token; temporarily hijacking these integration tests for fast development +- name: Get Keycloak token + ansible.builtin.uri: + url: "{{ url }}/realms/{{ admin_realm }}/protocol/openid-connect/token" + method: POST + return_content: true + status_code: 200 + body_format: form-urlencoded + body: + grant_type: "password" + client_id: "admin-cli" + username: "{{ admin_user }}" + password: "{{ admin_password }}" + register: token_response + +- name: Debug token response + ansible.builtin.debug: + var: token_response + +- name: Skip other tests + ansible.builtin.assert: + that: false + - name: Create realm community.general.keycloak_realm: auth_keycloak_url: "{{ url }}" @@ -43,10 +66,10 @@ - name: Assert realm role created assert: that: - - result is changed - - result.existing == {} - - result.end_state.name == "{{ role }}" - - result.end_state.containerId == "{{ realm }}" + - "result is changed" + - "result.existing == {}" + - "result.end_state.name == role" + - "result.end_state.containerId == realm" - name: Create existing realm role community.general.keycloak_role: @@ -154,10 +177,10 @@ - name: Assert client role created assert: that: - - result is changed - - result.existing == {} - - result.end_state.name == "{{ role }}" - - result.end_state.containerId == "{{ client.end_state.id }}" + - "result is changed" + - "result.existing == {}" + - "result.end_state.name == role" + - "result.end_state.containerId == client.end_state.id" - name: Create existing client role community.general.keycloak_role: From c7341dbc5b995c0e0a9a719a3414313303080195 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Sat, 28 Sep 2024 15:39:48 -0400 Subject: [PATCH 03/30] feat: progress towards supporting refresh token; token introspection not yet working [8857] --- .../identity/keycloak/keycloak.py | 108 ++- plugins/modules/keycloak_role.py | 2 +- .../targets/keycloak_role/tasks/main.yml | 808 ++++++++---------- 3 files changed, 443 insertions(+), 475 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 300695d3621..97540ac4273 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -22,7 +22,7 @@ URL_REALM_KEYS_METADATA = "{url}/admin/realms/{realm}/keys" URL_TOKEN = "{url}/realms/{realm}/protocol/openid-connect/token" -URL_USERINFO = "{url}/realms/{realm}/protocol/openid-connect/userinfo" +URL_INTROSPECT = "{url}/realms/{realm}/protocol/openid-connect/token/introspect" URL_CLIENT = "{url}/admin/realms/{realm}/clients/{id}" URL_CLIENTS = "{url}/admin/realms/{realm}/clients" @@ -179,49 +179,97 @@ def get_token(module_params): connection_timeout = module_params.get('connection_timeout') auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) - if token is not None: - # Check token is valid via userinfo route - userinfo_url = URL_USERINFO(url=base_url, realm=auth_realm) + def extract_token(response): try: - r = json.loads(to_native(open_url(userinfo_url, method='GET', - validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout,))) - except Exception as e: - raise KeycloakError('Could not check access token from %s: %s' - % (userinfo_url, str(e))) + token = response['access_token'] + except KeyError: + raise KeycloakError( + 'Could not obtain access token from %s' % auth_url) - # if r.StatusCode == 401: - # Fall back to refresh token and then re-authentication with username/password - else: + return token + + def wrap_token(token): + return { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json' + } + + def prepare_payload(grant_type, + client_id, + client_secret=None, + refresh_token=None, + username=None, + password=None): temp_payload = { - 'grant_type': 'password', + 'grant_type': grant_type, 'client_id': client_id, 'client_secret': client_secret, - 'username': auth_username, - 'password': auth_password, + 'refresh_token': refresh_token, + 'username': username, + 'password': password, } # Remove empty items, for instance missing client_secret payload = {k: v for k, v in temp_payload.items() if v is not None} + + return payload + + # Check existing token if it's provided + if token is not None: + # Check token is valid via userinfo route + userinfo_url = URL_INTROSPECT.format(url=base_url, realm=auth_realm) + try: + r = json.loads(to_native(open_url(userinfo_url, method='GET', headers=wrap_token(token), + validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout))) + except Exception as e: + raise KeycloakError('Could not check access token via %s: %s' + % (userinfo_url, str(e))) + + if r.StatusCode == 200: + return wrap_token(token) + + # Try authenticating via refresh token if it's provided + if refresh_token is not None: + payload = prepare_payload( + grant_type="refresh-token", + client_id=client_id, + client_secret=client_secret, + refresh_token=refresh_token, + ) + try: r = json.loads(to_native(open_url(auth_url, method='POST', validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout, data=urlencode(payload)).read())) - except ValueError as e: - raise KeycloakError( - 'API returned invalid JSON when trying to obtain access token from %s: %s' - % (auth_url, str(e))) - except Exception as e: - raise KeycloakError('Could not obtain access token from %s: %s' + except: + raise KeycloakError('Could not refresh token via %s: %s' % (auth_url, str(e))) - try: - token = r['access_token'] - except KeyError: - raise KeycloakError( - 'Could not obtain access token from %s' % auth_url) - return { - 'Authorization': 'Bearer ' + token, - 'Content-Type': 'application/json' - } + if r.statusCode == 200: + return wrap_token(extract_token(r)) + + # Access token and refresh token either not provided or not valid, + # authenticate via username/password + payload = prepare_payload( + grant_type="password", + client_id=client_id, + client_secret=client_secret, + username=auth_username, + password=auth_password, + ) + + try: + r = json.loads(to_native(open_url(auth_url, method='POST', + validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout, + data=urlencode(payload)).read())) + except ValueError as e: + raise KeycloakError( + 'API returned invalid JSON when trying to obtain access token from %s: %s' + % (auth_url, str(e))) + except Exception as e: + raise KeycloakError('Could not obtain access token from %s: %s' + % (auth_url, str(e))) + + return wrap_token(extract_token(r)) def is_struct_included(struct1, struct2, exclude=None): diff --git a/plugins/modules/keycloak_role.py b/plugins/modules/keycloak_role.py index f3e01483f80..e65fd7c3399 100644 --- a/plugins/modules/keycloak_role.py +++ b/plugins/modules/keycloak_role.py @@ -269,7 +269,7 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']])) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/tests/integration/targets/keycloak_role/tasks/main.yml b/tests/integration/targets/keycloak_role/tasks/main.yml index d2584964d4b..b3a89b16c4e 100644 --- a/tests/integration/targets/keycloak_role/tasks/main.yml +++ b/tests/integration/targets/keycloak_role/tasks/main.yml @@ -18,13 +18,10 @@ password: "{{ admin_password }}" register: token_response -- name: Debug token response - ansible.builtin.debug: - var: token_response - -- name: Skip other tests - ansible.builtin.assert: - that: false +- name: Extract tokens + ansible.builtin.set_fact: + access_token: "{{ token_response.json | json_query('access_token') }}" + refresh_token: "{{ token_response.json | json_query('refresh_token') }}" - name: Create realm community.general.keycloak_realm: @@ -47,460 +44,383 @@ state: present register: client -- name: Create new realm role - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ role }}" - description: "{{ description_1 }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert realm role created - assert: - that: - - "result is changed" - - "result.existing == {}" - - "result.end_state.name == role" - - "result.end_state.containerId == realm" - -- name: Create existing realm role - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ role }}" - description: "{{ description_1 }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert realm role unchanged - assert: - that: - - result is not changed - -- name: Update realm role - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ role }}" - description: "{{ description_2 }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert realm role updated - assert: - that: - - result is changed - - result.existing.description == "{{ description_1 }}" - - result.end_state.description == "{{ description_2 }}" - -- name: Delete existing realm role - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ role }}" - state: absent - register: result - -- name: Debug - debug: - var: result - -- name: Assert realm role deleted - assert: - that: - - result is changed - - result.end_state == {} - -- name: Delete absent realm role - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ role }}" - state: absent - register: result - -- name: Debug - debug: - var: result - -- name: Assert realm role unchanged - assert: - that: - - result is not changed - - result.end_state == {} - -- name: Create new client role +- name: Create new realm role via username/password auth community.general.keycloak_role: auth_keycloak_url: "{{ url }}" auth_realm: "{{ admin_realm }}" auth_username: "{{ admin_user }}" auth_password: "{{ admin_password }}" realm: "{{ realm }}" - client_id: "{{ client_id }}" name: "{{ role }}" description: "{{ description_1 }}" state: present register: result -- name: Debug - debug: - var: result - -- name: Assert client role created - assert: - that: - - "result is changed" - - "result.existing == {}" - - "result.end_state.name == role" - - "result.end_state.containerId == client.end_state.id" - -- name: Create existing client role +- name: Create new realm role via existing access token community.general.keycloak_role: auth_keycloak_url: "{{ url }}" auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" + token: "{{ access_token }}" realm: "{{ realm }}" - client_id: "{{ client_id }}" name: "{{ role }}" description: "{{ description_1 }}" state: present register: result -- name: Debug - debug: - var: result - -- name: Assert client role unchanged - assert: - that: - - result is not changed - -- name: Update client role - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - client_id: "{{ client_id }}" - name: "{{ role }}" - description: "{{ description_2 }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert client role updated - assert: - that: - - result is changed - - result.existing.description == "{{ description_1 }}" - - result.end_state.description == "{{ description_2 }}" - -- name: Delete existing client role - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - client_id: "{{ client_id }}" - name: "{{ role }}" - state: absent - register: result - -- name: Debug - debug: - var: result - -- name: Assert client role deleted - assert: - that: - - result is changed - - result.end_state == {} - -- name: Delete absent client role - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - client_id: "{{ client_id }}" - name: "{{ role }}" - state: absent - register: result - -- name: Debug - debug: - var: result - -- name: Assert client role unchanged - assert: - that: - - result is not changed - - result.end_state == {} - -- name: Create realm role with composites - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - name: "{{ keycloak_role_name }}" - realm: "{{ realm }}" - description: "{{ keycloak_role_description }}" - composite: "{{ keycloak_role_composite }}" - composites: "{{ keycloak_role_composites }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert realm role is created with composites - assert: - that: - - result is changed - - result.end_state.composites | length == 3 - -- name: Change realm role with composites no change - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - name: "{{ keycloak_role_name }}" - realm: "{{ realm }}" - description: "{{ keycloak_role_description }}" - composite: "{{ keycloak_role_composite }}" - composites: "{{ keycloak_role_composites }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert realm role with composites have not changed - assert: - that: - - result is not changed - - result.end_state.composites | length == 3 - -- name: Remove composite from realm role with composites - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - name: "{{ keycloak_role_name }}" - realm: "{{ realm }}" - description: "{{ keycloak_role_description }}" - composite: "{{ keycloak_role_composite }}" - composites: "{{ keycloak_role_composites_with_absent }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert composite was removed from realm role with composites - assert: - that: - - result is changed - - result.end_state.composites | length == 2 - -- name: Delete realm role with composites - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ keycloak_role_name }}" - state: absent - register: result - -- name: Debug - debug: - var: result - -- name: Assert realm role deleted - assert: - that: - - result is changed - - result.end_state == {} - -- name: Delete absent realm role with composites - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ keycloak_role_name }}" - state: absent - register: result - -- name: Debug - debug: - var: result - -- name: Assert not changed and realm role absent - assert: - that: - - result is not changed - - result.end_state == {} - -- name: Create client role with composites - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - name: "{{ keycloak_role_name }}" - client_id: "{{ client_id }}" - realm: "{{ realm }}" - description: "{{ keycloak_role_description }}" - composite: "{{ keycloak_role_composite }}" - composites: "{{ keycloak_role_composites }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert client role is created with composites - assert: - that: - - result is changed - - result.end_state.composites | length == 3 - -- name: Change client role with composites no change - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - name: "{{ keycloak_role_name }}" - client_id: "{{ client_id }}" - realm: "{{ realm }}" - description: "{{ keycloak_role_description }}" - composite: "{{ keycloak_role_composite }}" - composites: "{{ keycloak_role_composites }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert client role with composites have not changed - assert: - that: - - result is not changed - - result.end_state.composites | length == 3 - -- name: Remove composite from client role with composites - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - name: "{{ keycloak_role_name }}" - client_id: "{{ client_id }}" - realm: "{{ realm }}" - description: "{{ keycloak_role_description }}" - composite: "{{ keycloak_role_composite }}" - composites: "{{ keycloak_role_composites_with_absent }}" - state: present - register: result - -- name: Debug - debug: - var: result - -- name: Assert composite was removed from client role with composites - assert: - that: - - result is changed - - result.end_state.composites | length == 2 - -- name: Delete client role with composites - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ keycloak_role_name }}" - client_id: "{{ client_id }}" - state: absent - register: result - -- name: Debug - debug: - var: result - -- name: Assert client role deleted - assert: - that: - - result is changed - - result.end_state == {} - -- name: Delete absent client role with composites - community.general.keycloak_role: - auth_keycloak_url: "{{ url }}" - auth_realm: "{{ admin_realm }}" - auth_username: "{{ admin_user }}" - auth_password: "{{ admin_password }}" - realm: "{{ realm }}" - name: "{{ keycloak_role_name }}" - client_id: "{{ client_id }}" - state: absent - register: result - -- name: Debug - debug: - var: result - -- name: Assert not changed and client role absent - assert: - that: - - result is not changed - - result.end_state == {} \ No newline at end of file +# - name: Debug +# debug: +# var: result + +# - name: Assert realm role unchanged +# assert: +# that: +# - result is not changed +# - result.end_state == {} + +# - name: Create new client role +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# client_id: "{{ client_id }}" +# name: "{{ role }}" +# description: "{{ description_1 }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert client role created +# assert: +# that: +# - "result is changed" +# - "result.existing == {}" +# - "result.end_state.name == role" +# - "result.end_state.containerId == client.end_state.id" + +# - name: Create existing client role +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# client_id: "{{ client_id }}" +# name: "{{ role }}" +# description: "{{ description_1 }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert client role unchanged +# assert: +# that: +# - result is not changed + +# - name: Update client role +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# client_id: "{{ client_id }}" +# name: "{{ role }}" +# description: "{{ description_2 }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert client role updated +# assert: +# that: +# - result is changed +# - result.existing.description == "{{ description_1 }}" +# - result.end_state.description == "{{ description_2 }}" + +# - name: Delete existing client role +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# client_id: "{{ client_id }}" +# name: "{{ role }}" +# state: absent +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert client role deleted +# assert: +# that: +# - result is changed +# - result.end_state == {} + +# - name: Delete absent client role +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# client_id: "{{ client_id }}" +# name: "{{ role }}" +# state: absent +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert client role unchanged +# assert: +# that: +# - result is not changed +# - result.end_state == {} + +# - name: Create realm role with composites +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# name: "{{ keycloak_role_name }}" +# realm: "{{ realm }}" +# description: "{{ keycloak_role_description }}" +# composite: "{{ keycloak_role_composite }}" +# composites: "{{ keycloak_role_composites }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert realm role is created with composites +# assert: +# that: +# - result is changed +# - result.end_state.composites | length == 3 + +# - name: Change realm role with composites no change +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# name: "{{ keycloak_role_name }}" +# realm: "{{ realm }}" +# description: "{{ keycloak_role_description }}" +# composite: "{{ keycloak_role_composite }}" +# composites: "{{ keycloak_role_composites }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert realm role with composites have not changed +# assert: +# that: +# - result is not changed +# - result.end_state.composites | length == 3 + +# - name: Remove composite from realm role with composites +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# name: "{{ keycloak_role_name }}" +# realm: "{{ realm }}" +# description: "{{ keycloak_role_description }}" +# composite: "{{ keycloak_role_composite }}" +# composites: "{{ keycloak_role_composites_with_absent }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert composite was removed from realm role with composites +# assert: +# that: +# - result is changed +# - result.end_state.composites | length == 2 + +# - name: Delete realm role with composites +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# name: "{{ keycloak_role_name }}" +# state: absent +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert realm role deleted +# assert: +# that: +# - result is changed +# - result.end_state == {} + +# - name: Delete absent realm role with composites +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# name: "{{ keycloak_role_name }}" +# state: absent +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert not changed and realm role absent +# assert: +# that: +# - result is not changed +# - result.end_state == {} + +# - name: Create client role with composites +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# name: "{{ keycloak_role_name }}" +# client_id: "{{ client_id }}" +# realm: "{{ realm }}" +# description: "{{ keycloak_role_description }}" +# composite: "{{ keycloak_role_composite }}" +# composites: "{{ keycloak_role_composites }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert client role is created with composites +# assert: +# that: +# - result is changed +# - result.end_state.composites | length == 3 + +# - name: Change client role with composites no change +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# name: "{{ keycloak_role_name }}" +# client_id: "{{ client_id }}" +# realm: "{{ realm }}" +# description: "{{ keycloak_role_description }}" +# composite: "{{ keycloak_role_composite }}" +# composites: "{{ keycloak_role_composites }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert client role with composites have not changed +# assert: +# that: +# - result is not changed +# - result.end_state.composites | length == 3 + +# - name: Remove composite from client role with composites +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# name: "{{ keycloak_role_name }}" +# client_id: "{{ client_id }}" +# realm: "{{ realm }}" +# description: "{{ keycloak_role_description }}" +# composite: "{{ keycloak_role_composite }}" +# composites: "{{ keycloak_role_composites_with_absent }}" +# state: present +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert composite was removed from client role with composites +# assert: +# that: +# - result is changed +# - result.end_state.composites | length == 2 + +# - name: Delete client role with composites +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# name: "{{ keycloak_role_name }}" +# client_id: "{{ client_id }}" +# state: absent +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert client role deleted +# assert: +# that: +# - result is changed +# - result.end_state == {} + +# - name: Delete absent client role with composites +# community.general.keycloak_role: +# auth_keycloak_url: "{{ url }}" +# auth_realm: "{{ admin_realm }}" +# auth_username: "{{ admin_user }}" +# auth_password: "{{ admin_password }}" +# realm: "{{ realm }}" +# name: "{{ keycloak_role_name }}" +# client_id: "{{ client_id }}" +# state: absent +# register: result + +# - name: Debug +# debug: +# var: result + +# - name: Assert not changed and client role absent +# assert: +# that: +# - result is not changed +# - result.end_state == {} From 670db1aaab506d7093b16526566bcbea92ff022c Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Tue, 15 Oct 2024 22:42:09 -0400 Subject: [PATCH 04/30] chore: reset to main branch previous state; a different approach is needed [8857] --- .../identity/keycloak/keycloak.py | 119 +-- plugins/modules/keycloak_role.py | 2 +- .../targets/keycloak_role/tasks/main.yml | 817 ++++++++++-------- 3 files changed, 466 insertions(+), 472 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 97540ac4273..128b0fee134 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -22,7 +22,6 @@ URL_REALM_KEYS_METADATA = "{url}/admin/realms/{realm}/keys" URL_TOKEN = "{url}/realms/{realm}/protocol/openid-connect/token" -URL_INTROSPECT = "{url}/realms/{realm}/protocol/openid-connect/token/introspect" URL_CLIENT = "{url}/admin/realms/{realm}/clients/{id}" URL_CLIENTS = "{url}/admin/realms/{realm}/clients" @@ -162,114 +161,52 @@ def get_token(module_params): :return: connection header """ token = module_params.get('token') - refresh_token = module_params.get('refresh_token') base_url = module_params.get('auth_keycloak_url') http_agent = module_params.get('http_agent') if not base_url.lower().startswith(('http', 'https')): raise KeycloakError("auth_url '%s' should either start with 'http' or 'https'." % base_url) - base_url = module_params.get('auth_keycloak_url') - validate_certs = module_params.get('validate_certs') - auth_realm = module_params.get('auth_realm') - client_id = module_params.get('auth_client_id') - auth_username = module_params.get('auth_username') - auth_password = module_params.get('auth_password') - client_secret = module_params.get('auth_client_secret') - connection_timeout = module_params.get('connection_timeout') - auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) - - def extract_token(response): - try: - token = response['access_token'] - except KeyError: - raise KeycloakError( - 'Could not obtain access token from %s' % auth_url) - - return token - - def wrap_token(token): - return { - 'Authorization': 'Bearer ' + token, - 'Content-Type': 'application/json' - } - - def prepare_payload(grant_type, - client_id, - client_secret=None, - refresh_token=None, - username=None, - password=None): + if token is None: + base_url = module_params.get('auth_keycloak_url') + validate_certs = module_params.get('validate_certs') + auth_realm = module_params.get('auth_realm') + client_id = module_params.get('auth_client_id') + auth_username = module_params.get('auth_username') + auth_password = module_params.get('auth_password') + client_secret = module_params.get('auth_client_secret') + connection_timeout = module_params.get('connection_timeout') + auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) temp_payload = { - 'grant_type': grant_type, + 'grant_type': 'password', 'client_id': client_id, 'client_secret': client_secret, - 'refresh_token': refresh_token, - 'username': username, - 'password': password, + 'username': auth_username, + 'password': auth_password, } # Remove empty items, for instance missing client_secret payload = {k: v for k, v in temp_payload.items() if v is not None} - - return payload - - # Check existing token if it's provided - if token is not None: - # Check token is valid via userinfo route - userinfo_url = URL_INTROSPECT.format(url=base_url, realm=auth_realm) - try: - r = json.loads(to_native(open_url(userinfo_url, method='GET', headers=wrap_token(token), - validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout))) - except Exception as e: - raise KeycloakError('Could not check access token via %s: %s' - % (userinfo_url, str(e))) - - if r.StatusCode == 200: - return wrap_token(token) - - # Try authenticating via refresh token if it's provided - if refresh_token is not None: - payload = prepare_payload( - grant_type="refresh-token", - client_id=client_id, - client_secret=client_secret, - refresh_token=refresh_token, - ) - try: r = json.loads(to_native(open_url(auth_url, method='POST', validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout, data=urlencode(payload)).read())) - except: - raise KeycloakError('Could not refresh token via %s: %s' + except ValueError as e: + raise KeycloakError( + 'API returned invalid JSON when trying to obtain access token from %s: %s' + % (auth_url, str(e))) + except Exception as e: + raise KeycloakError('Could not obtain access token from %s: %s' % (auth_url, str(e))) - if r.statusCode == 200: - return wrap_token(extract_token(r)) - - # Access token and refresh token either not provided or not valid, - # authenticate via username/password - payload = prepare_payload( - grant_type="password", - client_id=client_id, - client_secret=client_secret, - username=auth_username, - password=auth_password, - ) - - try: - r = json.loads(to_native(open_url(auth_url, method='POST', - validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout, - data=urlencode(payload)).read())) - except ValueError as e: - raise KeycloakError( - 'API returned invalid JSON when trying to obtain access token from %s: %s' - % (auth_url, str(e))) - except Exception as e: - raise KeycloakError('Could not obtain access token from %s: %s' - % (auth_url, str(e))) - - return wrap_token(extract_token(r)) + try: + token = r['access_token'] + except KeyError: + raise KeycloakError( + 'Could not obtain access token from %s' % auth_url) + return { + 'Authorization': 'Bearer ' + token, + 'Content-Type': 'application/json' + } def is_struct_included(struct1, struct2, exclude=None): diff --git a/plugins/modules/keycloak_role.py b/plugins/modules/keycloak_role.py index e65fd7c3399..f3e01483f80 100644 --- a/plugins/modules/keycloak_role.py +++ b/plugins/modules/keycloak_role.py @@ -269,7 +269,7 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']])) + required_together=([['auth_realm', 'auth_username', 'auth_password']])) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/tests/integration/targets/keycloak_role/tasks/main.yml b/tests/integration/targets/keycloak_role/tasks/main.yml index b3a89b16c4e..c649b868089 100644 --- a/tests/integration/targets/keycloak_role/tasks/main.yml +++ b/tests/integration/targets/keycloak_role/tasks/main.yml @@ -3,26 +3,6 @@ # 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 -# Placeholder tests for shared token; temporarily hijacking these integration tests for fast development -- name: Get Keycloak token - ansible.builtin.uri: - url: "{{ url }}/realms/{{ admin_realm }}/protocol/openid-connect/token" - method: POST - return_content: true - status_code: 200 - body_format: form-urlencoded - body: - grant_type: "password" - client_id: "admin-cli" - username: "{{ admin_user }}" - password: "{{ admin_password }}" - register: token_response - -- name: Extract tokens - ansible.builtin.set_fact: - access_token: "{{ token_response.json | json_query('access_token') }}" - refresh_token: "{{ token_response.json | json_query('refresh_token') }}" - - name: Create realm community.general.keycloak_realm: auth_keycloak_url: "{{ url }}" @@ -44,383 +24,460 @@ state: present register: client -- name: Create new realm role via username/password auth +- name: Create new realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ description_1 }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role created + assert: + that: + - result is changed + - result.existing == {} + - result.end_state.name == "{{ role }}" + - result.end_state.containerId == "{{ realm }}" + +- name: Create existing realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ description_1 }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role unchanged + assert: + that: + - result is not changed + +- name: Update realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ description_2 }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role updated + assert: + that: + - result is changed + - result.existing.description == "{{ description_1 }}" + - result.end_state.description == "{{ description_2 }}" + +- name: Delete existing realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role deleted + assert: + that: + - result is changed + - result.end_state == {} + +- name: Delete absent realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role unchanged + assert: + that: + - result is not changed + - result.end_state == {} + +- name: Create new client role community.general.keycloak_role: auth_keycloak_url: "{{ url }}" auth_realm: "{{ admin_realm }}" auth_username: "{{ admin_user }}" auth_password: "{{ admin_password }}" realm: "{{ realm }}" + client_id: "{{ client_id }}" name: "{{ role }}" description: "{{ description_1 }}" state: present register: result -- name: Create new realm role via existing access token +- name: Debug + debug: + var: result + +- name: Assert client role created + assert: + that: + - result is changed + - result.existing == {} + - result.end_state.name == "{{ role }}" + - result.end_state.containerId == "{{ client.end_state.id }}" + +- name: Create existing client role community.general.keycloak_role: auth_keycloak_url: "{{ url }}" auth_realm: "{{ admin_realm }}" - token: "{{ access_token }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" realm: "{{ realm }}" + client_id: "{{ client_id }}" name: "{{ role }}" description: "{{ description_1 }}" state: present register: result -# - name: Debug -# debug: -# var: result - -# - name: Assert realm role unchanged -# assert: -# that: -# - result is not changed -# - result.end_state == {} - -# - name: Create new client role -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# client_id: "{{ client_id }}" -# name: "{{ role }}" -# description: "{{ description_1 }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert client role created -# assert: -# that: -# - "result is changed" -# - "result.existing == {}" -# - "result.end_state.name == role" -# - "result.end_state.containerId == client.end_state.id" - -# - name: Create existing client role -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# client_id: "{{ client_id }}" -# name: "{{ role }}" -# description: "{{ description_1 }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert client role unchanged -# assert: -# that: -# - result is not changed - -# - name: Update client role -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# client_id: "{{ client_id }}" -# name: "{{ role }}" -# description: "{{ description_2 }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert client role updated -# assert: -# that: -# - result is changed -# - result.existing.description == "{{ description_1 }}" -# - result.end_state.description == "{{ description_2 }}" - -# - name: Delete existing client role -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# client_id: "{{ client_id }}" -# name: "{{ role }}" -# state: absent -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert client role deleted -# assert: -# that: -# - result is changed -# - result.end_state == {} - -# - name: Delete absent client role -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# client_id: "{{ client_id }}" -# name: "{{ role }}" -# state: absent -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert client role unchanged -# assert: -# that: -# - result is not changed -# - result.end_state == {} - -# - name: Create realm role with composites -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# name: "{{ keycloak_role_name }}" -# realm: "{{ realm }}" -# description: "{{ keycloak_role_description }}" -# composite: "{{ keycloak_role_composite }}" -# composites: "{{ keycloak_role_composites }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert realm role is created with composites -# assert: -# that: -# - result is changed -# - result.end_state.composites | length == 3 - -# - name: Change realm role with composites no change -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# name: "{{ keycloak_role_name }}" -# realm: "{{ realm }}" -# description: "{{ keycloak_role_description }}" -# composite: "{{ keycloak_role_composite }}" -# composites: "{{ keycloak_role_composites }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert realm role with composites have not changed -# assert: -# that: -# - result is not changed -# - result.end_state.composites | length == 3 - -# - name: Remove composite from realm role with composites -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# name: "{{ keycloak_role_name }}" -# realm: "{{ realm }}" -# description: "{{ keycloak_role_description }}" -# composite: "{{ keycloak_role_composite }}" -# composites: "{{ keycloak_role_composites_with_absent }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert composite was removed from realm role with composites -# assert: -# that: -# - result is changed -# - result.end_state.composites | length == 2 - -# - name: Delete realm role with composites -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# name: "{{ keycloak_role_name }}" -# state: absent -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert realm role deleted -# assert: -# that: -# - result is changed -# - result.end_state == {} - -# - name: Delete absent realm role with composites -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# name: "{{ keycloak_role_name }}" -# state: absent -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert not changed and realm role absent -# assert: -# that: -# - result is not changed -# - result.end_state == {} - -# - name: Create client role with composites -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# name: "{{ keycloak_role_name }}" -# client_id: "{{ client_id }}" -# realm: "{{ realm }}" -# description: "{{ keycloak_role_description }}" -# composite: "{{ keycloak_role_composite }}" -# composites: "{{ keycloak_role_composites }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert client role is created with composites -# assert: -# that: -# - result is changed -# - result.end_state.composites | length == 3 - -# - name: Change client role with composites no change -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# name: "{{ keycloak_role_name }}" -# client_id: "{{ client_id }}" -# realm: "{{ realm }}" -# description: "{{ keycloak_role_description }}" -# composite: "{{ keycloak_role_composite }}" -# composites: "{{ keycloak_role_composites }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert client role with composites have not changed -# assert: -# that: -# - result is not changed -# - result.end_state.composites | length == 3 - -# - name: Remove composite from client role with composites -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# name: "{{ keycloak_role_name }}" -# client_id: "{{ client_id }}" -# realm: "{{ realm }}" -# description: "{{ keycloak_role_description }}" -# composite: "{{ keycloak_role_composite }}" -# composites: "{{ keycloak_role_composites_with_absent }}" -# state: present -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert composite was removed from client role with composites -# assert: -# that: -# - result is changed -# - result.end_state.composites | length == 2 - -# - name: Delete client role with composites -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# name: "{{ keycloak_role_name }}" -# client_id: "{{ client_id }}" -# state: absent -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert client role deleted -# assert: -# that: -# - result is changed -# - result.end_state == {} - -# - name: Delete absent client role with composites -# community.general.keycloak_role: -# auth_keycloak_url: "{{ url }}" -# auth_realm: "{{ admin_realm }}" -# auth_username: "{{ admin_user }}" -# auth_password: "{{ admin_password }}" -# realm: "{{ realm }}" -# name: "{{ keycloak_role_name }}" -# client_id: "{{ client_id }}" -# state: absent -# register: result - -# - name: Debug -# debug: -# var: result - -# - name: Assert not changed and client role absent -# assert: -# that: -# - result is not changed -# - result.end_state == {} +- name: Debug + debug: + var: result + +- name: Assert client role unchanged + assert: + that: + - result is not changed + +- name: Update client role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + name: "{{ role }}" + description: "{{ description_2 }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role updated + assert: + that: + - result is changed + - result.existing.description == "{{ description_1 }}" + - result.end_state.description == "{{ description_2 }}" + +- name: Delete existing client role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role deleted + assert: + that: + - result is changed + - result.end_state == {} + +- name: Delete absent client role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + client_id: "{{ client_id }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role unchanged + assert: + that: + - result is not changed + - result.end_state == {} + +- name: Create realm role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role is created with composites + assert: + that: + - result is changed + - result.end_state.composites | length == 3 + +- name: Change realm role with composites no change + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role with composites have not changed + assert: + that: + - result is not changed + - result.end_state.composites | length == 3 + +- name: Remove composite from realm role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites_with_absent }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert composite was removed from realm role with composites + assert: + that: + - result is changed + - result.end_state.composites | length == 2 + +- name: Delete realm role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ keycloak_role_name }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert realm role deleted + assert: + that: + - result is changed + - result.end_state == {} + +- name: Delete absent realm role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ keycloak_role_name }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert not changed and realm role absent + assert: + that: + - result is not changed + - result.end_state == {} + +- name: Create client role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role is created with composites + assert: + that: + - result is changed + - result.end_state.composites | length == 3 + +- name: Change client role with composites no change + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role with composites have not changed + assert: + that: + - result is not changed + - result.end_state.composites | length == 3 + +- name: Remove composite from client role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + realm: "{{ realm }}" + description: "{{ keycloak_role_description }}" + composite: "{{ keycloak_role_composite }}" + composites: "{{ keycloak_role_composites_with_absent }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Assert composite was removed from client role with composites + assert: + that: + - result is changed + - result.end_state.composites | length == 2 + +- name: Delete client role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert client role deleted + assert: + that: + - result is changed + - result.end_state == {} + +- name: Delete absent client role with composites + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ keycloak_role_name }}" + client_id: "{{ client_id }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Assert not changed and client role absent + assert: + that: + - result is not changed + - result.end_state == {} \ No newline at end of file From b7fd1e81415ed0a602950089152b6c074200f615 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Thu, 31 Oct 2024 22:47:55 -0400 Subject: [PATCH 05/30] feat: add request methods to keycloak class, which will be expanded with retry logic [8857] --- .../identity/keycloak/keycloak.py | 89 +++++++------------ 1 file changed, 32 insertions(+), 57 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 128b0fee134..302e3bb0415 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -280,6 +280,15 @@ def __init__(self, module, connection_header): self.restheaders = connection_header self.http_agent = self.module.params.get('http_agent') + def _request(self, url, method, data): + return open_url(url, method=method, data=data, + http_agent=self.http_agent, headers=self.restheaders, + timeout=self.connection_timeout, + validate_certs=self.validate_certs) + + def _request_and_deserialize(self, url, method, data): + return json.loads(to_native(self._request(self, url, method, data).read())) + def get_realm_info_by_id(self, realm='master'): """ Obtain realm public info by id @@ -289,9 +298,7 @@ def get_realm_info_by_id(self, realm='master'): realm_info_url = URL_REALM_INFO.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(realm_info_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(realm_info_url, method='GET') except HTTPError as e: if e.code == 404: @@ -320,9 +327,7 @@ def get_realm_keys_metadata_by_id(self, realm='master'): realm_keys_metadata_url = URL_REALM_KEYS_METADATA.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(realm_keys_metadata_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(realm_keys_metadata_url, method="GET") except HTTPError as e: if e.code == 404: @@ -346,8 +351,7 @@ def get_realm_by_id(self, realm='master'): realm_url = URL_REALM.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(realm_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(realm_url, method='GET') except HTTPError as e: if e.code == 404: @@ -371,8 +375,7 @@ def update_realm(self, realmrep, realm="master"): realm_url = URL_REALM.format(url=self.baseurl, realm=realm) try: - return open_url(realm_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(realmrep), validate_certs=self.validate_certs) + return self._request(realm_url, method='PUT', data=json.dumps(realmrep)) except Exception as e: self.fail_open_url(e, msg='Could not update realm %s: %s' % (realm, str(e)), exception=traceback.format_exc()) @@ -385,8 +388,7 @@ def create_realm(self, realmrep): realm_url = URL_REALMS.format(url=self.baseurl) try: - return open_url(realm_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(realmrep), validate_certs=self.validate_certs) + return self._request(realm_url, method='POST', data=json.dumps(realmrep)) except Exception as e: self.fail_open_url(e, msg='Could not create realm %s: %s' % (realmrep['id'], str(e)), exception=traceback.format_exc()) @@ -400,8 +402,7 @@ def delete_realm(self, realm="master"): realm_url = URL_REALM.format(url=self.baseurl, realm=realm) try: - return open_url(realm_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(realm_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not delete realm %s: %s' % (realm, str(e)), exception=traceback.format_exc()) @@ -418,9 +419,7 @@ def get_clients(self, realm='master', filter=None): clientlist_url += '?clientId=%s' % filter try: - return json.loads(to_native(open_url(clientlist_url, http_agent=self.http_agent, method='GET', headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(clientlist_url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of clients for realm %s: %s' % (realm, str(e))) @@ -450,9 +449,7 @@ def get_client_by_id(self, id, realm='master'): client_url = URL_CLIENT.format(url=self.baseurl, realm=realm, id=id) try: - return json.loads(to_native(open_url(client_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(client_url, method='GET') except HTTPError as e: if e.code == 404: @@ -490,8 +487,7 @@ def update_client(self, id, clientrep, realm="master"): client_url = URL_CLIENT.format(url=self.baseurl, realm=realm, id=id) try: - return open_url(client_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(clientrep), validate_certs=self.validate_certs) + return self._request(client_url, method='PUT', data=json.dumps(clientrep)) except Exception as e: self.fail_open_url(e, msg='Could not update client %s in realm %s: %s' % (id, realm, str(e))) @@ -505,8 +501,7 @@ def create_client(self, clientrep, realm="master"): client_url = URL_CLIENTS.format(url=self.baseurl, realm=realm) try: - return open_url(client_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(clientrep), validate_certs=self.validate_certs) + return self._request(client_url, method='POST', data=json.dumps(clientrep)) except Exception as e: self.fail_open_url(e, msg='Could not create client %s in realm %s: %s' % (clientrep['clientId'], realm, str(e))) @@ -521,8 +516,7 @@ def delete_client(self, id, realm="master"): client_url = URL_CLIENT.format(url=self.baseurl, realm=realm, id=id) try: - return open_url(client_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(client_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not delete client %s in realm %s: %s' % (id, realm, str(e))) @@ -536,9 +530,7 @@ def get_client_roles_by_id(self, cid, realm="master"): """ client_roles_url = URL_CLIENT_ROLES.format(url=self.baseurl, realm=realm, id=cid) try: - return json.loads(to_native(open_url(client_roles_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(client_roles_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch rolemappings for client %s in realm %s: %s" % (cid, realm, str(e))) @@ -568,9 +560,7 @@ def get_client_group_rolemapping_by_id(self, gid, cid, rid, realm='master'): """ rolemappings_url = URL_CLIENT_GROUP_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=gid, client=cid) try: - rolemappings = json.loads(to_native(open_url(rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + rolemappings = self._request_and_deserialize(rolemappings_url, method="GET") for role in rolemappings: if rid == role['id']: return role @@ -589,9 +579,7 @@ def get_client_group_available_rolemappings(self, gid, cid, realm="master"): """ available_rolemappings_url = URL_CLIENT_GROUP_ROLEMAPPINGS_AVAILABLE.format(url=self.baseurl, realm=realm, id=gid, client=cid) try: - return json.loads(to_native(open_url(available_rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(available_rolemappings_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" % (cid, gid, realm, str(e))) @@ -606,9 +594,7 @@ def get_client_group_composite_rolemappings(self, gid, cid, realm="master"): """ composite_rolemappings_url = URL_CLIENT_GROUP_ROLEMAPPINGS_COMPOSITE.format(url=self.baseurl, realm=realm, id=gid, client=cid) try: - return json.loads(to_native(open_url(composite_rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(composite_rolemappings_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" % (cid, gid, realm, str(e))) @@ -622,9 +608,7 @@ def get_role_by_id(self, rid, realm="master"): """ client_roles_url = URL_ROLES_BY_ID.format(url=self.baseurl, realm=realm, id=rid) try: - return json.loads(to_native(open_url(client_roles_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(client_roles_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch role for id %s in realm %s: %s" % (rid, realm, str(e))) @@ -639,9 +623,7 @@ def get_client_roles_by_id_composite_rolemappings(self, rid, cid, realm="master" """ client_roles_url = URL_ROLES_BY_ID_COMPOSITES_CLIENTS.format(url=self.baseurl, realm=realm, id=rid, cid=cid) try: - return json.loads(to_native(open_url(client_roles_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(client_roles_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch role for id %s and cid %s in realm %s: %s" % (rid, cid, realm, str(e))) @@ -656,8 +638,7 @@ def add_client_roles_by_id_composite_rolemapping(self, rid, roles_rep, realm="ma """ available_rolemappings_url = URL_ROLES_BY_ID_COMPOSITES.format(url=self.baseurl, realm=realm, id=rid) try: - open_url(available_rolemappings_url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(roles_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(available_rolemappings_url, method="POST", data=json.dumps(roles_rep)) except Exception as e: self.fail_open_url(e, msg="Could not assign roles to composite role %s and realm %s: %s" % (rid, realm, str(e))) @@ -672,8 +653,7 @@ def add_group_realm_rolemapping(self, gid, role_rep, realm="master"): """ url = URL_REALM_GROUP_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, group=gid) try: - open_url(url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(url, method="POST", data=json.dumps(role_rep)) except Exception as e: self.fail_open_url(e, msg="Could add realm role mappings for group %s, realm %s: %s" % (gid, realm, str(e))) @@ -688,8 +668,7 @@ def delete_group_realm_rolemapping(self, gid, role_rep, realm="master"): """ url = URL_REALM_GROUP_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, group=gid) try: - open_url(url, method="DELETE", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(url, method="DELETE", data=json.dumps(role_rep)) except Exception as e: self.fail_open_url(e, msg="Could not delete realm role mappings for group %s, realm %s: %s" % (gid, realm, str(e))) @@ -705,8 +684,7 @@ def add_group_rolemapping(self, gid, cid, role_rep, realm="master"): """ available_rolemappings_url = URL_CLIENT_GROUP_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=gid, client=cid) try: - open_url(available_rolemappings_url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(available_rolemappings_url, method="POST", data=json.dumps(role_rep)) except Exception as e: self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" % (cid, gid, realm, str(e))) @@ -722,8 +700,7 @@ def delete_group_rolemapping(self, gid, cid, role_rep, realm="master"): """ available_rolemappings_url = URL_CLIENT_GROUP_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=gid, client=cid) try: - open_url(available_rolemappings_url, method="DELETE", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(available_rolemappings_url, method="DELETE", data=json.dumps(role_rep)) except Exception as e: self.fail_open_url(e, msg="Could not delete available rolemappings for client %s in group %s, realm %s: %s" % (cid, gid, realm, str(e))) @@ -739,9 +716,7 @@ def get_client_user_rolemapping_by_id(self, uid, cid, rid, realm='master'): """ rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid, client=cid) try: - rolemappings = json.loads(to_native(open_url(rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + rolemappings = self._request_and_deserialize(rolemappings_url, method="GET") for role in rolemappings: if rid == role['id']: return role From b73c82b0cc0fd594a7b293ab3332053a60ff75e6 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Wed, 6 Nov 2024 01:26:39 -0500 Subject: [PATCH 06/30] feat: all requests to keycloak use request methods instead of open_url [8857] --- .../identity/keycloak/keycloak.py | 445 +++++------------- 1 file changed, 129 insertions(+), 316 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 9b57f36f578..7b6147ccb36 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -735,9 +735,7 @@ def get_client_user_available_rolemappings(self, uid, cid, realm="master"): """ available_rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS_AVAILABLE.format(url=self.baseurl, realm=realm, id=uid, client=cid) try: - return json.loads(to_native(open_url(available_rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(available_rolemappings_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch effective rolemappings for client %s and user %s, realm %s: %s" % (cid, uid, realm, str(e))) @@ -752,9 +750,7 @@ def get_client_user_composite_rolemappings(self, uid, cid, realm="master"): """ composite_rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS_COMPOSITE.format(url=self.baseurl, realm=realm, id=uid, client=cid) try: - return json.loads(to_native(open_url(composite_rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(composite_rolemappings_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch available rolemappings for user %s of realm %s: %s" % (uid, realm, str(e))) @@ -769,9 +765,7 @@ def get_realm_user_rolemapping_by_id(self, uid, rid, realm='master'): """ rolemappings_url = URL_REALM_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid) try: - rolemappings = json.loads(to_native(open_url(rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + rolemappings = self._request_and_deserialize(rolemappings_url, method="GET") for role in rolemappings: if rid == role['id']: return role @@ -789,9 +783,7 @@ def get_realm_user_available_rolemappings(self, uid, realm="master"): """ available_rolemappings_url = URL_REALM_ROLEMAPPINGS_AVAILABLE.format(url=self.baseurl, realm=realm, id=uid) try: - return json.loads(to_native(open_url(available_rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(available_rolemappings_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch available rolemappings for user %s of realm %s: %s" % (uid, realm, str(e))) @@ -805,9 +797,7 @@ def get_realm_user_composite_rolemappings(self, uid, realm="master"): """ composite_rolemappings_url = URL_REALM_ROLEMAPPINGS_COMPOSITE.format(url=self.baseurl, realm=realm, id=uid) try: - return json.loads(to_native(open_url(composite_rolemappings_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(composite_rolemappings_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch effective rolemappings for user %s, realm %s: %s" % (uid, realm, str(e))) @@ -823,9 +813,7 @@ def get_user_by_username(self, username, realm="master"): users_url += '?username=%s&exact=true' % username try: userrep = None - users = json.loads(to_native(open_url(users_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + users = self._request_and_deserialize(users_url, method='GET') for user in users: if user['username'] == username: userrep = user @@ -850,9 +838,7 @@ def get_service_account_user_by_client_id(self, client_id, realm="master"): service_account_user_url = URL_CLIENT_SERVICE_ACCOUNT_USER.format(url=self.baseurl, realm=realm, id=cid) try: - return json.loads(to_native(open_url(service_account_user_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(service_account_user_url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain the service-account-user for realm %s and client_id %s: %s' % (realm, client_id, str(e))) @@ -872,16 +858,14 @@ def add_user_rolemapping(self, uid, cid, role_rep, realm="master"): if cid is None: user_realm_rolemappings_url = URL_REALM_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid) try: - open_url(user_realm_rolemappings_url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(user_realm_rolemappings_url, method="POST", data=json.dumps(role_rep)) except Exception as e: self.fail_open_url(e, msg="Could not map roles to userId %s for realm %s and roles %s: %s" % (uid, realm, json.dumps(role_rep), str(e))) else: user_client_rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid, client=cid) try: - open_url(user_client_rolemappings_url, method="POST", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(user_client_rolemappings_url, method="POST", data=json.dumps(role_rep)) except Exception as e: self.fail_open_url(e, msg="Could not map roles to userId %s for client %s, realm %s and roles %s: %s" % (cid, uid, realm, json.dumps(role_rep), str(e))) @@ -898,16 +882,14 @@ def delete_user_rolemapping(self, uid, cid, role_rep, realm="master"): if cid is None: user_realm_rolemappings_url = URL_REALM_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid) try: - open_url(user_realm_rolemappings_url, method="DELETE", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(user_realm_rolemappings_url, method="DELETE", data=json.dumps(role_rep)) except Exception as e: self.fail_open_url(e, msg="Could not remove roles %s from userId %s, realm %s: %s" % (json.dumps(role_rep), uid, realm, str(e))) else: user_client_rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid, client=cid) try: - open_url(user_client_rolemappings_url, method="DELETE", http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(role_rep), - validate_certs=self.validate_certs, timeout=self.connection_timeout) + self._request(user_client_rolemappings_url, method="DELETE", data=json.dumps(role_rep)) except Exception as e: self.fail_open_url(e, msg="Could not remove roles %s for client %s from userId %s, realm %s: %s" % (json.dumps(role_rep), cid, uid, realm, str(e))) @@ -921,8 +903,7 @@ def get_client_templates(self, realm='master'): url = URL_CLIENTTEMPLATES.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(url, method='GET', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of client templates for realm %s: %s' % (realm, str(e))) @@ -940,8 +921,7 @@ def get_client_template_by_id(self, id, realm='master'): url = URL_CLIENTTEMPLATE.format(url=self.baseurl, id=id, realm=realm) try: - return json.loads(to_native(open_url(url, method='GET', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain client templates %s for realm %s: %s' % (id, realm, str(e))) @@ -986,8 +966,7 @@ def update_client_template(self, id, clienttrep, realm="master"): url = URL_CLIENTTEMPLATE.format(url=self.baseurl, realm=realm, id=id) try: - return open_url(url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(clienttrep), validate_certs=self.validate_certs) + return self._request(url, method='PUT', data=json.dumps(clienttrep)) except Exception as e: self.fail_open_url(e, msg='Could not update client template %s in realm %s: %s' % (id, realm, str(e))) @@ -1001,8 +980,7 @@ def create_client_template(self, clienttrep, realm="master"): url = URL_CLIENTTEMPLATES.format(url=self.baseurl, realm=realm) try: - return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(clienttrep), validate_certs=self.validate_certs) + return self._request(url, method='POST', data=json.dumps(clienttrep)) except Exception as e: self.fail_open_url(e, msg='Could not create client template %s in realm %s: %s' % (clienttrep['clientId'], realm, str(e))) @@ -1017,8 +995,7 @@ def delete_client_template(self, id, realm="master"): url = URL_CLIENTTEMPLATE.format(url=self.baseurl, realm=realm, id=id) try: - return open_url(url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not delete client template %s in realm %s: %s' % (id, realm, str(e))) @@ -1034,9 +1011,7 @@ def get_clientscopes(self, realm="master"): """ clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(clientscopes_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(clientscopes_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch list of clientscopes in realm %s: %s" % (realm, str(e))) @@ -1052,9 +1027,7 @@ def get_clientscope_by_clientscopeid(self, cid, realm="master"): """ clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=cid) try: - return json.loads(to_native(open_url(clientscope_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(clientscope_url, method="GET") except HTTPError as e: if e.code == 404: @@ -1098,8 +1071,7 @@ def create_clientscope(self, clientscoperep, realm="master"): """ clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm) try: - return open_url(clientscopes_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(clientscoperep), validate_certs=self.validate_certs) + return self._request(clientscopes_url, method='POST', data=json.dumps(clientscoperep)) except Exception as e: self.fail_open_url(e, msg="Could not create clientscope %s in realm %s: %s" % (clientscoperep['name'], realm, str(e))) @@ -1113,8 +1085,7 @@ def update_clientscope(self, clientscoperep, realm="master"): clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=clientscoperep['id']) try: - return open_url(clientscope_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(clientscoperep), validate_certs=self.validate_certs) + return self._request(clientscope_url, method='PUT', data=json.dumps(clientscoperep)) except Exception as e: self.fail_open_url(e, msg='Could not update clientscope %s in realm %s: %s' @@ -1151,8 +1122,7 @@ def delete_clientscope(self, name=None, cid=None, realm="master"): # should have a good cid by here. clientscope_url = URL_CLIENTSCOPE.format(realm=realm, id=cid, url=self.baseurl) try: - return open_url(clientscope_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(clientscope_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg="Unable to delete clientscope %s: %s" % (cid, str(e))) @@ -1169,9 +1139,7 @@ def get_clientscope_protocolmappers(self, cid, realm="master"): """ protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(id=cid, url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(protocolmappers_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(protocolmappers_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch list of protocolmappers in realm %s: %s" % (realm, str(e))) @@ -1189,9 +1157,7 @@ def get_clientscope_protocolmapper_by_protocolmapperid(self, pid, cid, realm="ma """ protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=pid) try: - return json.loads(to_native(open_url(protocolmapper_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(protocolmapper_url, method="GET") except HTTPError as e: if e.code == 404: @@ -1237,8 +1203,7 @@ def create_clientscope_protocolmapper(self, cid, mapper_rep, realm="master"): """ protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(url=self.baseurl, id=cid, realm=realm) try: - return open_url(protocolmappers_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(mapper_rep), validate_certs=self.validate_certs) + return self._request(protocolmappers_url, method='POST', data=json.dumps(mapper_rep)) except Exception as e: self.fail_open_url(e, msg="Could not create protocolmapper %s in realm %s: %s" % (mapper_rep['name'], realm, str(e))) @@ -1253,8 +1218,7 @@ def update_clientscope_protocolmappers(self, cid, mapper_rep, realm="master"): protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=mapper_rep['id']) try: - return open_url(protocolmapper_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(mapper_rep), validate_certs=self.validate_certs) + return self._request(protocolmapper_url, method='PUT', data=json.dumps(mapper_rep)) except Exception as e: self.fail_open_url(e, msg='Could not update protocolmappers for clientscope %s in realm %s: %s' @@ -1301,16 +1265,14 @@ def _get_clientscopes_of_type(self, realm, url_template, scope_type, client_id=N if client_id is None: clientscopes_url = url_template.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(clientscopes_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(clientscopes_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch list of %s clientscopes in realm %s: %s" % (scope_type, realm, str(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) try: - return json.loads(to_native(open_url(clientscopes_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(clientscopes_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch list of %s clientscopes in client %s: %s" % (scope_type, client_id, clientscopes_url)) @@ -1378,8 +1340,7 @@ def _action_type_clientscope(self, id=None, client_id=None, scope_type="default" clientscope_type_url = self._decide_url_type_clientscope(client_id, scope_type).format(realm=realm, id=id, cid=cid, url=self.baseurl) try: method = 'PUT' if action == "add" else 'DELETE' - return open_url(clientscope_type_url, method=method, http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(clientscope_type_url, method=method) except Exception as e: place = 'realm' if client_id is None else 'client ' + client_id @@ -1395,9 +1356,7 @@ def create_clientsecret(self, id, realm="master"): clientsecret_url = URL_CLIENTSECRET.format(url=self.baseurl, realm=realm, id=id) try: - return json.loads(to_native(open_url(clientsecret_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(clientsecret_url, method='POST') except HTTPError as e: if e.code == 404: @@ -1419,9 +1378,7 @@ def get_clientsecret(self, id, realm="master"): clientsecret_url = URL_CLIENTSECRET.format(url=self.baseurl, realm=realm, id=id) try: - return json.loads(to_native(open_url(clientsecret_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(clientsecret_url, method='GET') except HTTPError as e: if e.code == 404: @@ -1443,9 +1400,7 @@ def get_groups(self, realm="master"): """ groups_url = URL_GROUPS.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(groups_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(groups_url, method="GET") except Exception as e: self.fail_open_url(e, msg="Could not fetch list of groups in realm %s: %s" % (realm, str(e))) @@ -1461,9 +1416,7 @@ def get_group_by_groupid(self, gid, realm="master"): """ groups_url = URL_GROUP.format(url=self.baseurl, realm=realm, groupid=gid) try: - return json.loads(to_native(open_url(groups_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(groups_url, method="GET") except HTTPError as e: if e.code == 404: return None @@ -1483,9 +1436,7 @@ def get_subgroups(self, parent, realm="master"): group_children = [] else: group_children_url = URL_GROUP_CHILDREN.format(url=self.baseurl, realm=realm, groupid=parent['id']) - group_children = json.loads(to_native(open_url(group_children_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + group_children = self._request_and_deserialize(group_children_url, method="GET") subgroups = group_children else: subgroups = parent['subGroups'] @@ -1629,8 +1580,7 @@ def create_group(self, grouprep, realm="master"): """ groups_url = URL_GROUPS.format(url=self.baseurl, realm=realm) try: - return open_url(groups_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(grouprep), validate_certs=self.validate_certs) + return self._request(groups_url, method='POST', data=json.dumps(grouprep)) except Exception as e: self.fail_open_url(e, msg="Could not create group %s in realm %s: %s" % (grouprep['name'], realm, str(e))) @@ -1657,8 +1607,7 @@ def create_subgroup(self, parents, grouprep, realm="master"): parent_id = parent_id["id"] url = URL_GROUP_CHILDREN.format(url=self.baseurl, realm=realm, groupid=parent_id) - return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(grouprep), validate_certs=self.validate_certs) + return self._request(url, method='POST', data=json.dumps(grouprep)) except Exception as e: self.fail_open_url(e, msg="Could not create subgroup %s for parent group %s in realm %s: %s" % (grouprep['name'], parent_id, realm, str(e))) @@ -1672,8 +1621,7 @@ def update_group(self, grouprep, realm="master"): group_url = URL_GROUP.format(url=self.baseurl, realm=realm, groupid=grouprep['id']) try: - return open_url(group_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(grouprep), validate_certs=self.validate_certs) + return self._request(group_url, method='PUT', data=json.dumps(grouprep)) except Exception as e: self.fail_open_url(e, msg='Could not update group %s in realm %s: %s' % (grouprep['name'], realm, str(e))) @@ -1709,8 +1657,7 @@ def delete_group(self, name=None, groupid=None, realm="master"): # should have a good groupid by here. group_url = URL_GROUP.format(realm=realm, groupid=groupid, url=self.baseurl) try: - return open_url(group_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(group_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg="Unable to delete group %s: %s" % (groupid, str(e))) @@ -1722,9 +1669,7 @@ def get_realm_roles(self, realm='master'): """ rolelist_url = URL_REALM_ROLES.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(rolelist_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(rolelist_url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for realm %s: %s' % (realm, str(e))) @@ -1741,8 +1686,7 @@ def get_realm_role(self, name, realm='master'): """ role_url = URL_REALM_ROLE.format(url=self.baseurl, realm=realm, name=quote(name, safe='')) try: - return json.loads(to_native(open_url(role_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(role_url, method="GET") except HTTPError as e: if e.code == 404: return None @@ -1764,8 +1708,7 @@ def create_realm_role(self, rolerep, realm='master'): if "composites" in rolerep: keycloak_compatible_composites = self.convert_role_composites(rolerep["composites"]) rolerep["composites"] = keycloak_compatible_composites - return open_url(roles_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(rolerep), validate_certs=self.validate_certs) + return self._request(roles_url, method='POST', data=json.dumps(rolerep)) except Exception as e: self.fail_open_url(e, msg='Could not create role %s in realm %s: %s' % (rolerep['name'], realm, str(e))) @@ -1782,8 +1725,7 @@ def update_realm_role(self, rolerep, realm='master'): if "composites" in rolerep: composites = copy.deepcopy(rolerep["composites"]) del rolerep["composites"] - role_response = open_url(role_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(rolerep), validate_certs=self.validate_certs) + role_response = self._request(role_url, method='PUT', data=json.dumps(rolerep)) if composites is not None: self.update_role_composites(rolerep=rolerep, composites=composites, realm=realm) return role_response @@ -1801,13 +1743,7 @@ def get_role_composites(self, rolerep, clientid=None, realm='master'): else: composite_url = URL_REALM_ROLE_COMPOSITES.format(url=self.baseurl, realm=realm, name=quote(rolerep["name"], safe='')) # Get existing composites - return json.loads(to_native(open_url( - composite_url, - method='GET', - http_agent=self.http_agent, - headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + self._request_and_deserialize(composite_url, method='GET') except Exception as e: self.fail_open_url(e, msg='Could not get role %s composites in realm %s: %s' % (rolerep['name'], realm, str(e))) @@ -1823,8 +1759,7 @@ def create_role_composites(self, rolerep, composites, clientid=None, realm='mast composite_url = URL_REALM_ROLE_COMPOSITES.format(url=self.baseurl, realm=realm, name=quote(rolerep["name"], safe='')) # Get existing composites # create new composites - return open_url(composite_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(composites), validate_certs=self.validate_certs) + return self._request(composite_url, method='POST', data=json.dumps(composites)) except Exception as e: self.fail_open_url(e, msg='Could not create role %s composites in realm %s: %s' % (rolerep['name'], realm, str(e))) @@ -1840,8 +1775,7 @@ def delete_role_composites(self, rolerep, composites, clientid=None, realm='mast composite_url = URL_REALM_ROLE_COMPOSITES.format(url=self.baseurl, realm=realm, name=quote(rolerep["name"], safe='')) # Get existing composites # create new composites - return open_url(composite_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(composites), validate_certs=self.validate_certs) + return self._request(composite_url, method='DELETE', data=json.dumps(composites)) except Exception as e: self.fail_open_url(e, msg='Could not create role %s composites in realm %s: %s' % (rolerep['name'], realm, str(e))) @@ -1904,8 +1838,7 @@ def delete_realm_role(self, name, realm='master'): """ role_url = URL_REALM_ROLE.format(url=self.baseurl, realm=realm, name=quote(name, safe='')) try: - return open_url(role_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(role_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Unable to delete role %s in realm %s: %s' % (name, realm, str(e))) @@ -1923,9 +1856,7 @@ def get_client_roles(self, clientid, realm='master'): % (clientid, realm)) rolelist_url = URL_CLIENT_ROLES.format(url=self.baseurl, realm=realm, id=cid) try: - return json.loads(to_native(open_url(rolelist_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(rolelist_url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for client %s in realm %s: %s' % (clientid, realm, str(e))) @@ -1948,8 +1879,7 @@ def get_client_role(self, name, clientid, realm='master'): % (clientid, realm)) role_url = URL_CLIENT_ROLE.format(url=self.baseurl, realm=realm, id=cid, name=quote(name, safe='')) try: - return json.loads(to_native(open_url(role_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(role_url, method="GET") except HTTPError as e: if e.code == 404: return None @@ -1977,8 +1907,7 @@ def create_client_role(self, rolerep, clientid, realm='master'): if "composites" in rolerep: keycloak_compatible_composites = self.convert_role_composites(rolerep["composites"]) rolerep["composites"] = keycloak_compatible_composites - return open_url(roles_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(rolerep), validate_certs=self.validate_certs) + return self._request(roles_url, method='POST', data=json.dumps(rolerep)) except Exception as e: self.fail_open_url(e, msg='Could not create role %s for client %s in realm %s: %s' % (rolerep['name'], clientid, realm, str(e))) @@ -2016,8 +1945,7 @@ def update_client_role(self, rolerep, clientid, realm="master"): if "composites" in rolerep: composites = copy.deepcopy(rolerep["composites"]) del rolerep['composites'] - update_role_response = open_url(role_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(rolerep), validate_certs=self.validate_certs) + update_role_response = self._request(role_url, method='PUT', data=json.dumps(rolerep)) if composites is not None: self.update_role_composites(rolerep=rolerep, clientid=clientid, composites=composites, realm=realm) return update_role_response @@ -2038,8 +1966,7 @@ def delete_client_role(self, name, clientid, realm="master"): % (clientid, realm)) role_url = URL_CLIENT_ROLE.format(url=self.baseurl, realm=realm, id=cid, name=quote(name, safe='')) try: - return open_url(role_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(role_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Unable to delete role %s for client %s in realm %s: %s' % (name, clientid, realm, str(e))) @@ -2054,9 +1981,7 @@ def get_authentication_flow_by_alias(self, alias, realm='master'): try: authentication_flow = {} # Check if the authentication flow exists on the Keycloak serveraders - authentications = json.load(open_url(URL_AUTHENTICATION_FLOWS.format(url=self.baseurl, realm=realm), method='GET', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, validate_certs=self.validate_certs)) + authentications = json.load(self._request(URL_AUTHENTICATION_FLOWS.format(url=self.baseurl, realm=realm), method='GET')) for authentication in authentications: if authentication["alias"] == alias: authentication_flow = authentication @@ -2075,8 +2000,7 @@ def delete_authentication_flow_by_id(self, id, realm='master'): flow_url = URL_AUTHENTICATION_FLOW.format(url=self.baseurl, realm=realm, id=id) try: - return open_url(flow_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(flow_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not delete authentication flow %s in realm %s: %s' % (id, realm, str(e))) @@ -2092,24 +2016,18 @@ def copy_auth_flow(self, config, realm='master'): new_name = dict( newName=config["alias"] ) - open_url( + self._request( URL_AUTHENTICATION_FLOW_COPY.format( url=self.baseurl, realm=realm, copyfrom=quote(config["copyFrom"], safe='')), method='POST', - http_agent=self.http_agent, headers=self.restheaders, - data=json.dumps(new_name), - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + data=json.dumps(new_name)) flow_list = json.load( - open_url( + self._request( URL_AUTHENTICATION_FLOWS.format(url=self.baseurl, realm=realm), - method='GET', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs)) + method='GET')) for flow in flow_list: if flow["alias"] == config["alias"]: return flow @@ -2132,24 +2050,18 @@ def create_empty_auth_flow(self, config, realm='master'): description=config["description"], topLevel=True ) - open_url( + self._request( URL_AUTHENTICATION_FLOWS.format( url=self.baseurl, realm=realm), method='POST', - http_agent=self.http_agent, headers=self.restheaders, - data=json.dumps(new_flow), - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + data=json.dumps(new_flow)) flow_list = json.load( - open_url( + self._request( URL_AUTHENTICATION_FLOWS.format( url=self.baseurl, realm=realm), - method='GET', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs)) + method='GET')) for flow in flow_list: if flow["alias"] == config["alias"]: return flow @@ -2166,16 +2078,13 @@ def update_authentication_executions(self, flowAlias, updatedExec, realm='master :return: HTTPResponse object on success """ try: - open_url( + self._request( URL_AUTHENTICATION_FLOW_EXECUTIONS.format( url=self.baseurl, realm=realm, flowalias=quote(flowAlias, safe='')), method='PUT', - http_agent=self.http_agent, headers=self.restheaders, - data=json.dumps(updatedExec), - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + data=json.dumps(updatedExec)) except HTTPError as e: self.fail_open_url(e, msg="Unable to update execution '%s': %s: %s %s" % (flowAlias, repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(updatedExec))) @@ -2190,16 +2099,13 @@ def add_authenticationConfig_to_execution(self, executionId, authenticationConfi :return: HTTPResponse object on success """ try: - open_url( + self._request( URL_AUTHENTICATION_EXECUTION_CONFIG.format( url=self.baseurl, realm=realm, id=executionId), method='POST', - http_agent=self.http_agent, headers=self.restheaders, - data=json.dumps(authenticationConfig), - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + data=json.dumps(authenticationConfig)) except Exception as e: self.fail_open_url(e, msg="Unable to add authenticationConfig %s: %s" % (executionId, str(e))) @@ -2215,16 +2121,13 @@ def create_subflow(self, subflowName, flowAlias, realm='master', flowType='basic newSubFlow["alias"] = subflowName newSubFlow["provider"] = "registration-page-form" newSubFlow["type"] = flowType - open_url( + self._request( URL_AUTHENTICATION_FLOW_EXECUTIONS_FLOW.format( url=self.baseurl, realm=realm, flowalias=quote(flowAlias, safe='')), method='POST', - http_agent=self.http_agent, headers=self.restheaders, - data=json.dumps(newSubFlow), - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + data=json.dumps(newSubFlow)) except Exception as e: self.fail_open_url(e, msg="Unable to create new subflow %s: %s" % (subflowName, str(e))) @@ -2239,16 +2142,13 @@ def create_execution(self, execution, flowAlias, realm='master'): newExec = {} newExec["provider"] = execution["providerId"] newExec["requirement"] = execution["requirement"] - open_url( + self._request( URL_AUTHENTICATION_FLOW_EXECUTIONS_EXECUTION.format( url=self.baseurl, realm=realm, flowalias=quote(flowAlias, safe='')), method='POST', - http_agent=self.http_agent, headers=self.restheaders, - data=json.dumps(newExec), - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + data=json.dumps(newExec)) except HTTPError as e: self.fail_open_url(e, msg="Unable to create new execution '%s' %s: %s: %s %s" % (flowAlias, execution["providerId"], repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(newExec))) @@ -2266,26 +2166,20 @@ def change_execution_priority(self, executionId, diff, realm='master'): try: if diff > 0: for i in range(diff): - open_url( + self._request( URL_AUTHENTICATION_EXECUTION_RAISE_PRIORITY.format( url=self.baseurl, realm=realm, id=executionId), - method='POST', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + method='POST') elif diff < 0: for i in range(-diff): - open_url( + self._request( URL_AUTHENTICATION_EXECUTION_LOWER_PRIORITY.format( url=self.baseurl, realm=realm, id=executionId), - method='POST', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + method='POST') except Exception as e: self.fail_open_url(e, msg="Unable to change execution priority %s: %s" % (executionId, str(e))) @@ -2299,28 +2193,22 @@ def get_executions_representation(self, config, realm='master'): try: # Get executions created executions = json.load( - open_url( + self._request( URL_AUTHENTICATION_FLOW_EXECUTIONS.format( url=self.baseurl, realm=realm, flowalias=quote(config["alias"], safe='')), - method='GET', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs)) + method='GET')) for execution in executions: if "authenticationConfig" in execution: execConfigId = execution["authenticationConfig"] execConfig = json.load( - open_url( + self._request( URL_AUTHENTICATION_CONFIG.format( url=self.baseurl, realm=realm, id=execConfigId), - method='GET', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs)) + method='GET')) execution["authenticationConfig"] = execConfig return executions except Exception as e: @@ -2336,15 +2224,12 @@ def get_required_actions(self, realm='master'): try: required_actions = json.load( - open_url( + self._request( URL_AUTHENTICATION_REQUIRED_ACTIONS.format( url=self.baseurl, realm=realm ), - method='GET', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs + method='GET' ) ) @@ -2366,16 +2251,13 @@ def register_required_action(self, rep, realm='master'): } try: - return open_url( + return self._request( URL_AUTHENTICATION_REGISTER_REQUIRED_ACTION.format( url=self.baseurl, realm=realm ), method='POST', - http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(data), - timeout=self.connection_timeout, - validate_certs=self.validate_certs ) except Exception as e: self.fail_open_url( @@ -2394,17 +2276,14 @@ def update_required_action(self, alias, rep, realm='master'): """ try: - return open_url( + return self._request( URL_AUTHENTICATION_REQUIRED_ACTIONS_ALIAS.format( url=self.baseurl, alias=quote(alias, safe=''), realm=realm ), method='PUT', - http_agent=self.http_agent, headers=self.restheaders, data=json.dumps(rep), - timeout=self.connection_timeout, - validate_certs=self.validate_certs ) except Exception as e: self.fail_open_url( @@ -2422,16 +2301,13 @@ def delete_required_action(self, alias, realm='master'): """ try: - return open_url( + return self._request( URL_AUTHENTICATION_REQUIRED_ACTIONS_ALIAS.format( url=self.baseurl, alias=quote(alias, safe=''), realm=realm ), method='DELETE', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs ) except Exception as e: self.fail_open_url( @@ -2447,8 +2323,7 @@ def get_identity_providers(self, realm='master'): """ idps_url = URL_IDENTITY_PROVIDERS.format(url=self.baseurl, realm=realm) try: - return json.loads(to_native(open_url(idps_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(idps_url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of identity providers for realm %s: %s' % (realm, str(e))) @@ -2464,8 +2339,7 @@ def get_identity_provider(self, alias, realm='master'): """ idp_url = URL_IDENTITY_PROVIDER.format(url=self.baseurl, realm=realm, alias=alias) try: - return json.loads(to_native(open_url(idp_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(idp_url, method="GET") except HTTPError as e: if e.code == 404: return None @@ -2484,8 +2358,7 @@ def create_identity_provider(self, idprep, realm='master'): """ idps_url = URL_IDENTITY_PROVIDERS.format(url=self.baseurl, realm=realm) try: - return open_url(idps_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(idprep), validate_certs=self.validate_certs) + return self._request(idps_url, method='POST', data=json.dumps(idprep)) except Exception as e: self.fail_open_url(e, msg='Could not create identity provider %s in realm %s: %s' % (idprep['alias'], realm, str(e))) @@ -2498,8 +2371,7 @@ def update_identity_provider(self, idprep, realm='master'): """ idp_url = URL_IDENTITY_PROVIDER.format(url=self.baseurl, realm=realm, alias=idprep['alias']) try: - return open_url(idp_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(idprep), validate_certs=self.validate_certs) + return self._request(idp_url, method='PUT', data=json.dumps(idprep)) except Exception as e: self.fail_open_url(e, msg='Could not update identity provider %s in realm %s: %s' % (idprep['alias'], realm, str(e))) @@ -2511,8 +2383,7 @@ def delete_identity_provider(self, alias, realm='master'): """ idp_url = URL_IDENTITY_PROVIDER.format(url=self.baseurl, realm=realm, alias=alias) try: - return open_url(idp_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(idp_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Unable to delete identity provider %s in realm %s: %s' % (alias, realm, str(e))) @@ -2525,9 +2396,7 @@ def get_identity_provider_mappers(self, alias, realm='master'): """ mappers_url = URL_IDENTITY_PROVIDER_MAPPERS.format(url=self.baseurl, realm=realm, alias=alias) try: - return json.loads(to_native(open_url(mappers_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(mappers_url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of identity provider mappers for idp %s in realm %s: %s' % (alias, realm, str(e))) @@ -2544,9 +2413,7 @@ def get_identity_provider_mapper(self, mid, alias, realm='master'): """ mapper_url = URL_IDENTITY_PROVIDER_MAPPER.format(url=self.baseurl, realm=realm, alias=alias, id=mid) try: - return json.loads(to_native(open_url(mapper_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(mapper_url, method="GET") except HTTPError as e: if e.code == 404: return None @@ -2566,8 +2433,7 @@ def create_identity_provider_mapper(self, mapper, alias, realm='master'): """ mappers_url = URL_IDENTITY_PROVIDER_MAPPERS.format(url=self.baseurl, realm=realm, alias=alias) try: - return open_url(mappers_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(mapper), validate_certs=self.validate_certs) + return self._request(mappers_url, method='POST', data=json.dumps(mapper)) except Exception as e: self.fail_open_url(e, msg='Could not create identity provider mapper %s for idp %s in realm %s: %s' % (mapper['name'], alias, realm, str(e))) @@ -2581,8 +2447,7 @@ def update_identity_provider_mapper(self, mapper, alias, realm='master'): """ mapper_url = URL_IDENTITY_PROVIDER_MAPPER.format(url=self.baseurl, realm=realm, alias=alias, id=mapper['id']) try: - return open_url(mapper_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(mapper), validate_certs=self.validate_certs) + return self._request(mapper_url, method='PUT', data=json.dumps(mapper)) except Exception as e: self.fail_open_url(e, msg='Could not update mapper %s for identity provider %s in realm %s: %s' % (mapper['id'], alias, realm, str(e))) @@ -2595,8 +2460,7 @@ def delete_identity_provider_mapper(self, mid, alias, realm='master'): """ mapper_url = URL_IDENTITY_PROVIDER_MAPPER.format(url=self.baseurl, realm=realm, alias=alias, id=mid) try: - return open_url(mapper_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(mapper_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Unable to delete mapper %s for identity provider %s in realm %s: %s' % (mid, alias, realm, str(e))) @@ -2612,8 +2476,7 @@ def get_components(self, filter=None, realm='master'): comps_url += '?%s' % filter try: - return json.loads(to_native(open_url(comps_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(comps_url, method='GET') except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of components for realm %s: %s' % (realm, str(e))) @@ -2629,8 +2492,7 @@ def get_component(self, cid, realm='master'): """ comp_url = URL_COMPONENT.format(url=self.baseurl, realm=realm, id=cid) try: - return json.loads(to_native(open_url(comp_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(comp_url, method="GET") except HTTPError as e: if e.code == 404: return None @@ -2649,14 +2511,12 @@ def create_component(self, comprep, realm='master'): """ comps_url = URL_COMPONENTS.format(url=self.baseurl, realm=realm) try: - resp = open_url(comps_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(comprep), validate_certs=self.validate_certs) + resp = self._request(comps_url, method='POST', data=json.dumps(comprep)) comp_url = resp.getheader('Location') if comp_url is None: self.module.fail_json(msg='Could not create component in realm %s: %s' % (realm, 'unexpected response')) - return json.loads(to_native(open_url(comp_url, method="GET", http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(comp_url, method="GET") except Exception as e: self.fail_open_url(e, msg='Could not create component in realm %s: %s' % (realm, str(e))) @@ -2672,8 +2532,7 @@ def update_component(self, comprep, realm='master'): self.module.fail_json(msg='Cannot update component without id') comp_url = URL_COMPONENT.format(url=self.baseurl, realm=realm, id=cid) try: - return open_url(comp_url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(comprep), validate_certs=self.validate_certs) + return self._request(comp_url, method='PUT', data=json.dumps(comprep)) except Exception as e: self.fail_open_url(e, msg='Could not update component %s in realm %s: %s' % (cid, realm, str(e))) @@ -2685,8 +2544,7 @@ def delete_component(self, cid, realm='master'): """ comp_url = URL_COMPONENT.format(url=self.baseurl, realm=realm, id=cid) try: - return open_url(comp_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(comp_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Unable to delete component %s in realm %s: %s' % (cid, realm, str(e))) @@ -2696,9 +2554,7 @@ def get_authz_authorization_scope_by_name(self, name, client_id, realm): search_url = "%s/search?name=%s" % (url, quote(name, safe='')) try: - return json.loads(to_native(open_url(search_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(search_url, method='GET') except Exception: return False @@ -2707,8 +2563,7 @@ def create_authz_authorization_scope(self, payload, client_id, realm): url = URL_AUTHZ_AUTHORIZATION_SCOPES.format(url=self.baseurl, client_id=client_id, realm=realm) try: - return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + return self._request(url, method='POST', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not create authorization scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) @@ -2717,8 +2572,7 @@ def update_authz_authorization_scope(self, payload, id, client_id, realm): url = URL_AUTHZ_AUTHORIZATION_SCOPE.format(url=self.baseurl, id=id, client_id=client_id, realm=realm) try: - return open_url(url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + return self._request(url, method='PUT', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not create update scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) @@ -2727,8 +2581,7 @@ def remove_authz_authorization_scope(self, id, client_id, realm): url = URL_AUTHZ_AUTHORIZATION_SCOPE.format(url=self.baseurl, id=id, client_id=client_id, realm=realm) try: - return open_url(url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not delete scope %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) @@ -2745,12 +2598,9 @@ def get_user_by_id(self, user_id, realm='master'): realm=realm, id=user_id) userrep = json.load( - open_url( + self._request( user_url, - method='GET', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs)) + method='GET')) return userrep except Exception as e: self.fail_open_url(e, msg='Could not get user %s in realm %s: %s' @@ -2770,12 +2620,9 @@ def create_user(self, userrep, realm='master'): users_url = URL_USERS.format( url=self.baseurl, realm=realm) - open_url(users_url, + self._request(users_url, method='POST', - http_agent=self.http_agent, headers=self.restheaders, - data=json.dumps(userrep), - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + data=json.dumps(userrep)) created_user = self.get_user_by_username( username=userrep['username'], realm=realm) @@ -2815,13 +2662,10 @@ def update_user(self, userrep, realm='master'): url=self.baseurl, realm=realm, id=userrep["id"]) - open_url( + self._request( user_url, method='PUT', - http_agent=self.http_agent, headers=self.restheaders, - data=json.dumps(userrep), - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + data=json.dumps(userrep)) updated_user = self.get_user_by_id( user_id=userrep['id'], realm=realm) @@ -2842,12 +2686,9 @@ def delete_user(self, user_id, realm='master'): url=self.baseurl, realm=realm, id=user_id) - return open_url( + return self._request( user_url, - method='DELETE', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not delete user %s in realm %s: %s' % (user_id, realm, str(e))) @@ -2866,12 +2707,9 @@ def get_user_groups(self, user_id, realm='master'): realm=realm, id=user_id) user_groups = json.load( - open_url( + self._request( user_groups_url, - method='GET', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs)) + method='GET')) for user_group in user_groups: groups.append(user_group["name"]) return groups @@ -2893,12 +2731,9 @@ def add_user_in_group(self, user_id, group_id, realm='master'): realm=realm, id=user_id, group_id=group_id) - return open_url( + return self._request( user_group_url, - method='PUT', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + method='PUT') except Exception as e: self.fail_open_url(e, msg='Could not add user %s in group %s in realm %s: %s' % (user_id, group_id, realm, str(e))) @@ -2917,12 +2752,9 @@ def remove_user_from_group(self, user_id, group_id, realm='master'): realm=realm, id=user_id, group_id=group_id) - return open_url( + return self._request( user_group_url, - method='DELETE', - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not remove user %s from group %s in realm %s: %s' % (user_id, group_id, realm, str(e))) @@ -2993,8 +2825,7 @@ def create_authz_custom_policy(self, policy_type, payload, client_id, realm): url = URL_AUTHZ_CUSTOM_POLICY.format(url=self.baseurl, policy_type=policy_type, client_id=client_id, realm=realm) try: - return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + return self._request(url, method='POST', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) @@ -3004,8 +2835,7 @@ def remove_authz_custom_policy(self, policy_id, client_id, realm): delete_url = "%s/%s" % (url, policy_id) try: - return open_url(delete_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(delete_url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not delete custom policy %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) @@ -3015,9 +2845,7 @@ def get_authz_permission_by_name(self, name, client_id, realm): search_url = "%s/search?name=%s" % (url, name.replace(' ', '%20')) try: - return json.loads(to_native(open_url(search_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(search_url, method='GET') except Exception: return False @@ -3026,8 +2854,7 @@ def create_authz_permission(self, payload, permission_type, client_id, realm): url = URL_AUTHZ_PERMISSIONS.format(url=self.baseurl, permission_type=permission_type, client_id=client_id, realm=realm) try: - return open_url(url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + return self._request(url, method='POST', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) @@ -3036,8 +2863,7 @@ def remove_authz_permission(self, id, client_id, realm): url = URL_AUTHZ_POLICY.format(url=self.baseurl, id=id, client_id=client_id, realm=realm) try: - return open_url(url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - validate_certs=self.validate_certs) + return self._request(url, method='DELETE') except Exception as e: self.fail_open_url(e, msg='Could not delete permission %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) @@ -3046,8 +2872,7 @@ def update_authz_permission(self, payload, permission_type, id, client_id, realm url = URL_AUTHZ_PERMISSION.format(url=self.baseurl, permission_type=permission_type, id=id, client_id=client_id, realm=realm) try: - return open_url(url, method='PUT', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + return self._request(url, method='PUT', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not create update permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) @@ -3057,9 +2882,7 @@ def get_authz_resource_by_name(self, name, client_id, realm): search_url = "%s/search?name=%s" % (url, name.replace(' ', '%20')) try: - return json.loads(to_native(open_url(search_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(search_url, method='GET') except Exception: return False @@ -3069,9 +2892,7 @@ def get_authz_policy_by_name(self, name, client_id, realm): search_url = "%s/search?name=%s&permission=false" % (url, name.replace(' ', '%20')) try: - return json.loads(to_native(open_url(search_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(search_url, method='GET') except Exception: return False @@ -3084,9 +2905,7 @@ def get_client_role_scope_from_client(self, clientid, clientscopeid, realm="mast """ client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid) try: - return json.loads(to_native(open_url(client_role_scope_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(client_role_scope_url) except Exception as e: self.fail_open_url(e, msg='Could not fetch roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) @@ -3100,8 +2919,7 @@ def update_client_role_scope_from_client(self, payload, clientid, clientscopeid, """ client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid) try: - open_url(client_role_scope_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + self._request(client_role_scope_url, method='POST', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not update roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) @@ -3118,8 +2936,7 @@ def delete_client_role_scope_from_client(self, payload, clientid, clientscopeid, """ client_role_scope_url = URL_CLIENT_ROLE_SCOPE_CLIENTS.format(url=self.baseurl, realm=realm, id=clientid, scopeid=clientscopeid) try: - open_url(client_role_scope_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + self._request(client_role_scope_url, method='DELETE', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not delete roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) @@ -3134,9 +2951,7 @@ def get_client_role_scope_from_realm(self, clientid, realm="master"): """ client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid) try: - return json.loads(to_native(open_url(client_role_scope_url, method='GET', http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs).read())) + return self._request_and_deserialize(client_role_scope_url) except Exception as e: self.fail_open_url(e, msg='Could not fetch roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) @@ -3149,8 +2964,7 @@ def update_client_role_scope_from_realm(self, payload, clientid, realm="master") """ client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid) try: - open_url(client_role_scope_url, method='POST', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + self._request(client_role_scope_url, method='POST', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not update roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) @@ -3166,8 +2980,7 @@ def delete_client_role_scope_from_realm(self, payload, clientid, realm="master") """ client_role_scope_url = URL_CLIENT_ROLE_SCOPE_REALM.format(url=self.baseurl, realm=realm, id=clientid) try: - open_url(client_role_scope_url, method='DELETE', http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, - data=json.dumps(payload), validate_certs=self.validate_certs) + self._request(client_role_scope_url, method='DELETE', data=json.dumps(payload)) except Exception as e: self.fail_open_url(e, msg='Could not delete roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) From d6a273070304714bf6bbd6e4df95d3450a05e7e8 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Sat, 9 Nov 2024 12:39:40 -0500 Subject: [PATCH 07/30] fix: data argument is optional in keycloak request methods [8857] --- plugins/module_utils/identity/keycloak/keycloak.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 7b6147ccb36..e1a36d6e769 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -280,14 +280,14 @@ def __init__(self, module, connection_header): self.restheaders = connection_header self.http_agent = self.module.params.get('http_agent') - def _request(self, url, method, data): + def _request(self, url, method, data=None): return open_url(url, method=method, data=data, http_agent=self.http_agent, headers=self.restheaders, timeout=self.connection_timeout, validate_certs=self.validate_certs) - def _request_and_deserialize(self, url, method, data): - return json.loads(to_native(self._request(self, url, method, data).read())) + def _request_and_deserialize(self, url, method, data=None): + return json.loads(to_native(self._request(url, method, data).read())) def get_realm_info_by_id(self, realm='master'): """ Obtain realm public info by id From 8c7684a9fcc3fa335e0d1c1a13ded1612f8a8328 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Sat, 9 Nov 2024 12:46:37 -0500 Subject: [PATCH 08/30] feat: add integration test for keycloak module authentication methods [8857] --- .../keycloak_modules_authentication/README.md | 26 +++++ .../keycloak_modules_authentication/aliases | 5 + .../tasks/main.yml | 104 ++++++++++++++++++ .../vars/main.yml | 20 ++++ 4 files changed, 155 insertions(+) create mode 100644 tests/integration/targets/keycloak_modules_authentication/README.md create mode 100644 tests/integration/targets/keycloak_modules_authentication/aliases create mode 100644 tests/integration/targets/keycloak_modules_authentication/tasks/main.yml create mode 100644 tests/integration/targets/keycloak_modules_authentication/vars/main.yml diff --git a/tests/integration/targets/keycloak_modules_authentication/README.md b/tests/integration/targets/keycloak_modules_authentication/README.md new file mode 100644 index 00000000000..a3d40a56740 --- /dev/null +++ b/tests/integration/targets/keycloak_modules_authentication/README.md @@ -0,0 +1,26 @@ + +# Running keycloak module authentication integration test + +To run the Keycloak module authentication integration test, start a keycloak server using Docker or Podman: + +```sh + podman|docker run -d --rm --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=password quay.io/keycloak/keycloak:latest start-dev --http-relative-path /auth +``` + +Source Ansible env-setup from ansible github repository. + +Run the integration tests: + +```sh + ansible-test integration keycloak_role --python 3.10 --allow-unsupported +``` + +To cleanup, run: + +```sh + podman|docker stop mykeycloak +``` diff --git a/tests/integration/targets/keycloak_modules_authentication/aliases b/tests/integration/targets/keycloak_modules_authentication/aliases new file mode 100644 index 00000000000..bd1f0244415 --- /dev/null +++ b/tests/integration/targets/keycloak_modules_authentication/aliases @@ -0,0 +1,5 @@ +# 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 + +unsupported diff --git a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml new file mode 100644 index 00000000000..d60b311739a --- /dev/null +++ b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml @@ -0,0 +1,104 @@ +--- +# 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: 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: Create 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 + register: client + +- name: Create new realm role with username/password authentication + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ keycloak_role_description }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Remove created realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result + +- name: Get Keycloak token + ansible.builtin.uri: + url: "{{ url }}/realms/{{ admin_realm }}/protocol/openid-connect/token" + method: POST + return_content: true + status_code: 200 + body_format: form-urlencoded + body: + grant_type: "password" + client_id: "admin-cli" + username: "{{ admin_user }}" + password: "{{ admin_password }}" + register: token_response + +- name: Extract tokens + ansible.builtin.set_fact: + access_token: "{{ token_response.json | json_query('access_token') }}" + refresh_token: "{{ token_response.json | json_query('refresh_token') }}" + +- name: Create new realm role with provided token authentication + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + token: "{{ access_token }}" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ keycloak_role_description }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Remove created realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result diff --git a/tests/integration/targets/keycloak_modules_authentication/vars/main.yml b/tests/integration/targets/keycloak_modules_authentication/vars/main.yml new file mode 100644 index 00000000000..02ad618e1b0 --- /dev/null +++ b/tests/integration/targets/keycloak_modules_authentication/vars/main.yml @@ -0,0 +1,20 @@ +--- +# 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 + +url: http://localhost:8080/auth +admin_realm: master +admin_user: admin +admin_password: password +realm: myrealm +client_id: myclient +role: myrole + +keycloak_role_name: test +keycloak_role_description: test +keycloak_role_composite: false +keycloak_client_id: test-client +keycloak_client_name: test-client +keycloak_client_description: This is a client for testing purpose +role_state: present From 08650d007ecf310a97bfc5c0b412b69abfa0f11b Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Sat, 9 Nov 2024 13:08:22 -0500 Subject: [PATCH 09/30] chore: refactor get token logic to separate logic using username/pass credentials [8857] --- .../identity/keycloak/keycloak.py | 85 ++++++++++--------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index e1a36d6e769..3389a7d00e0 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -154,6 +154,51 @@ class KeycloakError(Exception): pass +def _get_token_with_credentials(module_params): + base_url = module_params.get('auth_keycloak_url') + if not base_url.lower().startswith(('http', 'https')): + raise KeycloakError("auth_url '%s' should either start with 'http' or 'https'." % base_url) + + http_agent = module_params.get('http_agent') + validate_certs = module_params.get('validate_certs') + auth_realm = module_params.get('auth_realm') + client_id = module_params.get('auth_client_id') + auth_username = module_params.get('auth_username') + auth_password = module_params.get('auth_password') + client_secret = module_params.get('auth_client_secret') + connection_timeout = module_params.get('connection_timeout') + auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) + + temp_payload = { + 'grant_type': 'password', + 'client_id': client_id, + 'client_secret': client_secret, + 'username': auth_username, + 'password': auth_password, + } + # Remove empty items, for instance missing client_secret + payload = {k: v for k, v in temp_payload.items() if v is not None} + try: + r = json.loads(to_native(open_url(auth_url, method='POST', + validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout, + data=urlencode(payload)).read())) + except ValueError as e: + raise KeycloakError( + 'API returned invalid JSON when trying to obtain access token from %s: %s' + % (auth_url, str(e))) + except Exception as e: + raise KeycloakError('Could not obtain access token from %s: %s' + % (auth_url, str(e))) + + try: + token = r['access_token'] + except KeyError: + raise KeycloakError( + 'Could not obtain access token from %s' % auth_url) + + return token + + def get_token(module_params): """ Obtains connection header with token for the authentication, token already given or obtained from credentials @@ -161,48 +206,10 @@ def get_token(module_params): :return: connection header """ token = module_params.get('token') - base_url = module_params.get('auth_keycloak_url') - http_agent = module_params.get('http_agent') - - if not base_url.lower().startswith(('http', 'https')): - raise KeycloakError("auth_url '%s' should either start with 'http' or 'https'." % base_url) if token is None: - base_url = module_params.get('auth_keycloak_url') - validate_certs = module_params.get('validate_certs') - auth_realm = module_params.get('auth_realm') - client_id = module_params.get('auth_client_id') - auth_username = module_params.get('auth_username') - auth_password = module_params.get('auth_password') - client_secret = module_params.get('auth_client_secret') - connection_timeout = module_params.get('connection_timeout') - auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) - temp_payload = { - 'grant_type': 'password', - 'client_id': client_id, - 'client_secret': client_secret, - 'username': auth_username, - 'password': auth_password, - } - # Remove empty items, for instance missing client_secret - payload = {k: v for k, v in temp_payload.items() if v is not None} - try: - r = json.loads(to_native(open_url(auth_url, method='POST', - validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout, - data=urlencode(payload)).read())) - except ValueError as e: - raise KeycloakError( - 'API returned invalid JSON when trying to obtain access token from %s: %s' - % (auth_url, str(e))) - except Exception as e: - raise KeycloakError('Could not obtain access token from %s: %s' - % (auth_url, str(e))) + token = _get_token_with_credentials(module_params) - try: - token = r['access_token'] - except KeyError: - raise KeycloakError( - 'Could not obtain access token from %s' % auth_url) return { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' From ccea7aa67133bebb4d76e1b6a0d2a3ba37631d93 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Sat, 9 Nov 2024 13:17:59 -0500 Subject: [PATCH 10/30] chore: refactor token request logic further to isolate request logic [8857] --- .../identity/keycloak/keycloak.py | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 3389a7d00e0..60b3beedda8 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -154,30 +154,16 @@ class KeycloakError(Exception): pass -def _get_token_with_credentials(module_params): +def _token_request(module_params, payload): base_url = module_params.get('auth_keycloak_url') if not base_url.lower().startswith(('http', 'https')): raise KeycloakError("auth_url '%s' should either start with 'http' or 'https'." % base_url) - + auth_realm = module_params.get('auth_realm') + auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) http_agent = module_params.get('http_agent') validate_certs = module_params.get('validate_certs') - auth_realm = module_params.get('auth_realm') - client_id = module_params.get('auth_client_id') - auth_username = module_params.get('auth_username') - auth_password = module_params.get('auth_password') - client_secret = module_params.get('auth_client_secret') connection_timeout = module_params.get('connection_timeout') - auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) - temp_payload = { - 'grant_type': 'password', - 'client_id': client_id, - 'client_secret': client_secret, - 'username': auth_username, - 'password': auth_password, - } - # Remove empty items, for instance missing client_secret - payload = {k: v for k, v in temp_payload.items() if v is not None} try: r = json.loads(to_native(open_url(auth_url, method='POST', validate_certs=validate_certs, http_agent=http_agent, timeout=connection_timeout, @@ -199,6 +185,25 @@ def _get_token_with_credentials(module_params): return token +def _get_token_using_credentials(module_params): + client_id = module_params.get('auth_client_id') + auth_username = module_params.get('auth_username') + auth_password = module_params.get('auth_password') + client_secret = module_params.get('auth_client_secret') + + temp_payload = { + 'grant_type': 'password', + 'client_id': client_id, + 'client_secret': client_secret, + 'username': auth_username, + 'password': auth_password, + } + # Remove empty items, for instance missing client_secret + payload = {k: v for k, v in temp_payload.items() if v is not None} + + return _token_request(module_params, payload) + + def get_token(module_params): """ Obtains connection header with token for the authentication, token already given or obtained from credentials @@ -208,7 +213,7 @@ def get_token(module_params): token = module_params.get('token') if token is None: - token = _get_token_with_credentials(module_params) + token = _get_token_using_credentials(module_params) return { 'Authorization': 'Bearer ' + token, From bd31be4c4e07ff41e97be94f7d0440f8590df1c6 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Mon, 30 Dec 2024 15:29:00 -0500 Subject: [PATCH 11/30] chore: fix minor lint issues [8857] --- plugins/module_utils/identity/keycloak/keycloak.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 60b3beedda8..08a58d64c62 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -284,6 +284,7 @@ class KeycloakAPI(object): """ Keycloak API access; Keycloak uses OAuth 2.0 to protect its API, an access token for which is obtained through OpenID connect """ + def __init__(self, module, connection_header): self.module = module self.baseurl = self.module.params.get('auth_keycloak_url') @@ -1466,7 +1467,6 @@ def get_group_by_name(self, name, realm="master", parents=None): :param realm: Realm in which the group resides; default 'master' :param parents: Optional list of parents when group to look for is a subgroup """ - groups_url = URL_GROUPS.format(url=self.baseurl, realm=realm) try: if parents: parent = self.get_subgroup_direct_parent(parents, realm) @@ -2633,8 +2633,8 @@ def create_user(self, userrep, realm='master'): url=self.baseurl, realm=realm) self._request(users_url, - method='POST', - data=json.dumps(userrep)) + method='POST', + data=json.dumps(userrep)) created_user = self.get_user_by_username( username=userrep['username'], realm=realm) @@ -3003,6 +3003,6 @@ def fail_open_url(self, e, msg, **kwargs): try: if isinstance(e, HTTPError): msg = "%s: %s" % (msg, to_native(e.read())) - except Exception as ingore: + except Exception: pass self.module.fail_json(msg, **kwargs) From 817c085400c808eb2b52f98c100aedf008876889 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Mon, 30 Dec 2024 16:09:03 -0500 Subject: [PATCH 12/30] test: add (currently failing) test for request with invalid auth token, valid refresh token [8857] --- .../tasks/main.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml index d60b311739a..fc93c844ba8 100644 --- a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml +++ b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml @@ -102,3 +102,33 @@ - name: Debug debug: var: result + +- name: Create new realm role with invalid auth token and valid refresh token + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + token: "invalidtoken!!!" + refresh_token: "{{ refresh_token }}" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ keycloak_role_description }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Remove created realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result From 67700851a336a39318cc9f6b2d88762ca7a2bfed Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Mon, 30 Dec 2024 16:54:35 -0500 Subject: [PATCH 13/30] chore: allow realm to be provided to role module with refresh_token, without username/pass [8857] --- plugins/modules/keycloak_role.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/modules/keycloak_role.py b/plugins/modules/keycloak_role.py index f3e01483f80..d33e7b5d6a6 100644 --- a/plugins/modules/keycloak_role.py +++ b/plugins/modules/keycloak_role.py @@ -269,7 +269,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) From 3b90af532fcffcc0d338e6a5bd403f486b9b20da Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Mon, 30 Dec 2024 16:56:39 -0500 Subject: [PATCH 14/30] feat: add retry logic to requests in keycloak module utils [8857] --- .../identity/keycloak/keycloak.py | 56 +++++++++++++++++-- .../tasks/main.yml | 1 + 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 08a58d64c62..28845db9d78 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -142,6 +142,7 @@ def keycloak_argument_spec(): validate_certs=dict(type='bool', default=True), connection_timeout=dict(type='int', default=10), token=dict(type='str', no_log=True), + refresh_token=dict(type='str', no_log=True), http_agent=dict(type='str', default='Ansible'), ) @@ -204,6 +205,23 @@ def _get_token_using_credentials(module_params): return _token_request(module_params, payload) +def _get_token_using_refresh_token(module_params): + client_id = module_params.get('auth_client_id') + refresh_token = module_params.get('refresh_token') + client_secret = module_params.get('auth_client_secret') + + temp_payload = { + 'grant_type': 'refresh_token', + 'client_id': client_id, + 'client_secret': client_secret, + 'refresh_token': refresh_token, + } + # Remove empty items, for instance missing client_secret + payload = {k: v for k, v in temp_payload.items() if v is not None} + + return _token_request(module_params, payload) + + def get_token(module_params): """ Obtains connection header with token for the authentication, token already given or obtained from credentials @@ -294,10 +312,40 @@ def __init__(self, module, connection_header): self.http_agent = self.module.params.get('http_agent') def _request(self, url, method, data=None): - return open_url(url, method=method, data=data, - http_agent=self.http_agent, headers=self.restheaders, - timeout=self.connection_timeout, - validate_certs=self.validate_certs) + def make_request_ignoring_401(): + try: + return open_url(url, method=method, data=data, + http_agent=self.http_agent, headers=self.restheaders, + timeout=self.connection_timeout, + validate_certs=self.validate_certs) + except HTTPError as e: + if e.code != 401: + raise e + + return None + + r = make_request_ignoring_401() + if r is not None: + return r + + # Authentication may have expired, re-authenticate with refresh token and retry + refresh_token = self.module.params.get('refresh_token') + if refresh_token is not None: + token = _get_token_using_refresh_token(self.module.params) + self.restheaders['Authorization'] = 'Bearer ' + token + + r = make_request_ignoring_401() + if r is not None: + return r + + # Retry once more with username and password + auth_username = self.module.params.get('auth_username') + auth_password = self.module.params.get('auth_password') + if auth_username is not None and auth_password is not None: + token = _get_token_using_credentials(self.module.params) + self.restheaders['Authorization'] = 'Bearer ' + token + + return make_request_ignoring_401() def _request_and_deserialize(self, url, method, data=None): return json.loads(to_native(self._request(url, method, data).read())) diff --git a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml index fc93c844ba8..0c8df69a346 100644 --- a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml +++ b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml @@ -106,6 +106,7 @@ - name: Create new realm role with invalid auth token and valid refresh token community.general.keycloak_role: auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" token: "invalidtoken!!!" refresh_token: "{{ refresh_token }}" realm: "{{ realm }}" From 76a95ed7ccf0c65443bfd5cd1df3cb220110aa8e Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Mon, 30 Dec 2024 16:58:20 -0500 Subject: [PATCH 15/30] chore: rename keycloak module fail_open_url method to fail_request [8857] --- .../identity/keycloak/keycloak.py | 434 +++++++++--------- 1 file changed, 217 insertions(+), 217 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 28845db9d78..b232ae8f9de 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -365,8 +365,8 @@ def get_realm_info_by_id(self, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not obtain realm %s: %s' % (realm, str(e)), - exception=traceback.format_exc()) + self.fail_request(e, msg='Could not obtain realm %s: %s' % (realm, str(e)), + exception=traceback.format_exc()) except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain realm %s: %s' % (realm, str(e)), exception=traceback.format_exc()) @@ -394,8 +394,8 @@ def get_realm_keys_metadata_by_id(self, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not obtain realm %s: %s' % (realm, str(e)), - exception=traceback.format_exc()) + self.fail_request(e, msg='Could not obtain realm %s: %s' % (realm, str(e)), + exception=traceback.format_exc()) except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain realm %s: %s' % (realm, str(e)), exception=traceback.format_exc()) @@ -418,8 +418,8 @@ def get_realm_by_id(self, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not obtain realm %s: %s' % (realm, str(e)), - exception=traceback.format_exc()) + self.fail_request(e, msg='Could not obtain realm %s: %s' % (realm, str(e)), + exception=traceback.format_exc()) except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain realm %s: %s' % (realm, str(e)), exception=traceback.format_exc()) @@ -438,8 +438,8 @@ def update_realm(self, realmrep, realm="master"): try: return self._request(realm_url, method='PUT', data=json.dumps(realmrep)) except Exception as e: - self.fail_open_url(e, msg='Could not update realm %s: %s' % (realm, str(e)), - exception=traceback.format_exc()) + self.fail_request(e, msg='Could not update realm %s: %s' % (realm, str(e)), + exception=traceback.format_exc()) def create_realm(self, realmrep): """ Create a realm in keycloak @@ -451,8 +451,8 @@ def create_realm(self, realmrep): try: return self._request(realm_url, method='POST', data=json.dumps(realmrep)) except Exception as e: - self.fail_open_url(e, msg='Could not create realm %s: %s' % (realmrep['id'], str(e)), - exception=traceback.format_exc()) + self.fail_request(e, msg='Could not create realm %s: %s' % (realmrep['id'], str(e)), + exception=traceback.format_exc()) def delete_realm(self, realm="master"): """ Delete a realm from Keycloak @@ -465,8 +465,8 @@ def delete_realm(self, realm="master"): try: return self._request(realm_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not delete realm %s: %s' % (realm, str(e)), - exception=traceback.format_exc()) + self.fail_request(e, msg='Could not delete realm %s: %s' % (realm, str(e)), + exception=traceback.format_exc()) def get_clients(self, realm='master', filter=None): """ Obtains client representations for clients in a realm @@ -485,8 +485,8 @@ def get_clients(self, realm='master', filter=None): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of clients for realm %s: %s' % (realm, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain list of clients for realm %s: %s' - % (realm, str(e))) + self.fail_request(e, msg='Could not obtain list of clients for realm %s: %s' + % (realm, str(e))) def get_client_by_clientid(self, client_id, realm='master'): """ Get client representation by clientId @@ -516,8 +516,8 @@ def get_client_by_id(self, id, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not obtain client %s for realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not obtain client %s for realm %s: %s' + % (id, realm, str(e))) except ValueError as e: self.module.fail_json(msg='API returned incorrect JSON when trying to obtain client %s for realm %s: %s' % (id, realm, str(e))) @@ -550,8 +550,8 @@ def update_client(self, id, clientrep, realm="master"): try: return self._request(client_url, method='PUT', data=json.dumps(clientrep)) except Exception as e: - self.fail_open_url(e, msg='Could not update client %s in realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not update client %s in realm %s: %s' + % (id, realm, str(e))) def create_client(self, clientrep, realm="master"): """ Create a client in keycloak @@ -564,8 +564,8 @@ def create_client(self, clientrep, realm="master"): try: return self._request(client_url, method='POST', data=json.dumps(clientrep)) except Exception as e: - self.fail_open_url(e, msg='Could not create client %s in realm %s: %s' - % (clientrep['clientId'], realm, str(e))) + self.fail_request(e, msg='Could not create client %s in realm %s: %s' + % (clientrep['clientId'], realm, str(e))) def delete_client(self, id, realm="master"): """ Delete a client from Keycloak @@ -579,8 +579,8 @@ def delete_client(self, id, realm="master"): try: return self._request(client_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not delete client %s in realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not delete client %s in realm %s: %s' + % (id, realm, str(e))) def get_client_roles_by_id(self, cid, realm="master"): """ Fetch the roles of the a client on the Keycloak server. @@ -593,8 +593,8 @@ def get_client_roles_by_id(self, cid, realm="master"): try: return self._request_and_deserialize(client_roles_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch rolemappings for client %s in realm %s: %s" - % (cid, realm, str(e))) + self.fail_request(e, msg="Could not fetch rolemappings for client %s in realm %s: %s" + % (cid, realm, str(e))) def get_client_role_id_by_name(self, cid, name, realm="master"): """ Get the role ID of a client. @@ -626,8 +626,8 @@ def get_client_group_rolemapping_by_id(self, gid, cid, rid, realm='master'): if rid == role['id']: return role except Exception as e: - self.fail_open_url(e, msg="Could not fetch rolemappings for client %s in group %s, realm %s: %s" - % (cid, gid, realm, str(e))) + self.fail_request(e, msg="Could not fetch rolemappings for client %s in group %s, realm %s: %s" + % (cid, gid, realm, str(e))) return None def get_client_group_available_rolemappings(self, gid, cid, realm="master"): @@ -642,8 +642,8 @@ def get_client_group_available_rolemappings(self, gid, cid, realm="master"): try: return self._request_and_deserialize(available_rolemappings_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" - % (cid, gid, realm, str(e))) + self.fail_request(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" + % (cid, gid, realm, str(e))) def get_client_group_composite_rolemappings(self, gid, cid, realm="master"): """ Fetch the composite role of a client in a specified group on the Keycloak server. @@ -657,8 +657,8 @@ def get_client_group_composite_rolemappings(self, gid, cid, realm="master"): try: return self._request_and_deserialize(composite_rolemappings_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" - % (cid, gid, realm, str(e))) + self.fail_request(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" + % (cid, gid, realm, str(e))) def get_role_by_id(self, rid, realm="master"): """ Fetch a role by its id on the Keycloak server. @@ -671,8 +671,8 @@ def get_role_by_id(self, rid, realm="master"): try: return self._request_and_deserialize(client_roles_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch role for id %s in realm %s: %s" - % (rid, realm, str(e))) + self.fail_request(e, msg="Could not fetch role for id %s in realm %s: %s" + % (rid, realm, str(e))) def get_client_roles_by_id_composite_rolemappings(self, rid, cid, realm="master"): """ Fetch a role by its id on the Keycloak server. @@ -686,8 +686,8 @@ def get_client_roles_by_id_composite_rolemappings(self, rid, cid, realm="master" try: return self._request_and_deserialize(client_roles_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch role for id %s and cid %s in realm %s: %s" - % (rid, cid, realm, str(e))) + self.fail_request(e, msg="Could not fetch role for id %s and cid %s in realm %s: %s" + % (rid, cid, realm, str(e))) def add_client_roles_by_id_composite_rolemapping(self, rid, roles_rep, realm="master"): """ Assign roles to composite role @@ -701,8 +701,8 @@ def add_client_roles_by_id_composite_rolemapping(self, rid, roles_rep, realm="ma try: self._request(available_rolemappings_url, method="POST", data=json.dumps(roles_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not assign roles to composite role %s and realm %s: %s" - % (rid, realm, str(e))) + self.fail_request(e, msg="Could not assign roles to composite role %s and realm %s: %s" + % (rid, realm, str(e))) def add_group_realm_rolemapping(self, gid, role_rep, realm="master"): """ Add the specified realm role to specified group on the Keycloak server. @@ -716,8 +716,8 @@ def add_group_realm_rolemapping(self, gid, role_rep, realm="master"): try: self._request(url, method="POST", data=json.dumps(role_rep)) except Exception as e: - self.fail_open_url(e, msg="Could add realm role mappings for group %s, realm %s: %s" - % (gid, realm, str(e))) + self.fail_request(e, msg="Could add realm role mappings for group %s, realm %s: %s" + % (gid, realm, str(e))) def delete_group_realm_rolemapping(self, gid, role_rep, realm="master"): """ Delete the specified realm role from the specified group on the Keycloak server. @@ -731,8 +731,8 @@ def delete_group_realm_rolemapping(self, gid, role_rep, realm="master"): try: self._request(url, method="DELETE", data=json.dumps(role_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not delete realm role mappings for group %s, realm %s: %s" - % (gid, realm, str(e))) + self.fail_request(e, msg="Could not delete realm role mappings for group %s, realm %s: %s" + % (gid, realm, str(e))) def add_group_rolemapping(self, gid, cid, role_rep, realm="master"): """ Fetch the composite role of a client in a specified group on the Keycloak server. @@ -747,8 +747,8 @@ def add_group_rolemapping(self, gid, cid, role_rep, realm="master"): try: self._request(available_rolemappings_url, method="POST", data=json.dumps(role_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" - % (cid, gid, realm, str(e))) + self.fail_request(e, msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" + % (cid, gid, realm, str(e))) def delete_group_rolemapping(self, gid, cid, role_rep, realm="master"): """ Delete the rolemapping of a client in a specified group on the Keycloak server. @@ -763,8 +763,8 @@ def delete_group_rolemapping(self, gid, cid, role_rep, realm="master"): try: self._request(available_rolemappings_url, method="DELETE", data=json.dumps(role_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not delete available rolemappings for client %s in group %s, realm %s: %s" - % (cid, gid, realm, str(e))) + self.fail_request(e, msg="Could not delete available rolemappings for client %s in group %s, realm %s: %s" + % (cid, gid, realm, str(e))) def get_client_user_rolemapping_by_id(self, uid, cid, rid, realm='master'): """ Obtain client representation by id @@ -782,8 +782,8 @@ def get_client_user_rolemapping_by_id(self, uid, cid, rid, realm='master'): if rid == role['id']: return role except Exception as e: - self.fail_open_url(e, msg="Could not fetch rolemappings for client %s and user %s, realm %s: %s" - % (cid, uid, realm, str(e))) + self.fail_request(e, msg="Could not fetch rolemappings for client %s and user %s, realm %s: %s" + % (cid, uid, realm, str(e))) return None def get_client_user_available_rolemappings(self, uid, cid, realm="master"): @@ -798,8 +798,8 @@ def get_client_user_available_rolemappings(self, uid, cid, realm="master"): try: return self._request_and_deserialize(available_rolemappings_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch effective rolemappings for client %s and user %s, realm %s: %s" - % (cid, uid, realm, str(e))) + self.fail_request(e, msg="Could not fetch effective rolemappings for client %s and user %s, realm %s: %s" + % (cid, uid, realm, str(e))) def get_client_user_composite_rolemappings(self, uid, cid, realm="master"): """ Fetch the composite role of a client for a specified user on the Keycloak server. @@ -813,8 +813,8 @@ def get_client_user_composite_rolemappings(self, uid, cid, realm="master"): try: return self._request_and_deserialize(composite_rolemappings_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch available rolemappings for user %s of realm %s: %s" - % (uid, realm, str(e))) + self.fail_request(e, msg="Could not fetch available rolemappings for user %s of realm %s: %s" + % (uid, realm, str(e))) def get_realm_user_rolemapping_by_id(self, uid, rid, realm='master'): """ Obtain role representation by id @@ -831,8 +831,8 @@ def get_realm_user_rolemapping_by_id(self, uid, rid, realm='master'): if rid == role['id']: return role except Exception as e: - self.fail_open_url(e, msg="Could not fetch rolemappings for user %s, realm %s: %s" - % (uid, realm, str(e))) + self.fail_request(e, msg="Could not fetch rolemappings for user %s, realm %s: %s" + % (uid, realm, str(e))) return None def get_realm_user_available_rolemappings(self, uid, realm="master"): @@ -846,8 +846,8 @@ def get_realm_user_available_rolemappings(self, uid, realm="master"): try: return self._request_and_deserialize(available_rolemappings_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch available rolemappings for user %s of realm %s: %s" - % (uid, realm, str(e))) + self.fail_request(e, msg="Could not fetch available rolemappings for user %s of realm %s: %s" + % (uid, realm, str(e))) def get_realm_user_composite_rolemappings(self, uid, realm="master"): """ Fetch the composite role of a realm for a specified user on the Keycloak server. @@ -860,8 +860,8 @@ def get_realm_user_composite_rolemappings(self, uid, realm="master"): try: return self._request_and_deserialize(composite_rolemappings_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch effective rolemappings for user %s, realm %s: %s" - % (uid, realm, str(e))) + self.fail_request(e, msg="Could not fetch effective rolemappings for user %s, realm %s: %s" + % (uid, realm, str(e))) def get_user_by_username(self, username, realm="master"): """ Fetch a keycloak user within a realm based on its username. @@ -885,8 +885,8 @@ def get_user_by_username(self, username, realm="master"): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain the user for realm %s and username %s: %s' % (realm, username, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain the user for realm %s and username %s: %s' - % (realm, username, str(e))) + self.fail_request(e, msg='Could not obtain the user for realm %s and username %s: %s' + % (realm, username, str(e))) def get_service_account_user_by_client_id(self, client_id, realm="master"): """ Fetch a keycloak service account user within a realm based on its client_id. @@ -904,8 +904,8 @@ def get_service_account_user_by_client_id(self, client_id, realm="master"): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain the service-account-user for realm %s and client_id %s: %s' % (realm, client_id, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain the service-account-user for realm %s and client_id %s: %s' - % (realm, client_id, str(e))) + self.fail_request(e, msg='Could not obtain the service-account-user for realm %s and client_id %s: %s' + % (realm, client_id, str(e))) def add_user_rolemapping(self, uid, cid, role_rep, realm="master"): """ Assign a realm or client role to a specified user on the Keycloak server. @@ -921,15 +921,15 @@ def add_user_rolemapping(self, uid, cid, role_rep, realm="master"): try: self._request(user_realm_rolemappings_url, method="POST", data=json.dumps(role_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not map roles to userId %s for realm %s and roles %s: %s" - % (uid, realm, json.dumps(role_rep), str(e))) + self.fail_request(e, msg="Could not map roles to userId %s for realm %s and roles %s: %s" + % (uid, realm, json.dumps(role_rep), str(e))) else: user_client_rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid, client=cid) try: self._request(user_client_rolemappings_url, method="POST", data=json.dumps(role_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not map roles to userId %s for client %s, realm %s and roles %s: %s" - % (cid, uid, realm, json.dumps(role_rep), str(e))) + self.fail_request(e, msg="Could not map roles to userId %s for client %s, realm %s and roles %s: %s" + % (cid, uid, realm, json.dumps(role_rep), str(e))) def delete_user_rolemapping(self, uid, cid, role_rep, realm="master"): """ Delete the rolemapping of a client in a specified user on the Keycloak server. @@ -945,15 +945,15 @@ def delete_user_rolemapping(self, uid, cid, role_rep, realm="master"): try: self._request(user_realm_rolemappings_url, method="DELETE", data=json.dumps(role_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not remove roles %s from userId %s, realm %s: %s" - % (json.dumps(role_rep), uid, realm, str(e))) + self.fail_request(e, msg="Could not remove roles %s from userId %s, realm %s: %s" + % (json.dumps(role_rep), uid, realm, str(e))) else: user_client_rolemappings_url = URL_CLIENT_USER_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=uid, client=cid) try: self._request(user_client_rolemappings_url, method="DELETE", data=json.dumps(role_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not remove roles %s for client %s from userId %s, realm %s: %s" - % (json.dumps(role_rep), cid, uid, realm, str(e))) + self.fail_request(e, msg="Could not remove roles %s for client %s from userId %s, realm %s: %s" + % (json.dumps(role_rep), cid, uid, realm, str(e))) def get_client_templates(self, realm='master'): """ Obtains client template representations for client templates in a realm @@ -969,8 +969,8 @@ def get_client_templates(self, realm='master'): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of client templates for realm %s: %s' % (realm, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain list of client templates for realm %s: %s' - % (realm, str(e))) + self.fail_request(e, msg='Could not obtain list of client templates for realm %s: %s' + % (realm, str(e))) def get_client_template_by_id(self, id, realm='master'): """ Obtain client template representation by id @@ -987,8 +987,8 @@ def get_client_template_by_id(self, id, realm='master'): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain client templates %s for realm %s: %s' % (id, realm, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain client template %s for realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not obtain client template %s for realm %s: %s' + % (id, realm, str(e))) def get_client_template_by_name(self, name, realm='master'): """ Obtain client template representation by name @@ -1029,8 +1029,8 @@ def update_client_template(self, id, clienttrep, realm="master"): try: return self._request(url, method='PUT', data=json.dumps(clienttrep)) except Exception as e: - self.fail_open_url(e, msg='Could not update client template %s in realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not update client template %s in realm %s: %s' + % (id, realm, str(e))) def create_client_template(self, clienttrep, realm="master"): """ Create a client in keycloak @@ -1043,8 +1043,8 @@ def create_client_template(self, clienttrep, realm="master"): try: return self._request(url, method='POST', data=json.dumps(clienttrep)) except Exception as e: - self.fail_open_url(e, msg='Could not create client template %s in realm %s: %s' - % (clienttrep['clientId'], realm, str(e))) + self.fail_request(e, msg='Could not create client template %s in realm %s: %s' + % (clienttrep['clientId'], realm, str(e))) def delete_client_template(self, id, realm="master"): """ Delete a client template from Keycloak @@ -1058,8 +1058,8 @@ def delete_client_template(self, id, realm="master"): try: return self._request(url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not delete client template %s in realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not delete client template %s in realm %s: %s' + % (id, realm, str(e))) def get_clientscopes(self, realm="master"): """ Fetch the name and ID of all clientscopes on the Keycloak server. @@ -1074,8 +1074,8 @@ def get_clientscopes(self, realm="master"): try: return self._request_and_deserialize(clientscopes_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch list of clientscopes in realm %s: %s" - % (realm, str(e))) + self.fail_request(e, msg="Could not fetch list of clientscopes in realm %s: %s" + % (realm, str(e))) def get_clientscope_by_clientscopeid(self, cid, realm="master"): """ Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID. @@ -1094,8 +1094,8 @@ def get_clientscope_by_clientscopeid(self, cid, realm="master"): if e.code == 404: return None else: - self.fail_open_url(e, msg="Could not fetch clientscope %s in realm %s: %s" - % (cid, realm, str(e))) + self.fail_request(e, msg="Could not fetch clientscope %s in realm %s: %s" + % (cid, realm, str(e))) except Exception as e: self.module.fail_json(msg="Could not clientscope group %s in realm %s: %s" % (cid, realm, str(e))) @@ -1134,8 +1134,8 @@ def create_clientscope(self, clientscoperep, realm="master"): try: return self._request(clientscopes_url, method='POST', data=json.dumps(clientscoperep)) except Exception as e: - self.fail_open_url(e, msg="Could not create clientscope %s in realm %s: %s" - % (clientscoperep['name'], realm, str(e))) + self.fail_request(e, msg="Could not create clientscope %s in realm %s: %s" + % (clientscoperep['name'], realm, str(e))) def update_clientscope(self, clientscoperep, realm="master"): """ Update an existing clientscope. @@ -1149,8 +1149,8 @@ def update_clientscope(self, clientscoperep, realm="master"): return self._request(clientscope_url, method='PUT', data=json.dumps(clientscoperep)) except Exception as e: - self.fail_open_url(e, msg='Could not update clientscope %s in realm %s: %s' - % (clientscoperep['name'], realm, str(e))) + self.fail_request(e, msg='Could not update clientscope %s in realm %s: %s' + % (clientscoperep['name'], realm, str(e))) def delete_clientscope(self, name=None, cid=None, realm="master"): """ Delete a clientscope. One of name or cid must be provided. @@ -1186,7 +1186,7 @@ def delete_clientscope(self, name=None, cid=None, realm="master"): return self._request(clientscope_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg="Unable to delete clientscope %s: %s" % (cid, str(e))) + self.fail_request(e, msg="Unable to delete clientscope %s: %s" % (cid, str(e))) def get_clientscope_protocolmappers(self, cid, realm="master"): """ Fetch the name and ID of all clientscopes on the Keycloak server. @@ -1202,8 +1202,8 @@ def get_clientscope_protocolmappers(self, cid, realm="master"): try: return self._request_and_deserialize(protocolmappers_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch list of protocolmappers in realm %s: %s" - % (realm, str(e))) + self.fail_request(e, msg="Could not fetch list of protocolmappers in realm %s: %s" + % (realm, str(e))) def get_clientscope_protocolmapper_by_protocolmapperid(self, pid, cid, realm="master"): """ Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID. @@ -1224,8 +1224,8 @@ def get_clientscope_protocolmapper_by_protocolmapperid(self, pid, cid, realm="ma if e.code == 404: return None else: - self.fail_open_url(e, msg="Could not fetch protocolmapper %s in realm %s: %s" - % (pid, realm, str(e))) + self.fail_request(e, msg="Could not fetch protocolmapper %s in realm %s: %s" + % (pid, realm, str(e))) except Exception as e: self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s" % (cid, realm, str(e))) @@ -1266,8 +1266,8 @@ def create_clientscope_protocolmapper(self, cid, mapper_rep, realm="master"): try: return self._request(protocolmappers_url, method='POST', data=json.dumps(mapper_rep)) except Exception as e: - self.fail_open_url(e, msg="Could not create protocolmapper %s in realm %s: %s" - % (mapper_rep['name'], realm, str(e))) + self.fail_request(e, msg="Could not create protocolmapper %s in realm %s: %s" + % (mapper_rep['name'], realm, str(e))) def update_clientscope_protocolmappers(self, cid, mapper_rep, realm="master"): """ Update an existing clientscope. @@ -1282,8 +1282,8 @@ def update_clientscope_protocolmappers(self, cid, mapper_rep, realm="master"): return self._request(protocolmapper_url, method='PUT', data=json.dumps(mapper_rep)) except Exception as e: - self.fail_open_url(e, msg='Could not update protocolmappers for clientscope %s in realm %s: %s' - % (mapper_rep, realm, str(e))) + self.fail_request(e, msg='Could not update protocolmappers for clientscope %s in realm %s: %s' + % (mapper_rep, realm, str(e))) def get_default_clientscopes(self, realm, client_id=None): """Fetch the name and ID of all clientscopes on the Keycloak server. @@ -1328,14 +1328,14 @@ def _get_clientscopes_of_type(self, realm, url_template, scope_type, client_id=N try: return self._request_and_deserialize(clientscopes_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch list of %s clientscopes in realm %s: %s" % (scope_type, realm, str(e))) + self.fail_request(e, msg="Could not fetch list of %s clientscopes in realm %s: %s" % (scope_type, realm, str(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) try: return self._request_and_deserialize(clientscopes_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch list of %s clientscopes in client %s: %s" % (scope_type, client_id, clientscopes_url)) + self.fail_request(e, msg="Could not fetch list of %s clientscopes in client %s: %s" % (scope_type, client_id, clientscopes_url)) def _decide_url_type_clientscope(self, client_id=None, scope_type="default"): """Decides which url to use. @@ -1405,7 +1405,7 @@ def _action_type_clientscope(self, id=None, client_id=None, scope_type="default" except Exception as e: place = 'realm' if client_id is None else 'client ' + client_id - self.fail_open_url(e, msg="Unable to %s %s clientscope %s @ %s : %s" % (action, scope_type, id, place, str(e))) + self.fail_request(e, msg="Unable to %s %s clientscope %s @ %s : %s" % (action, scope_type, id, place, str(e))) def create_clientsecret(self, id, realm="master"): """ Generate a new client secret by id @@ -1423,8 +1423,8 @@ def create_clientsecret(self, id, realm="master"): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not obtain clientsecret of client %s for realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not obtain clientsecret of client %s for realm %s: %s' + % (id, realm, str(e))) except Exception as e: self.module.fail_json(msg='Could not obtain clientsecret of client %s for realm %s: %s' % (id, realm, str(e))) @@ -1445,8 +1445,8 @@ def get_clientsecret(self, id, realm="master"): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not obtain clientsecret of client %s for realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not obtain clientsecret of client %s for realm %s: %s' + % (id, realm, str(e))) except Exception as e: self.module.fail_json(msg='Could not obtain clientsecret of client %s for realm %s: %s' % (id, realm, str(e))) @@ -1463,8 +1463,8 @@ def get_groups(self, realm="master"): try: return self._request_and_deserialize(groups_url, method="GET") except Exception as e: - self.fail_open_url(e, msg="Could not fetch list of groups in realm %s: %s" - % (realm, str(e))) + self.fail_request(e, msg="Could not fetch list of groups in realm %s: %s" + % (realm, str(e))) def get_group_by_groupid(self, gid, realm="master"): """ Fetch a keycloak group from the provided realm using the group's unique ID. @@ -1482,8 +1482,8 @@ def get_group_by_groupid(self, gid, realm="master"): if e.code == 404: return None else: - self.fail_open_url(e, msg="Could not fetch group %s in realm %s: %s" - % (gid, realm, str(e))) + self.fail_request(e, msg="Could not fetch group %s in realm %s: %s" + % (gid, realm, str(e))) except Exception as e: self.module.fail_json(msg="Could not fetch group %s in realm %s: %s" % (gid, realm, str(e))) @@ -1642,8 +1642,8 @@ def create_group(self, grouprep, realm="master"): try: return self._request(groups_url, method='POST', data=json.dumps(grouprep)) except Exception as e: - self.fail_open_url(e, msg="Could not create group %s in realm %s: %s" - % (grouprep['name'], realm, str(e))) + self.fail_request(e, msg="Could not create group %s in realm %s: %s" + % (grouprep['name'], realm, str(e))) def create_subgroup(self, parents, grouprep, realm="master"): """ Create a Keycloak subgroup. @@ -1669,8 +1669,8 @@ def create_subgroup(self, parents, grouprep, realm="master"): url = URL_GROUP_CHILDREN.format(url=self.baseurl, realm=realm, groupid=parent_id) return self._request(url, method='POST', data=json.dumps(grouprep)) except Exception as e: - self.fail_open_url(e, msg="Could not create subgroup %s for parent group %s in realm %s: %s" - % (grouprep['name'], parent_id, realm, str(e))) + self.fail_request(e, msg="Could not create subgroup %s for parent group %s in realm %s: %s" + % (grouprep['name'], parent_id, realm, str(e))) def update_group(self, grouprep, realm="master"): """ Update an existing group. @@ -1683,8 +1683,8 @@ def update_group(self, grouprep, realm="master"): try: return self._request(group_url, method='PUT', data=json.dumps(grouprep)) except Exception as e: - self.fail_open_url(e, msg='Could not update group %s in realm %s: %s' - % (grouprep['name'], realm, str(e))) + self.fail_request(e, msg='Could not update group %s in realm %s: %s' + % (grouprep['name'], realm, str(e))) def delete_group(self, name=None, groupid=None, realm="master"): """ Delete a group. One of name or groupid must be provided. @@ -1719,7 +1719,7 @@ def delete_group(self, name=None, groupid=None, realm="master"): try: return self._request(group_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg="Unable to delete group %s: %s" % (groupid, str(e))) + self.fail_request(e, msg="Unable to delete group %s: %s" % (groupid, str(e))) def get_realm_roles(self, realm='master'): """ Obtains role representations for roles in a realm @@ -1734,8 +1734,8 @@ def get_realm_roles(self, realm='master'): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for realm %s: %s' % (realm, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain list of roles for realm %s: %s' - % (realm, str(e))) + self.fail_request(e, msg='Could not obtain list of roles for realm %s: %s' + % (realm, str(e))) def get_realm_role(self, name, realm='master'): """ Fetch a keycloak role from the provided realm using the role's name. @@ -1751,8 +1751,8 @@ def get_realm_role(self, name, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not fetch role %s in realm %s: %s' - % (name, realm, str(e))) + self.fail_request(e, msg='Could not fetch role %s in realm %s: %s' + % (name, realm, str(e))) except Exception as e: self.module.fail_json(msg='Could not fetch role %s in realm %s: %s' % (name, realm, str(e))) @@ -1770,8 +1770,8 @@ def create_realm_role(self, rolerep, realm='master'): rolerep["composites"] = keycloak_compatible_composites return self._request(roles_url, method='POST', data=json.dumps(rolerep)) except Exception as e: - self.fail_open_url(e, msg='Could not create role %s in realm %s: %s' - % (rolerep['name'], realm, str(e))) + self.fail_request(e, msg='Could not create role %s in realm %s: %s' + % (rolerep['name'], realm, str(e))) def update_realm_role(self, rolerep, realm='master'): """ Update an existing realm role. @@ -1790,8 +1790,8 @@ def update_realm_role(self, rolerep, realm='master'): self.update_role_composites(rolerep=rolerep, composites=composites, realm=realm) return role_response except Exception as e: - self.fail_open_url(e, msg='Could not update role %s in realm %s: %s' - % (rolerep['name'], realm, str(e))) + self.fail_request(e, msg='Could not update role %s in realm %s: %s' + % (rolerep['name'], realm, str(e))) def get_role_composites(self, rolerep, clientid=None, realm='master'): composite_url = '' @@ -1805,8 +1805,8 @@ def get_role_composites(self, rolerep, clientid=None, realm='master'): # Get existing composites self._request_and_deserialize(composite_url, method='GET') except Exception as e: - self.fail_open_url(e, msg='Could not get role %s composites in realm %s: %s' - % (rolerep['name'], realm, str(e))) + self.fail_request(e, msg='Could not get role %s composites in realm %s: %s' + % (rolerep['name'], realm, str(e))) def create_role_composites(self, rolerep, composites, clientid=None, realm='master'): composite_url = '' @@ -1821,8 +1821,8 @@ def create_role_composites(self, rolerep, composites, clientid=None, realm='mast # create new composites return self._request(composite_url, method='POST', data=json.dumps(composites)) except Exception as e: - self.fail_open_url(e, msg='Could not create role %s composites in realm %s: %s' - % (rolerep['name'], realm, str(e))) + self.fail_request(e, msg='Could not create role %s composites in realm %s: %s' + % (rolerep['name'], realm, str(e))) def delete_role_composites(self, rolerep, composites, clientid=None, realm='master'): composite_url = '' @@ -1837,8 +1837,8 @@ def delete_role_composites(self, rolerep, composites, clientid=None, realm='mast # create new composites return self._request(composite_url, method='DELETE', data=json.dumps(composites)) except Exception as e: - self.fail_open_url(e, msg='Could not create role %s composites in realm %s: %s' - % (rolerep['name'], realm, str(e))) + self.fail_request(e, msg='Could not create role %s composites in realm %s: %s' + % (rolerep['name'], realm, str(e))) def update_role_composites(self, rolerep, composites, clientid=None, realm='master'): # Get existing composites @@ -1900,8 +1900,8 @@ def delete_realm_role(self, name, realm='master'): try: return self._request(role_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Unable to delete role %s in realm %s: %s' - % (name, realm, str(e))) + self.fail_request(e, msg='Unable to delete role %s in realm %s: %s' + % (name, realm, str(e))) def get_client_roles(self, clientid, realm='master'): """ Obtains role representations for client roles in a specific client @@ -1921,8 +1921,8 @@ def get_client_roles(self, clientid, realm='master'): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for client %s in realm %s: %s' % (clientid, realm, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain list of roles for client %s in realm %s: %s' - % (clientid, realm, str(e))) + self.fail_request(e, msg='Could not obtain list of roles for client %s in realm %s: %s' + % (clientid, realm, str(e))) def get_client_role(self, name, clientid, realm='master'): """ Fetch a keycloak client role from the provided realm using the role's name. @@ -1944,8 +1944,8 @@ def get_client_role(self, name, clientid, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not fetch role %s in client %s of realm %s: %s' - % (name, clientid, realm, str(e))) + self.fail_request(e, msg='Could not fetch role %s in client %s of realm %s: %s' + % (name, clientid, realm, str(e))) except Exception as e: self.module.fail_json(msg='Could not fetch role %s for client %s in realm %s: %s' % (name, clientid, realm, str(e))) @@ -1969,8 +1969,8 @@ def create_client_role(self, rolerep, clientid, realm='master'): rolerep["composites"] = keycloak_compatible_composites return self._request(roles_url, method='POST', data=json.dumps(rolerep)) except Exception as e: - self.fail_open_url(e, msg='Could not create role %s for client %s in realm %s: %s' - % (rolerep['name'], clientid, realm, str(e))) + self.fail_request(e, msg='Could not create role %s for client %s in realm %s: %s' + % (rolerep['name'], clientid, realm, str(e))) def convert_role_composites(self, composites): keycloak_compatible_composites = { @@ -2010,8 +2010,8 @@ def update_client_role(self, rolerep, clientid, realm="master"): self.update_role_composites(rolerep=rolerep, clientid=clientid, composites=composites, realm=realm) return update_role_response except Exception as e: - self.fail_open_url(e, msg='Could not update role %s for client %s in realm %s: %s' - % (rolerep['name'], clientid, realm, str(e))) + self.fail_request(e, msg='Could not update role %s for client %s in realm %s: %s' + % (rolerep['name'], clientid, realm, str(e))) def delete_client_role(self, name, clientid, realm="master"): """ Delete a role. One of name or roleid must be provided. @@ -2028,8 +2028,8 @@ def delete_client_role(self, name, clientid, realm="master"): try: return self._request(role_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Unable to delete role %s for client %s in realm %s: %s' - % (name, clientid, realm, str(e))) + self.fail_request(e, msg='Unable to delete role %s for client %s in realm %s: %s' + % (name, clientid, realm, str(e))) def get_authentication_flow_by_alias(self, alias, realm='master'): """ @@ -2048,7 +2048,7 @@ def get_authentication_flow_by_alias(self, alias, realm='master'): break return authentication_flow except Exception as e: - self.fail_open_url(e, msg="Unable get authentication flow %s: %s" % (alias, str(e))) + self.fail_request(e, msg="Unable get authentication flow %s: %s" % (alias, str(e))) def delete_authentication_flow_by_id(self, id, realm='master'): """ @@ -2062,8 +2062,8 @@ def delete_authentication_flow_by_id(self, id, realm='master'): try: return self._request(flow_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not delete authentication flow %s in realm %s: %s' - % (id, realm, str(e))) + self.fail_request(e, msg='Could not delete authentication flow %s in realm %s: %s' + % (id, realm, str(e))) def copy_auth_flow(self, config, realm='master'): """ @@ -2093,8 +2093,8 @@ def copy_auth_flow(self, config, realm='master'): return flow return None except Exception as e: - self.fail_open_url(e, msg='Could not copy authentication flow %s in realm %s: %s' - % (config["alias"], realm, str(e))) + self.fail_request(e, msg='Could not copy authentication flow %s in realm %s: %s' + % (config["alias"], realm, str(e))) def create_empty_auth_flow(self, config, realm='master'): """ @@ -2127,8 +2127,8 @@ def create_empty_auth_flow(self, config, realm='master'): return flow return None except Exception as e: - self.fail_open_url(e, msg='Could not create empty authentication flow %s in realm %s: %s' - % (config["alias"], realm, str(e))) + self.fail_request(e, msg='Could not create empty authentication flow %s in realm %s: %s' + % (config["alias"], realm, str(e))) def update_authentication_executions(self, flowAlias, updatedExec, realm='master'): """ Update authentication executions @@ -2146,8 +2146,8 @@ def update_authentication_executions(self, flowAlias, updatedExec, realm='master method='PUT', data=json.dumps(updatedExec)) except HTTPError as e: - self.fail_open_url(e, msg="Unable to update execution '%s': %s: %s %s" - % (flowAlias, repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(updatedExec))) + self.fail_request(e, msg="Unable to update execution '%s': %s: %s %s" + % (flowAlias, repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(updatedExec))) except Exception as e: self.module.fail_json(msg="Unable to update executions %s: %s" % (updatedExec, str(e))) @@ -2167,7 +2167,7 @@ def add_authenticationConfig_to_execution(self, executionId, authenticationConfi method='POST', data=json.dumps(authenticationConfig)) except Exception as e: - self.fail_open_url(e, msg="Unable to add authenticationConfig %s: %s" % (executionId, str(e))) + self.fail_request(e, msg="Unable to add authenticationConfig %s: %s" % (executionId, str(e))) def create_subflow(self, subflowName, flowAlias, realm='master', flowType='basic-flow'): """ Create new sublow on the flow @@ -2189,7 +2189,7 @@ def create_subflow(self, subflowName, flowAlias, realm='master', flowType='basic method='POST', data=json.dumps(newSubFlow)) except Exception as e: - self.fail_open_url(e, msg="Unable to create new subflow %s: %s" % (subflowName, str(e))) + self.fail_request(e, msg="Unable to create new subflow %s: %s" % (subflowName, str(e))) def create_execution(self, execution, flowAlias, realm='master'): """ Create new execution on the flow @@ -2210,8 +2210,8 @@ def create_execution(self, execution, flowAlias, realm='master'): method='POST', data=json.dumps(newExec)) except HTTPError as e: - self.fail_open_url(e, msg="Unable to create new execution '%s' %s: %s: %s %s" - % (flowAlias, execution["providerId"], repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(newExec))) + self.fail_request(e, msg="Unable to create new execution '%s' %s: %s: %s %s" + % (flowAlias, execution["providerId"], repr(e), ";".join([e.url, e.msg, str(e.code), str(e.hdrs)]), str(newExec))) except Exception as e: self.module.fail_json(msg="Unable to create new execution '%s' %s: %s" % (flowAlias, execution["providerId"], repr(e))) @@ -2241,7 +2241,7 @@ def change_execution_priority(self, executionId, diff, realm='master'): id=executionId), method='POST') except Exception as e: - self.fail_open_url(e, msg="Unable to change execution priority %s: %s" % (executionId, str(e))) + self.fail_request(e, msg="Unable to change execution priority %s: %s" % (executionId, str(e))) def get_executions_representation(self, config, realm='master'): """ @@ -2272,8 +2272,8 @@ def get_executions_representation(self, config, realm='master'): execution["authenticationConfig"] = execConfig return executions except Exception as e: - self.fail_open_url(e, msg='Could not get executions for authentication flow %s in realm %s: %s' - % (config["alias"], realm, str(e))) + self.fail_request(e, msg='Could not get executions for authentication flow %s in realm %s: %s' + % (config["alias"], realm, str(e))) def get_required_actions(self, realm='master'): """ @@ -2320,7 +2320,7 @@ def register_required_action(self, rep, realm='master'): data=json.dumps(data), ) except Exception as e: - self.fail_open_url( + self.fail_request( e, msg='Unable to register required action %s in realm %s: %s' % (rep["name"], realm, str(e)) @@ -2346,7 +2346,7 @@ def update_required_action(self, alias, rep, realm='master'): data=json.dumps(rep), ) except Exception as e: - self.fail_open_url( + self.fail_request( e, msg='Unable to update required action %s in realm %s: %s' % (alias, realm, str(e)) @@ -2370,7 +2370,7 @@ def delete_required_action(self, alias, realm='master'): method='DELETE', ) except Exception as e: - self.fail_open_url( + self.fail_request( e, msg='Unable to delete required action %s in realm %s: %s' % (alias, realm, str(e)) @@ -2388,8 +2388,8 @@ def get_identity_providers(self, realm='master'): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of identity providers for realm %s: %s' % (realm, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain list of identity providers for realm %s: %s' - % (realm, str(e))) + self.fail_request(e, msg='Could not obtain list of identity providers for realm %s: %s' + % (realm, str(e))) def get_identity_provider(self, alias, realm='master'): """ Fetch identity provider representation from a realm using the idp's alias. @@ -2404,8 +2404,8 @@ def get_identity_provider(self, alias, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not fetch identity provider %s in realm %s: %s' - % (alias, realm, str(e))) + self.fail_request(e, msg='Could not fetch identity provider %s in realm %s: %s' + % (alias, realm, str(e))) except Exception as e: self.module.fail_json(msg='Could not fetch identity provider %s in realm %s: %s' % (alias, realm, str(e))) @@ -2420,8 +2420,8 @@ def create_identity_provider(self, idprep, realm='master'): try: return self._request(idps_url, method='POST', data=json.dumps(idprep)) except Exception as e: - self.fail_open_url(e, msg='Could not create identity provider %s in realm %s: %s' - % (idprep['alias'], realm, str(e))) + self.fail_request(e, msg='Could not create identity provider %s in realm %s: %s' + % (idprep['alias'], realm, str(e))) def update_identity_provider(self, idprep, realm='master'): """ Update an existing identity provider. @@ -2433,8 +2433,8 @@ def update_identity_provider(self, idprep, realm='master'): try: return self._request(idp_url, method='PUT', data=json.dumps(idprep)) except Exception as e: - self.fail_open_url(e, msg='Could not update identity provider %s in realm %s: %s' - % (idprep['alias'], realm, str(e))) + self.fail_request(e, msg='Could not update identity provider %s in realm %s: %s' + % (idprep['alias'], realm, str(e))) def delete_identity_provider(self, alias, realm='master'): """ Delete an identity provider. @@ -2445,8 +2445,8 @@ def delete_identity_provider(self, alias, realm='master'): try: return self._request(idp_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Unable to delete identity provider %s in realm %s: %s' - % (alias, realm, str(e))) + self.fail_request(e, msg='Unable to delete identity provider %s in realm %s: %s' + % (alias, realm, str(e))) def get_identity_provider_mappers(self, alias, realm='master'): """ Fetch representations for identity provider mappers @@ -2461,8 +2461,8 @@ def get_identity_provider_mappers(self, alias, realm='master'): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of identity provider mappers for idp %s in realm %s: %s' % (alias, realm, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain list of identity provider mappers for idp %s in realm %s: %s' - % (alias, realm, str(e))) + self.fail_request(e, msg='Could not obtain list of identity provider mappers for idp %s in realm %s: %s' + % (alias, realm, str(e))) def get_identity_provider_mapper(self, mid, alias, realm='master'): """ Fetch identity provider representation from a realm using the idp's alias. @@ -2478,8 +2478,8 @@ def get_identity_provider_mapper(self, mid, alias, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not fetch mapper %s for identity provider %s in realm %s: %s' - % (mid, alias, realm, str(e))) + self.fail_request(e, msg='Could not fetch mapper %s for identity provider %s in realm %s: %s' + % (mid, alias, realm, str(e))) except Exception as e: self.module.fail_json(msg='Could not fetch mapper %s for identity provider %s in realm %s: %s' % (mid, alias, realm, str(e))) @@ -2495,8 +2495,8 @@ def create_identity_provider_mapper(self, mapper, alias, realm='master'): try: return self._request(mappers_url, method='POST', data=json.dumps(mapper)) except Exception as e: - self.fail_open_url(e, msg='Could not create identity provider mapper %s for idp %s in realm %s: %s' - % (mapper['name'], alias, realm, str(e))) + self.fail_request(e, msg='Could not create identity provider mapper %s for idp %s in realm %s: %s' + % (mapper['name'], alias, realm, str(e))) def update_identity_provider_mapper(self, mapper, alias, realm='master'): """ Update an existing identity provider. @@ -2509,8 +2509,8 @@ def update_identity_provider_mapper(self, mapper, alias, realm='master'): try: return self._request(mapper_url, method='PUT', data=json.dumps(mapper)) except Exception as e: - self.fail_open_url(e, msg='Could not update mapper %s for identity provider %s in realm %s: %s' - % (mapper['id'], alias, realm, str(e))) + self.fail_request(e, msg='Could not update mapper %s for identity provider %s in realm %s: %s' + % (mapper['id'], alias, realm, str(e))) def delete_identity_provider_mapper(self, mid, alias, realm='master'): """ Delete an identity provider. @@ -2522,8 +2522,8 @@ def delete_identity_provider_mapper(self, mid, alias, realm='master'): try: return self._request(mapper_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Unable to delete mapper %s for identity provider %s in realm %s: %s' - % (mid, alias, realm, str(e))) + self.fail_request(e, msg='Unable to delete mapper %s for identity provider %s in realm %s: %s' + % (mid, alias, realm, str(e))) def get_components(self, filter=None, realm='master'): """ Fetch representations for components in a realm @@ -2541,8 +2541,8 @@ def get_components(self, filter=None, realm='master'): self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of components for realm %s: %s' % (realm, str(e))) except Exception as e: - self.fail_open_url(e, msg='Could not obtain list of components for realm %s: %s' - % (realm, str(e))) + self.fail_request(e, msg='Could not obtain list of components for realm %s: %s' + % (realm, str(e))) def get_component(self, cid, realm='master'): """ Fetch component representation from a realm using its cid. @@ -2557,8 +2557,8 @@ def get_component(self, cid, realm='master'): if e.code == 404: return None else: - self.fail_open_url(e, msg='Could not fetch component %s in realm %s: %s' - % (cid, realm, str(e))) + self.fail_request(e, msg='Could not fetch component %s in realm %s: %s' + % (cid, realm, str(e))) except Exception as e: self.module.fail_json(msg='Could not fetch component %s in realm %s: %s' % (cid, realm, str(e))) @@ -2578,8 +2578,8 @@ def create_component(self, comprep, realm='master'): % (realm, 'unexpected response')) return self._request_and_deserialize(comp_url, method="GET") except Exception as e: - self.fail_open_url(e, msg='Could not create component in realm %s: %s' - % (realm, str(e))) + self.fail_request(e, msg='Could not create component in realm %s: %s' + % (realm, str(e))) def update_component(self, comprep, realm='master'): """ Update an existing component. @@ -2594,8 +2594,8 @@ def update_component(self, comprep, realm='master'): try: return self._request(comp_url, method='PUT', data=json.dumps(comprep)) except Exception as e: - self.fail_open_url(e, msg='Could not update component %s in realm %s: %s' - % (cid, realm, str(e))) + self.fail_request(e, msg='Could not update component %s in realm %s: %s' + % (cid, realm, str(e))) def delete_component(self, cid, realm='master'): """ Delete an component. @@ -2606,8 +2606,8 @@ def delete_component(self, cid, realm='master'): try: return self._request(comp_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Unable to delete component %s in realm %s: %s' - % (cid, realm, str(e))) + self.fail_request(e, msg='Unable to delete component %s in realm %s: %s' + % (cid, realm, str(e))) def get_authz_authorization_scope_by_name(self, name, client_id, realm): url = URL_AUTHZ_AUTHORIZATION_SCOPES.format(url=self.baseurl, client_id=client_id, realm=realm) @@ -2625,7 +2625,7 @@ def create_authz_authorization_scope(self, payload, client_id, realm): try: return self._request(url, method='POST', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not create authorization scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) + self.fail_request(e, msg='Could not create authorization scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) def update_authz_authorization_scope(self, payload, id, client_id, realm): """Update an authorization scope for a Keycloak client""" @@ -2634,7 +2634,7 @@ def update_authz_authorization_scope(self, payload, id, client_id, realm): try: return self._request(url, method='PUT', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not create update scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) + self.fail_request(e, msg='Could not create update scope %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) def remove_authz_authorization_scope(self, id, client_id, realm): """Remove an authorization scope from a Keycloak client""" @@ -2643,7 +2643,7 @@ def remove_authz_authorization_scope(self, id, client_id, realm): try: return self._request(url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not delete scope %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) + self.fail_request(e, msg='Could not delete scope %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) def get_user_by_id(self, user_id, realm='master'): """ @@ -2663,8 +2663,8 @@ def get_user_by_id(self, user_id, realm='master'): method='GET')) return userrep except Exception as e: - self.fail_open_url(e, msg='Could not get user %s in realm %s: %s' - % (user_id, realm, str(e))) + self.fail_request(e, msg='Could not get user %s in realm %s: %s' + % (user_id, realm, str(e))) def create_user(self, userrep, realm='master'): """ @@ -2688,8 +2688,8 @@ def create_user(self, userrep, realm='master'): realm=realm) return created_user except Exception as e: - self.fail_open_url(e, msg='Could not create user %s in realm %s: %s' - % (userrep['username'], realm, str(e))) + self.fail_request(e, msg='Could not create user %s in realm %s: %s' + % (userrep['username'], realm, str(e))) def convert_user_attributes_to_keycloak_dict(self, attributes): keycloak_user_attributes_dict = {} @@ -2731,8 +2731,8 @@ def update_user(self, userrep, realm='master'): realm=realm) return updated_user except Exception as e: - self.fail_open_url(e, msg='Could not update user %s in realm %s: %s' - % (userrep['username'], realm, str(e))) + self.fail_request(e, msg='Could not update user %s in realm %s: %s' + % (userrep['username'], realm, str(e))) def delete_user(self, user_id, realm='master'): """ @@ -2750,8 +2750,8 @@ def delete_user(self, user_id, realm='master'): user_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not delete user %s in realm %s: %s' - % (user_id, realm, str(e))) + self.fail_request(e, msg='Could not delete user %s in realm %s: %s' + % (user_id, realm, str(e))) def get_user_groups(self, user_id, realm='master'): """ @@ -2774,8 +2774,8 @@ def get_user_groups(self, user_id, realm='master'): groups.append(user_group["name"]) return groups except Exception as e: - self.fail_open_url(e, msg='Could not get groups for user %s in realm %s: %s' - % (user_id, realm, str(e))) + self.fail_request(e, msg='Could not get groups for user %s in realm %s: %s' + % (user_id, realm, str(e))) def add_user_in_group(self, user_id, group_id, realm='master'): """ @@ -2795,8 +2795,8 @@ def add_user_in_group(self, user_id, group_id, realm='master'): user_group_url, method='PUT') except Exception as e: - self.fail_open_url(e, msg='Could not add user %s in group %s in realm %s: %s' - % (user_id, group_id, realm, str(e))) + self.fail_request(e, msg='Could not add user %s in group %s in realm %s: %s' + % (user_id, group_id, realm, str(e))) def remove_user_from_group(self, user_id, group_id, realm='master'): """ @@ -2816,8 +2816,8 @@ def remove_user_from_group(self, user_id, group_id, realm='master'): user_group_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not remove user %s from group %s in realm %s: %s' - % (user_id, group_id, realm, str(e))) + self.fail_request(e, msg='Could not remove user %s from group %s in realm %s: %s' + % (user_id, group_id, realm, str(e))) def update_user_groups_membership(self, userrep, groups, realm='master'): """ @@ -2887,7 +2887,7 @@ def create_authz_custom_policy(self, policy_type, payload, client_id, realm): try: return self._request(url, method='POST', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) + self.fail_request(e, msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) def remove_authz_custom_policy(self, policy_id, client_id, realm): """Remove a custom policy from a Keycloak client""" @@ -2897,7 +2897,7 @@ def remove_authz_custom_policy(self, policy_id, client_id, realm): try: return self._request(delete_url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not delete custom policy %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) + self.fail_request(e, msg='Could not delete custom policy %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) def get_authz_permission_by_name(self, name, client_id, realm): """Get authorization permission by name""" @@ -2916,7 +2916,7 @@ def create_authz_permission(self, payload, permission_type, client_id, realm): try: return self._request(url, method='POST', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) + self.fail_request(e, msg='Could not create permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) def remove_authz_permission(self, id, client_id, realm): """Create an authorization permission for a Keycloak client""" @@ -2925,7 +2925,7 @@ def remove_authz_permission(self, id, client_id, realm): try: return self._request(url, method='DELETE') except Exception as e: - self.fail_open_url(e, msg='Could not delete permission %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) + self.fail_request(e, msg='Could not delete permission %s for client %s in realm %s: %s' % (id, client_id, realm, str(e))) def update_authz_permission(self, payload, permission_type, id, client_id, realm): """Update a permission for a Keycloak client""" @@ -2934,7 +2934,7 @@ def update_authz_permission(self, payload, permission_type, id, client_id, realm try: return self._request(url, method='PUT', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not create update permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) + self.fail_request(e, msg='Could not create update permission %s for client %s in realm %s: %s' % (payload['name'], client_id, realm, str(e))) def get_authz_resource_by_name(self, name, client_id, realm): """Get authorization resource by name""" @@ -2967,7 +2967,7 @@ def get_client_role_scope_from_client(self, clientid, clientscopeid, realm="mast try: return self._request_and_deserialize(client_role_scope_url) except Exception as e: - self.fail_open_url(e, msg='Could not fetch roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + self.fail_request(e, msg='Could not fetch roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) def update_client_role_scope_from_client(self, payload, clientid, clientscopeid, realm="master"): """ Update and fetch the roles associated with the client's scope on the Keycloak server. @@ -2982,7 +2982,7 @@ def update_client_role_scope_from_client(self, payload, clientid, clientscopeid, self._request(client_role_scope_url, method='POST', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not update roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + self.fail_request(e, msg='Could not update roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) return self.get_client_role_scope_from_client(clientid, clientscopeid, realm) @@ -2999,7 +2999,7 @@ def delete_client_role_scope_from_client(self, payload, clientid, clientscopeid, self._request(client_role_scope_url, method='DELETE', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not delete roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + self.fail_request(e, msg='Could not delete roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) return self.get_client_role_scope_from_client(clientid, clientscopeid, realm) @@ -3013,7 +3013,7 @@ def get_client_role_scope_from_realm(self, clientid, realm="master"): try: return self._request_and_deserialize(client_role_scope_url) except Exception as e: - self.fail_open_url(e, msg='Could not fetch roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + self.fail_request(e, msg='Could not fetch roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) def update_client_role_scope_from_realm(self, payload, clientid, realm="master"): """ Update and fetch the realm roles from the client's scope on the Keycloak server. @@ -3027,7 +3027,7 @@ def update_client_role_scope_from_realm(self, payload, clientid, realm="master") self._request(client_role_scope_url, method='POST', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not update roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + self.fail_request(e, msg='Could not update roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) return self.get_client_role_scope_from_realm(clientid, realm) @@ -3043,11 +3043,11 @@ def delete_client_role_scope_from_realm(self, payload, clientid, realm="master") self._request(client_role_scope_url, method='DELETE', data=json.dumps(payload)) except Exception as e: - self.fail_open_url(e, msg='Could not delete roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) + self.fail_request(e, msg='Could not delete roles scope for client %s in realm %s: %s' % (clientid, realm, str(e))) return self.get_client_role_scope_from_realm(clientid, realm) - def fail_open_url(self, e, msg, **kwargs): + def fail_request(self, e, msg, **kwargs): try: if isinstance(e, HTTPError): msg = "%s: %s" % (msg, to_native(e.read())) From 5b23f10b34de9029e5f5cb1e7bd9758569e9e22f Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Mon, 30 Dec 2024 18:21:35 -0500 Subject: [PATCH 16/30] chore: update all keycloak modules to support refresh token param [8857] --- plugins/modules/keycloak_authentication.py | 7 +++++-- .../modules/keycloak_authentication_required_actions.py | 6 +++++- plugins/modules/keycloak_authz_authorization_scope.py | 6 +++++- plugins/modules/keycloak_authz_custom_policy.py | 6 +++++- plugins/modules/keycloak_authz_permission.py | 6 +++++- plugins/modules/keycloak_authz_permission_info.py | 6 +++++- plugins/modules/keycloak_client.py | 6 +++++- plugins/modules/keycloak_client_rolemapping.py | 6 +++++- plugins/modules/keycloak_clientscope.py | 6 +++++- plugins/modules/keycloak_clientscope_type.py | 6 +++++- plugins/modules/keycloak_clienttemplate.py | 6 +++++- plugins/modules/keycloak_component.py | 6 +++++- plugins/modules/keycloak_group.py | 6 +++++- plugins/modules/keycloak_identity_provider.py | 6 +++++- plugins/modules/keycloak_realm.py | 6 +++++- plugins/modules/keycloak_realm_key.py | 6 +++++- plugins/modules/keycloak_realm_keys_metadata_info.py | 6 +++++- plugins/modules/keycloak_realm_rolemapping.py | 6 +++++- plugins/modules/keycloak_user.py | 6 +++++- plugins/modules/keycloak_user_federation.py | 6 +++++- plugins/modules/keycloak_user_rolemapping.py | 6 +++++- plugins/modules/keycloak_userprofile.py | 6 +++++- 22 files changed, 110 insertions(+), 23 deletions(-) diff --git a/plugins/modules/keycloak_authentication.py b/plugins/modules/keycloak_authentication.py index 58878c069dc..f32bbddb86b 100644 --- a/plugins/modules/keycloak_authentication.py +++ b/plugins/modules/keycloak_authentication.py @@ -359,8 +359,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']]) - ) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', flow={}) diff --git a/plugins/modules/keycloak_authentication_required_actions.py b/plugins/modules/keycloak_authentication_required_actions.py index 60b47d7a6a6..f545022f797 100644 --- a/plugins/modules/keycloak_authentication_required_actions.py +++ b/plugins/modules/keycloak_authentication_required_actions.py @@ -238,7 +238,11 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']]) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + } ) result = dict(changed=False, msg='', end_state={}, diff=dict(before={}, after={})) diff --git a/plugins/modules/keycloak_authz_authorization_scope.py b/plugins/modules/keycloak_authz_authorization_scope.py index cd1ff57afc9..c823d68ba2a 100644 --- a/plugins/modules/keycloak_authz_authorization_scope.py +++ b/plugins/modules/keycloak_authz_authorization_scope.py @@ -153,7 +153,11 @@ def main(): supports_check_mode=True, required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', end_state={}, diff=dict(before={}, after={})) diff --git a/plugins/modules/keycloak_authz_custom_policy.py b/plugins/modules/keycloak_authz_custom_policy.py index ef6c9b09737..985facdd9af 100644 --- a/plugins/modules/keycloak_authz_custom_policy.py +++ b/plugins/modules/keycloak_authz_custom_policy.py @@ -138,7 +138,11 @@ def main(): supports_check_mode=True, required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', end_state={}) diff --git a/plugins/modules/keycloak_authz_permission.py b/plugins/modules/keycloak_authz_permission.py index e4ab9fe14dd..60d60a59c42 100644 --- a/plugins/modules/keycloak_authz_permission.py +++ b/plugins/modules/keycloak_authz_permission.py @@ -252,7 +252,11 @@ def main(): supports_check_mode=True, required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) # Convenience variables state = module.params.get('state') diff --git a/plugins/modules/keycloak_authz_permission_info.py b/plugins/modules/keycloak_authz_permission_info.py index 6851abb311f..624a24c69fd 100644 --- a/plugins/modules/keycloak_authz_permission_info.py +++ b/plugins/modules/keycloak_authz_permission_info.py @@ -134,7 +134,11 @@ def main(): supports_check_mode=True, required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) # Convenience variables name = module.params.get('name') diff --git a/plugins/modules/keycloak_client.py b/plugins/modules/keycloak_client.py index 6b19711e3d0..b14850f6240 100644 --- a/plugins/modules/keycloak_client.py +++ b/plugins/modules/keycloak_client.py @@ -903,7 +903,11 @@ def main(): supports_check_mode=True, required_one_of=([['client_id', 'id'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_client_rolemapping.py b/plugins/modules/keycloak_client_rolemapping.py index 23dad803d7a..72167467909 100644 --- a/plugins/modules/keycloak_client_rolemapping.py +++ b/plugins/modules/keycloak_client_rolemapping.py @@ -265,7 +265,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_clientscope.py b/plugins/modules/keycloak_clientscope.py index ed82e0c0f76..3c665ad5911 100644 --- a/plugins/modules/keycloak_clientscope.py +++ b/plugins/modules/keycloak_clientscope.py @@ -353,7 +353,11 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_clientscope_type.py b/plugins/modules/keycloak_clientscope_type.py index ef4233fea47..624a545916d 100644 --- a/plugins/modules/keycloak_clientscope_type.py +++ b/plugins/modules/keycloak_clientscope_type.py @@ -147,7 +147,11 @@ def keycloak_clientscope_type_module(): ['token', 'auth_realm', 'auth_username', 'auth_password'], ['default_clientscopes', 'optional_clientscopes'] ]), - required_together=([['auth_realm', 'auth_username', 'auth_password']]), + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }, mutually_exclusive=[ ['token', 'auth_realm'], ['token', 'auth_username'], diff --git a/plugins/modules/keycloak_clienttemplate.py b/plugins/modules/keycloak_clienttemplate.py index bfd138c3f23..94b0e58ef7b 100644 --- a/plugins/modules/keycloak_clienttemplate.py +++ b/plugins/modules/keycloak_clienttemplate.py @@ -293,7 +293,11 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_component.py b/plugins/modules/keycloak_component.py index 2b402ddb623..52e227eb154 100644 --- a/plugins/modules/keycloak_component.py +++ b/plugins/modules/keycloak_component.py @@ -155,7 +155,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', end_state={}, diff=dict(before={}, after={})) diff --git a/plugins/modules/keycloak_group.py b/plugins/modules/keycloak_group.py index 796f5fc56fc..d52bbd89108 100644 --- a/plugins/modules/keycloak_group.py +++ b/plugins/modules/keycloak_group.py @@ -330,7 +330,11 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, group='') diff --git a/plugins/modules/keycloak_identity_provider.py b/plugins/modules/keycloak_identity_provider.py index ee631bf19cc..7e701d516d6 100644 --- a/plugins/modules/keycloak_identity_provider.py +++ b/plugins/modules/keycloak_identity_provider.py @@ -496,7 +496,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_realm.py b/plugins/modules/keycloak_realm.py index d2ae4f33c82..af857651d8c 100644 --- a/plugins/modules/keycloak_realm.py +++ b/plugins/modules/keycloak_realm.py @@ -705,7 +705,11 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'realm', 'enabled'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_realm_key.py b/plugins/modules/keycloak_realm_key.py index 0f7c5ae1143..1527315eaad 100644 --- a/plugins/modules/keycloak_realm_key.py +++ b/plugins/modules/keycloak_realm_key.py @@ -257,7 +257,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) # Initialize the result object. Only "changed" seems to have special # meaning for Ansible. diff --git a/plugins/modules/keycloak_realm_keys_metadata_info.py b/plugins/modules/keycloak_realm_keys_metadata_info.py index d116e3435b0..1d005464117 100644 --- a/plugins/modules/keycloak_realm_keys_metadata_info.py +++ b/plugins/modules/keycloak_realm_keys_metadata_info.py @@ -105,7 +105,11 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, required_one_of=([["token", "auth_realm", "auth_username", "auth_password"]]), - required_together=([["auth_realm", "auth_username", "auth_password"]]), + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + } ) result = dict(changed=False, msg="", keys_metadata="") diff --git a/plugins/modules/keycloak_realm_rolemapping.py b/plugins/modules/keycloak_realm_rolemapping.py index bed65057a49..15eefc8e02e 100644 --- a/plugins/modules/keycloak_realm_rolemapping.py +++ b/plugins/modules/keycloak_realm_rolemapping.py @@ -250,7 +250,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_user.py b/plugins/modules/keycloak_user.py index 65880548ab1..9dd2b0880dd 100644 --- a/plugins/modules/keycloak_user.py +++ b/plugins/modules/keycloak_user.py @@ -408,7 +408,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_user_federation.py b/plugins/modules/keycloak_user_federation.py index be8b75fc854..40375e0feb6 100644 --- a/plugins/modules/keycloak_user_federation.py +++ b/plugins/modules/keycloak_user_federation.py @@ -828,7 +828,11 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_user_rolemapping.py b/plugins/modules/keycloak_user_rolemapping.py index 319aa5350bd..a9380eeff4e 100644 --- a/plugins/modules/keycloak_user_rolemapping.py +++ b/plugins/modules/keycloak_user_rolemapping.py @@ -238,7 +238,11 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password'], ['uid', 'target_username', 'service_account_user_client_id']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_userprofile.py b/plugins/modules/keycloak_userprofile.py index 49b52c4521a..624b13d83aa 100644 --- a/plugins/modules/keycloak_userprofile.py +++ b/plugins/modules/keycloak_userprofile.py @@ -533,7 +533,11 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_realm', 'auth_username', 'auth_password']])) + required_together=([['auth_username', 'auth_password']]), + required_by={ + 'auth_username': 'auth_realm', + 'refresh_token': 'auth_realm', + }) # Initialize the result object. Only "changed" seems to have special # meaning for Ansible. From a58ea2b455e55b0fff1254d41337a362befc38f9 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Mon, 30 Dec 2024 21:07:27 -0500 Subject: [PATCH 17/30] chore: add refresh_token param to keycloak doc_fragments [8857] --- plugins/doc_fragments/keycloak.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/doc_fragments/keycloak.py b/plugins/doc_fragments/keycloak.py index 102a60ab33c..d4140fe6780 100644 --- a/plugins/doc_fragments/keycloak.py +++ b/plugins/doc_fragments/keycloak.py @@ -57,6 +57,12 @@ class ModuleDocFragment(object): type: str version_added: 3.0.0 + refresh_token: + description: + - Authentication refresh token for Keycloak API. + type: str + version_added: 10.2.0 + validate_certs: description: - Verify TLS certificates (do not disable this in production). From 85296f9c29f441765ad15aaacb0b90cdba869692 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Mon, 30 Dec 2024 21:37:56 -0500 Subject: [PATCH 18/30] chore: restore dependency between auth_realm and auth_username,auth_password params [8857] --- plugins/modules/keycloak_authentication.py | 3 +-- plugins/modules/keycloak_authentication_required_actions.py | 3 +-- plugins/modules/keycloak_authz_authorization_scope.py | 3 +-- plugins/modules/keycloak_authz_custom_policy.py | 3 +-- plugins/modules/keycloak_authz_permission.py | 3 +-- plugins/modules/keycloak_authz_permission_info.py | 3 +-- plugins/modules/keycloak_client.py | 3 +-- plugins/modules/keycloak_client_rolemapping.py | 3 +-- plugins/modules/keycloak_clientscope.py | 3 +-- plugins/modules/keycloak_clientscope_type.py | 3 +-- plugins/modules/keycloak_clienttemplate.py | 3 +-- plugins/modules/keycloak_component.py | 3 +-- plugins/modules/keycloak_group.py | 3 +-- plugins/modules/keycloak_identity_provider.py | 3 +-- plugins/modules/keycloak_realm.py | 3 +-- plugins/modules/keycloak_realm_key.py | 3 +-- plugins/modules/keycloak_realm_keys_metadata_info.py | 3 +-- plugins/modules/keycloak_realm_rolemapping.py | 3 +-- plugins/modules/keycloak_role.py | 3 +-- plugins/modules/keycloak_user.py | 3 +-- plugins/modules/keycloak_user_federation.py | 3 +-- plugins/modules/keycloak_user_rolemapping.py | 3 +-- plugins/modules/keycloak_userprofile.py | 3 +-- 23 files changed, 23 insertions(+), 46 deletions(-) diff --git a/plugins/modules/keycloak_authentication.py b/plugins/modules/keycloak_authentication.py index f32bbddb86b..d31e95c8b9d 100644 --- a/plugins/modules/keycloak_authentication.py +++ b/plugins/modules/keycloak_authentication.py @@ -359,9 +359,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_authentication_required_actions.py b/plugins/modules/keycloak_authentication_required_actions.py index f545022f797..fa29b9718e8 100644 --- a/plugins/modules/keycloak_authentication_required_actions.py +++ b/plugins/modules/keycloak_authentication_required_actions.py @@ -238,9 +238,8 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', } ) diff --git a/plugins/modules/keycloak_authz_authorization_scope.py b/plugins/modules/keycloak_authz_authorization_scope.py index c823d68ba2a..0fac3fda309 100644 --- a/plugins/modules/keycloak_authz_authorization_scope.py +++ b/plugins/modules/keycloak_authz_authorization_scope.py @@ -153,9 +153,8 @@ def main(): supports_check_mode=True, required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_authz_custom_policy.py b/plugins/modules/keycloak_authz_custom_policy.py index 985facdd9af..01005fa43d2 100644 --- a/plugins/modules/keycloak_authz_custom_policy.py +++ b/plugins/modules/keycloak_authz_custom_policy.py @@ -138,9 +138,8 @@ def main(): supports_check_mode=True, required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_authz_permission.py b/plugins/modules/keycloak_authz_permission.py index 60d60a59c42..f62e4ee4d62 100644 --- a/plugins/modules/keycloak_authz_permission.py +++ b/plugins/modules/keycloak_authz_permission.py @@ -252,9 +252,8 @@ def main(): supports_check_mode=True, required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_authz_permission_info.py b/plugins/modules/keycloak_authz_permission_info.py index 624a24c69fd..05ef6b909e6 100644 --- a/plugins/modules/keycloak_authz_permission_info.py +++ b/plugins/modules/keycloak_authz_permission_info.py @@ -134,9 +134,8 @@ def main(): supports_check_mode=True, required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_client.py b/plugins/modules/keycloak_client.py index b14850f6240..fa1e69f645d 100644 --- a/plugins/modules/keycloak_client.py +++ b/plugins/modules/keycloak_client.py @@ -903,9 +903,8 @@ def main(): supports_check_mode=True, required_one_of=([['client_id', 'id'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_client_rolemapping.py b/plugins/modules/keycloak_client_rolemapping.py index 72167467909..1697cfbfca8 100644 --- a/plugins/modules/keycloak_client_rolemapping.py +++ b/plugins/modules/keycloak_client_rolemapping.py @@ -265,9 +265,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_clientscope.py b/plugins/modules/keycloak_clientscope.py index 3c665ad5911..590ed13373c 100644 --- a/plugins/modules/keycloak_clientscope.py +++ b/plugins/modules/keycloak_clientscope.py @@ -353,9 +353,8 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_clientscope_type.py b/plugins/modules/keycloak_clientscope_type.py index 624a545916d..e6345316900 100644 --- a/plugins/modules/keycloak_clientscope_type.py +++ b/plugins/modules/keycloak_clientscope_type.py @@ -147,9 +147,8 @@ def keycloak_clientscope_type_module(): ['token', 'auth_realm', 'auth_username', 'auth_password'], ['default_clientscopes', 'optional_clientscopes'] ]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }, mutually_exclusive=[ diff --git a/plugins/modules/keycloak_clienttemplate.py b/plugins/modules/keycloak_clienttemplate.py index 94b0e58ef7b..e41ff9b1a18 100644 --- a/plugins/modules/keycloak_clienttemplate.py +++ b/plugins/modules/keycloak_clienttemplate.py @@ -293,9 +293,8 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_component.py b/plugins/modules/keycloak_component.py index 52e227eb154..b7386e65fd9 100644 --- a/plugins/modules/keycloak_component.py +++ b/plugins/modules/keycloak_component.py @@ -155,9 +155,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_group.py b/plugins/modules/keycloak_group.py index d52bbd89108..00edfd8d858 100644 --- a/plugins/modules/keycloak_group.py +++ b/plugins/modules/keycloak_group.py @@ -330,9 +330,8 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_identity_provider.py b/plugins/modules/keycloak_identity_provider.py index 7e701d516d6..09694ea6046 100644 --- a/plugins/modules/keycloak_identity_provider.py +++ b/plugins/modules/keycloak_identity_provider.py @@ -496,9 +496,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_realm.py b/plugins/modules/keycloak_realm.py index af857651d8c..cfad704e095 100644 --- a/plugins/modules/keycloak_realm.py +++ b/plugins/modules/keycloak_realm.py @@ -705,9 +705,8 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'realm', 'enabled'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_realm_key.py b/plugins/modules/keycloak_realm_key.py index 1527315eaad..0361a3968a5 100644 --- a/plugins/modules/keycloak_realm_key.py +++ b/plugins/modules/keycloak_realm_key.py @@ -257,9 +257,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_realm_keys_metadata_info.py b/plugins/modules/keycloak_realm_keys_metadata_info.py index 1d005464117..4425ae45ab9 100644 --- a/plugins/modules/keycloak_realm_keys_metadata_info.py +++ b/plugins/modules/keycloak_realm_keys_metadata_info.py @@ -105,9 +105,8 @@ def main(): argument_spec=argument_spec, supports_check_mode=True, required_one_of=([["token", "auth_realm", "auth_username", "auth_password"]]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', } ) diff --git a/plugins/modules/keycloak_realm_rolemapping.py b/plugins/modules/keycloak_realm_rolemapping.py index 15eefc8e02e..e4e7056fc01 100644 --- a/plugins/modules/keycloak_realm_rolemapping.py +++ b/plugins/modules/keycloak_realm_rolemapping.py @@ -250,9 +250,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_role.py b/plugins/modules/keycloak_role.py index b8522395a5c..914cb40aa60 100644 --- a/plugins/modules/keycloak_role.py +++ b/plugins/modules/keycloak_role.py @@ -246,9 +246,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_user.py b/plugins/modules/keycloak_user.py index 9dd2b0880dd..418be78bf62 100644 --- a/plugins/modules/keycloak_user.py +++ b/plugins/modules/keycloak_user.py @@ -408,9 +408,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_user_federation.py b/plugins/modules/keycloak_user_federation.py index 40375e0feb6..33dca764c62 100644 --- a/plugins/modules/keycloak_user_federation.py +++ b/plugins/modules/keycloak_user_federation.py @@ -828,9 +828,8 @@ def main(): supports_check_mode=True, required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_user_rolemapping.py b/plugins/modules/keycloak_user_rolemapping.py index a9380eeff4e..e13ba073bac 100644 --- a/plugins/modules/keycloak_user_rolemapping.py +++ b/plugins/modules/keycloak_user_rolemapping.py @@ -238,9 +238,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password'], ['uid', 'target_username', 'service_account_user_client_id']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) diff --git a/plugins/modules/keycloak_userprofile.py b/plugins/modules/keycloak_userprofile.py index 624b13d83aa..3b77ebe324a 100644 --- a/plugins/modules/keycloak_userprofile.py +++ b/plugins/modules/keycloak_userprofile.py @@ -533,9 +533,8 @@ def main(): module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), - required_together=([['auth_username', 'auth_password']]), + required_together=([['auth_realm', 'auth_username', 'auth_password']]), required_by={ - 'auth_username': 'auth_realm', 'refresh_token': 'auth_realm', }) From 42dfe5deca4a5cbccc8ebf2cb11577665d051682 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Tue, 31 Dec 2024 16:33:52 -0500 Subject: [PATCH 19/30] chore: rearrange module param checks to reduce future pr size [8857] --- plugins/modules/keycloak_authentication.py | 5 ++--- .../modules/keycloak_authentication_required_actions.py | 4 +--- plugins/modules/keycloak_authz_authorization_scope.py | 5 ++--- plugins/modules/keycloak_authz_custom_policy.py | 5 ++--- plugins/modules/keycloak_authz_permission.py | 5 ++--- plugins/modules/keycloak_authz_permission_info.py | 5 ++--- plugins/modules/keycloak_client.py | 5 ++--- plugins/modules/keycloak_client_rolemapping.py | 5 ++--- plugins/modules/keycloak_clientscope.py | 5 ++--- plugins/modules/keycloak_clientscope_type.py | 7 +++---- plugins/modules/keycloak_clienttemplate.py | 5 ++--- plugins/modules/keycloak_component.py | 5 ++--- plugins/modules/keycloak_group.py | 5 ++--- plugins/modules/keycloak_identity_provider.py | 5 ++--- plugins/modules/keycloak_realm.py | 5 ++--- plugins/modules/keycloak_realm_key.py | 5 ++--- plugins/modules/keycloak_realm_keys_metadata_info.py | 4 +--- plugins/modules/keycloak_realm_rolemapping.py | 5 ++--- plugins/modules/keycloak_role.py | 5 ++--- plugins/modules/keycloak_user.py | 5 ++--- plugins/modules/keycloak_user_federation.py | 5 ++--- plugins/modules/keycloak_user_rolemapping.py | 5 ++--- plugins/modules/keycloak_userprofile.py | 5 ++--- 23 files changed, 45 insertions(+), 70 deletions(-) diff --git a/plugins/modules/keycloak_authentication.py b/plugins/modules/keycloak_authentication.py index d31e95c8b9d..a117c730e69 100644 --- a/plugins/modules/keycloak_authentication.py +++ b/plugins/modules/keycloak_authentication.py @@ -360,9 +360,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', flow={}) diff --git a/plugins/modules/keycloak_authentication_required_actions.py b/plugins/modules/keycloak_authentication_required_actions.py index fa29b9718e8..147acf9a1eb 100644 --- a/plugins/modules/keycloak_authentication_required_actions.py +++ b/plugins/modules/keycloak_authentication_required_actions.py @@ -239,9 +239,7 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - } + required_by={'refresh_token': 'auth_realm'}, ) result = dict(changed=False, msg='', end_state={}, diff=dict(before={}, after={})) diff --git a/plugins/modules/keycloak_authz_authorization_scope.py b/plugins/modules/keycloak_authz_authorization_scope.py index 0fac3fda309..b2a4c3703d9 100644 --- a/plugins/modules/keycloak_authz_authorization_scope.py +++ b/plugins/modules/keycloak_authz_authorization_scope.py @@ -154,9 +154,8 @@ def main(): required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', end_state={}, diff=dict(before={}, after={})) diff --git a/plugins/modules/keycloak_authz_custom_policy.py b/plugins/modules/keycloak_authz_custom_policy.py index 01005fa43d2..643518caec3 100644 --- a/plugins/modules/keycloak_authz_custom_policy.py +++ b/plugins/modules/keycloak_authz_custom_policy.py @@ -139,9 +139,8 @@ def main(): required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', end_state={}) diff --git a/plugins/modules/keycloak_authz_permission.py b/plugins/modules/keycloak_authz_permission.py index f62e4ee4d62..aea5bec5533 100644 --- a/plugins/modules/keycloak_authz_permission.py +++ b/plugins/modules/keycloak_authz_permission.py @@ -253,9 +253,8 @@ def main(): required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) # Convenience variables state = module.params.get('state') diff --git a/plugins/modules/keycloak_authz_permission_info.py b/plugins/modules/keycloak_authz_permission_info.py index 05ef6b909e6..bc301b5dec3 100644 --- a/plugins/modules/keycloak_authz_permission_info.py +++ b/plugins/modules/keycloak_authz_permission_info.py @@ -135,9 +135,8 @@ def main(): required_one_of=( [['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) # Convenience variables name = module.params.get('name') diff --git a/plugins/modules/keycloak_client.py b/plugins/modules/keycloak_client.py index fa1e69f645d..ec183b82276 100644 --- a/plugins/modules/keycloak_client.py +++ b/plugins/modules/keycloak_client.py @@ -904,9 +904,8 @@ def main(): required_one_of=([['client_id', 'id'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_client_rolemapping.py b/plugins/modules/keycloak_client_rolemapping.py index 1697cfbfca8..6e569d0d86e 100644 --- a/plugins/modules/keycloak_client_rolemapping.py +++ b/plugins/modules/keycloak_client_rolemapping.py @@ -266,9 +266,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_clientscope.py b/plugins/modules/keycloak_clientscope.py index 590ed13373c..06dd81e85f7 100644 --- a/plugins/modules/keycloak_clientscope.py +++ b/plugins/modules/keycloak_clientscope.py @@ -354,9 +354,8 @@ def main(): required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_clientscope_type.py b/plugins/modules/keycloak_clientscope_type.py index e6345316900..48b890e5364 100644 --- a/plugins/modules/keycloak_clientscope_type.py +++ b/plugins/modules/keycloak_clientscope_type.py @@ -148,14 +148,13 @@ def keycloak_clientscope_type_module(): ['default_clientscopes', 'optional_clientscopes'] ]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }, + required_by={'refresh_token': 'auth_realm'}, mutually_exclusive=[ ['token', 'auth_realm'], ['token', 'auth_username'], ['token', 'auth_password'] - ]) + ], + ) return module diff --git a/plugins/modules/keycloak_clienttemplate.py b/plugins/modules/keycloak_clienttemplate.py index e41ff9b1a18..21e9453bb27 100644 --- a/plugins/modules/keycloak_clienttemplate.py +++ b/plugins/modules/keycloak_clienttemplate.py @@ -294,9 +294,8 @@ def main(): required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_component.py b/plugins/modules/keycloak_component.py index b7386e65fd9..995b174ccfe 100644 --- a/plugins/modules/keycloak_component.py +++ b/plugins/modules/keycloak_component.py @@ -156,9 +156,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', end_state={}, diff=dict(before={}, after={})) diff --git a/plugins/modules/keycloak_group.py b/plugins/modules/keycloak_group.py index 00edfd8d858..b232c4d7112 100644 --- a/plugins/modules/keycloak_group.py +++ b/plugins/modules/keycloak_group.py @@ -331,9 +331,8 @@ def main(): required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, group='') diff --git a/plugins/modules/keycloak_identity_provider.py b/plugins/modules/keycloak_identity_provider.py index 09694ea6046..9e046690548 100644 --- a/plugins/modules/keycloak_identity_provider.py +++ b/plugins/modules/keycloak_identity_provider.py @@ -497,9 +497,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_realm.py b/plugins/modules/keycloak_realm.py index cfad704e095..d57fd832aa9 100644 --- a/plugins/modules/keycloak_realm.py +++ b/plugins/modules/keycloak_realm.py @@ -706,9 +706,8 @@ def main(): required_one_of=([['id', 'realm', 'enabled'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_realm_key.py b/plugins/modules/keycloak_realm_key.py index 0361a3968a5..a5e855b5b52 100644 --- a/plugins/modules/keycloak_realm_key.py +++ b/plugins/modules/keycloak_realm_key.py @@ -258,9 +258,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm',}, + ) # Initialize the result object. Only "changed" seems to have special # meaning for Ansible. diff --git a/plugins/modules/keycloak_realm_keys_metadata_info.py b/plugins/modules/keycloak_realm_keys_metadata_info.py index 4425ae45ab9..97d5f19301a 100644 --- a/plugins/modules/keycloak_realm_keys_metadata_info.py +++ b/plugins/modules/keycloak_realm_keys_metadata_info.py @@ -106,9 +106,7 @@ def main(): supports_check_mode=True, required_one_of=([["token", "auth_realm", "auth_username", "auth_password"]]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - } + required_by={'refresh_token': 'auth_realm'}, ) result = dict(changed=False, msg="", keys_metadata="") diff --git a/plugins/modules/keycloak_realm_rolemapping.py b/plugins/modules/keycloak_realm_rolemapping.py index e4e7056fc01..9e48bd5f3e0 100644 --- a/plugins/modules/keycloak_realm_rolemapping.py +++ b/plugins/modules/keycloak_realm_rolemapping.py @@ -251,9 +251,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_role.py b/plugins/modules/keycloak_role.py index 914cb40aa60..4c71181684e 100644 --- a/plugins/modules/keycloak_role.py +++ b/plugins/modules/keycloak_role.py @@ -247,9 +247,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_user.py b/plugins/modules/keycloak_user.py index 418be78bf62..9c2c110903a 100644 --- a/plugins/modules/keycloak_user.py +++ b/plugins/modules/keycloak_user.py @@ -409,9 +409,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_user_federation.py b/plugins/modules/keycloak_user_federation.py index 33dca764c62..599945150bb 100644 --- a/plugins/modules/keycloak_user_federation.py +++ b/plugins/modules/keycloak_user_federation.py @@ -829,9 +829,8 @@ def main(): required_one_of=([['id', 'name'], ['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_user_rolemapping.py b/plugins/modules/keycloak_user_rolemapping.py index e13ba073bac..621b8b584d2 100644 --- a/plugins/modules/keycloak_user_rolemapping.py +++ b/plugins/modules/keycloak_user_rolemapping.py @@ -239,9 +239,8 @@ def main(): required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password'], ['uid', 'target_username', 'service_account_user_client_id']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={}) diff --git a/plugins/modules/keycloak_userprofile.py b/plugins/modules/keycloak_userprofile.py index 3b77ebe324a..2273b862f58 100644 --- a/plugins/modules/keycloak_userprofile.py +++ b/plugins/modules/keycloak_userprofile.py @@ -534,9 +534,8 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={ - 'refresh_token': 'auth_realm', - }) + required_by={'refresh_token': 'auth_realm'}, + ) # Initialize the result object. Only "changed" seems to have special # meaning for Ansible. From c7a92078477e48980b480d1123fb597f0b58a3ae Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Tue, 31 Dec 2024 16:50:31 -0500 Subject: [PATCH 20/30] chore: remove extra comma [8857] --- plugins/modules/keycloak_realm_key.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/keycloak_realm_key.py b/plugins/modules/keycloak_realm_key.py index a5e855b5b52..156678ba222 100644 --- a/plugins/modules/keycloak_realm_key.py +++ b/plugins/modules/keycloak_realm_key.py @@ -258,7 +258,7 @@ def main(): supports_check_mode=True, required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]), required_together=([['auth_realm', 'auth_username', 'auth_password']]), - required_by={'refresh_token': 'auth_realm',}, + required_by={'refresh_token': 'auth_realm'}, ) # Initialize the result object. Only "changed" seems to have special From 28fc38154a604c8c922e4e3324444b1c04a4597a Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Tue, 31 Dec 2024 23:01:48 -0500 Subject: [PATCH 21/30] chore: update version added for refresh token param [8857] --- plugins/doc_fragments/keycloak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/doc_fragments/keycloak.py b/plugins/doc_fragments/keycloak.py index d4140fe6780..75c458d5fcd 100644 --- a/plugins/doc_fragments/keycloak.py +++ b/plugins/doc_fragments/keycloak.py @@ -61,7 +61,7 @@ class ModuleDocFragment(object): description: - Authentication refresh token for Keycloak API. type: str - version_added: 10.2.0 + version_added: 10.3.0 validate_certs: description: From 24dbd85fb82384b695aa3a5a00a15d47fe33178c Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Fri, 3 Jan 2025 15:39:11 -0500 Subject: [PATCH 22/30] chore: add changelog fragment [8857] --- ...-keycloak-modules-retry-request-on-authentication-error.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelogs/fragments/9494-keycloak-modules-retry-request-on-authentication-error.yaml diff --git a/changelogs/fragments/9494-keycloak-modules-retry-request-on-authentication-error.yaml b/changelogs/fragments/9494-keycloak-modules-retry-request-on-authentication-error.yaml new file mode 100644 index 00000000000..1e4df6dc76a --- /dev/null +++ b/changelogs/fragments/9494-keycloak-modules-retry-request-on-authentication-error.yaml @@ -0,0 +1,2 @@ +major_changes: + - keycloak_* modules - ``refresh_token`` parameter added. When multiple authentication parameters are provided (``token``, ``refresh_token``, and ``auth_username``/``auth_password``), modules will now automatically retry requests upon authentication errors (401), using in order the token, refresh token, and username/password (https://github.com/ansible-collections/community.general/pull/9494). From 3ebc7571b73a6f4f76f8910086591b9492c12465 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Fri, 3 Jan 2025 16:15:15 -0500 Subject: [PATCH 23/30] chore: re-add fail_open_url to keycloak module utils for backward compatability [8857] --- plugins/module_utils/identity/keycloak/keycloak.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 052e2bb1adf..dad3ab16bcf 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -3054,3 +3054,6 @@ def fail_request(self, e, msg, **kwargs): except Exception: pass self.module.fail_json(msg, **kwargs) + + def fail_open_url(self, e, msg, **kwargs): + return self.fail_request(e, msg, **kwargs) From ef79f26df00f6dd8c803495ac0a6eb1f493f3c02 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Wed, 22 Jan 2025 20:07:01 -0500 Subject: [PATCH 24/30] fix: do not make a new request to keycloak without reauth when refresh token not provided (#8857) --- plugins/module_utils/identity/keycloak/keycloak.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index dad3ab16bcf..e7d547355a3 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -334,9 +334,9 @@ def make_request_ignoring_401(): token = _get_token_using_refresh_token(self.module.params) self.restheaders['Authorization'] = 'Bearer ' + token - r = make_request_ignoring_401() - if r is not None: - return r + r = make_request_ignoring_401() + if r is not None: + return r # Retry once more with username and password auth_username = self.module.params.get('auth_username') From ab26910c2323a8bcb69822ab31488d78dc6f596c Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Wed, 22 Jan 2025 20:15:38 -0500 Subject: [PATCH 25/30] fix: only make final auth attempt if username/pass provided, and return exception on failure (#8857) --- .../module_utils/identity/keycloak/keycloak.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index e7d547355a3..c0bb5700dcd 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -312,7 +312,7 @@ def __init__(self, module, connection_header): self.http_agent = self.module.params.get('http_agent') def _request(self, url, method, data=None): - def make_request_ignoring_401(): + def make_request_catching_401(): try: return open_url(url, method=method, data=data, http_agent=self.http_agent, headers=self.restheaders, @@ -321,11 +321,10 @@ def make_request_ignoring_401(): except HTTPError as e: if e.code != 401: raise e + return e - return None - - r = make_request_ignoring_401() - if r is not None: + r = make_request_catching_401() + if not isinstance(r, Exception): return r # Authentication may have expired, re-authenticate with refresh token and retry @@ -334,8 +333,8 @@ def make_request_ignoring_401(): token = _get_token_using_refresh_token(self.module.params) self.restheaders['Authorization'] = 'Bearer ' + token - r = make_request_ignoring_401() - if r is not None: + r = make_request_catching_401() + if not isinstance(r, Exception): return r # Retry once more with username and password @@ -345,7 +344,9 @@ def make_request_ignoring_401(): token = _get_token_using_credentials(self.module.params) self.restheaders['Authorization'] = 'Bearer ' + token - return make_request_ignoring_401() + r = make_request_catching_401() + + return r def _request_and_deserialize(self, url, method, data=None): return json.loads(to_native(self._request(url, method, data).read())) From e62e04ba66aa8a9f4ad1af964455969945878623 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Wed, 22 Jan 2025 21:47:01 -0500 Subject: [PATCH 26/30] fix: make re-auth and retry code more consistent, ensure final exceptions are thrown (#8857) --- .../identity/keycloak/keycloak.py | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index c0bb5700dcd..567113a2240 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -324,27 +324,29 @@ def make_request_catching_401(): return e r = make_request_catching_401() - if not isinstance(r, Exception): - return r - - # Authentication may have expired, re-authenticate with refresh token and retry - refresh_token = self.module.params.get('refresh_token') - if refresh_token is not None: - token = _get_token_using_refresh_token(self.module.params) - self.restheaders['Authorization'] = 'Bearer ' + token - - r = make_request_catching_401() - if not isinstance(r, Exception): - return r - - # Retry once more with username and password - auth_username = self.module.params.get('auth_username') - auth_password = self.module.params.get('auth_password') - if auth_username is not None and auth_password is not None: - token = _get_token_using_credentials(self.module.params) - self.restheaders['Authorization'] = 'Bearer ' + token - - r = make_request_catching_401() + + if isinstance(r, Exception): + # Try to refresh token and retry, if available + refresh_token = self.module.params.get('refresh_token') + if refresh_token is not None: + token = _get_token_using_refresh_token(self.module.params) + self.restheaders['Authorization'] = 'Bearer ' + token + + r = make_request_catching_401() + + if isinstance(r, Exception): + # Try to re-auth with username/password, if available + auth_username = self.module.params.get('auth_username') + auth_password = self.module.params.get('auth_password') + if auth_username is not None and auth_password is not None: + token = _get_token_using_credentials(self.module.params) + self.restheaders['Authorization'] = 'Bearer ' + token + + r = make_request_catching_401() + + if isinstance(r, Exception): + # Either no re-auth options were available, or they all failed + raise r return r From f37d7cf145a780529029458445359a5e5a1d44cc Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Wed, 22 Jan 2025 22:32:24 -0500 Subject: [PATCH 27/30] test: fix arguments for invalid token, valid refresh token test (#8857) --- .../targets/keycloak_modules_authentication/tasks/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml index 0c8df69a346..90cd700cdc6 100644 --- a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml +++ b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml @@ -107,6 +107,8 @@ community.general.keycloak_role: auth_keycloak_url: "{{ url }}" auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" token: "invalidtoken!!!" refresh_token: "{{ refresh_token }}" realm: "{{ realm }}" From 57270fb38df222402d08fc4a766c779cb157314a Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Wed, 22 Jan 2025 22:33:59 -0500 Subject: [PATCH 28/30] feat: catch invalid refresh token errors during re-auth attempt (#8857) Add test to verify this behaviour works. --- .../identity/keycloak/keycloak.py | 15 ++++++--- .../tasks/main.yml | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 567113a2240..468efeb0784 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -175,7 +175,7 @@ def _token_request(module_params, payload): % (auth_url, str(e))) except Exception as e: raise KeycloakError('Could not obtain access token from %s: %s' - % (auth_url, str(e))) + % (auth_url, str(e))) from e try: token = r['access_token'] @@ -329,10 +329,15 @@ def make_request_catching_401(): # Try to refresh token and retry, if available refresh_token = self.module.params.get('refresh_token') if refresh_token is not None: - token = _get_token_using_refresh_token(self.module.params) - self.restheaders['Authorization'] = 'Bearer ' + token - - r = make_request_catching_401() + try: + token = _get_token_using_refresh_token(self.module.params) + self.restheaders['Authorization'] = 'Bearer ' + token + + r = make_request_catching_401() + except KeycloakError as e: + # Token refresh returns 400 if token is expired/invalid, so continue on if we get a 400 + if isinstance(e.__cause__, HTTPError) and e.__cause__.code != 400: + raise e if isinstance(r, Exception): # Try to re-auth with username/password, if available diff --git a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml index 90cd700cdc6..369e65bd7f5 100644 --- a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml +++ b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml @@ -135,3 +135,36 @@ - name: Debug debug: var: result + +- name: Create new realm role with invalid auth token, invalid refresh token, and valid username/password + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + token: "invalidtoken!!!" + refresh_token: "invalidrefreshtoken!!!" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ keycloak_role_description }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Remove created realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result From a10f37bf4ffb674fd986bbfc3cc5b5407632edd9 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Wed, 22 Jan 2025 22:53:27 -0500 Subject: [PATCH 29/30] test: improve test coverage, including some unhappy path tests for authentication failures (#8857) --- .../tasks/main.yml | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml index 369e65bd7f5..1553e29c1c8 100644 --- a/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml +++ b/tests/integration/targets/keycloak_modules_authentication/tasks/main.yml @@ -136,6 +136,38 @@ debug: var: result +- name: Create new realm role with invalid auth token and valid username/password + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + token: "invalidtoken!!!" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ keycloak_role_description }}" + state: present + register: result + +- name: Debug + debug: + var: result + +- name: Remove created realm role + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "{{ admin_password }}" + realm: "{{ realm }}" + name: "{{ role }}" + state: absent + register: result + +- name: Debug + debug: + var: result + - name: Create new realm role with invalid auth token, invalid refresh token, and valid username/password community.general.keycloak_role: auth_keycloak_url: "{{ url }}" @@ -168,3 +200,50 @@ - name: Debug debug: var: result + +### Unhappy path tests + +- name: Fail to create new realm role with invalid username/password + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "invalid_password" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ keycloak_role_description }}" + state: present + register: result + failed_when: > + (result.exception is not defined) or + ("HTTP Error 401: Unauthorized" not in result.msg) + +- name: Fail to create new realm role with invalid auth token + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + token: "invalidtoken!!!" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ keycloak_role_description }}" + state: present + register: result + failed_when: > + (result.exception is not defined) or + ("HTTP Error 401: Unauthorized" not in result.msg) + +- name: Fail to create new realm role with invalid auth and refresh tokens, and invalid username/password + community.general.keycloak_role: + auth_keycloak_url: "{{ url }}" + auth_realm: "{{ admin_realm }}" + auth_username: "{{ admin_user }}" + auth_password: "invalid_password" + token: "invalidtoken!!!" + refresh_token: "invalidtoken!!!" + realm: "{{ realm }}" + name: "{{ role }}" + description: "{{ keycloak_role_description }}" + state: present + register: result + failed_when: > + (result.exception is not defined) or + ("HTTP Error 401: Unauthorized" not in result.msg) From b8b79b19597b6b93f5254b120e1829368e617a07 Mon Sep 17 00:00:00 2001 From: Mark Armstrong Date: Wed, 22 Jan 2025 23:41:48 -0500 Subject: [PATCH 30/30] chore: store auth errors from token request in backwards compatible way (#8857) --- plugins/module_utils/identity/keycloak/keycloak.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/module_utils/identity/keycloak/keycloak.py b/plugins/module_utils/identity/keycloak/keycloak.py index 468efeb0784..ae539cfa064 100644 --- a/plugins/module_utils/identity/keycloak/keycloak.py +++ b/plugins/module_utils/identity/keycloak/keycloak.py @@ -152,7 +152,12 @@ def camel(words): class KeycloakError(Exception): - pass + def __init__(self, msg, authError=None): + self.msg = msg + self.authError = authError + + def __str__(self): + return str(self.msg) def _token_request(module_params, payload): @@ -175,7 +180,7 @@ def _token_request(module_params, payload): % (auth_url, str(e))) except Exception as e: raise KeycloakError('Could not obtain access token from %s: %s' - % (auth_url, str(e))) from e + % (auth_url, str(e)), authError=e) try: token = r['access_token'] @@ -336,7 +341,7 @@ def make_request_catching_401(): r = make_request_catching_401() except KeycloakError as e: # Token refresh returns 400 if token is expired/invalid, so continue on if we get a 400 - if isinstance(e.__cause__, HTTPError) and e.__cause__.code != 400: + if e.authError is not None and e.authError.code != 400: raise e if isinstance(r, Exception):