diff --git a/src/keycloak/keycloak_admin.py b/src/keycloak/keycloak_admin.py index 15423a1..90c8eef 100644 --- a/src/keycloak/keycloak_admin.py +++ b/src/keycloak/keycloak_admin.py @@ -578,17 +578,21 @@ class KeycloakAdmin: data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) - def get_user_groups(self, user_id): + def get_user_groups(self, user_id, brief_representation=True): """Get user groups. Returns a list of groups of which the user is a member :param user_id: User id + :param brief_representation: whether to omit attributes in the response :return: user groups list """ + params = {"briefRepresentation": brief_representation} params_path = {"realm-name": self.realm_name, "id": user_id} - data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER_GROUPS.format(**params_path)) + data_raw = self.raw_get( + urls_patterns.URL_ADMIN_USER_GROUPS.format(**params_path), **params + ) return raise_error_from_response(data_raw, KeycloakGetError) def update_user(self, user_id, payload): @@ -1300,16 +1304,20 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) - def get_realm_roles(self): + def get_realm_roles(self, brief_representation=True): """Get all roles for the realm or client. RoleRepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation - :return: Keycloak server response (RoleRepresentation) + :param brief_representation: whether to omit role attributes in the response + :return: Keycloak server response (array RoleRepresentation) """ params_path = {"realm-name": self.realm_name} - data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path)) + params = {"briefRepresentation": brief_representation} + data_raw = self.raw_get( + urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), **params + ) return raise_error_from_response(data_raw, KeycloakGetError) def get_realm_role_members(self, role_name, query=None): @@ -1326,18 +1334,21 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), query ) - def get_client_roles(self, client_id): + def get_client_roles(self, client_id, brief_representation=True): """Get all roles for the client. - :param client_id: id of client (not client-id) - RoleRepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation - :return: Keycloak server response (RoleRepresentation) + :param client_id: id of client (not client-id) + :param brief_representation: whether to omit role attributes in the response + :return: Keycloak server response (array RoleRepresentation) """ params_path = {"realm-name": self.realm_name, "id": client_id} - data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path)) + params = {"briefRepresentation": brief_representation} + data_raw = self.raw_get( + urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), **params + ) return raise_error_from_response(data_raw, KeycloakGetError) def get_client_role(self, client_id, role_name): @@ -1345,13 +1356,12 @@ class KeycloakAdmin: This is required for further actions with this role. - :param client_id: id of client (not client-id) - :param role_name: role’s name (not id!) - RoleRepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation - :return: role_id + :param client_id: id of client (not client-id) + :param role_name: role’s name (not id!) + :return: Keycloak server response (RoleRepresentation) """ params_path = {"realm-name": self.realm_name, "id": client_id, "role-name": role_name} data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path)) @@ -1517,11 +1527,11 @@ class KeycloakAdmin: def get_realm_role(self, role_name): """Get realm role by role name. - :param role_name: role's name, not id! - RoleRepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation - :return: role_id + + :param role_name: role's name, not id! + :return: Keycloak server response (RoleRepresentation) """ params_path = {"realm-name": self.realm_name, "role-name": role_name} data_raw = self.raw_get( @@ -1748,15 +1758,17 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakGetError) - def get_composite_realm_roles_of_user(self, user_id): + def get_composite_realm_roles_of_user(self, user_id, brief_representation=True): """Get all composite (i.e. implicit) realm roles for a user. :param user_id: id of user + :param brief_representation: whether to omit role attributes in the response :return: Keycloak server response (array RoleRepresentation) """ params_path = {"realm-name": self.realm_name, "id": user_id} + params = {"briefRepresentation": brief_representation} data_raw = self.raw_get( - urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path) + urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), **params ) return raise_error_from_response(data_raw, KeycloakGetError) @@ -1790,14 +1802,18 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) - def get_group_realm_roles(self, group_id): + def get_group_realm_roles(self, group_id, brief_representation=True): """Get all realm roles for a group. :param user_id: id of the group + :param brief_representation: whether to omit role attributes in the response :return: Keycloak server response (array RoleRepresentation) """ params_path = {"realm-name": self.realm_name, "id": group_id} - data_raw = self.raw_get(urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path)) + params = {"briefRepresentation": brief_representation} + data_raw = self.raw_get( + urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), **params + ) return raise_error_from_response(data_raw, KeycloakGetError) def assign_group_client_roles(self, group_id, client_id, roles): @@ -1865,20 +1881,24 @@ class KeycloakAdmin: urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, user_id, client_id ) - def get_composite_client_roles_of_user(self, user_id, client_id): + def get_composite_client_roles_of_user(self, user_id, client_id, brief_representation=False): """Get composite client role-mappings for a user. :param user_id: id of user :param client_id: id of client (not client-id) + :param brief_representation: whether to omit attributes in the response :return: Keycloak server response (array RoleRepresentation) """ + params = {"briefRepresentation": brief_representation} return self._get_client_roles_of_user( - urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id + urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id, **params ) - def _get_client_roles_of_user(self, client_level_role_mapping_url, user_id, client_id): + def _get_client_roles_of_user( + self, client_level_role_mapping_url, user_id, client_id, **params + ): params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id} - data_raw = self.raw_get(client_level_role_mapping_url.format(**params_path)) + data_raw = self.raw_get(client_level_role_mapping_url.format(**params_path), **params) return raise_error_from_response(data_raw, KeycloakGetError) def delete_client_roles_of_user(self, user_id, client_id, roles): @@ -2913,19 +2933,22 @@ class KeycloakAdmin: ) return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) - def get_composite_client_roles_of_group(self, client_id, group_id): + def get_composite_client_roles_of_group(self, client_id, group_id, brief_representation=True): """Get the composite client roles of the given group for the given client. :param client_id: id of the client. :type client_id: str :param group_id: id of the group. :type group_id: str + :param brief_representation: whether to omit attributes in the response + :type brief_representation: bool :return: the composite client roles of the group (list of RoleRepresentation). :rtype: list """ params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id} + params = {"briefRepresentation": brief_representation} data_raw = self.raw_get( - urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path) + urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), **params ) return raise_error_from_response(data_raw, KeycloakGetError) diff --git a/tests/test_keycloak_admin.py b/tests/test_keycloak_admin.py index 0f5af95..327ea39 100644 --- a/tests/test_keycloak_admin.py +++ b/tests/test_keycloak_admin.py @@ -1030,6 +1030,63 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str): assert err.match('404: b\'{"error":"Could not find role"}\'') +@pytest.mark.parametrize( + "testcase, arg_brief_repr, includes_attributes", + [ + ("brief True", {"brief_representation": True}, False), + ("brief False", {"brief_representation": False}, True), + ("default", {}, False), + ], +) +def test_role_attributes( + admin: KeycloakAdmin, + realm: str, + client: str, + arg_brief_repr: dict, + includes_attributes: bool, + testcase: str, +): + """Test getting role attributes for bulk calls.""" + # setup + attribute_role = "test-realm-role-w-attr" + test_attrs = {"attr1": ["val1"], "attr2": ["val2-1", "val2-2"]} + role_id = admin.create_realm_role( + payload={"name": attribute_role, "attributes": test_attrs}, + skip_exists=True, + ) + assert role_id, role_id + + cli_role_id = admin.create_client_role( + client, + payload={"name": attribute_role, "attributes": test_attrs}, + skip_exists=True, + ) + assert cli_role_id, cli_role_id + + if not includes_attributes: + test_attrs = None + + # tests + roles = admin.get_realm_roles(**arg_brief_repr) + roles_filtered = [role for role in roles if role["name"] == role_id] + assert roles_filtered, roles_filtered + role = roles_filtered[0] + assert role.get("attributes") == test_attrs, testcase + + roles = admin.get_client_roles(client, **arg_brief_repr) + roles_filtered = [role for role in roles if role["name"] == cli_role_id] + assert roles_filtered, roles_filtered + role = roles_filtered[0] + assert role.get("attributes") == test_attrs, testcase + + # cleanup + res = admin.delete_realm_role(role_name=attribute_role) + assert res == dict(), res + + res = admin.delete_client_role(client, role_name=attribute_role) + assert res == dict(), res + + def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str): """Test client realm roles.""" admin.realm_name = realm