From e549341196d131a1ef321ada7f7d14b5cf688325 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 15 Aug 2023 21:50:37 +0200 Subject: [PATCH] Secured config endpoints #334 --- kdb-bot/src/bot_graphql/abc/query_abc.py | 28 +++++++++++++++++-- .../src/bot_graphql/model/serverConfig.gql | 2 ++ .../bot_graphql/model/technicianConfig.gql | 2 ++ .../mutations/server_config_mutation.py | 6 +++- .../mutations/technician_config_mutation.py | 9 +++++- .../queries/server_config_query.py | 7 +++-- .../technician_config_history_query.py | 8 ++++-- .../queries/technician_config_query.py | 6 ++-- .../modules/shared/guards/auth/auth.guard.ts | 7 +++-- .../app/services/sidebar/sidebar.service.ts | 11 +++++--- 10 files changed, 67 insertions(+), 19 deletions(-) diff --git a/kdb-bot/src/bot_graphql/abc/query_abc.py b/kdb-bot/src/bot_graphql/abc/query_abc.py index a0435218..22db1896 100644 --- a/kdb-bot/src/bot_graphql/abc/query_abc.py +++ b/kdb-bot/src/bot_graphql/abc/query_abc.py @@ -9,6 +9,7 @@ from cpl_query.extension import List from bot_api.exception.service_error_code_enum import ServiceErrorCode from bot_api.exception.service_exception import ServiceException from bot_api.route.route import Route +from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_user import AuthUser from bot_data.model.auto_role import AutoRole @@ -17,6 +18,7 @@ from bot_data.model.client import Client from bot_data.model.known_user import KnownUser from bot_data.model.level import Level from bot_data.model.server import Server +from bot_data.model.server_config import ServerConfig from bot_data.model.user import User from bot_data.model.user_joined_game_server import UserJoinedGameServer from bot_data.model.user_joined_server import UserJoinedServer @@ -66,8 +68,12 @@ class QueryABC(ObjectType): self.set_field(f"{name}s", wrapper) self.set_field(f"{name}Count", lambda *args: wrapper(*args).count()) - @ServiceProviderABC.inject - def _can_user_see_element(self, user: AuthUser, element: T, services: ServiceProviderABC) -> bool: + def _can_user_see_element(self, user: AuthUser, element: T) -> bool: + @ServiceProviderABC.inject + def get_services(services: ServiceProviderABC) -> ServiceProviderABC: + return services + + services = get_services() permissions: PermissionService = services.get_service(PermissionService) bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC) @@ -158,6 +164,24 @@ class QueryABC(ObjectType): access = True break + elif type(element) == ServerConfig: + for u in user.users: + u: User = u + guild = bot.get_guild(u.server.discord_id) + member = guild.get_member(u.discord_id) + if permissions.is_member_technician(member): + access = True + break + + elif type(element) == dict and "key" in element and element["key"] in [e.value for e in FeatureFlagsEnum]: + for u in user.users: + u: User = u + guild = bot.get_guild(u.server.discord_id) + member = guild.get_member(u.discord_id) + if permissions.is_member_technician(member): + access = True + break + return access @ServiceProviderABC.inject diff --git a/kdb-bot/src/bot_graphql/model/serverConfig.gql b/kdb-bot/src/bot_graphql/model/serverConfig.gql index 0f327ae2..1734114a 100644 --- a/kdb-bot/src/bot_graphql/model/serverConfig.gql +++ b/kdb-bot/src/bot_graphql/model/serverConfig.gql @@ -13,6 +13,7 @@ type ServerConfig implements TableWithHistoryQuery { helpVoiceChannelId: String teamChannelId: String loginMessageChannelId: String + featureFlagCount: Int featureFlags: [FeatureFlag] afkChannelIds: [String] @@ -42,6 +43,7 @@ type ServerConfigHistory implements HistoryTableQuery { helpVoiceChannelId: String teamChannelId: String loginMessageChannelId: String + featureFlagCount: Int featureFlags: [FeatureFlag] serverId: ID diff --git a/kdb-bot/src/bot_graphql/model/technicianConfig.gql b/kdb-bot/src/bot_graphql/model/technicianConfig.gql index 0983c25b..dd91ceab 100644 --- a/kdb-bot/src/bot_graphql/model/technicianConfig.gql +++ b/kdb-bot/src/bot_graphql/model/technicianConfig.gql @@ -4,6 +4,7 @@ type TechnicianConfig implements TableWithHistoryQuery { waitForRestart: Int waitForShutdown: Int cacheMaxMessages: Int + featureFlagCount: Int featureFlags: [FeatureFlag] pingURLs: [String] technicianIds: [String] @@ -22,6 +23,7 @@ type TechnicianConfigHistory implements HistoryTableQuery { waitForRestart: Int waitForShutdown: Int cacheMaxMessages: Int + featureFlagCount: Int featureFlags: [FeatureFlag] deleted: Boolean diff --git a/kdb-bot/src/bot_graphql/mutations/server_config_mutation.py b/kdb-bot/src/bot_graphql/mutations/server_config_mutation.py index df8996e2..220dfd36 100644 --- a/kdb-bot/src/bot_graphql/mutations/server_config_mutation.py +++ b/kdb-bot/src/bot_graphql/mutations/server_config_mutation.py @@ -3,6 +3,7 @@ from cpl_discord.service import DiscordBotServiceABC from cpl_query.extension import List from bot_api.logging.api_logger import ApiLogger +from bot_api.route.route import Route from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.model.server_afk_channel_ids_config import ServerAFKChannelIdsConfig @@ -37,7 +38,7 @@ class ServerConfigMutation(QueryABC): raise ValueError("Id not set") server_config = self._server_configs.get_server_config_by_server(input["id"]) - self._can_user_mutate_data(server_config, UserRoleEnum.admin) + self._can_user_mutate_data(Route.get_user().users[0].server, UserRoleEnum.technician) server_config.message_delete_timer = ( input["messageDeleteTimer"] if "messageDeleteTimer" in input else server_config.message_delete_timer @@ -166,3 +167,6 @@ class ServerConfigMutation(QueryABC): continue self._server_configs.add_server_team_role_id_config(role_id) + + # todo reload config + # reload permissions diff --git a/kdb-bot/src/bot_graphql/mutations/technician_config_mutation.py b/kdb-bot/src/bot_graphql/mutations/technician_config_mutation.py index 1ac2e475..e0e52472 100644 --- a/kdb-bot/src/bot_graphql/mutations/technician_config_mutation.py +++ b/kdb-bot/src/bot_graphql/mutations/technician_config_mutation.py @@ -3,6 +3,7 @@ from cpl_discord.service import DiscordBotServiceABC from cpl_query.extension import List from bot_api.logging.api_logger import ApiLogger +from bot_api.route.route import Route from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.technician_config_repository_abc import TechnicianConfigRepositoryABC from bot_data.model.technician_config import TechnicianConfig @@ -10,6 +11,7 @@ from bot_data.model.technician_id_config import TechnicianIdConfig from bot_data.model.technician_ping_url_config import TechnicianPingUrlConfig from bot_data.model.user_role_enum import UserRoleEnum from bot_graphql.abc.query_abc import QueryABC +from modules.permission.abc.permission_service_abc import PermissionServiceABC class TechnicianConfigMutation(QueryABC): @@ -20,6 +22,7 @@ class TechnicianConfigMutation(QueryABC): servers: ServerRepositoryABC, technician_configs: TechnicianConfigRepositoryABC, db: DatabaseContextABC, + permissions: PermissionServiceABC, ): QueryABC.__init__(self, "TechnicianConfigMutation") @@ -28,12 +31,13 @@ class TechnicianConfigMutation(QueryABC): self._servers = servers self._technician_configs = technician_configs self._db = db + self._permissions = permissions self.set_field("updateTechnicianConfig", self.resolve_update_technician_config) def resolve_update_technician_config(self, *_, input: dict): technician_config = self._technician_configs.get_technician_config() - self._can_user_mutate_data(technician_config, UserRoleEnum.admin) + self._can_user_mutate_data(Route.get_user().users[0].server, UserRoleEnum.technician) technician_config.help_command_reference_url = ( input["helpCommandReferenceUrl"] @@ -105,3 +109,6 @@ class TechnicianConfigMutation(QueryABC): continue self._technician_configs.add_technician_id_config(TechnicianIdConfig(technician_id)) + + # todo reload config + self._permissions.on_ready() diff --git a/kdb-bot/src/bot_graphql/queries/server_config_query.py b/kdb-bot/src/bot_graphql/queries/server_config_query.py index 189a86ee..73e61828 100644 --- a/kdb-bot/src/bot_graphql/queries/server_config_query.py +++ b/kdb-bot/src/bot_graphql/queries/server_config_query.py @@ -1,4 +1,5 @@ from cpl_core.database.context import DatabaseContextABC +from cpl_query.extension import List from bot_data.model.server_config_history import ServerConfigHistory from bot_data.model.team_member_type_enum import TeamMemberTypeEnum @@ -23,9 +24,9 @@ class ServerConfigQuery(DataQueryWithHistoryABC): self.set_field("helpVoiceChannelId", lambda config, *_: config.help_voice_channel_id) self.set_field("teamChannelId", lambda config, *_: config.team_channel_id) self.set_field("loginMessageChannelId", lambda config, *_: config.login_message_channel_id) - self.set_field( - "featureFlags", - lambda config, *_: [{"key": x, "value": config.feature_flags[x]} for x in config.feature_flags], + self.add_collection( + "featureFlag", + lambda config, *_: List(any, [{"key": x, "value": config.feature_flags[x]} for x in config.feature_flags]), ) self.set_field("afkChannelIds", lambda config, *_: config.afk_channel_ids) self.set_field( diff --git a/kdb-bot/src/bot_graphql/queries/technician_config_history_query.py b/kdb-bot/src/bot_graphql/queries/technician_config_history_query.py index ccdfc6df..a63f88f8 100644 --- a/kdb-bot/src/bot_graphql/queries/technician_config_history_query.py +++ b/kdb-bot/src/bot_graphql/queries/technician_config_history_query.py @@ -1,3 +1,5 @@ +from cpl_query.extension import List + from bot_graphql.abc.history_query_abc import HistoryQueryABC @@ -9,7 +11,7 @@ class TechnicianConfigHistoryQuery(HistoryQueryABC): self.set_field("waitForRestart", lambda config, *_: config.wait_for_restart) self.set_field("waitForShutdown", lambda config, *_: config.wait_for_shutdown) self.set_field("cacheMaxMessages", lambda config, *_: config.cache_max_messages) - self.set_field( - "featureFlags", - lambda config, *_: [{"key": x, "value": config.feature_flags[x]} for x in config.feature_flags], + self.add_collection( + "featureFlag", + lambda config, *_: List(any, [{"key": x, "value": config.feature_flags[x]} for x in config.feature_flags]), ) diff --git a/kdb-bot/src/bot_graphql/queries/technician_config_query.py b/kdb-bot/src/bot_graphql/queries/technician_config_query.py index e97707df..2d8bb8f9 100644 --- a/kdb-bot/src/bot_graphql/queries/technician_config_query.py +++ b/kdb-bot/src/bot_graphql/queries/technician_config_query.py @@ -16,9 +16,9 @@ class TechnicianConfigQuery(DataQueryWithHistoryABC): self.set_field("waitForRestart", lambda config, *_: config.wait_for_restart) self.set_field("waitForShutdown", lambda config, *_: config.wait_for_shutdown) self.set_field("cacheMaxMessages", lambda config, *_: config.cache_max_messages) - self.set_field( - "featureFlags", - lambda config, *_: [{"key": x, "value": config.feature_flags[x]} for x in config.feature_flags], + self.add_collection( + "featureFlag", + lambda config, *_: List(any, [{"key": x, "value": config.feature_flags[x]} for x in config.feature_flags]), ) self.set_field("pingURLs", lambda config, *_: config.ping_urls) self.set_field("technicianIds", lambda config, *_: config.technician_ids) diff --git a/kdb-web/src/app/modules/shared/guards/auth/auth.guard.ts b/kdb-web/src/app/modules/shared/guards/auth/auth.guard.ts index deb1be40..4711dad9 100644 --- a/kdb-web/src/app/modules/shared/guards/auth/auth.guard.ts +++ b/kdb-web/src/app/modules/shared/guards/auth/auth.guard.ts @@ -28,8 +28,11 @@ export class AuthGuard implements CanActivate { const role = route.data['role']; if (role) { - this.authService.hasUserPermission(role).then(hasPermission => { - if (!hasPermission) { + this.authService.hasUserPermission(role).then(async hasPermission => { + let authUser = await this.authService.getLoggedInUser(); + let isTechnician = authUser?.users?.map(u => u.isTechnician).filter(u => u) ?? []; + + if (!hasPermission && !isTechnician) { this.router.navigate(['/dashboard']); return false; } diff --git a/kdb-web/src/app/services/sidebar/sidebar.service.ts b/kdb-web/src/app/services/sidebar/sidebar.service.ts index 5654a30e..c4d1a102 100644 --- a/kdb-web/src/app/services/sidebar/sidebar.service.ts +++ b/kdb-web/src/app/services/sidebar/sidebar.service.ts @@ -67,7 +67,7 @@ export class SidebarService { } } - async buildMenu(user: UserDTO | null, hasPermission: boolean) { + async buildMenu(user: UserDTO | null, hasPermission: boolean, isTechnician: boolean = false) { this.dashboard = { label: this.isSidebarOpen ? this.translateService.instant("sidebar.dashboard") : "", icon: "pi pi-th-large", @@ -127,18 +127,20 @@ export class SidebarService { }; this.adminConfig = { label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "", + visible: hasPermission || isTechnician, icon: "pi pi-cog", routerLink: "/admin/settings" }; this.adminUsers = { label: this.isSidebarOpen ? this.translateService.instant("sidebar.auth_user_list") : "", + visible: hasPermission, icon: "pi pi-user-edit", routerLink: "/admin/users" }; this.adminMenu = { label: this.isSidebarOpen ? this.translateService.instant("sidebar.administration") : "", icon: "pi pi-cog", - visible: hasPermission, + visible: hasPermission || isTechnician, expanded: true, items: [this.adminConfig, this.adminUsers] }; @@ -148,9 +150,10 @@ export class SidebarService { this.authService.hasUserPermission(AuthRoles.Admin).then(async hasPermission => { let authUser = await this.authService.getLoggedInUser(); let user: UserDTO | null = authUser?.users?.find(u => u.server == this.server$.value?.id) ?? null; + let isTechnician = authUser?.users?.map(u => u.isTechnician).filter(u => u) ?? []; if (build || this.menuItems$.value.length == 0) { - await this.buildMenu(user, hasPermission); + await this.buildMenu(user, hasPermission, isTechnician.length > 0); } if (this.server$.value) { @@ -159,7 +162,7 @@ export class SidebarService { this.serverAutoRoles.visible = !!user?.isModerator; this.serverLevels.visible = !!user?.isModerator; this.serverAchievements.visible = !!user?.isModerator; - this.serverConfig.visible = !!user?.isAdmin; + this.serverConfig.visible = !!user?.isAdmin || isTechnician.length > 0; } else { this.serverMenu.visible = false; }