Skip to content

Commit

Permalink
chore: Merge pull request #627 from marcospereirampj/feat/fixing_issues
Browse files Browse the repository at this point in the history
Feat/fixing issues
  • Loading branch information
ryshoooo authored Dec 14, 2024
2 parents d1984c7 + 3548e1e commit d0d2b60
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 59 deletions.
12 changes: 6 additions & 6 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 38 additions & 29 deletions src/keycloak/keycloak_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,18 +619,23 @@ def get_user_id(self, username):
users = self.get_users(query={"username": lower_user_name, "max": 1, "exact": True})
return users[0]["id"] if len(users) == 1 else None

def get_user(self, user_id):
def get_user(self, user_id, user_profile_metadata=False):
"""Get representation of the user.
UserRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_userrepresentation
:param user_id: User id
:type user_id: str
:param user_profile_metadata: Whether to include user profile metadata in the response
:type user_profile_metadata: bool
:return: UserRepresentation
"""
params_path = {"realm-name": self.connection.realm_name, "id": user_id}
data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_USER.format(**params_path))
data_raw = self.connection.raw_get(
urls_patterns.URL_ADMIN_USER.format(**params_path),
userProfileMetadata=user_profile_metadata,
)
return raise_error_from_response(data_raw, KeycloakGetError)

def get_user_groups(self, user_id, query=None, brief_representation=True):
Expand Down Expand Up @@ -1149,7 +1154,7 @@ def get_group_by_path(self, path):
data_raw = self.connection.raw_get(
urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path)
)
return raise_error_from_response(data_raw, KeycloakGetError)
return raise_error_from_response(data_raw, KeycloakGetError, [200, 404])

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

def get_client_role(self, client_id, role_name):
"""Get client role id by name.
This is required for further actions with this role.
"""Get client role by name.
RoleRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation
Expand All @@ -2135,8 +2138,8 @@ def get_client_role(self, client_id, role_name):
:type client_id: str
:param role_name: role's name (not id!)
:type role_name: str
:return: role_id
:rtype: str
:return: Role object
:rtype: dict
"""
params_path = {
"realm-name": self.connection.realm_name,
Expand Down Expand Up @@ -3963,22 +3966,26 @@ def set_events(self, payload):
)
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])

def get_client_all_sessions(self, client_id):
def get_client_all_sessions(self, client_id, query=None):
"""Get sessions associated with the client.
UserSessionRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_usersessionrepresentation
:param client_id: id of client
:type client_id: str
:param query: Additional query parameters
:type query: dict
:return: UserSessionRepresentation
:rtype: list
"""
query = query or {}
params_path = {"realm-name": self.connection.realm_name, "id": client_id}
data_raw = self.connection.raw_get(
urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
)
return raise_error_from_response(data_raw, KeycloakGetError)
url = urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
if "first" in query or "max" in query:
return self.__fetch_paginated(url, query)

return self.__fetch_all(url, query)

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

async def a_get_user(self, user_id):
async def a_get_user(self, user_id, user_profile_metadata=False):
"""Get representation of the user asynchronously.
UserRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_userrepresentation
:param user_id: User id
:type user_id: str
:param user_profile_metadata: whether to include user profile metadata in the response
:type user_profile_metadata: bool
:return: UserRepresentation
"""
params_path = {"realm-name": self.connection.realm_name, "id": user_id}
data_raw = await self.connection.a_raw_get(
urls_patterns.URL_ADMIN_USER.format(**params_path)
urls_patterns.URL_ADMIN_USER.format(**params_path),
userProfileMetadata=user_profile_metadata,
)
return raise_error_from_response(data_raw, KeycloakGetError)

Expand Down Expand Up @@ -5460,7 +5470,7 @@ async def a_get_group_by_path(self, path):
data_raw = await self.connection.a_raw_get(
urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path)
)
return raise_error_from_response(data_raw, KeycloakGetError)
return raise_error_from_response(data_raw, KeycloakGetError, [200, 404])

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

async def a_get_client_role(self, client_id, role_name):
"""Get client role id by name asynchronously.
This is required for further actions with this role.
RoleRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_rolerepresentation
"""Get client role by name asynchronously.
:param client_id: id of client (not client-id)
:type client_id: str
:param role_name: role's name (not id!)
:type role_name: str
:return: role_id
:rtype: str
:return: Role object
:rtype: dict
"""
params_path = {
"realm-name": self.connection.realm_name,
Expand Down Expand Up @@ -8299,22 +8304,26 @@ async def a_set_events(self, payload):
)
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])

async def a_get_client_all_sessions(self, client_id):
async def a_get_client_all_sessions(self, client_id, query=None):
"""Get sessions associated with the client asynchronously.
UserSessionRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/index.html#_usersessionrepresentation
:param client_id: id of client
:type client_id: str
:param query: Additional query parameters
:type query: dict
:return: UserSessionRepresentation
:rtype: list
"""
query = query or {}
params_path = {"realm-name": self.connection.realm_name, "id": client_id}
data_raw = await self.connection.a_raw_get(
urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
)
return raise_error_from_response(data_raw, KeycloakGetError)
url = urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
if "first" in query or "max" in query:
return await self.a___fetch_paginated(url, query)

return await self.a___fetch_all(url, query)

