Skip to content

Commit d0d2b60

Browse files
authored
chore: Merge pull request #627 from marcospereirampj/feat/fixing_issues
Feat/fixing issues
2 parents d1984c7 + 3548e1e commit d0d2b60

5 files changed

Lines changed: 80 additions & 59 deletions

File tree

poetry.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/keycloak/keycloak_admin.py

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -619,18 +619,23 @@ def get_user_id(self, username):
619619
users = self.get_users(query={"username": lower_user_name, "max": 1, "exact": True})
620620
return users[0]["id"] if len(users) == 1 else None
621621

622-
def get_user(self, user_id):
622+
def get_user(self, user_id, user_profile_metadata=False):
623623
"""Get representation of the user.
624624
625625
UserRepresentation
626626
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_userrepresentation
627627
628628
:param user_id: User id
629629
:type user_id: str
630+
:param user_profile_metadata: Whether to include user profile metadata in the response
631+
:type user_profile_metadata: bool
630632
:return: UserRepresentation
631633
"""
632634
params_path = {"realm-name": self.connection.realm_name, "id": user_id}
633-
data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_USER.format(**params_path))
635+
data_raw = self.connection.raw_get(
636+
urls_patterns.URL_ADMIN_USER.format(**params_path),
637+
userProfileMetadata=user_profile_metadata,
638+
)
634639
return raise_error_from_response(data_raw, KeycloakGetError)
635640

636641
def get_user_groups(self, user_id, query=None, brief_representation=True):
@@ -1149,7 +1154,7 @@ def get_group_by_path(self, path):
11491154
data_raw = self.connection.raw_get(
11501155
urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path)
11511156
)
1152-
return raise_error_from_response(data_raw, KeycloakGetError)
1157+
return raise_error_from_response(data_raw, KeycloakGetError, [200, 404])
11531158

11541159
def create_group(self, payload, parent=None, skip_exists=False):
11551160
"""Create a group in the Realm.
@@ -2124,9 +2129,7 @@ def get_client_roles(self, client_id, brief_representation=True):
21242129
return raise_error_from_response(data_raw, KeycloakGetError)
21252130

21262131
def get_client_role(self, client_id, role_name):
2127-
"""Get client role id by name.
2128-
2129-
This is required for further actions with this role.
2132+
"""Get client role by name.
21302133
21312134
RoleRepresentation
21322135
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation
@@ -2135,8 +2138,8 @@ def get_client_role(self, client_id, role_name):
21352138
:type client_id: str
21362139
:param role_name: role's name (not id!)
21372140
:type role_name: str
2138-
:return: role_id
2139-
:rtype: str
2141+
:return: Role object
2142+
:rtype: dict
21402143
"""
21412144
params_path = {
21422145
"realm-name": self.connection.realm_name,
@@ -3963,22 +3966,26 @@ def set_events(self, payload):
39633966
)
39643967
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
39653968

3966-
def get_client_all_sessions(self, client_id):
3969+
def get_client_all_sessions(self, client_id, query=None):
39673970
"""Get sessions associated with the client.
39683971
39693972
UserSessionRepresentation
39703973
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_usersessionrepresentation
39713974
39723975
:param client_id: id of client
39733976
:type client_id: str
3977+
:param query: Additional query parameters
3978+
:type query: dict
39743979
:return: UserSessionRepresentation
39753980
:rtype: list
39763981
"""
3982+
query = query or {}
39773983
params_path = {"realm-name": self.connection.realm_name, "id": client_id}
3978-
data_raw = self.connection.raw_get(
3979-
urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
3980-
)
3981-
return raise_error_from_response(data_raw, KeycloakGetError)
3984+
url = urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
3985+
if "first" in query or "max" in query:
3986+
return self.__fetch_paginated(url, query)
3987+
3988+
return self.__fetch_all(url, query)
39823989

