From b29c43470a2c9f21e76d62111954609832b7a448 Mon Sep 17 00:00:00 2001 From: Richard Nemeth Date: Sat, 27 Dec 2025 21:41:34 +0100 Subject: [PATCH] test: full e2e pkce test --- tests/test_pkce_flow.py | 75 ++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/tests/test_pkce_flow.py b/tests/test_pkce_flow.py index c52b465..fefb75c 100644 --- a/tests/test_pkce_flow.py +++ b/tests/test_pkce_flow.py @@ -1,53 +1,66 @@ """Tests for PKCE flow: code verifier and code challenge handling.""" -from unittest import mock +import re +import urllib.parse -from keycloak import KeycloakOpenID +import requests + +from keycloak import KeycloakAdmin, KeycloakOpenID from keycloak.pkce_utils import generate_code_challenge, generate_code_verifier -def test_pkce_auth_url_and_token(env: object) -> None: +def test_pkce_auth_url_and_token(env: object, admin: KeycloakAdmin) -> None: """Test PKCE flow: auth_url includes code_challenge, token includes code_verifier.""" + client_representation = { + "clientId": "pkce-test", + "enabled": True, + "publicClient": True, + "standardFlowEnabled": True, + "directAccessGrantsEnabled": False, + "serviceAccountsEnabled": False, + "implicitFlowEnabled": False, + "redirectUris": ["http://test.test/callback"], + "webOrigins": ["*"], + } + admin.create_client(client_representation) + oid = KeycloakOpenID( server_url=f"http://{env.keycloak_host}:{env.keycloak_port}", realm_name="master", - client_id="admin-cli", + client_id="pkce-test", ) code_verifier = generate_code_verifier() code_challenge, code_challenge_method = generate_code_challenge(code_verifier) # Build PKCE auth URL url = oid.auth_url( - redirect_uri="http://test.test/*", + redirect_uri="http://test.test/callback", code_challenge=code_challenge, code_challenge_method=code_challenge_method, + scope="openid%20email", ) assert f"code_challenge={code_challenge}" in url assert f"code_challenge_method={code_challenge_method}" in url - # Simulate token exchange with PKCE - # This part would require a real code from Keycloak, so we mock the response - with mock.patch.object( - oid, - "token", - return_value={ - "access_token": mock.ANY, - "refresh_token": mock.ANY, - "token_type": "Bearer", - }, - ) as mocked_token: - token = oid.token( - grant_type="authorization_code", - code="dummy_code", - redirect_uri="http://test.test/*", - code_verifier=code_verifier, - ) - mocked_token.assert_called_with( - grant_type="authorization_code", - code="dummy_code", - redirect_uri="http://test.test/*", - code_verifier=code_verifier, - ) - assert "access_token" in token - assert "refresh_token" in token - assert token["token_type"] == "Bearer" # noqa: S105 + session = requests.Session() + resp = session.get(url, allow_redirects=False) + cookies = resp.cookies.get_dict() + assert resp.status_code == 200 + resp_url = re.findall(r"action=\"(.*)\" method", resp.text)[0] + resp = session.post( + resp_url, + data={"username": env.keycloak_admin, "password": env.keycloak_admin_password}, + allow_redirects=False, + cookies=cookies, + ) + assert resp.status_code == 302, resp.text + resp_code = urllib.parse.parse_qs(resp.headers["Location"])["code"][0] + + access_token = oid.token( + grant_type="authorization_code", + code=resp_code, + redirect_uri="http://test.test/callback", + code_verifier=code_verifier, + ) + info = oid.userinfo(access_token["access_token"]) + assert info["preferred_username"] == env.keycloak_admin