forked from sh-edraft.de/sh_discord_bot
		
	Added authorize check to controller #70
This commit is contained in:
		| @@ -11,6 +11,7 @@ from bot.startup_discord_extension import StartupDiscordExtension | ||||
| from bot.startup_migration_extension import StartupMigrationExtension | ||||
| from bot.startup_module_extension import StartupModuleExtension | ||||
| from bot.startup_settings_extension import StartupSettingsExtension | ||||
| from bot_api.app_api_extension import AppApiExtension | ||||
| from modules.boot_log.boot_log_extension import BootLogExtension | ||||
| from modules.database.database_extension import DatabaseExtension | ||||
|  | ||||
| @@ -29,6 +30,7 @@ class Program: | ||||
|             .use_extension(StartupMigrationExtension) \ | ||||
|             .use_extension(BootLogExtension) \ | ||||
|             .use_extension(DatabaseExtension) \ | ||||
|             .use_extension(AppApiExtension) \ | ||||
|             .use_startup(Startup) | ||||
|         self.app: Application = await app_builder.build_async() | ||||
|         await self.app.run_async() | ||||
|   | ||||
| @@ -9,6 +9,7 @@ from bot_api.model.email_string_dto import EMailStringDTO | ||||
| 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_data.model.auth_user import AuthUser | ||||
|  | ||||
|  | ||||
| class AuthServiceABC(ABC): | ||||
| @@ -16,6 +17,12 @@ class AuthServiceABC(ABC): | ||||
|     @abstractmethod | ||||
|     def __init__(self): pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def generate_token(self, user: AuthUser) -> str: pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     def decode_token(self, token: str) -> dict: pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def get_all_auth_users_async(self) -> List[AuthUserDTO]: pass | ||||
|  | ||||
| @@ -43,6 +50,9 @@ class AuthServiceABC(ABC): | ||||
|     @abstractmethod | ||||
|     async def delete_auth_user_async(self, user_dto: AuthUserDTO): pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def verify_login(self, token_str: str) -> bool: pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass | ||||
|  | ||||
|   | ||||
							
								
								
									
										26
									
								
								kdb-bot/src/bot_api/app_api_extension.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								kdb-bot/src/bot_api/app_api_extension.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| from cpl_core.application import ApplicationExtensionABC | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
