From f87dd7631566e4294982512e7d5b3dcfb19465c9 Mon Sep 17 00:00:00 2001 From: Michael Mair Date: Mon, 16 Feb 2026 13:10:34 +0100 Subject: [PATCH 1/2] fix(uma): correctly provide permission parameter for more than one permission in permissions_check method Fixes #705 --- src/keycloak/keycloak_uma.py | 53 ++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/keycloak/keycloak_uma.py b/src/keycloak/keycloak_uma.py index 6c822ed..70db5ab 100644 --- a/src/keycloak/keycloak_uma.py +++ b/src/keycloak/keycloak_uma.py @@ -442,17 +442,24 @@ class KeycloakUMA: :returns: Keycloak decision :rtype: boolean """ - payload = { - "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket", - "permission": ",".join(str(permission) for permission in permissions), - "response_mode": "decision", - "audience": self.connection.client_id, - **extra_payload, - } + payload = [ + ("grant_type", "urn:ietf:params:oauth:grant-type:uma-ticket"), + ("response_mode", "decision"), + ("audience", self.connection.client_id), + ] + for permission in permissions: + payload.append(("permission", str(permission))) + + if extra_payload and isinstance(extra_payload, dict): + for k, v in extra_payload.items(): + payload.append((k, v)) + elif extra_payload: + msg = "Attribute extra_payload needs to be of type dict." + raise AttributeError(msg) # Everyone always has the null set of permissions # However keycloak cannot evaluate the null set - if len(payload["permission"]) == 0: + if len([k for k, _ in payload if k == "permission"]) == 0: return True if self.connection.base_url is None: @@ -472,7 +479,10 @@ class KeycloakUMA: ) connection.add_param_headers("Authorization", "Bearer " + token) connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") - data_raw = connection.raw_post(self.uma_well_known["token_endpoint"], data=payload) + data_raw = connection.raw_post( + self.uma_well_known["token_endpoint"], + data="&".join(["{}={}".format(k, v) for k, v in payload]), + ) try: data = raise_error_from_response(data_raw, KeycloakPostError) except KeycloakPostError: @@ -935,17 +945,24 @@ class KeycloakUMA: :returns: Keycloak decision :rtype: boolean """ - payload = { - "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket", - "permission": ",".join(str(permission) for permission in permissions), - "response_mode": "decision", - "audience": self.connection.client_id, - **extra_payload, - } + payload = [ + ("grant_type", "urn:ietf:params:oauth:grant-type:uma-ticket"), + ("response_mode", "decision"), + ("audience", self.connection.client_id), + ] + for permission in permissions: + payload.append(("permission", str(permission))) + + if extra_payload and isinstance(extra_payload, dict): + for k, v in extra_payload.items(): + payload.append((k, v)) + elif extra_payload: + msg = "Attribute extra_payload needs to be of type dict." + raise AttributeError(msg) # Everyone always has the null set of permissions # However keycloak cannot evaluate the null set - if len(payload["permission"]) == 0: + if len([k for k, _ in payload if k == "permission"]) == 0: return True if self.connection.base_url is None: @@ -967,7 +984,7 @@ class KeycloakUMA: connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = await connection.a_raw_post( (await self.a_uma_well_known)["token_endpoint"], - data=payload, + data="&".join(["{}={}".format(k, v) for k, v in payload]), ) try: data = raise_error_from_response(data_raw, KeycloakPostError) From 63f4bee421eaf0e1a02df499a71da1a69e29f2dc Mon Sep 17 00:00:00 2001 From: Michael Mair Date: Mon, 16 Feb 2026 13:15:54 +0100 Subject: [PATCH 2/2] fix: fixed linting issues --- src/keycloak/keycloak_uma.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/keycloak/keycloak_uma.py b/src/keycloak/keycloak_uma.py index 70db5ab..cc0ea42 100644 --- a/src/keycloak/keycloak_uma.py +++ b/src/keycloak/keycloak_uma.py @@ -447,8 +447,7 @@ class KeycloakUMA: ("response_mode", "decision"), ("audience", self.connection.client_id), ] - for permission in permissions: - payload.append(("permission", str(permission))) + payload.extend([("permission", str(p)) for p in permissions]) if extra_payload and isinstance(extra_payload, dict): for k, v in extra_payload.items(): @@ -481,7 +480,7 @@ class KeycloakUMA: connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = connection.raw_post( self.uma_well_known["token_endpoint"], - data="&".join(["{}={}".format(k, v) for k, v in payload]), + data="&".join([f"{k}={v}" for k, v in payload]), ) try: data = raise_error_from_response(data_raw, KeycloakPostError) @@ -950,8 +949,7 @@ class KeycloakUMA: ("response_mode", "decision"), ("audience", self.connection.client_id), ] - for permission in permissions: - payload.append(("permission", str(permission))) + payload.extend([("permission", str(p)) for p in permissions]) if extra_payload and isinstance(extra_payload, dict): for k, v in extra_payload.items(): @@ -984,7 +982,7 @@ class KeycloakUMA: connection.add_param_headers("Content-Type", "application/x-www-form-urlencoded") data_raw = await connection.a_raw_post( (await self.a_uma_well_known)["token_endpoint"], - data="&".join(["{}={}".format(k, v) for k, v in payload]), + data="&".join([f"{k}={v}" for k, v in payload]), ) try: data = raise_error_from_response(data_raw, KeycloakPostError)