Browse Source

feat: re-enable full group hierarchy fetching

pull/556/head
Richard Nemeth 6 months ago
parent
commit
f0c731b652
No known key found for this signature in database GPG Key ID: 21C39470DF3DEC39
  1. 49
      src/keycloak/keycloak_admin.py
  2. 40
      tests/test_keycloak_admin.py

49
src/keycloak/keycloak_admin.py

@ -954,7 +954,7 @@ class KeycloakAdmin:
data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_SERVER_INFO) data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_SERVER_INFO)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def get_groups(self, query=None):
def get_groups(self, query=None, full_hierarchy=False):
"""Get groups. """Get groups.
Returns a list of groups belonging to the realm Returns a list of groups belonging to the realm
@ -962,8 +962,15 @@ class KeycloakAdmin:
GroupRepresentation GroupRepresentation
https://www.keycloak.org/docs-api/24.0.2/rest-api/#_grouprepresentation https://www.keycloak.org/docs-api/24.0.2/rest-api/#_grouprepresentation
Notice that when using full_hierarchy=True, the response will be a nested structure
containing all the children groups. If used with query parameters, the full_hierarchy
will be applied to the received groups only.
:param query: Additional query options :param query: Additional query options
:type query: dict :type query: dict
:param full_hierarchy: If True, return all of the nested children groups, otherwise only
the first level children are returned
:type full_hierarchy: bool
:return: array GroupRepresentation :return: array GroupRepresentation
:rtype: list :rtype: list
""" """
@ -979,11 +986,15 @@ class KeycloakAdmin:
# For version +23.0.0 # For version +23.0.0
for group in groups: for group in groups:
if group.get("subGroupCount"): if group.get("subGroupCount"):
group["subGroups"] = self.get_group_children(group.get("id"))
group["subGroups"] = self.get_group_children(
group_id=group.get("id"), full_hierarchy=full_hierarchy
)
else:
group["subGroups"] = []
return groups return groups
def get_group(self, group_id):
def get_group(self, group_id, full_hierarchy=False):
"""Get group by id. """Get group by id.
Returns full group details Returns full group details
@ -993,6 +1004,9 @@ class KeycloakAdmin:
:param group_id: The group id :param group_id: The group id
:type group_id: str :type group_id: str
:param full_hierarchy: If True, return all of the nested children groups, otherwise only
the first level children are returned
:type full_hierarchy: bool
:return: Keycloak server response (GroupRepresentation) :return: Keycloak server response (GroupRepresentation)
:rtype: dict :rtype: dict
""" """
@ -1005,7 +1019,11 @@ class KeycloakAdmin:
# For version +23.0.0 # For version +23.0.0
group = response.json() group = response.json()
if group.get("subGroupCount"): if group.get("subGroupCount"):
group["subGroups"] = self.get_group_children(group.get("id"))
group["subGroups"] = self.get_group_children(
group.get("id"), full_hierarchy=full_hierarchy
)
else:
group["subGroups"] = []
return group return group
@ -1035,7 +1053,7 @@ class KeycloakAdmin:
# went through the tree without hits # went through the tree without hits
return None return None
def get_group_children(self, group_id, query=None):
def get_group_children(self, group_id, query=None, full_hierarchy=False):
"""Get group children by parent id. """Get group children by parent id.
Returns full group children details Returns full group children details
@ -1044,15 +1062,34 @@ class KeycloakAdmin:
:type group_id: str :type group_id: str
:param query: Additional query options :param query: Additional query options
:type query: dict :type query: dict
:param full_hierarchy: If True, return all of the nested children groups
:type full_hierarchy: bool
:return: Keycloak server response (GroupRepresentation) :return: Keycloak server response (GroupRepresentation)
:rtype: dict :rtype: dict
:raises ValueError: If both query and full_hierarchy parameters are used
""" """
query = query or {} query = query or {}
if query and full_hierarchy:
raise ValueError("Cannot use both query and full_hierarchy parameters")
params_path = {"realm-name": self.connection.realm_name, "id": group_id} params_path = {"realm-name": self.connection.realm_name, "id": group_id}
url = urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path) url = urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path)
if "first" in query or "max" in query: if "first" in query or "max" in query:
return self.__fetch_paginated(url, query) return self.__fetch_paginated(url, query)
return self.__fetch_all(url, query)
res = self.__fetch_all(url, query)
if not full_hierarchy:
return res
for group in res:
if group.get("subGroupCount"):
group["subGroups"] = self.get_group_children(
group_id=group.get("id"), full_hierarchy=full_hierarchy
)
else:
group["subGroups"] = []
return res
def get_group_members(self, group_id, query=None): def get_group_members(self, group_id, query=None):
"""Get members by group id. """Get members by group id.

40
tests/test_keycloak_admin.py

@ -794,6 +794,33 @@ def test_groups(admin: KeycloakAdmin, user: str):
assert res is not None, res assert res is not None, res
assert res["id"] == subsubgroup_id_1 assert res["id"] == subsubgroup_id_1
# Test nested search from main group
res = admin.get_subgroups(
group=admin.get_group(group_id=group_id, full_hierarchy=True),
path="/main-group/subgroup-2/subsubgroup-1",
)
assert res["id"] == subsubgroup_id_1
# Test nested search from all groups
res = admin.get_groups(full_hierarchy=True)
assert len(res) == 1
assert len(res[0]["subGroups"]) == 2
assert len(res[0]["subGroups"][0]["subGroups"]) == 0
assert len(res[0]["subGroups"][1]["subGroups"]) == 1
# Test that query params are not allowed for full hierarchy
with pytest.raises(ValueError) as err:
admin.get_group_children(group_id=group_id, full_hierarchy=True, query={"max": 10})
# Test that query params are passed
res = admin.get_group_children(group_id=group_id, query={"max": 1})
assert len(res) == 1
assert err.match("Cannot use both query and full_hierarchy parameters")
main_group_id_2 = admin.create_group(payload={"name": "main-group-2"})
assert len(admin.get_groups(full_hierarchy=True)) == 2
# Test empty search # Test empty search
res = admin.get_subgroups(group=main_group, path="/none") res = admin.get_subgroups(group=main_group, path="/none")
assert res is None, res assert res is None, res
@ -865,6 +892,8 @@ def test_groups(admin: KeycloakAdmin, user: str):
# Test delete # Test delete
res = admin.delete_group(group_id=group_id) res = admin.delete_group(group_id=group_id)
assert res == dict(), res assert res == dict(), res
res = admin.delete_group(group_id=main_group_id_2)
assert res == dict(), res
assert len(admin.get_groups()) == 0 assert len(admin.get_groups()) == 0
# Test delete fail # Test delete fail
@ -3017,3 +3046,14 @@ def test_initial_access_token(
new_secret = str(uuid.uuid4()) new_secret = str(uuid.uuid4())
res = oid.update_client(res["registrationAccessToken"], client, payload={"secret": new_secret}) res = oid.update_client(res["registrationAccessToken"], client, payload={"secret": new_secret})
assert res["secret"] == new_secret assert res["secret"] == new_secret
def test_refresh_token(admin: KeycloakAdmin):
"""Test refresh token on connection even if it is expired.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
"""
assert admin.connection.token is not None
admin.user_logout(admin.get_user_id(admin.connection.username))
admin.connection.refresh_token()
Loading…
Cancel
Save