From 1defe29406fa5a9ebda3db20a15057237ea2c422 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Fri, 14 Oct 2022 15:37:52 +0200 Subject: [PATCH] Added auth user controller #70 --- src/bot_api/abc/auth_service_abc.py | 28 +++--- src/bot_api/api.py | 1 - src/bot_api/api_module.py | 3 + src/bot_api/controller/auth_controller.py | 97 ++++++++++++++++--- src/bot_api/json_processor.py | 32 ++++++ src/bot_api/model/auth_user_dto.py | 42 +++++--- .../model/auth_user_filtered_result_dto.py | 21 ++++ src/bot_api/model/email_string_dto.py | 18 ++++ src/bot_api/route/route.py | 20 ++++ src/bot_api/service/auth_service.py | 15 ++- 10 files changed, 232 insertions(+), 45 deletions(-) create mode 100644 src/bot_api/json_processor.py create mode 100644 src/bot_api/model/auth_user_filtered_result_dto.py create mode 100644 src/bot_api/model/email_string_dto.py diff --git a/src/bot_api/abc/auth_service_abc.py b/src/bot_api/abc/auth_service_abc.py index c34af36997..45788c9a52 100644 --- a/src/bot_api/abc/auth_service_abc.py +++ b/src/bot_api/abc/auth_service_abc.py @@ -4,6 +4,8 @@ from cpl_query.extension import List from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria from bot_api.model.auth_user_dto import AuthUserDTO +from bot_api.model.auth_user_filtered_result_dto import AuthUserFilteredResultDTO +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 @@ -19,7 +21,7 @@ class AuthServiceABC(ABC): async def get_all_auth_users_async(self) -> List[AuthUser]: pass @abstractmethod - async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> List[AuthUser]: pass + async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO: pass @abstractmethod async def get_auth_user_by_email_async(self, email: str) -> AuthUser: pass @@ -28,37 +30,37 @@ class AuthServiceABC(ABC): async def find_auth_user_by_email_async(self, email: str) -> AuthUser: pass @abstractmethod - async def add_auth_user_async(self, user_dto: AuthUserDTO) -> AuthUser: pass + async def add_auth_user_async(self, user_dto: AuthUserDTO) -> int: pass @abstractmethod - async def confirm_email_async(self, id: str) -> AuthUser: pass + async def confirm_email_async(self, id: str) -> bool: pass @abstractmethod - async def login_async(self, user_dto: AuthUserDTO) -> AuthUser: pass + async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass @abstractmethod - async def forgot_password_async(self, email: str) -> AuthUser: pass + async def forgot_password_async(self, email: str): pass @abstractmethod - async def confirm_forgot_password_async(self, id: str) -> AuthUser: pass + async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO: pass @abstractmethod - async def reset_password_async(self, rp_dto: ResetPasswordDTO) -> AuthUser: pass + async def reset_password_async(self, rp_dto: ResetPasswordDTO): pass @abstractmethod - async def update_user_async(self, update_user_dto: UpdateAuthUserDTO) -> AuthUser: pass + async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): pass @abstractmethod - async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO) -> AuthUser: pass + async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO): pass @abstractmethod - async def refresh_async(self, token_dto: TokenDTO) -> AuthUser: pass + async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: pass @abstractmethod - async def revoke_async(self, token_dto: TokenDTO) -> AuthUser: pass + async def revoke_async(self, token_dto: TokenDTO): pass @abstractmethod - async def delete_auth_user_by_email_async(self, email: str) -> AuthUser: pass + async def delete_auth_user_by_email_async(self, email: str): pass @abstractmethod - async def delete_auth_user_async(self, user_dto: AuthUserDTO) -> AuthUser: pass + async def delete_auth_user_async(self, user_dto: AuthUserDTO): pass diff --git a/src/bot_api/api.py b/src/bot_api/api.py index 29c176fa5e..8760377bbe 100644 --- a/src/bot_api/api.py +++ b/src/bot_api/api.py @@ -54,7 +54,6 @@ class Api(Flask): self._logger.debug(__name__, f'Received GET @{request.url}') headers = str(request.headers).replace("\n", "\n\t") self._logger.trace(__name__, f'Headers: \n\t{headers}') - self._logger.trace(__name__, f'Body: {request.get_json(force=True, silent=True)}') def start(self): self._logger.info(__name__, f'Starting API {self._apt_settings.host}:{self._apt_settings.port}') diff --git a/src/bot_api/api_module.py b/src/bot_api/api_module.py index be126e6550..ff1be35b2e 100644 --- a/src/bot_api/api_module.py +++ b/src/bot_api/api_module.py @@ -7,10 +7,12 @@ from cpl_core.mailing import EMailClientABC, EMailClient from cpl_discord.service.discord_collection_abc import DiscordCollectionABC from flask import Flask +from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.api import Api from bot_api.api_thread import ApiThread from bot_api.controller.api_controller import ApiController from bot_api.controller.auth_controller import AuthController +from bot_api.service.auth_service import AuthService from bot_core.abc.module_abc import ModuleABC from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum @@ -34,5 +36,6 @@ class ApiModule(ModuleABC): services.add_singleton(ApiThread) services.add_singleton(Flask, Api) + services.add_transient(AuthServiceABC, AuthService) services.add_transient(AuthController) services.add_transient(ApiController) diff --git a/src/bot_api/controller/auth_controller.py b/src/bot_api/controller/auth_controller.py index 40427bdab5..adb6f3a249 100644 --- a/src/bot_api/controller/auth_controller.py +++ b/src/bot_api/controller/auth_controller.py @@ -1,19 +1,22 @@ from cpl_core.configuration import ConfigurationABC from cpl_core.environment import ApplicationEnvironmentABC from cpl_core.mailing import EMailClientABC, EMailClientSettings -from cpl_query.extension import List from cpl_translation import TranslatePipe -from flask import request -from flask_cors import cross_origin +from flask import request, jsonify, Response +from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.api import Api +from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria +from bot_api.json_processor import JSONProcessor from bot_api.logging.api_logger import ApiLogger +from bot_api.model.auth_user_dto import AuthUserDTO from bot_api.model.token_dto import TokenDTO +from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO from bot_api.route.route import Route -from bot_data.model.auth_user import AuthUser class AuthController: + BasePath = '/api/auth' def __init__( self, @@ -23,7 +26,8 @@ class AuthController: t: TranslatePipe, api: Api, mail_settings: EMailClientSettings, - mailer: EMailClientABC + mailer: EMailClientABC, + auth_service: AuthServiceABC ): self._config = config self._env = env @@ -32,16 +36,79 @@ class AuthController: self._api = api self._mail_settings = mail_settings self._mailer = mailer + self._auth_service = auth_service - @Route.route('/api/auth/get-all-users') - async def get_all_users(self) -> List[AuthUser]: - pass + @Route.get(f'{BasePath}/users') + async def get_all_users(self) -> Response: + return jsonify(await self._auth_service.get_all_auth_users_async()) - @Route.route('/api/auth/get-filtered-users', methods=['POST']) - async def get_filtered_users(self) -> List[AuthUser]: - pass + @Route.post(f'{BasePath}/users/get/filtered') + 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) + return jsonify(result.to_dict()) - @Route.route('/api/auth/login', methods=['POST']) - async def login(self) -> dict: - self._logger.warn(__name__, f'Received {request.json}') - return TokenDTO().to_dict() + @Route.get(f'{BasePath}/users/get/') + async def get_user_from_email(self, email: str) -> Response: + return jsonify(await self._auth_service.get_auth_user_by_email_async(email)) + + @Route.get(f'{BasePath}/users/find/') + async def find_user_from_email(self, email: str) -> Response: + return jsonify(await self._auth_service.find_auth_user_by_email_async(email)) + + @Route.post(f'{BasePath}/register') + async def register(self): + dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) + await self._auth_service.add_auth_user_async(dto) + return '', 200 + + @Route.post(f'{BasePath}/login') + async def login(self) -> Response: + dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) + result = await self._auth_service.login_async(dto) + return jsonify(result.to_dict()) + + @Route.post(f'{BasePath}/forgot-password/') + async def forgot_password(self, email: str): + await self._auth_service.forgot_password_async(email) + return '', 200 + + @Route.post(f'{BasePath}/confirm-forgot-password/') + async def confirm_forgot_password(self, id: str): + await self._auth_service.confirm_forgot_password_async(id) + return '', 200 + + @Route.post(f'{BasePath}/update-user') + 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') + 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') + 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') + 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') + 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/') + async def delete_user_by_mail(self, email: str): + await self._auth_service.delete_auth_user_by_email_async(email) + return '', 200 diff --git a/src/bot_api/json_processor.py b/src/bot_api/json_processor.py new file mode 100644 index 0000000000..e629258ecf --- /dev/null +++ b/src/bot_api/json_processor.py @@ -0,0 +1,32 @@ +from inspect import signature, Parameter + +from cpl_core.utils import String + + +class JSONProcessor: + + @staticmethod + def process(_t: type, values: dict) -> object: + args = [] + + sig = signature(_t.__init__) + for param in sig.parameters.items(): + parameter = param[1] + if parameter.name == 'self' or parameter.annotation == Parameter.empty: + continue + + name = String.convert_to_camel_case(parameter.name) + name_first_lower = String.first_to_lower(name) + if name in values or name_first_lower in values: + if name in values: + args.append(values[name]) + else: + args.append(values[name_first_lower]) + + elif parameter.default != Parameter.empty: + args.append(parameter.default) + + else: + args.append(None) + + return _t(*args) diff --git a/src/bot_api/model/auth_user_dto.py b/src/bot_api/model/auth_user_dto.py index 7ca44a9e6c..8d7bce2b93 100644 --- a/src/bot_api/model/auth_user_dto.py +++ b/src/bot_api/model/auth_user_dto.py @@ -14,7 +14,7 @@ class AuthUserDTO(DtoABC): id: int, first_name: str, last_name: str, - email: str, + e_mail: str, password: str, confirmation_id: Optional[str], auth_role: AuthRoleEnum, @@ -24,20 +24,38 @@ class AuthUserDTO(DtoABC): self._id = id self._first_name = first_name self._last_name = last_name - self._email = email + self._email = e_mail self._password = password self._is_confirmed = confirmation_id is None self._auth_role = auth_role - - """ - long Id { get; set; } - string FirstName { get; set; } - string LastName { get; set; } - string EMail { get; set; } - string Password { get; set; } - bool IsConfirmed { get; set; } - AuthRoles AuthRole { get; set; } - """ + + @property + def id(self) -> int: + return self._id + + @property + def first_name(self) -> str: + return self._first_name + + @property + def last_name(self) -> str: + return self._last_name + + @property + def email(self) -> str: + return self._email + + @property + def password(self) -> str: + return self._password + + @property + def is_confirmed(self) -> bool: + return self._is_confirmed + + @property + def auth_role(self) -> AuthRoleEnum: + return self._auth_role def from_dict(self, values: dict): self._id = values['Id'] diff --git a/src/bot_api/model/auth_user_filtered_result_dto.py b/src/bot_api/model/auth_user_filtered_result_dto.py new file mode 100644 index 0000000000..6edbdb4462 --- /dev/null +++ b/src/bot_api/model/auth_user_filtered_result_dto.py @@ -0,0 +1,21 @@ +from cpl_query.extension import List + +from bot_api.abc.dto_abc import DtoABC +from bot_data.filtered_result import FilteredResult + + +class AuthUserFilteredResultDTO(DtoABC, FilteredResult): + + def __init__(self, result: List = None, total_count: int = 0): + DtoABC.__init__(self) + FilteredResult.__init__(self, result, total_count) + + def from_dict(self, values: dict): + self._result = values['Users'] + self._total_count = values['TotalCount'] + + def to_dict(self) -> dict: + return { + 'Users': self.result, + 'TotalCount': self.total_count + } diff --git a/src/bot_api/model/email_string_dto.py b/src/bot_api/model/email_string_dto.py new file mode 100644 index 0000000000..bc70239dc4 --- /dev/null +++ b/src/bot_api/model/email_string_dto.py @@ -0,0 +1,18 @@ +import traceback + +from cpl_core.console import Console + +from bot_api.abc.dto_abc import DtoABC + + +class EMailStringDTO(DtoABC): + + def __init__(self): + DtoABC.__init__(self) + + def from_dict(self, values: dict): + pass + + def to_dict(self) -> dict: + return { + } diff --git a/src/bot_api/route/route.py b/src/bot_api/route/route.py index 9dbe7fcbfa..fbf1a9f8dc 100644 --- a/src/bot_api/route/route.py +++ b/src/bot_api/route/route.py @@ -9,3 +9,23 @@ class Route: return fn return inner + + @classmethod + def get(cls, path=None, **kwargs): + return cls.route(path, methods=['GET'], **kwargs) + + @classmethod + def post(cls, path=None, **kwargs): + return cls.route(path, methods=['POST'], **kwargs) + + @classmethod + def head(cls, path=None, **kwargs): + return cls.route(path, methods=['HEAD'], **kwargs) + + @classmethod + def put(cls, path=None, **kwargs): + return cls.route(path, methods=['PUT'], **kwargs) + + @classmethod + def delete(cls, path=None, **kwargs): + return cls.route(path, methods=['DELETE'], **kwargs) diff --git a/src/bot_api/service/auth_service.py b/src/bot_api/service/auth_service.py index 3455c08328..7aa072dc04 100644 --- a/src/bot_api/service/auth_service.py +++ b/src/bot_api/service/auth_service.py @@ -2,7 +2,9 @@ from cpl_query.extension import List from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria +from bot_api.logging.api_logger import ApiLogger from bot_api.model.auth_user_dto import AuthUserDTO +from bot_api.model.auth_user_filtered_result_dto import AuthUserFilteredResultDTO 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 @@ -11,13 +13,18 @@ from bot_data.model.auth_user import AuthUser class AuthService(AuthServiceABC): - def __init__(self): - pass + def __init__( + self, + logger: ApiLogger, + ): + AuthServiceABC.__init__(self) + + self._logger = logger async def get_all_auth_users_async(self) -> List[AuthUser]: pass - async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> List[AuthUser]: + async def get_filtered_auth_users_async(self, criteria: AuthUserSelectCriteria) -> AuthUserFilteredResultDTO: pass async def get_auth_user_by_email_async(self, email: str) -> AuthUser: @@ -32,7 +39,7 @@ class AuthService(AuthServiceABC): async def confirm_email_async(self, id: str) -> AuthUser: pass - async def login_async(self, user_dto: AuthUserDTO) -> AuthUser: + async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass async def forgot_password_async(self, email: str) -> AuthUser: