Added role check to authorization #72

This commit is contained in:
Sven Heidemann 2022-10-17 13:53:37 +02:00
parent 1857473ccc
commit dfcc516389
7 changed files with 43 additions and 12 deletions

View File

@ -11,10 +11,12 @@ from flask import Flask
from bot_api.abc.auth_service_abc import AuthServiceABC from bot_api.abc.auth_service_abc import AuthServiceABC
from bot_api.api import Api from bot_api.api import Api
from bot_api.api_thread import ApiThread from bot_api.api_thread import ApiThread
from bot_api.controller.discord.server_controller import ServerController
from bot_api.controller.gui_controller import GuiController from bot_api.controller.gui_controller import GuiController
from bot_api.controller.auth_controller import AuthController from bot_api.controller.auth_controller import AuthController
from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent
from bot_api.service.auth_service import AuthService from bot_api.service.auth_service import AuthService
from bot_api.service.discord_service import DiscordService
from bot_core.abc.module_abc import ModuleABC from bot_core.abc.module_abc import ModuleABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
@ -41,6 +43,8 @@ class ApiModule(ModuleABC):
services.add_transient(AuthServiceABC, AuthService) services.add_transient(AuthServiceABC, AuthService)
services.add_transient(AuthController) services.add_transient(AuthController)
services.add_transient(GuiController) services.add_transient(GuiController)
services.add_transient(DiscordService)
services.add_transient(ServerController)
# cpl-discord # cpl-discord
self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent) self._dc.add_event(DiscordEventTypesEnum.on_ready.value, BotApiOnReadyEvent)

View File

@ -15,6 +15,7 @@ from bot_api.model.auth_user_dto import AuthUserDTO
from bot_api.model.token_dto import TokenDTO from bot_api.model.token_dto import TokenDTO
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
from bot_api.route.route import Route from bot_api.route.route import Route
from bot_data.model.auth_role_enum import AuthRoleEnum
class AuthController: class AuthController:
@ -41,13 +42,13 @@ class AuthController:
self._auth_service = auth_service self._auth_service = auth_service
@Route.get(f'{BasePath}/users') @Route.get(f'{BasePath}/users')
@Route.authorize @Route.authorize(role=AuthRoleEnum.admin)
async def get_all_users(self) -> Response: async def get_all_users(self) -> Response:
result = await self._auth_service.get_all_auth_users_async() result = await self._auth_service.get_all_auth_users_async()
return jsonify(result.select(lambda x: x.to_dict())) return jsonify(result.select(lambda x: x.to_dict()))
@Route.post(f'{BasePath}/users/get/filtered') @Route.post(f'{BasePath}/users/get/filtered')
@Route.authorize @Route.authorize(role=AuthRoleEnum.admin)
async def get_filtered_users(self) -> Response: async def get_filtered_users(self) -> Response:
dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True)) dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True))
result = await self._auth_service.get_filtered_auth_users_async(dto) result = await self._auth_service.get_filtered_auth_users_async(dto)
@ -55,13 +56,13 @@ class AuthController:
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.get(f'{BasePath}/users/get/<email>') @Route.get(f'{BasePath}/users/get/<email>')
@Route.authorize @Route.authorize(role=AuthRoleEnum.admin)
async def get_user_from_email(self, email: str) -> Response: async def get_user_from_email(self, email: str) -> Response:
result = await self._auth_service.get_auth_user_by_email_async(email) result = await self._auth_service.get_auth_user_by_email_async(email)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@Route.get(f'{BasePath}/users/find/<email>') @Route.get(f'{BasePath}/users/find/<email>')
@Route.authorize @Route.authorize(role=AuthRoleEnum.admin)
async def find_user_from_email(self, email: str) -> Response: async def find_user_from_email(self, email: str) -> Response:
result = await self._auth_service.find_auth_user_by_email_async(email) result = await self._auth_service.find_auth_user_by_email_async(email)
return jsonify(result.to_dict()) return jsonify(result.to_dict())
@ -109,7 +110,7 @@ class AuthController:
return '', 200 return '', 200
@Route.post(f'{BasePath}/update-user-as-admin') @Route.post(f'{BasePath}/update-user-as-admin')
@Route.authorize @Route.authorize(role=AuthRoleEnum.admin)
async def update_user_as_admin(self): async def update_user_as_admin(self):
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True)) dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
await self._auth_service.update_user_as_admin_async(dto) await self._auth_service.update_user_as_admin_async(dto)
@ -129,14 +130,14 @@ class AuthController:
return '', 200 return '', 200
@Route.post(f'{BasePath}/delete-user') @Route.post(f'{BasePath}/delete-user')
@Route.authorize @Route.authorize(role=AuthRoleEnum.admin)
async def delete_user(self): async def delete_user(self):
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True)) dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
await self._auth_service.delete_auth_user_async(dto) await self._auth_service.delete_auth_user_async(dto)
return '', 200 return '', 200
@Route.post(f'{BasePath}/delete-user-by-mail/<email>') @Route.post(f'{BasePath}/delete-user-by-mail/<email>')
@Route.authorize @Route.authorize(role=AuthRoleEnum.admin)
async def delete_user_by_mail(self, email: str): async def delete_user_by_mail(self, email: str):
await self._auth_service.delete_auth_user_by_email_async(email) await self._auth_service.delete_auth_user_by_email_async(email)
return '', 200 return '', 200

