mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-03-22 05:09:12 +00:00
keycloak: URL-encode query parameters for usernames with special characters (#11472)
* fix(keycloak): URL-encode query params for usernames with special chars
get_user_by_username() concatenates the username directly into the URL
query string. When the username contains a +, it is interpreted as a
space by the server, returning no match and causing a TypeError.
Use urllib.parse.quote() (already imported) for the username parameter.
Also replace three fragile .replace(' ', '%20') calls in the authz
search methods with proper quote() calls.
Fixes #10305
* Update changelogs/fragments/keycloak-url-encode-query-params.yml
Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
---------
Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
This commit is contained in:
parent
b236772e57
commit
c41de53dbb
3 changed files with 67 additions and 4 deletions
|
|
@ -0,0 +1,7 @@
|
||||||
|
bugfixes:
|
||||||
|
- keycloak module utils - fix ``TypeError`` crash when managing users whose username
|
||||||
|
or email contains special characters such as ``+``
|
||||||
|
(https://github.com/ansible-collections/community.general/issues/10305, https://github.com/ansible-collections/community.general/pull/11472).
|
||||||
|
- keycloak module utils - use proper URL encoding (``urllib.parse.quote``) for query
|
||||||
|
parameters in authorization permission name searches, replacing fragile
|
||||||
|
manual space replacement (https://github.com/ansible-collections/community.general/pull/11472).
|
||||||
|
|
@ -998,7 +998,7 @@ class KeycloakAPI:
|
||||||
:param realm: Realm in which the user resides; default 'master'
|
:param realm: Realm in which the user resides; default 'master'
|
||||||
"""
|
"""
|
||||||
users_url = URL_USERS.format(url=self.baseurl, realm=realm)
|
users_url = URL_USERS.format(url=self.baseurl, realm=realm)
|
||||||
users_url += f"?username={username}&exact=true"
|
users_url += f"?username={quote(username, safe='')}&exact=true"
|
||||||
try:
|
try:
|
||||||
userrep = None
|
userrep = None
|
||||||
users = self._request_and_deserialize(users_url, method="GET")
|
users = self._request_and_deserialize(users_url, method="GET")
|
||||||
|
|
@ -3018,7 +3018,7 @@ class KeycloakAPI:
|
||||||
def get_authz_permission_by_name(self, name, client_id, realm):
|
def get_authz_permission_by_name(self, name, client_id, realm):
|
||||||
"""Get authorization permission by name"""
|
"""Get authorization permission by name"""
|
||||||
url = URL_AUTHZ_POLICIES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
url = URL_AUTHZ_POLICIES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
||||||
search_url = f"{url}/search?name={name.replace(' ', '%20')}"
|
search_url = f"{url}/search?name={quote(name, safe='')}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._request_and_deserialize(search_url, method="GET")
|
return self._request_and_deserialize(search_url, method="GET")
|
||||||
|
|
@ -3064,7 +3064,7 @@ class KeycloakAPI:
|
||||||
def get_authz_resource_by_name(self, name, client_id, realm):
|
def get_authz_resource_by_name(self, name, client_id, realm):
|
||||||
"""Get authorization resource by name"""
|
"""Get authorization resource by name"""
|
||||||
url = URL_AUTHZ_RESOURCES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
url = URL_AUTHZ_RESOURCES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
||||||
search_url = f"{url}/search?name={name.replace(' ', '%20')}"
|
search_url = f"{url}/search?name={quote(name, safe='')}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._request_and_deserialize(search_url, method="GET")
|
return self._request_and_deserialize(search_url, method="GET")
|
||||||
|
|
@ -3074,7 +3074,7 @@ class KeycloakAPI:
|
||||||
def get_authz_policy_by_name(self, name, client_id, realm):
|
def get_authz_policy_by_name(self, name, client_id, realm):
|
||||||
"""Get authorization policy by name"""
|
"""Get authorization policy by name"""
|
||||||
url = URL_AUTHZ_POLICIES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
url = URL_AUTHZ_POLICIES.format(url=self.baseurl, client_id=client_id, realm=realm)
|
||||||
search_url = f"{url}/search?name={name.replace(' ', '%20')}"
|
search_url = f"{url}/search?name={quote(name, safe='')}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._request_and_deserialize(search_url, method="GET")
|
return self._request_and_deserialize(search_url, method="GET")
|
||||||
|
|
|
||||||
|
|
@ -112,3 +112,59 @@
|
||||||
that:
|
that:
|
||||||
- delete_result.changed
|
- delete_result.changed
|
||||||
- delete_result.end_state | length == 0
|
- delete_result.end_state | length == 0
|
||||||
|
|
||||||
|
- name: Create user with plus-addressed email
|
||||||
|
community.general.keycloak_user:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
username: "testuser+tag"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
first_name: Plus
|
||||||
|
last_name: User
|
||||||
|
email: "testuser+tag@example.org"
|
||||||
|
state: present
|
||||||
|
register: plus_create_result
|
||||||
|
|
||||||
|
- name: Assert plus-addressed user is created
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- plus_create_result.changed
|
||||||
|
- plus_create_result.end_state.username == 'testuser+tag'
|
||||||
|
- plus_create_result.end_state.email == 'testuser+tag@example.org'
|
||||||
|
|
||||||
|
- name: Re-run plus-addressed user creation (idempotency)
|
||||||
|
community.general.keycloak_user:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
username: "testuser+tag"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
first_name: Plus
|
||||||
|
last_name: User
|
||||||
|
email: "testuser+tag@example.org"
|
||||||
|
state: present
|
||||||
|
register: plus_idempotent_result
|
||||||
|
|
||||||
|
- name: Assert plus-addressed user is idempotent
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- plus_idempotent_result is not changed
|
||||||
|
|
||||||
|
- name: Delete plus-addressed user
|
||||||
|
community.general.keycloak_user:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
username: "testuser+tag"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
state: absent
|
||||||
|
register: plus_delete_result
|
||||||
|
|
||||||
|
- name: Assert plus-addressed user is deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- plus_delete_result.changed
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue