1.0.0 #253
| @@ -83,6 +83,10 @@ class AuthServiceABC(ABC): | ||||
|     async def verify_login(self, token_str: str) -> bool: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def verify_api_key(self, api_key: str) -> bool: | ||||
|         pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: | ||||
|         pass | ||||
|   | ||||
| @@ -13,7 +13,7 @@ from bot_api.api import Api | ||||
| from bot_api.api_thread import ApiThread | ||||
| from bot_api.controller.auth_controller import AuthController | ||||
| from bot_api.controller.auth_discord_controller import AuthDiscordController | ||||
| from bot_api.controller.grahpql_controller import GraphQLController | ||||
| from bot_api.controller.graphql_controller import GraphQLController | ||||
| from bot_api.controller.gui_controller import GuiController | ||||
| from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent | ||||
| from bot_api.service.auth_service import AuthService | ||||
|   | ||||
| @@ -33,7 +33,7 @@ class GraphQLController: | ||||
|         return PLAYGROUND_HTML, 200 | ||||
| 
 | ||||
|     @Route.post(f"{BasePath}") | ||||
|     @Route.authorize | ||||
|     @Route.authorize(by_api_key=True) | ||||
|     async def graphql(self): | ||||
|         data = request.get_json() | ||||
| 
 | ||||
