Добавление custom_exception_handler
This commit is contained in:
12
net/xeaf/rack/utils/drf/__init__.py
Normal file
12
net/xeaf/rack/utils/drf/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Пакет классов расширения Django REST Framework
|
||||
"""
|
||||
|
||||
from .custom_exception_handler import custom_exception_handler
|
||||
from .expiring_token_authentication import ExpiringTokenAuthentication
|
||||
87
net/xeaf/rack/utils/drf/custom_exception_handler.py
Normal file
87
net/xeaf/rack/utils/drf/custom_exception_handler.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание функции custom_exception_handler
|
||||
"""
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.exceptions import MethodNotAllowed
|
||||
from rest_framework.exceptions import NotAuthenticated
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
from net.xeaf.rack.core import CoreException
|
||||
from net.xeaf.rack.models.responses import ErrorResponse
|
||||
from net.xeaf.rack.utils.exceptions import ForbiddenException
|
||||
from net.xeaf.rack.utils.exceptions import NotFoundException
|
||||
from net.xeaf.rack.utils.exceptions import UnauthorizedException
|
||||
|
||||
|
||||
def custom_exception_handler(exc: CoreException | Exception, context: dict) -> Response:
|
||||
"""
|
||||
Универсальный обработчик исключений
|
||||
|
||||
:param exc: Исключение
|
||||
:param context: Контекст
|
||||
|
||||
:return: Ответ
|
||||
"""
|
||||
|
||||
# Получаем информацию о запросе
|
||||
request = context.get("request")
|
||||
assert isinstance(request, Request)
|
||||
|
||||
# Это наше исключение
|
||||
if isinstance(exc, CoreException):
|
||||
return ErrorResponse(status_code=exc.status_code, detail=exc.detail, meta=exc.meta)
|
||||
|
||||
# Проверка на стандартное исключение валидации
|
||||
if isinstance(exc, ValidationError):
|
||||
return _process_validation_exception(exc)
|
||||
|
||||
# Проверка на стандартное исключение авторизации
|
||||
if isinstance(exc, NotAuthenticated):
|
||||
return ErrorResponse(status_code=status.HTTP_401_UNAUTHORIZED, detail=UnauthorizedException.Msg.DEFAULT_MESSAGE)
|
||||
|
||||
# Проверка на стандартное исключение прав доступа
|
||||
if isinstance(exc, PermissionDenied):
|
||||
return ErrorResponse(status_code=status.HTTP_403_FORBIDDEN, detail=ForbiddenException.Msg.DEFAULT_MESSAGE)
|
||||
|
||||
# Проверка на стандартное исключение отсутствия реализации
|
||||
if isinstance(exc, MethodNotAllowed):
|
||||
return ErrorResponse(status_code=status.HTTP_404_NOT_FOUND, detail=NotFoundException.Msg.DEFAULT_MESSAGE)
|
||||
|
||||
# Если ничего другое не прошло
|
||||
return ErrorResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc))
|
||||
|
||||
|
||||
def _process_validation_exception(exc: ValidationError) -> ErrorResponse:
|
||||
"""
|
||||
Обрабатывает частный случай стандартного исключения валидации
|
||||
|
||||
:param exc: Исключение
|
||||
|
||||
:return: Ответ
|
||||
"""
|
||||
|
||||
if isinstance(exc.detail, list):
|
||||
if len(exc.detail) == 1:
|
||||
detail = str(exc.detail[0])
|
||||
else:
|
||||
detail = exc.detail
|
||||
elif isinstance(exc.detail, dict):
|
||||
detail = {}
|
||||
for field, error_info in exc.detail.items():
|
||||
if isinstance(error_info, list):
|
||||
error = error_info[0]
|
||||
detail[field] = str(error)
|
||||
else:
|
||||
detail = str(exc.detail)
|
||||
|
||||
return ErrorResponse(status_code=status.HTTP_400_BAD_REQUEST, detail=detail)
|
||||
75
net/xeaf/rack/utils/drf/expiring_token_authentication.py
Normal file
75
net/xeaf/rack/utils/drf/expiring_token_authentication.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# DRF Rack
|
||||
# Библиотека классов расширений для Django REST Framework
|
||||
#
|
||||
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||
# Все права защищены. Лицензия: MIT
|
||||
|
||||
"""
|
||||
Описание класса ExpiringTokenAuthentication
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
from rest_framework.request import Request
|
||||
|
||||
from net.xeaf.rack.managers import AccountManager
|
||||
from net.xeaf.rack.managers import LocaleManager
|
||||
from net.xeaf.rack.managers import SessionManager
|
||||
from net.xeaf.rack.models import AccountModel
|
||||
from net.xeaf.rack.models import SessionModel
|
||||
|
||||
|
||||
class ExpiringTokenAuthentication(TokenAuthentication):
|
||||
"""
|
||||
Реализует механизм авторизации для Токенов со сроком действия
|
||||
"""
|
||||
|
||||
AUTH_TOKEN_PARAM = "x-auth-token"
|
||||
""" Идентификатор параметра токена """
|
||||
|
||||
def authenticate(self, request: Request) -> tuple[AccountModel | None, SessionModel | None] | tuple[Any, Any] | None:
|
||||
"""
|
||||
Метод авторизации
|
||||
|
||||
:param request: Информация о запросе
|
||||
"""
|
||||
|
||||
LocaleManager.adjust_request_language(request)
|
||||
|
||||
key = request.query_params.get(self.AUTH_TOKEN_PARAM, None)
|
||||
if key is not None:
|
||||
return self.authenticate_credentials(key)
|
||||
|
||||
return super().authenticate(request)
|
||||
|
||||
def authenticate_credentials(self, key: str) -> tuple[AccountModel | None, SessionModel | None]:
|
||||
"""
|
||||
Проверка Токена авторизации
|
||||
|
||||
:param key: Токен авторизации
|
||||
"""
|
||||
|
||||
session = SessionManager[SessionModel].find_by_token(token=key)
|
||||
if not session or session.is_expired():
|
||||
return None, None
|
||||
|
||||
account = session.account
|
||||
if not self._check_account(account):
|
||||
return None, None
|
||||
|
||||
SessionManager.renew_session(session)
|
||||
return session.account, session
|
||||
|
||||
@classmethod
|
||||
def _check_account(cls, account: AccountModel) -> bool:
|
||||
"""
|
||||
Проверка учетной записи
|
||||
|
||||
:param account: Учетная запись
|
||||
"""
|
||||
|
||||
# Можно прошедшим проверку и не удаленным
|
||||
reject = AccountManager.check_account_auth(account)
|
||||
result = reject is None and not account.is_deleted()
|
||||
return result
|
||||
Reference in New Issue
Block a user