From 7f14aff1bdae4e55e513114ccc9cbe3ae2b57834 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 9 Feb 2023 20:22:46 +0100 Subject: [PATCH] Added api-key commands #162-3 --- kdb-bot/src/bot/translation/de.json | 12 +- .../bot_data/abc/api_key_repository_abc.py | 4 + .../bot_data/migration/api_key_migration.py | 3 +- .../migration/auto_role_fix1_migration.py | 2 +- kdb-bot/src/bot_data/model/api_key.py | 9 ++ .../service/api_key_repository_service.py | 11 +- .../technician/command/api_key_group.py | 139 ++++++++++++++++++ .../modules/technician/technician_module.py | 4 +- 8 files changed, 174 insertions(+), 10 deletions(-) create mode 100644 kdb-bot/src/modules/technician/command/api_key_group.py diff --git a/kdb-bot/src/bot/translation/de.json b/kdb-bot/src/bot/translation/de.json index 17524fc7..25c1301e 100644 --- a/kdb-bot/src/bot/translation/de.json +++ b/kdb-bot/src/bot/translation/de.json @@ -283,7 +283,17 @@ "technician": { "restart_message": "Bin gleich wieder da :D", "shutdown_message": "Trauert nicht um mich, es war eine logische Entscheidung. Das Wohl von Vielen, es wiegt schwerer als das Wohl von Wenigen oder eines Einzelnen. Ich war es und ich werde es immer sein, Euer Freund. Lebt lange und in Frieden :)", - "log_message": "Hier sind deine Logdateien! :)" + "log_message": "Hier sind deine Logdateien! :)", + "api_key": { + "get": "API-Schlüssel für {}: {}", + "add": { + "success": "API-Schlüssel für {} wurde erstellt: {}" + }, + "remove": { + "not_found": "API-Schlüssel konnte nicht gefunden werden!", + "success": "API-Schlüssel wurde entfernt :D" + } + } } }, "api": { diff --git a/kdb-bot/src/bot_data/abc/api_key_repository_abc.py b/kdb-bot/src/bot_data/abc/api_key_repository_abc.py index 13a97464..c21d22df 100644 --- a/kdb-bot/src/bot_data/abc/api_key_repository_abc.py +++ b/kdb-bot/src/bot_data/abc/api_key_repository_abc.py @@ -18,6 +18,10 @@ class ApiKeyRepositoryABC(ABC): def get_api_key(self, identifier: str, key: str) -> ApiKey: pass + @abstractmethod + def get_api_key_by_key(self, key: str) -> ApiKey: + pass + @abstractmethod def add_api_key(self, api_key: ApiKey): pass diff --git a/kdb-bot/src/bot_data/migration/api_key_migration.py b/kdb-bot/src/bot_data/migration/api_key_migration.py index 5c9808a4..df4c603a 100644 --- a/kdb-bot/src/bot_data/migration/api_key_migration.py +++ b/kdb-bot/src/bot_data/migration/api_key_migration.py @@ -27,7 +27,8 @@ class ApiKeyMigration(MigrationABC): `LastModifiedAt` DATETIME(6), PRIMARY KEY(`Id`), FOREIGN KEY (`CreatorId`) REFERENCES `Users`(`UserId`), - CONSTRAINT UC_Identifier_Key UNIQUE (`Identifier`,`Key`) + CONSTRAINT UC_Identifier_Key UNIQUE (`Identifier`,`Key`), + CONSTRAINT UC_Key UNIQUE (`Key`) ); """ ) diff --git a/kdb-bot/src/bot_data/migration/auto_role_fix1_migration.py b/kdb-bot/src/bot_data/migration/auto_role_fix1_migration.py index 7a5766c2..70a8e30e 100644 --- a/kdb-bot/src/bot_data/migration/auto_role_fix1_migration.py +++ b/kdb-bot/src/bot_data/migration/auto_role_fix1_migration.py @@ -4,7 +4,7 @@ from bot_data.db_context import DBContext class AutoRoleFix1Migration(MigrationABC): - name = "0.3.0_AutoRoleMigration" + name = "0.3.0_AutoRoleFixMigration" def __init__(self, logger: DatabaseLogger, db: DBContext): MigrationABC.__init__(self) diff --git a/kdb-bot/src/bot_data/model/api_key.py b/kdb-bot/src/bot_data/model/api_key.py index 19a14210..e0e0de9d 100644 --- a/kdb-bot/src/bot_data/model/api_key.py +++ b/kdb-bot/src/bot_data/model/api_key.py @@ -54,6 +54,15 @@ class ApiKey(TableABC): """ ) + @staticmethod + def get_select_by_key(key: str) -> str: + return str( + f""" + SELECT * FROM `ApiKeys` + WHERE `Key` = '{key}'; + """ + ) + @property def insert_string(self) -> str: return str( diff --git a/kdb-bot/src/bot_data/service/api_key_repository_service.py b/kdb-bot/src/bot_data/service/api_key_repository_service.py index dfa7a833..1825dae8 100644 --- a/kdb-bot/src/bot_data/service/api_key_repository_service.py +++ b/kdb-bot/src/bot_data/service/api_key_repository_service.py @@ -1,7 +1,6 @@ from typing import Optional from cpl_core.database.context import DatabaseContextABC -from cpl_core.utils import CredentialManager from cpl_query.extension import List from bot_core.logging.database_logger import DatabaseLogger @@ -32,10 +31,6 @@ class ApiKeyRepositoryService(ApiKeyRepositoryABC): return value def _api_key_from_result(self, sql_result: tuple) -> ApiKey: - code = self._get_value_from_result(sql_result[3]) - if code is not None: - code = CredentialManager.decrypt(code) - api_key = ApiKey( self._get_value_from_result(sql_result[1]), self._get_value_from_result(sql_result[2]), @@ -58,7 +53,11 @@ class ApiKeyRepositoryService(ApiKeyRepositoryABC): def get_api_key(self, identifier: str, key: str) -> ApiKey: self._logger.trace(__name__, f"Send SQL command: {ApiKey.get_select_string(identifier, key)}") - return self._get_value_from_result(self._context.select(ApiKey.get_select_string(identifier, key))[0]) + return self._api_key_from_result(self._context.select(ApiKey.get_select_string(identifier, key))[0]) + + def get_api_key_by_key(self, key: str) -> ApiKey: + self._logger.trace(__name__, f"Send SQL command: {ApiKey.get_select_by_key(key)}") + return self._api_key_from_result(self._context.select(ApiKey.get_select_by_key(key))[0]) def add_api_key(self, api_key: ApiKey): self._logger.trace(__name__, f"Send SQL command: {api_key.insert_string}") diff --git a/kdb-bot/src/modules/technician/command/api_key_group.py b/kdb-bot/src/modules/technician/command/api_key_group.py new file mode 100644 index 00000000..1f7f0c0d --- /dev/null +++ b/kdb-bot/src/modules/technician/command/api_key_group.py @@ -0,0 +1,139 @@ +import hashlib +import uuid +from typing import List as TList + +import discord +from cpl_core.database.context import DatabaseContextABC +from cpl_discord.command import DiscordCommandABC +from cpl_discord.service import DiscordBotServiceABC +from cpl_translation import TranslatePipe +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + +from bot_api.configuration.authentication_settings import AuthenticationSettings +from bot_core.abc.client_utils_abc import ClientUtilsABC +from bot_core.abc.message_service_abc import MessageServiceABC +from bot_core.helper.command_checks import CommandChecks +from bot_core.logging.command_logger import CommandLogger +from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC +from bot_data.abc.server_repository_abc import ServerRepositoryABC +from bot_data.abc.user_repository_abc import UserRepositoryABC +from bot_data.model.api_key import ApiKey +from modules.permission.abc.permission_service_abc import PermissionServiceABC + + +class ApiKeyGroup(DiscordCommandABC): + def __init__( + self, + logger: CommandLogger, + auth_settings: AuthenticationSettings, + message_service: MessageServiceABC, + bot: DiscordBotServiceABC, + client_utils: ClientUtilsABC, + permission_service: PermissionServiceABC, + translate: TranslatePipe, + db: DatabaseContextABC, + servers: ServerRepositoryABC, + users: UserRepositoryABC, + api_keys: ApiKeyRepositoryABC, + ): + DiscordCommandABC.__init__(self) + + self._logger = logger + self._auth_settings = auth_settings + self._message_service = message_service + self._bot = bot + self._client_utils = client_utils + self._permissions = permission_service + self._t = translate + self._db = db + self._servers = servers + self._users = users + self._api_keys = api_keys + + def _get_api_key_str(self, api_key: ApiKey) -> str: + return hashlib.sha256( + f"{api_key.identifier}:{api_key.key}+{self._auth_settings.secret_key}".encode("utf-8") + ).hexdigest() + + @commands.hybrid_group(name="api-key") + @commands.guild_only() + async def api_key(self, ctx: Context): + pass + + @api_key.command() + @commands.guild_only() + @CommandChecks.check_is_ready() + @CommandChecks.check_is_member_technician() + async def get(self, ctx: Context, key: str, wait: int = None): + self._logger.debug(__name__, f"Received command api-key get {ctx}: {key},{wait}") + + api_key = self._api_keys.get_api_key_by_key(key) + await self._message_service.send_ctx_msg( + ctx, + self._t.transform("modules.technician.api_key.get").format( + api_key.identifier, self._get_api_key_str(api_key) + ), + ) + self._logger.trace(__name__, f"Finished command api-key get") + + @get.autocomplete("key") + async def get_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + keys = self._api_keys.get_api_keys() + + return [ + app_commands.Choice(name=f"{key.identifier}: {key.key}", value=key.key) + for key in self._client_utils.get_auto_complete_list(keys, current, lambda x: x.key) + ] + + @api_key.command() + @commands.guild_only() + @CommandChecks.check_is_ready() + @CommandChecks.check_is_member_moderator() + async def add(self, ctx: Context, identifier: str): + self._logger.debug(__name__, f"Received command api-key add {ctx}: {identifier}") + + server = self._servers.get_server_by_discord_id(ctx.guild.id) + user = self._users.get_user_by_discord_id_and_server_id(ctx.author.id, server.server_id) + api_key = ApiKey(identifier, str(uuid.uuid4()), user) + self._api_keys.add_api_key(api_key) + self._db.save_changes() + await self._message_service.send_ctx_msg( + ctx, + self._t.transform("modules.technician.api_key.add.success").format( + identifier, self._get_api_key_str(api_key) + ), + ) + + self._logger.trace(__name__, f"Finished command api-key add") + + @api_key.command() + @commands.guild_only() + @CommandChecks.check_is_ready() + @CommandChecks.check_is_member_moderator() + async def remove(self, ctx: Context, key: str): + self._logger.debug(__name__, f"Received command api-key remove {ctx}: {key}") + + keys = self._api_keys.get_api_keys().where(lambda x: x.key == key) + if keys.count() < 1: + await self._message_service.send_ctx_msg( + ctx, + self._t.transform("modules.technician.api_key.remove.not_found"), + ) + + api_key = keys.single() + self._api_keys.delete_api_key(api_key) + self._db.save_changes() + await self._message_service.send_ctx_msg(ctx, self._t.transform("modules.technician.api_key.remove.success")) + + self._logger.trace(__name__, f"Finished command api-key remove") + + @remove.autocomplete("key") + async def set_autocomplete(self, interaction: discord.Interaction, current: str) -> TList[app_commands.Choice[str]]: + keys = self._api_keys.get_api_keys() + + return [ + app_commands.Choice(name=f"{key.identifier}: {key.key}", value=key.key) + for key in self._client_utils.get_auto_complete_list(keys, current, lambda x: x.key) + ] diff --git a/kdb-bot/src/modules/technician/technician_module.py b/kdb-bot/src/modules/technician/technician_module.py index 9a9ca7ba..ab3e2210 100644 --- a/kdb-bot/src/modules/technician/technician_module.py +++ b/kdb-bot/src/modules/technician/technician_module.py @@ -6,10 +6,11 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC from bot_core.abc.module_abc import ModuleABC from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from modules.base.abc.base_helper_abc import BaseHelperABC +from modules.base.service.base_helper_service import BaseHelperService +from modules.technician.command.api_key_group import ApiKeyGroup from modules.technician.command.log_command import LogCommand from modules.technician.command.restart_command import RestartCommand from modules.technician.command.shutdown_command import ShutdownCommand -from modules.base.service.base_helper_service import BaseHelperService class TechnicianModule(ModuleABC): @@ -25,4 +26,5 @@ class TechnicianModule(ModuleABC): self._dc.add_command(RestartCommand) self._dc.add_command(ShutdownCommand) self._dc.add_command(LogCommand) + self._dc.add_command(ApiKeyGroup) # events