async def a_get_client_sessions_stats(self):
"""Get current session count for all clients with active sessions asynchronously.
Expand Down
38 changes: 22 additions & 16 deletions src/keycloak/keycloak_openid.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ def get_permissions(self, token, method_token_info="introspect", **kwargs):

return list(set(permissions))

def uma_permissions(self, token, permissions=""):
def uma_permissions(self, token, permissions="", **extra_payload):
"""Get UMA permissions by user token with requested permissions.
The token endpoint is used to retrieve UMA permissions from Keycloak. It can only be
Expand All @@ -743,6 +743,8 @@ def uma_permissions(self, token, permissions=""):
:type token: str
:param permissions: list of uma permissions list(resource:scope) requested by the user
:type permissions: str
:param extra_payload: Additional payload data
:type extra_payload: dict
:returns: Keycloak server response
:rtype: dict
"""
Expand All @@ -754,6 +756,7 @@ def uma_permissions(self, token, permissions=""):
"permission": permission,
"response_mode": "permissions",
"audience": self.client_id,
**extra_payload,
}

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

for resource_struct in granted:
resource = resource_struct["rsname"]
scopes = resource_struct.get("scopes", None)
if not scopes:
needed.discard(resource)
continue
for scope in scopes: # pragma: no cover
needed.discard("{}#{}".format(resource, scope))
for resource in (resource_struct["rsname"], resource_struct["rsid"]):
scopes = resource_struct.get("scopes", None)
if not scopes:
needed.discard(resource)
continue
for scope in scopes: # pragma: no cover
needed.discard("{}#{}".format(resource, scope))

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

return list(set(permissions))

async def a_uma_permissions(self, token, permissions=""):
async def a_uma_permissions(self, token, permissions="", **extra_payload):
"""Get UMA permissions by user token with requested permissions asynchronously.
The token endpoint is used to retrieve UMA permissions from Keycloak. It can only be
Expand All @@ -1406,6 +1409,8 @@ async def a_uma_permissions(self, token, permissions=""):
:type token: str
:param permissions: list of uma permissions list(resource:scope) requested by the user
:type permissions: str
:param extra_payload: Additional payload data
:type extra_payload: dict
:returns: Keycloak server response
:rtype: dict
"""
Expand All @@ -1417,6 +1422,7 @@ async def a_uma_permissions(self, token, permissions=""):
"permission": list(permission), # httpx does not handle `set` correctly
"response_mode": "permissions",
"audience": self.client_id,
**extra_payload,
}

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

for resource_struct in granted:
resource = resource_struct["rsname"]
scopes = resource_struct.get("scopes", None)
if not scopes:
needed.discard(resource)
continue
for scope in scopes: # pragma: no cover
needed.discard("{}#{}".format(resource, scope))
for resource in (resource_struct["rsname"], resource_struct["rsid"]):
scopes = resource_struct.get("scopes", None)
if not scopes:
needed.discard(resource)
continue
for scope in scopes: # pragma: no cover
needed.discard("{}#{}".format(resource, scope))

return AuthStatus(
is_logged_in=True, is_authorized=len(needed) == 0, missing_permissions=needed
Expand Down
12 changes: 10 additions & 2 deletions src/keycloak/keycloak_uma.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def permission_ticket_create(self, permissions: Iterable[UMAPermission]):
)
return raise_error_from_response(data_raw, KeycloakPostError)

def permissions_check(self, token, permissions: Iterable[UMAPermission]):
def permissions_check(self, token, permissions: Iterable[UMAPermission], **extra_payload):
"""Check UMA permissions by user token with requested permissions.
The token endpoint is used to check UMA permissions from Keycloak. It can only be
Expand All @@ -330,6 +330,8 @@ def permissions_check(self, token, permissions: Iterable[UMAPermission]):
:type token: str
:param permissions: Iterable of uma permissions to validate the token against
:type permissions: Iterable[UMAPermission]
:param extra_payload: extra payload data
:type extra_payload: dict
:returns: Keycloak decision
:rtype: boolean
"""
Expand All @@ -338,6 +340,7 @@ def permissions_check(self, token, permissions: Iterable[UMAPermission]):
"permission": ",".join(str(permission) for permission in permissions),
"response_mode": "decision",
"audience": self.connection.client_id,
**extra_payload,
}

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

async def a_permissions_check(self, token, permissions: Iterable[UMAPermission]):
async def a_permissions_check(
self, token, permissions: Iterable[UMAPermission], **extra_payload
):
"""Check UMA permissions by user token with requested permissions asynchronously.
The token endpoint is used to check UMA permissions from Keycloak. It can only be
Expand All @@ -669,6 +674,8 @@ async def a_permissions_check(self, token, permissions: Iterable[UMAPermission])
:type token: str
:param permissions: Iterable of uma permissions to validate the token against
:type permissions: Iterable[UMAPermission]
:param extra_payload: extra payload data
:type extra_payload: dict
:returns: Keycloak decision
:rtype: boolean
"""
Expand All @@ -677,6 +684,7 @@ async def a_permissions_check(self, token, permissions: Iterable[UMAPermission])
"permission": ",".join(str(permission) for permission in permissions),
"response_mode": "decision",
"audience": self.connection.client_id,
**extra_payload,
}

# Everyone always has the null set of permissions
Expand Down
10 changes: 4 additions & 6 deletions tests/test_keycloak_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,9 +845,8 @@ def test_groups(admin: KeycloakAdmin, user: str):
assert res is not None, res
assert res["id"] == subgroup_id_1, res

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

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

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

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

0 comments on commit d0d2b60

Please sign in to comment.