| @@ -30,15 +30,32 @@ class Route: | ||||
|         cls._env = env.environment_name | ||||
|  | ||||
|     @classmethod | ||||
|     def authorize(cls, f: Callable = None, role: AuthRoleEnum = None, skip_in_dev=False): | ||||
|     def authorize(cls, f: Callable = None, role: AuthRoleEnum = None, skip_in_dev=False, by_api_key=False): | ||||
|         if f is None: | ||||
|             return functools.partial(cls.authorize, role=role, skip_in_dev=skip_in_dev) | ||||
|             return functools.partial(cls.authorize, role=role, skip_in_dev=skip_in_dev, by_api_key=by_api_key) | ||||
|  | ||||
|         @wraps(f) | ||||
|         async def decorator(*args, **kwargs): | ||||
|             if skip_in_dev and cls._env == "development": | ||||
|                 return await f(*args, **kwargs) | ||||
|  | ||||
|             if "Authorization" not in request.headers and by_api_key and "API-Key" in request.headers: | ||||
|                 valid = False | ||||
|                 try: | ||||
|                     valid = cls._auth.verify_api_key(request.headers["API-Key"]) | ||||
|                 except ServiceException as e: | ||||
|                     error = ErrorDTO(e.error_code, e.message) | ||||
|                     return jsonify(error.to_dict()), 403 | ||||
|                 except Exception as e: | ||||
|                     return jsonify(e), 500 | ||||
|  | ||||
|                 if not valid: | ||||
|                     ex = ServiceException(ServiceErrorCode.Unauthorized, f"API-Key invalid") | ||||
|                     error = ErrorDTO(ex.error_code, ex.message) | ||||
|                     return jsonify(error.to_dict()), 401 | ||||
|  | ||||
|                 return await f(*args, **kwargs) | ||||
|  | ||||
|             token = None | ||||
|             if "Authorization" in request.headers: | ||||
|                 bearer = request.headers.get("Authorization") | ||||
|   | ||||
| @@ -31,9 +31,11 @@ from bot_api.model.reset_password_dto import ResetPasswordDTO | ||||
| from bot_api.model.token_dto import TokenDTO | ||||
| from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO | ||||
| from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT | ||||
| from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC | ||||
| from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC | ||||
| 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 bot_data.model.auth_role_enum import AuthRoleEnum | ||||
| from bot_data.model.auth_user import AuthUser | ||||
| from bot_data.model.auth_user_users_relation import AuthUserUsersRelation | ||||
| @@ -49,9 +51,9 @@ class AuthService(AuthServiceABC): | ||||
|         bot: DiscordBotServiceABC, | ||||
|         db: DatabaseContextABC, | ||||
|         auth_users: AuthUserRepositoryABC, | ||||
|         api_keys: ApiKeyRepositoryABC, | ||||
|         users: UserRepositoryABC, | ||||
|         servers: ServerRepositoryABC, | ||||
|         # mailer: MailThread, | ||||
|         mailer: EMailClientABC, | ||||
|         t: TranslatePipe, | ||||
|         auth_settings: AuthenticationSettings, | ||||
| @@ -64,6 +66,7 @@ class AuthService(AuthServiceABC): | ||||
|         self._bot = bot | ||||
|         self._db = db | ||||
|         self._auth_users = auth_users | ||||
|         self._api_keys = api_keys | ||||
|         self._users = users | ||||
|         self._servers = servers | ||||
|         self._mailer = mailer | ||||
| @@ -82,6 +85,11 @@ class AuthService(AuthServiceABC): | ||||
|  | ||||
|         return False | ||||
|  | ||||
|     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() | ||||
|  | ||||
|     def generate_token(self, user: AuthUser) -> str: | ||||
|         token = jwt.encode( | ||||
|             payload={ | ||||
| @@ -221,7 +229,12 @@ class AuthService(AuthServiceABC): | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") | ||||
|  | ||||
|         user = AUT.to_db(user_dto) | ||||
|         if self._auth_users.get_all_auth_users().count() == 0: | ||||
|         if ( | ||||
|             self._auth_users.get_all_auth_users() | ||||
|             .where(lambda x: x.name != "internal" and x.email != "internal@localhost") | ||||
|             .count() | ||||
|             == 0 | ||||
|         ): | ||||
|             user.auth_role = AuthRoleEnum.admin | ||||
|  | ||||
|         user.password_salt = uuid.uuid4() | ||||
| @@ -478,6 +491,18 @@ class AuthService(AuthServiceABC): | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     def verify_api_key(self, api_key: str) -> bool: | ||||
|         try: | ||||
|             keys = self._api_keys.get_api_keys().select(self._get_api_key_str) | ||||
|  | ||||
|             if not keys.contains(api_key): | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, "API-Key invalid") | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f"Token invalid", e) | ||||
|             return False | ||||
|  | ||||
|         return True | ||||
|  | ||||
|     async def login_async(self, user_dto: AuthUser) -> TokenDTO: | ||||
|         if user_dto is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, "User not set") | ||||
|   | ||||
| @@ -22,7 +22,7 @@ class ApiKeyMigration(MigrationABC): | ||||
|                 `Id` BIGINT NOT NULL AUTO_INCREMENT, | ||||
|                 `Identifier` VARCHAR(255) NOT NULL, | ||||
|                 `Key` VARCHAR(255) NOT NULL, | ||||
|                 `CreatorId` BIGINT, | ||||
|                 `CreatorId` BIGINT NULL, | ||||
|                 `CreatedAt` DATETIME(6), | ||||
|                 `LastModifiedAt` DATETIME(6), | ||||
|                 PRIMARY KEY(`Id`), | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| from datetime import datetime | ||||
| from typing import Optional | ||||
|  | ||||
| from cpl_core.database import TableABC | ||||
|  | ||||
| @@ -10,7 +11,7 @@ class ApiKey(TableABC): | ||||
|         self, | ||||
|         identifier: str, | ||||
|         key: str, | ||||
|         creator: User, | ||||
|         creator: Optional[User], | ||||
|         created_at: datetime = None, | ||||
|         modified_at: datetime = None, | ||||
|         id=0, | ||||
| @@ -33,7 +34,7 @@ class ApiKey(TableABC): | ||||
|         return self._key | ||||
|  | ||||
|     @property | ||||
|     def creator(self) -> User: | ||||
|     def creator(self) -> Optional[User]: | ||||
|         return self._creator | ||||
|  | ||||
|     @staticmethod | ||||
| @@ -72,7 +73,7 @@ class ApiKey(TableABC): | ||||
|             ) VALUES ( | ||||
|                 '{self._identifier}', | ||||
|                 '{self._key}', | ||||
|                 '{self._creator.user_id}', | ||||
|                 {"NULL" if self._creator is None else self._creator.user_id}, | ||||
|                 '{self._created_at}', | ||||
|                 '{self._modified_at}' | ||||
|             ); | ||||
|   | ||||
| @@ -31,10 +31,11 @@ class ApiKeyRepositoryService(ApiKeyRepositoryABC): | ||||
|         return value | ||||
|  | ||||
|     def _api_key_from_result(self, sql_result: tuple) -> ApiKey: | ||||
|         creator = self._get_value_from_result(sql_result[3]) | ||||
|         api_key = ApiKey( | ||||
|             self._get_value_from_result(sql_result[1]), | ||||
|             self._get_value_from_result(sql_result[2]), | ||||
|             self._users.get_user_by_id(int(self._get_value_from_result(sql_result[3]))), | ||||
|             None if creator is None else self._users.get_user_by_id(int(creator)), | ||||
|             self._get_value_from_result(sql_result[4]), | ||||
|             self._get_value_from_result(sql_result[5]), | ||||
|             id=self._get_value_from_result(sql_result[0]), | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
| from cpl_query.extension import List | ||||
|  | ||||
| from bot_core.logging.database_logger import DatabaseLogger | ||||
| from bot_data.abc.data_seeder_abc import DataSeederABC | ||||
| @@ -18,12 +17,10 @@ class SeederService: | ||||
|  | ||||
|         self._db = db | ||||
|  | ||||
|         self._seeder = List(type, DataSeederABC.__subclasses__()) | ||||
|  | ||||
|     async def seed(self): | ||||
|         self._logger.info(__name__, f"Seed data") | ||||
|         for seeder in self._seeder: | ||||
|             seeder_as_service: DataSeederABC = self._services.get_service(seeder) | ||||
|             self._logger.debug(__name__, f"Starting seeder {seeder.__name__}") | ||||
|             await seeder_as_service.seed() | ||||
|         for seeder in self._services.get_services(list[DataSeederABC]): | ||||
|             seeder: DataSeederABC = seeder | ||||
|             self._logger.debug(__name__, f"Starting seeder {type(seeder).__name__}") | ||||
|             await seeder.seed() | ||||
|             self._db.save_changes() | ||||
|   | ||||
| @@ -8,6 +8,7 @@ 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 bot_data.abc.data_seeder_abc import DataSeederABC | ||||
| from modules.level.command.level_group import LevelGroup | ||||
| from modules.level.events.level_on_member_join_event import LevelOnMemberJoinEvent | ||||
| from modules.level.events.level_on_message_event import LevelOnMessageEvent | ||||
| @@ -29,7 +30,7 @@ class LevelModule(ModuleABC): | ||||
|         env.set_working_directory(cwd) | ||||
|  | ||||
|     def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): | ||||
|         services.add_transient(LevelSeeder) | ||||
|         services.add_transient(DataSeederABC, LevelSeeder) | ||||
|         services.add_transient(LevelService) | ||||
|  | ||||
|         # commands | ||||
|   | ||||
							
								
								
									
										44
									
								
								kdb-bot/src/modules/technician/api_key_seeder.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								kdb-bot/src/modules/technician/api_key_seeder.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
