Skip to content

Commit 5f8c030

Browse files
authored
fix: correctly pass query params in a_send_update_account and a_send_verify_email (#581)
1 parent 139d528 commit 5f8c030

File tree

3 files changed

+76
-7
lines changed

3 files changed

+76
-7
lines changed

src/keycloak/connection.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ async def a_raw_get(self, path, **kwargs):
307307
try:
308308
return await self.async_s.get(
309309
urljoin(self.base_url, path),
310-
params=kwargs,
310+
params=self._filter_query_params(kwargs),
311311
headers=self.headers,
312312
timeout=self.timeout,
313313
)
@@ -331,7 +331,7 @@ async def a_raw_post(self, path, data, **kwargs):
331331
return await self.async_s.request(
332332
method="POST",
333333
url=urljoin(self.base_url, path),
334-
params=kwargs,
334+
params=self._filter_query_params(kwargs),
335335
data=data,
336336
headers=self.headers,
337337
timeout=self.timeout,
@@ -355,7 +355,7 @@ async def a_raw_put(self, path, data, **kwargs):
355355
try:
356356
return await self.async_s.put(
357357
urljoin(self.base_url, path),
358-
params=kwargs,
358+
params=self._filter_query_params(kwargs),
359359
data=data,
360360
headers=self.headers,
361361
timeout=self.timeout,
@@ -381,9 +381,23 @@ async def a_raw_delete(self, path, data=None, **kwargs):
381381
method="DELETE",
382382
url=urljoin(self.base_url, path),
383383
data=data or dict(),
384-
params=kwargs,
384+
params=self._filter_query_params(kwargs),
385385
headers=self.headers,
386386
timeout=self.timeout,
387387
)
388388
except Exception as e:
389389
raise KeycloakConnectionError("Can't connect to server (%s)" % e)
390+
391+
@staticmethod
392+
def _filter_query_params(query_params):
393+
"""Explicitly filter query params with None values for compatibility.
394+
395+
Httpx and requests differ in the way they handle query params with the value None,
396+
requests does not include params with the value None while httpx includes them as-is.
397+
398+
:param query_params: the query params
399+
:type query_params: dict
400+
:returns: the filtered query params
401+
:rtype: dict
402+
"""
403+
return {k: v for k, v in query_params.items() if v is not None}

src/keycloak/keycloak_admin.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5073,7 +5073,7 @@ async def a_send_update_account(
50735073
data_raw = await self.connection.a_raw_put(
50745074
urls_patterns.URL_ADMIN_SEND_UPDATE_ACCOUNT.format(**params_path),
50755075
data=json.dumps(payload),
5076-
kwargs=params_query,
5076+
**params_query,
50775077
)
50785078
return raise_error_from_response(data_raw, KeycloakPutError)
50795079

@@ -5097,7 +5097,7 @@ async def a_send_verify_email(self, user_id, client_id=None, redirect_uri=None):
50975097
data_raw = await self.connection.a_raw_put(
50985098
urls_patterns.URL_ADMIN_SEND_VERIFY_EMAIL.format(**params_path),
50995099
data={},
5100-
kwargs=params_query,
5100+
**params_query,
51015101
)
51025102
return raise_error_from_response(data_raw, KeycloakPutError)
51035103

tests/test_keycloak_admin.py

+56-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@
55
import uuid
66
from inspect import iscoroutinefunction, signature
77
from typing import Tuple
8+
from unittest.mock import ANY, patch
89

910
import freezegun
1011
import pytest
1112
from dateutil import parser as datetime_parser
1213
from packaging.version import Version
1314

1415
import keycloak
15-
from keycloak import KeycloakAdmin, KeycloakOpenID, KeycloakOpenIDConnection
16+
from keycloak import (
17+
KeycloakAdmin,
18+
KeycloakConnectionError,
19+
KeycloakOpenID,
20+
KeycloakOpenIDConnection,
21+
)
1622
from keycloak.connection import ConnectionManager
1723
from keycloak.exceptions import (
1824
KeycloakAuthenticationError,
@@ -5170,6 +5176,55 @@ async def test_a_email(admin: KeycloakAdmin, user: str):
51705176
assert err.match('500: b\'{"errorMessage":"Failed to send .*"}\'')
51715177

51725178

5179+
@pytest.mark.asyncio
5180+
async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str):
5181+
"""Test that the optional parameters are correctly transformed into query params.
5182+
5183+
:param admin: Keycloak Admin client
5184+
:type admin: KeycloakAdmin
5185+
:param user: Keycloak user
5186+
:type user: str
5187+
"""
5188+
with patch.object(
5189+
admin.connection.async_s, "put", side_effect=Exception("An expected error")
5190+
) as mock_put, pytest.raises(KeycloakConnectionError):
5191+
await admin.a_send_update_account(
5192+
user_id=user,
5193+
payload=["UPDATE_PASSWORD"],
5194+
client_id="update-account-client-id",
5195+
redirect_uri="https://example.com",
5196+
)
5197+
5198+
mock_put.assert_awaited_once_with(
5199+
ANY,
5200+
data='["UPDATE_PASSWORD"]',
5201+
params={
5202+
"client_id": "update-account-client-id",
5203+
"redirect_uri": "https://example.com",
5204+
},
5205+
headers=ANY,
5206+
timeout=60,
5207+
)
5208+
5209+
with patch.object(
5210+
admin.connection.async_s, "put", side_effect=Exception("An expected error")
5211+
) as mock_put, pytest.raises(KeycloakConnectionError):
5212+
await admin.a_send_verify_email(
5213+
user_id=user, client_id="verify-client-id", redirect_uri="https://example.com"
5214+
)
5215+
5216+
mock_put.assert_awaited_once_with(
5217+
ANY,
5218+
data=ANY,
5219+
params={
5220+
"client_id": "verify-client-id",
5221+
"redirect_uri": "https://example.com",
5222+
},
5223+
headers=ANY,
5224+
timeout=60,
5225+
)
5226+
5227+
51735228
@pytest.mark.asyncio
51745229
async def test_a_get_sessions(admin: KeycloakAdmin):
51755230
"""Test get sessions.

0 commit comments

Comments
 (0)