View File

@ -8,6 +8,7 @@ from bot_api.api import Api
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route from bot_api.route.route import Route
from bot_api.service.discord_service import DiscordService from bot_api.service.discord_service import DiscordService
from bot_data.model.auth_role_enum import AuthRoleEnum
class ServerController: class ServerController:
@ -34,6 +35,6 @@ class ServerController:
self._discord_service = discord_service self._discord_service = discord_service
@Route.get(f'{BasePath}/servers') @Route.get(f'{BasePath}/servers')
@Route.authorize @Route.authorize(role=AuthRoleEnum.admin)
async def get_all_servers(self) -> Response: async def get_all_servers(self) -> Response:
return jsonify(self._discord_service.get_all_servers().select(lambda x: x.to_dict())) return jsonify(self._discord_service.get_all_servers().select(lambda x: x.to_dict()))

View File

@ -21,3 +21,4 @@ class ServiceErrorCode(Enum):
MailError = 10 MailError = 10
Unauthorized = 11 Unauthorized = 11
Forbidden = 12

View File

@ -1,5 +1,6 @@
import functools
from functools import wraps from functools import wraps
from typing import Optional from typing import Optional, Callable
from flask import request, jsonify from flask import request, jsonify
from flask_cors import cross_origin from flask_cors import cross_origin
@ -9,6 +10,7 @@ from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.exception.service_exception import ServiceException from bot_api.exception.service_exception import ServiceException
from bot_api.model.error_dto import ErrorDTO from bot_api.model.error_dto import ErrorDTO
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.model.auth_role_enum import AuthRoleEnum
class Route: class Route:
@ -23,7 +25,10 @@ class Route:
cls._auth = auth cls._auth = auth
@classmethod @classmethod
def authorize(cls, f): def authorize(cls, f: Callable = None, role: AuthRoleEnum = None):
if f is None:
return functools.partial(cls.authorize, role=role)
@wraps(f) @wraps(f)
async def decorator(*args, **kwargs): async def decorator(*args, **kwargs):
token = None token = None
@ -46,6 +51,23 @@ class Route:
error = ErrorDTO(ex.error_code, ex.message) error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401 return jsonify(error.to_dict()), 401
token = cls._auth.decode_token(token)
if token is None or 'email' not in token:
ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token invalid')
error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401
user = cls._auth_users.get_auth_user_by_email(token['email'])
if user is None:
ex = ServiceException(ServiceErrorCode.Unauthorized, f'Token invalid')
error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 401
if role is not None and user.auth_role.value < role.value:
ex = ServiceException(ServiceErrorCode.Unauthorized, f'Role {role} required')
error = ErrorDTO(ex.error_code, ex.message)
return jsonify(error.to_dict()), 403
return await f(*args, **kwargs) return await f(*args, **kwargs)
return decorator return decorator

View File

@ -27,6 +27,8 @@ from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.model.auth_user import AuthUser from bot_data.model.auth_user import AuthUser
_email_regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
class AuthService(AuthServiceABC): class AuthService(AuthServiceABC):
@ -65,7 +67,7 @@ class AuthService(AuthServiceABC):
@staticmethod @staticmethod
def _is_email_valid(email: str) -> bool: def _is_email_valid(email: str) -> bool:
if re.match(re.compile(r'^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$'), email) is not None: if re.fullmatch(_email_regex, email) is not None:
return True return True
return False return False

View File

@ -19,7 +19,7 @@ class AuthUserTransformer(TransformerABC):
None, None,
None, None,
datetime.now(tz=timezone.utc), datetime.now(tz=timezone.utc),
AuthRoleEnum.normal if dto.auth_role is None else dto.auth_role, AuthRoleEnum.normal if dto.auth_role is None else AuthRoleEnum(dto.auth_role),
id=0 if dto.id is None else dto.id id=0 if dto.id is None else dto.id
) )