|  | ||||
| from bot_core.logging.database_logger import DatabaseLogger | ||||
| from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC | ||||
| from bot_data.abc.data_seeder_abc import DataSeederABC | ||||
| from bot_data.abc.user_repository_abc import UserRepositoryABC | ||||
| from bot_data.model.api_key import ApiKey | ||||
|  | ||||
|  | ||||
| class ApiKeySeeder(DataSeederABC): | ||||
|     def __init__( | ||||
|         self, | ||||
|         logger: DatabaseLogger, | ||||
|         config: ConfigurationABC, | ||||
|         bot: DiscordBotServiceABC, | ||||
|         db: DatabaseContextABC, | ||||
|         users: UserRepositoryABC, | ||||
|         api_keys: ApiKeyRepositoryABC, | ||||
|     ): | ||||
|         DataSeederABC.__init__(self) | ||||
|  | ||||
|         self._logger = logger | ||||
|         self._config = config | ||||
|         self._bot = bot | ||||
|         self._db = db | ||||
|         self._users = users | ||||
|         self._api_keys = api_keys | ||||
|  | ||||
|     async def seed(self): | ||||
|         self._logger.debug(__name__, f"API-Key seeder started") | ||||
|  | ||||
|         if self._api_keys.get_api_keys().count() > 0: | ||||
|             self._logger.debug(__name__, f"Skip API-Key seeder") | ||||
|             return | ||||
|  | ||||
|         try: | ||||
|             frontend_key = ApiKey("frontend", "87f529fd-a32e-40b3-a1d1-7a1583cf3ff5", None) | ||||
|             self._api_keys.add_api_key(frontend_key) | ||||
|             self._db.save_changes() | ||||
|             self._logger.info(__name__, f"Created frontend API-Key") | ||||
|         except Exception as e: | ||||
|             self._logger.fatal(__name__, "Cannot create frontend API-Key", e) | ||||
| @@ -5,8 +5,10 @@ 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 bot_data.abc.data_seeder_abc import DataSeederABC | ||||
| from modules.base.abc.base_helper_abc import BaseHelperABC | ||||
| from modules.base.service.base_helper_service import BaseHelperService | ||||
| from modules.technician.api_key_seeder import ApiKeySeeder | ||||
| 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 | ||||
| @@ -21,6 +23,7 @@ class TechnicianModule(ModuleABC): | ||||
|         pass | ||||
|  | ||||
|     def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): | ||||
|         services.add_transient(DataSeederABC, ApiKeySeeder) | ||||
|         services.add_transient(BaseHelperABC, BaseHelperService) | ||||
|         # commands | ||||
|         self._dc.add_command(RestartCommand) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user