|  | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.configuration.authentication_settings import AuthenticationSettings | ||||
| from bot_api.route.route import Route | ||||
| from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum | ||||
| from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings | ||||
| from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC | ||||
|  | ||||
|  | ||||
| class AppApiExtension(ApplicationExtensionABC): | ||||
|  | ||||
|     def __init__(self): | ||||
|         ApplicationExtensionABC.__init__(self) | ||||
|  | ||||
|     async def run(self, config: ConfigurationABC, services: ServiceProviderABC): | ||||
|         feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings) | ||||
|         if not feature_flags.get_flag(FeatureFlagsEnum.api_module): | ||||
|             return | ||||
|  | ||||
|         auth_settings: AuthenticationSettings = config.get_configuration(AuthenticationSettings) | ||||
|         auth_users: AuthUserRepositoryABC = services.get_service(AuthUserRepositoryABC) | ||||
|         auth: AuthServiceABC = services.get_service(AuthServiceABC) | ||||
|         Route.init_authorize(auth_users, auth) | ||||
| @@ -39,11 +39,13 @@ class AuthController: | ||||
|         self._auth_service = auth_service | ||||
|  | ||||
|     @Route.get(f'{BasePath}/users') | ||||
|     @Route.authorize | ||||
|     async def get_all_users(self) -> Response: | ||||
|         result = await self._auth_service.get_all_auth_users_async() | ||||
|         return jsonify(result.select(lambda x: x.to_dict())) | ||||
|  | ||||
|     @Route.post(f'{BasePath}/users/get/filtered') | ||||
|     @Route.authorize | ||||
|     async def get_filtered_users(self) -> Response: | ||||
|         dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True)) | ||||
|         result = await self._auth_service.get_filtered_auth_users_async(dto) | ||||
| @@ -51,11 +53,13 @@ class AuthController: | ||||
|         return jsonify(result.to_dict()) | ||||
|  | ||||
|     @Route.get(f'{BasePath}/users/get/<email>') | ||||
|     @Route.authorize | ||||
|     async def get_user_from_email(self, email: str) -> Response: | ||||
|         result = await self._auth_service.get_auth_user_by_email_async(email) | ||||
|         return jsonify(result.to_dict()) | ||||
|  | ||||
|     @Route.get(f'{BasePath}/users/find/<email>') | ||||
|     @Route.authorize | ||||
|     async def find_user_from_email(self, email: str) -> Response: | ||||
|         result = await self._auth_service.find_auth_user_by_email_async(email) | ||||
|         return jsonify(result.to_dict()) | ||||
| @@ -83,36 +87,42 @@ class AuthController: | ||||
|         return '', 200 | ||||
|  | ||||
|     @Route.post(f'{BasePath}/update-user') | ||||
|     @Route.authorize | ||||
|     async def update_user(self): | ||||
|         dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True)) | ||||
|         await self._auth_service.update_user_async(dto) | ||||
|         return '', 200 | ||||
|  | ||||
|     @Route.post(f'{BasePath}/update-user-as-admin') | ||||
|     @Route.authorize | ||||
|     async def update_user_as_admin(self): | ||||
|         dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True)) | ||||
|         await self._auth_service.update_user_async(dto) | ||||
|         return '', 200 | ||||
|  | ||||
|     @Route.post(f'{BasePath}/refresh') | ||||
|     @Route.authorize | ||||
|     async def refresh(self) -> Response: | ||||
|         dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True)) | ||||
|         result = await self._auth_service.refresh_async(dto) | ||||
|         return jsonify(result.to_dict()) | ||||
|  | ||||
|     @Route.post(f'{BasePath}/revoke') | ||||
|     @Route.authorize | ||||
|     async def revoke(self): | ||||
|         dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True)) | ||||
|         await self._auth_service.revoke_async(dto) | ||||
|         return '', 200 | ||||
|  | ||||
|     @Route.post(f'{BasePath}/delete-user') | ||||
|     @Route.authorize | ||||
|     async def delete_user(self): | ||||
|         dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) | ||||
|         await self._auth_service.delete_auth_user_async(dto) | ||||
|         return '', 200 | ||||
|  | ||||
|     @Route.post(f'{BasePath}/delete-user-by-mail/<email>') | ||||
|     @Route.authorize | ||||
|     async def delete_user_by_mail(self, email: str): | ||||
|         await self._auth_service.delete_auth_user_by_email_async(email) | ||||
|         return '', 200 | ||||
|   | ||||
| @@ -40,6 +40,7 @@ class GuiController: | ||||
|         return VersionDTO(version.major, version.minor, version.micro).to_dict() | ||||
|  | ||||
|     @Route.get(f'{BasePath}/settings') | ||||
|     @Route.authorize | ||||
|     async def settings(self): | ||||
|         # TODO: Authentication | ||||
|         import bot_api | ||||
| @@ -61,6 +62,7 @@ class GuiController: | ||||
|         ).to_dict() | ||||
|  | ||||
|     @Route.get(f'{BasePath}/send-test-mail/<email>') | ||||
|     @Route.authorize | ||||
|     async def send_test_mail(self, email: str): | ||||
|         # TODO: Authentication | ||||
|         mail = EMail() | ||||
|   | ||||
| @@ -1,9 +1,48 @@ | ||||
| from functools import wraps | ||||
| from typing import Optional | ||||
|  | ||||
| from flask import request | ||||
| from flask_cors import cross_origin | ||||
|  | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.exception.service_error_code_enum import ServiceErrorCode | ||||
| from bot_api.exception.service_exception import ServiceException | ||||
| from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC | ||||
|  | ||||
|  | ||||
| class Route: | ||||
|     registered_routes = {} | ||||
|  | ||||
|     _auth_users: Optional[AuthUserRepositoryABC] = None | ||||
|     _auth: Optional[AuthServiceABC] = None | ||||
|  | ||||
|     @classmethod | ||||
|     def init_authorize(cls, auth_users: AuthUserRepositoryABC, auth: AuthServiceABC): | ||||
|         cls._auth_users = auth_users | ||||
|         cls._auth = auth | ||||
|  | ||||
|     @classmethod | ||||
|     def authorize(cls, f): | ||||
|         @wraps(f) | ||||
|         async def decorator(*args, **kwargs): | ||||
|             token = None | ||||
|             if 'Authorization' in request.headers: | ||||
|                 bearer = request.headers.get('Authorization') | ||||
|                 token = bearer.split()[1] | ||||
|  | ||||
|             if not token: | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set') | ||||
|  | ||||
|             if cls._auth_users is None or cls._auth is None: | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidDependencies, f'Authorize is not initialized') | ||||
|  | ||||
|             if not cls._auth.verify_login(token): | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidUser, f'Token expired') | ||||
|  | ||||
|             return await f(*args, **kwargs) | ||||
|  | ||||
|         return decorator | ||||
|  | ||||
|     @classmethod | ||||
|     def route(cls, path=None, **kwargs): | ||||
|         # simple decorator for class based views | ||||
|   | ||||
| @@ -70,7 +70,7 @@ class AuthService(AuthServiceABC): | ||||
|  | ||||
|         return False | ||||
|  | ||||
|     def _generate_token(self, user: AuthUser) -> str: | ||||
|     def generate_token(self, user: AuthUser) -> str: | ||||
|         token = jwt.encode( | ||||
|             payload={ | ||||
|                 'user_id': user.id, | ||||
| @@ -85,7 +85,7 @@ class AuthService(AuthServiceABC): | ||||
|  | ||||
|         return token | ||||
|  | ||||
|     def _decode_token(self, token: str) -> dict: | ||||
|     def decode_token(self, token: str) -> dict: | ||||
|         return jwt.decode( | ||||
|             token, | ||||
|             key=self._auth_settings.secret_key, | ||||
| @@ -292,6 +292,21 @@ class AuthService(AuthServiceABC): | ||||
|             self._logger.error(__name__, f'Cannot delete user', e) | ||||
|             raise ServiceException(ServiceErrorCode.UnableToDelete, f'Cannot delete user by mail {user_dto.email}') | ||||
|  | ||||
|     def verify_login(self, token_str: str) -> bool: | ||||
|         try: | ||||
|             token = self.decode_token(token_str) | ||||
|             if token is None or 'email' not in token: | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid') | ||||
|  | ||||
|             user = self._auth_users.find_auth_user_by_email(token['email']) | ||||
|             if user is None: | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired') | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f'Refreshing token failed', 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') | ||||
| @@ -304,7 +319,7 @@ class AuthService(AuthServiceABC): | ||||
|         if db_user.password != user_dto.password: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, 'Wrong password') | ||||
|  | ||||
|         token = self._generate_token(db_user) | ||||
|         token = self.generate_token(db_user) | ||||
|         refresh_token = self._create_and_save_refresh_token(db_user) | ||||
|         if db_user.forgot_password_id is not None: | ||||
|             db_user.forgot_password_id = None | ||||
| @@ -316,16 +331,16 @@ class AuthService(AuthServiceABC): | ||||
|         if token_dto is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set') | ||||
|  | ||||
|         token = self._decode_token(token_dto.token) | ||||
|         if token is None or 'email' not in token: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid') | ||||
|  | ||||
|         try: | ||||
|             token = self.decode_token(token_dto.token) | ||||
|             if token is None or 'email' not in token: | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid') | ||||
|  | ||||
|             user = self._auth_users.get_auth_user_by_email(token['email']) | ||||
|             if user is None or user.refresh_token != token_dto.refresh_token or user.refresh_token_expire_time <= datetime.now(): | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired') | ||||
|  | ||||
|             return TokenDTO(self._generate_token(user), self._create_and_save_refresh_token(user)) | ||||
|             return TokenDTO(self.generate_token(user), self._create_and_save_refresh_token(user)) | ||||
|         except Exception as e: | ||||
|             self._logger.error(__name__, f'Refreshing token failed', e) | ||||
|             return TokenDTO('', '') | ||||
| @@ -334,8 +349,9 @@ class AuthService(AuthServiceABC): | ||||
|         if token_dto is None or token_dto.token is None or token_dto.refresh_token is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, 'Token not set') | ||||
|  | ||||
|         token = self._decode_token(token_dto.token) | ||||
|         try: | ||||
|             token = self.decode_token(token_dto.token) | ||||
|  | ||||
|             user = self._auth_users.get_auth_user_by_email(token['email']) | ||||
|             if user is None or user.refresh_token != token_dto.refresh_token or user.refresh_token_expire_time <= datetime.now(): | ||||
|                 raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired') | ||||
|   | ||||
		Reference in New Issue
	
	Block a user