From 285fb1af20575786a3cbd8a1a5f6b2bebf8dae75 Mon Sep 17 00:00:00 2001 From: Richard Nemeth Date: Fri, 13 Feb 2026 22:30:43 +0100 Subject: [PATCH] feat: new methods for certificate key info --- src/keycloak/keycloak_admin.py | 58 ++++++++++++++++++++++++++++++++++ tests/test_keycloak_admin.py | 18 +++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 6579e93..16e2a8c 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -7518,6 +7518,35 @@ class KeycloakAdmin: return res + def get_client_certificate_key_info(self, client_id: str, attribute: str) -> dict: + """ + Get client certificate key info. + + :param client_id: id of the client + :type client_id: str + :param attribute: attribute to query for + :type attribute: str + :return: Certificate key info + :rtype: dict + """ + params_path = { + "realm-name": self.connection.realm_name, + "id": client_id, + "attr": attribute, + } + data_raw = self.connection.raw_get( + urls_patterns.URL_ADMIN_CLIENT_CERTS.format(**params_path) + ) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res + def upload_certificate(self, client_id: str, certcont: str) -> dict: """ Upload a new certificate for the client. @@ -14313,6 +14342,35 @@ class KeycloakAdmin: return res + async def a_get_client_certificate_key_info(self, client_id: str, attribute: str) -> dict: + """ + Get client certificate key info. + + :param client_id: id of the client + :type client_id: str + :param attribute: attribute to query for + :type attribute: str + :return: Certificate key info + :rtype: dict + """ + params_path = { + "realm-name": self.connection.realm_name, + "id": client_id, + "attr": attribute, + } + data_raw = await self.connection.a_raw_get( + urls_patterns.URL_ADMIN_CLIENT_CERTS.format(**params_path) + ) + res = raise_error_from_response(data_raw, KeycloakGetError) + if not isinstance(res, dict): + msg = ( + "Unexpected response type. Expected 'dict', received " + f"'{type(res)}', value '{res}'." + ) + raise TypeError(msg) + + return res + async def a_upload_certificate(self, client_id: str, certcont: str) -> dict: """ Upload a new certificate for the client asynchronously. diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index a6f8a5a..5a6caec 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -40,6 +40,7 @@ ILLEGAL_EXECUTION_REGEX = '404: b\'{"error":"Illegal execution".*}\'' NO_CLIENT_SCOPE_REGEX = '404: b\'{"error":"Could not find client scope".*}\'' UNKOWN_ERROR_REGEX = 'b\'{"error":"unknown_error".*}\'' USER_NOT_FOUND_REGEX = '404: b\'{"error":"User not found".*}\'' +COULD_NOT_FIND_CLIENT_REGEX = '404: b\'{"error":"Could not find client"}\'' def test_keycloak_version() -> None: @@ -7697,6 +7698,23 @@ async def test_a_consents( assert err.match(CONSENT_NOT_FOUND_REGEX) +def test_keycloak_client_get_cert_info(admin: KeycloakAdmin, client: str) -> None: + """Test get cert info.""" + assert admin.get_client_certificate_key_info(client, "jwt.credential") == {} + with pytest.raises(KeycloakGetError) as res: + admin.get_client_certificate_key_info("blah", "blah") + assert res.match(COULD_NOT_FIND_CLIENT_REGEX) + + +@pytest.mark.asyncio +async def test_a_keycloak_client_get_cert_info(admin: KeycloakAdmin, client: str) -> None: + """Test get cert info.""" + assert await admin.a_get_client_certificate_key_info(client, "jwt.credential") == {} + with pytest.raises(KeycloakGetError) as res: + await admin.a_get_client_certificate_key_info("blah", "blah") + assert res.match(COULD_NOT_FIND_CLIENT_REGEX) + + def test_counter_part() -> None: """Test that each function has its async counter part.""" admin_methods = [func for func in dir(KeycloakAdmin) if callable(getattr(KeycloakAdmin, func))]