39833990
def get_client_sessions_stats(self):
39843991
"""Get current session count for all clients with active sessions.
@@ -4922,19 +4929,22 @@ async def a_get_user_id(self, username):
49224929
)
49234930
return users[0]["id"] if len(users) == 1 else None
49244931

4925-
async def a_get_user(self, user_id):
4932+
async def a_get_user(self, user_id, user_profile_metadata=False):
49264933
"""Get representation of the user asynchronously.
49274934
49284935
UserRepresentation
49294936
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_userrepresentation
49304937
49314938
:param user_id: User id
49324939
:type user_id: str
4940+
:param user_profile_metadata: whether to include user profile metadata in the response
4941+
:type user_profile_metadata: bool
49334942
:return: UserRepresentation
49344943
"""
49354944
params_path = {"realm-name": self.connection.realm_name, "id": user_id}
49364945
data_raw = await self.connection.a_raw_get(
4937-
urls_patterns.URL_ADMIN_USER.format(**params_path)
4946+
urls_patterns.URL_ADMIN_USER.format(**params_path),
4947+
userProfileMetadata=user_profile_metadata,
49384948
)
49394949
return raise_error_from_response(data_raw, KeycloakGetError)
49404950

@@ -5460,7 +5470,7 @@ async def a_get_group_by_path(self, path):
54605470
data_raw = await self.connection.a_raw_get(
54615471
urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path)
54625472
)
5463-
return raise_error_from_response(data_raw, KeycloakGetError)
5473+
return raise_error_from_response(data_raw, KeycloakGetError, [200, 404])
54645474

54655475
async def a_create_group(self, payload, parent=None, skip_exists=False):
54665476
"""Create a group in the Realm asynchronously.
@@ -6445,19 +6455,14 @@ async def a_get_client_roles(self, client_id, brief_representation=True):
64456455
return raise_error_from_response(data_raw, KeycloakGetError)
64466456

64476457
async def a_get_client_role(self, client_id, role_name):
6448-
"""Get client role id by name asynchronously.
6449-
6450-
This is required for further actions with this role.
6451-
6452-
RoleRepresentation
6453-
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation
6458+
"""Get client role by name asynchronously.
64546459
64556460
:param client_id: id of client (not client-id)
64566461
:type client_id: str
64576462
:param role_name: role's name (not id!)
64586463
:type role_name: str
6459-
:return: role_id
6460-
:rtype: str
6464+
:return: Role object
6465+
:rtype: dict
64616466
"""
64626467
params_path = {
64636468
"realm-name": self.connection.realm_name,
@@ -8299,22 +8304,26 @@ async def a_set_events(self, payload):
82998304
)
83008305
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
83018306

8302-
async def a_get_client_all_sessions(self, client_id):
8307+
async def a_get_client_all_sessions(self, client_id, query=None):
83038308
"""Get sessions associated with the client asynchronously.
83048309
83058310
UserSessionRepresentation
83068311
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_usersessionrepresentation
83078312
83088313
:param client_id: id of client
83098314
:type client_id: str
8315+
:param query: Additional query parameters
8316+
:type query: dict
83108317
:return: UserSessionRepresentation
83118318
:rtype: list
83128319
"""
8320+
query = query or {}
83138321
params_path = {"realm-name": self.connection.realm_name, "id": client_id}
8314-
data_raw = await self.connection.a_raw_get(
8315-
urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
8316-
)
8317-
return raise_error_from_response(data_raw, KeycloakGetError)
8322+
url = urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
8323+
if "first" in query or "max" in query:
8324+
return await self.a___fetch_paginated(url, query)
8325+
8326+
return await self.a___fetch_all(url, query)
83188327

83198328
async def a_get_client_sessions_stats(self):
83208329
"""Get current session count for all clients with active sessions asynchronously.

src/keycloak/keycloak_openid.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -731,7 +731,7 @@ def get_permissions(self, token, method_token_info="introspect", **kwargs):
731731

732732
return list(set(permissions))
733733

