From cb3cd05cf2628eb680a4363ebbb7952917f58dd2 Mon Sep 17 00:00:00 2001 From: edraft Date: Sat, 11 Jan 2025 14:30:54 +0100 Subject: [PATCH] Keycloak role management Closes #10 --- api/src/api/route_user_extension.py | 55 +++++++++++++++++++++++++++-- web/src/assets/i18n/de.json | 1 + web/src/assets/i18n/en.json | 1 + 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/api/src/api/route_user_extension.py b/api/src/api/route_user_extension.py index acfca18..2240f9f 100644 --- a/api/src/api/route_user_extension.py +++ b/api/src/api/route_user_extension.py @@ -52,7 +52,11 @@ class RouteUserExtension: if not user_id: return None - return await userDao.find_by_keycloak_id(user_id) + user = await userDao.find_by_keycloak_id(user_id) + if user is None: + return None + + return user @classmethod async def get_dev_user(cls) -> Optional[User]: @@ -67,6 +71,50 @@ class RouteUserExtension: user = await cls.get_dev_user() return user + @classmethod + def _flatten_groups(cls, groups): + flat_list = [] + for group in groups: + flat_list.append(group) + if 'subGroups' in group and group['subGroups']: + flat_list.extend(cls._flatten_groups(group['subGroups'])) + return flat_list + + @classmethod + async def _map_keycloak_groups_with_roles(cls, user: User): + try: + roles = {x.name: x for x in await roleDao.get_all()} + groups = cls._flatten_groups(Keycloak.admin.get_groups(full_hierarchy=True)) + groups_with_role = [x["name"] for x in groups if x["name"] in roles.keys()] + + user_groups_with_role = [ + x["name"] for x in Keycloak.admin.get_user_groups(user.keycloak_id) if x["name"] in roles.keys() + ] + user_roles = set(x.name for x in await user.roles if x.name in groups_with_role) + missing_groups = set(user_groups_with_role) - set(user_roles) + missing_roles = set(user_roles) - set(user_groups_with_role) + + if len(missing_groups) > 0: + await roleUserDao.create_many( + [ + RoleUser(0, (await roleDao.get_by_name(group)).id, user.id) + for group in missing_groups + ] + ) + + if len(missing_roles) > 0: + await roleUserDao.delete_many( + [ + await roleUserDao.get_single_by([ + {RoleUser.role_id: roles[role].id}, + {RoleUser.user_id: user.id}, + ]) + for role in missing_roles + ] + ) + except Exception as e: + logger.error("Failed to map user groups", e) + @classmethod async def _create_user(cls, kc_user: KeycloakUser): try: @@ -101,8 +149,11 @@ class RouteUserExtension: user = await cls.get_user() if user is None: - await cls._create_user(KeycloakUser(user_info)) + u_id = await cls._create_user(KeycloakUser(user_info)) + await cls._map_keycloak_groups_with_roles(await userDao.get_by_id(u_id)) return True + else: + await cls._map_keycloak_groups_with_roles(user) if user.deleted: return False diff --git a/web/src/assets/i18n/de.json b/web/src/assets/i18n/de.json index 6d40c4a..9f6c4e7 100644 --- a/web/src/assets/i18n/de.json +++ b/web/src/assets/i18n/de.json @@ -150,6 +150,7 @@ "update": "Bearbeiten" }, "user": { + "assign_roles": "Rollen zuweisen", "count_header": "Benutzer", "email": "E-Mail", "user": "Benutzer", diff --git a/web/src/assets/i18n/en.json b/web/src/assets/i18n/en.json index 27c6fde..dac8071 100644 --- a/web/src/assets/i18n/en.json +++ b/web/src/assets/i18n/en.json @@ -150,6 +150,7 @@ "update": "Update" }, "user": { + "assign_roles": "Assign roles", "count_header": "User(s)", "email": "E-Mail", "user": "User",