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.api import Api
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.auth_controller import AuthController
from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent
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.configuration.feature_flags_enum import FeatureFlagsEnum
@ -41,6 +43,8 @@ class ApiModule(ModuleABC):
services.add_transient(AuthServiceABC, AuthService)
services.add_transient(AuthController)
services.add_transient(GuiController)
services.add_transient(DiscordService)
services.add_transient(ServerController)
# cpl-discord
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.update_auth_user_dto import UpdateAuthUserDTO
from bot_api.route.route import Route
from bot_data.model.auth_role_enum import AuthRoleEnum
class AuthController:
@ -41,13 +42,13 @@ class AuthController:
self._auth_service = auth_service
@Route.get(f'{BasePath}/users')
@Route.authorize
@Route.authorize(role=AuthRoleEnum.admin)
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
@Route.authorize(role=AuthRoleEnum.admin)
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)
@ -55,13 +56,13 @@ class AuthController:
return jsonify(result.to_dict())
@Route.get(f'{BasePath}/users/get/<email>')
@Route.authorize
@Route.authorize(role=AuthRoleEnum.admin)
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
@Route.authorize(role=AuthRoleEnum.admin)
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())
@ -109,7 +110,7 @@ class AuthController:
return '', 200
@Route.post(f'{BasePath}/update-user-as-admin')
@Route.authorize
@Route.authorize(role=AuthRoleEnum.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_as_admin_async(dto)
@ -129,14 +130,14 @@ class AuthController:
return '', 200
@Route.post(f'{BasePath}/delete-user')
@Route.authorize
@Route.authorize(role=AuthRoleEnum.admin)
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
@Route.authorize(role=AuthRoleEnum.admin)
async def delete_user_by_mail(self, email: str):
await self._auth_service.delete_auth_user_by_email_async(email)
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.route.route import Route
from bot_api.service.discord_service import DiscordService
from bot_data.model.auth_role_enum import AuthRoleEnum
class ServerController:
@ -34,6 +35,6 @@ class ServerController:
self._discord_service = discord_service
@Route.get(f'{BasePath}/servers')
@Route.authorize
@Route.authorize(role=AuthRoleEnum.admin)
async def get_all_servers(self) -> Response:
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
Unauthorized = 11
Forbidden = 12

View File

@ -1,5 +1,6 @@
import functools
from functools import wraps
from typing import Optional
from typing import Optional, Callable
from flask import request, jsonify
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.model.error_dto import ErrorDTO
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.model.auth_role_enum import AuthRoleEnum
class Route:
@ -23,7 +25,10 @@ class Route:
cls._auth = auth
@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)
async def decorator(*args, **kwargs):
token = None
@ -46,6 +51,23 @@ class Route:
error = ErrorDTO(ex.error_code, ex.message)
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 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.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):
@ -65,7 +67,7 @@ class AuthService(AuthServiceABC):
@staticmethod
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 False

View File

@ -19,7 +19,7 @@ class AuthUserTransformer(TransformerABC):
None,
None,
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
)