734-
def uma_permissions(self, token, permissions=""):
734+
def uma_permissions(self, token, permissions="", **extra_payload):
735735
"""Get UMA permissions by user token with requested permissions.
736736
737737
The token endpoint is used to retrieve UMA permissions from Keycloak. It can only be
@@ -743,6 +743,8 @@ def uma_permissions(self, token, permissions=""):
743743
:type token: str
744744
:param permissions: list of uma permissions list(resource:scope) requested by the user
745745
:type permissions: str
746+
:param extra_payload: Additional payload data
747+
:type extra_payload: dict
746748
:returns: Keycloak server response
747749
:rtype: dict
748750
"""
@@ -754,6 +756,7 @@ def uma_permissions(self, token, permissions=""):
754756
"permission": permission,
755757
"response_mode": "permissions",
756758
"audience": self.client_id,
759+
**extra_payload,
757760
}
758761

759762
orig_bearer = self.connection.headers.get("Authorization")
@@ -800,13 +803,13 @@ def has_uma_access(self, token, permissions):
800803
raise
801804

802805
for resource_struct in granted:
803-
resource = resource_struct["rsname"]
804-
scopes = resource_struct.get("scopes", None)
805-
if not scopes:
806-
needed.discard(resource)
807-
continue
808-
for scope in scopes: # pragma: no cover
809-
needed.discard("{}#{}".format(resource, scope))
806+
for resource in (resource_struct["rsname"], resource_struct["rsid"]):
807+
scopes = resource_struct.get("scopes", None)
808+
if not scopes:
809+
needed.discard(resource)
810+
continue
811+
for scope in scopes: # pragma: no cover
812+
needed.discard("{}#{}".format(resource, scope))
810813

811814
return AuthStatus(
812815
is_logged_in=True, is_authorized=len(needed) == 0, missing_permissions=needed
@@ -1394,7 +1397,7 @@ async def a_get_permissions(self, token, method_token_info="introspect", **kwarg
13941397

13951398
return list(set(permissions))
13961399

1397-
async def a_uma_permissions(self, token, permissions=""):
1400+
async def a_uma_permissions(self, token, permissions="", **extra_payload):
13981401
"""Get UMA permissions by user token with requested permissions asynchronously.
13991402
14001403
The token endpoint is used to retrieve UMA permissions from Keycloak. It can only be
@@ -1406,6 +1409,8 @@ async def a_uma_permissions(self, token, permissions=""):
14061409
:type token: str
14071410
:param permissions: list of uma permissions list(resource:scope) requested by the user
14081411
:type permissions: str
1412+
:param extra_payload: Additional payload data
1413+
:type extra_payload: dict
14091414
:returns: Keycloak server response
14101415
:rtype: dict
14111416
"""
@@ -1417,6 +1422,7 @@ async def a_uma_permissions(self, token, permissions=""):
14171422
"permission": list(permission), # httpx does not handle `set` correctly
14181423
"response_mode": "permissions",
14191424
"audience": self.client_id,
1425+
**extra_payload,
14201426
}
14211427

14221428
orig_bearer = self.connection.headers.get("Authorization")
@@ -1463,13 +1469,13 @@ async def a_has_uma_access(self, token, permissions):
14631469
raise
14641470

14651471
for resource_struct in granted:
1466-
resource = resource_struct["rsname"]
1467-
scopes = resource_struct.get("scopes", None)
1468-
if not scopes:
1469-
needed.discard(resource)
1470-
continue
1471-
for scope in scopes: # pragma: no cover
1472-
needed.discard("{}#{}".format(resource, scope))
1472+
for resource in (resource_struct["rsname"], resource_struct["rsid"]):
1473+
scopes = resource_struct.get("scopes", None)
1474+
if not scopes:
1475+
needed.discard(resource)
1476+
continue
1477+
for scope in scopes: # pragma: no cover
1478+
needed.discard("{}#{}".format(resource, scope))
14731479

14741480
return AuthStatus(
14751481
is_logged_in=True, is_authorized=len(needed) == 0, missing_permissions=needed

src/keycloak/keycloak_uma.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def permission_ticket_create(self, permissions: Iterable[UMAPermission]):
318318
)
319319
return raise_error_from_response(data_raw, KeycloakPostError)
320320

321-
def permissions_check(self, token, permissions: Iterable[UMAPermission]):
321+
def permissions_check(self, token, permissions: Iterable[UMAPermission], **extra_payload):
322322
"""Check UMA permissions by user token with requested permissions.
323323
324324
The token endpoint is used to check UMA permissions from Keycloak. It can only be
@@ -330,6 +330,8 @@ def permissions_check(self, token, permissions: Iterable[UMAPermission]):
330330
:type token: str
331331
:param permissions: Iterable of uma permissions to validate the token against
332332
:type permissions: Iterable[UMAPermission]
333+
:param extra_payload: extra payload data
334+
:type extra_payload: dict
333335
:returns: Keycloak decision
334336
:rtype: boolean
335337
"""
@@ -338,6 +340,7 @@ def permissions_check(self, token, permissions: Iterable[UMAPermission]):
338340
"permission": ",".join(str(permission) for permission in permissions),
339341
"response_mode": "decision",
340342
"audience": self.connection.client_id,
343+
**extra_payload,
341344
}
342345

343346
# Everyone always has the null set of permissions
@@ -657,7 +660,9 @@ async def a_permission_ticket_create(self, permissions: Iterable[UMAPermission])
657660
)
658661
return raise_error_from_response(data_raw, KeycloakPostError)
659662

660-
async def a_permissions_check(self, token, permissions: Iterable[UMAPermission]):
663+
async def a_permissions_check(
664+
self, token, permissions: Iterable[UMAPermission], **extra_payload
665+
):
661666
"""Check UMA permissions by user token with requested permissions asynchronously.
662667
663668
The token endpoint is used to check UMA permissions from Keycloak. It can only be
@@ -669,6 +674,8 @@ async def a_permissions_check(self, token, permissions: Iterable[UMAPermission])
669674
:type token: str
670675
:param permissions: Iterable of uma permissions to validate the token against
671676
:type permissions: Iterable[UMAPermission]
677+
:param extra_payload: extra payload data
678+
:type extra_payload: dict
672679
:returns: Keycloak decision
673680
:rtype: boolean
674681
"""
@@ -677,6 +684,7 @@ async def a_permissions_check(self, token, permissions: Iterable[UMAPermission])
677684
"permission": ",".join(str(permission) for permission in permissions),
678685
"response_mode": "decision",
679686
"audience": self.connection.client_id,
687+
**extra_payload,
680688
}
681689

682690
# Everyone always has the null set of permissions

tests/test_keycloak_admin.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -845,9 +845,8 @@ def test_groups(admin: KeycloakAdmin, user: str):
845845
assert res is not None, res
846846
assert res["id"] == subgroup_id_1, res
847847

848-
with pytest.raises(KeycloakGetError) as err:
849-
admin.get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1/test")
850-
assert err.match('404: b\'{"error":"Group path does not exist".*}\'')
848+
res = admin.get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1/test")
849+
assert res["error"] == "Group path does not exist"
851850

852851
res = admin.get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1")
853852
assert res is not None, res
@@ -3947,9 +3946,8 @@ async def test_a_groups(admin: KeycloakAdmin, user: str):
39473946
assert res is not None, res
39483947
assert res["id"] == subgroup_id_1, res
39493948

3950-
with pytest.raises(KeycloakGetError) as err:
3951-
await admin.a_get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1/test")
3952-
assert err.match('404: b\'{"error":"Group path does not exist".*}\'')
3949+
res = await admin.a_get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1/test")
3950+
assert res["error"] == "Group path does not exist"
39533951

39543952
res = await admin.a_get_group_by_path(path="/main-group/subgroup-2/subsubgroup-1")
39553953
assert res is not None, res

0 commit comments

Comments
 (0)