This commit is contained in:
@@ -1,200 +1,30 @@
|
||||
from collections.abc import Callable
|
||||
from functools import wraps
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from graphql import GraphQLError, GraphQLResolveInfo
|
||||
from sqlalchemy import exc
|
||||
|
||||
from auth.credentials import AuthCredentials
|
||||
from auth.exceptions import OperationNotAllowedError
|
||||
from auth.internal import authenticate
|
||||
# Импорт базовых функций из реструктурированных модулей
|
||||
from auth.core import authenticate
|
||||
from auth.utils import get_auth_token
|
||||
from auth.orm import Author
|
||||
from orm.community import CommunityAuthor
|
||||
from services.db import local_session
|
||||
from services.redis import redis as redis_adapter
|
||||
from settings import ADMIN_EMAILS as ADMIN_EMAILS_LIST
|
||||
from settings import SESSION_COOKIE_NAME, SESSION_TOKEN_HEADER
|
||||
from utils.logger import root_logger as logger
|
||||
|
||||
ADMIN_EMAILS = ADMIN_EMAILS_LIST.split(",")
|
||||
|
||||
|
||||
def get_safe_headers(request: Any) -> dict[str, str]:
|
||||
"""
|
||||
Безопасно получает заголовки запроса.
|
||||
|
||||
Args:
|
||||
request: Объект запроса
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: Словарь заголовков
|
||||
"""
|
||||
headers = {}
|
||||
try:
|
||||
# Первый приоритет: scope из ASGI (самый надежный источник)
|
||||
if hasattr(request, "scope") and isinstance(request.scope, dict):
|
||||
scope_headers = request.scope.get("headers", [])
|
||||
if scope_headers:
|
||||
headers.update({k.decode("utf-8").lower(): v.decode("utf-8") for k, v in scope_headers})
|
||||
logger.debug(f"[decorators] Получены заголовки из request.scope: {len(headers)}")
|
||||
logger.debug(f"[decorators] Заголовки из request.scope: {list(headers.keys())}")
|
||||
|
||||
# Второй приоритет: метод headers() или атрибут headers
|
||||
if hasattr(request, "headers"):
|
||||
if callable(request.headers):
|
||||
h = request.headers()
|
||||
if h:
|
||||
headers.update({k.lower(): v for k, v in h.items()})
|
||||
logger.debug(f"[decorators] Получены заголовки из request.headers() метода: {len(headers)}")
|
||||
else:
|
||||
h = request.headers
|
||||
if hasattr(h, "items") and callable(h.items):
|
||||
headers.update({k.lower(): v for k, v in h.items()})
|
||||
logger.debug(f"[decorators] Получены заголовки из request.headers атрибута: {len(headers)}")
|
||||
elif isinstance(h, dict):
|
||||
headers.update({k.lower(): v for k, v in h.items()})
|
||||
logger.debug(f"[decorators] Получены заголовки из request.headers словаря: {len(headers)}")
|
||||
|
||||
# Третий приоритет: атрибут _headers
|
||||
if hasattr(request, "_headers") and request._headers:
|
||||
headers.update({k.lower(): v for k, v in request._headers.items()})
|
||||
logger.debug(f"[decorators] Получены заголовки из request._headers: {len(headers)}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[decorators] Ошибка при доступе к заголовкам: {e}")
|
||||
|
||||
return headers
|
||||
# Импортируем get_safe_headers из utils
|
||||
from auth.utils import get_safe_headers
|
||||
|
||||
|
||||
async def get_auth_token(request: Any) -> Optional[str]:
|
||||
"""
|
||||
Извлекает токен авторизации из запроса.
|
||||
Порядок проверки:
|
||||
1. Проверяет auth из middleware
|
||||
2. Проверяет auth из scope
|
||||
3. Проверяет заголовок Authorization
|
||||
4. Проверяет cookie с именем auth_token
|
||||
|
||||
Args:
|
||||
request: Объект запроса
|
||||
|
||||
Returns:
|
||||
Optional[str]: Токен авторизации или None
|
||||
"""
|
||||
try:
|
||||
# 1. Проверяем auth из middleware (если middleware уже обработал токен)
|
||||
if hasattr(request, "auth") and request.auth:
|
||||
token = getattr(request.auth, "token", None)
|
||||
if token:
|
||||
token_len = len(token) if hasattr(token, "__len__") else "unknown"
|
||||
logger.debug(f"[decorators] Токен получен из request.auth: {token_len}")
|
||||
return token
|
||||
logger.debug("[decorators] request.auth есть, но token НЕ найден")
|
||||
else:
|
||||
logger.debug("[decorators] request.auth НЕ найден")
|
||||
|
||||
# 2. Проверяем наличие auth_token в scope (приоритет)
|
||||
if hasattr(request, "scope") and isinstance(request.scope, dict) and "auth_token" in request.scope:
|
||||
token = request.scope.get("auth_token")
|
||||
if token is not None:
|
||||
token_len = len(token) if hasattr(token, "__len__") else "unknown"
|
||||
logger.debug(f"[decorators] Токен получен из request.scope['auth_token']: {token_len}")
|
||||
return token
|
||||
logger.debug("[decorators] request.scope['auth_token'] НЕ найден")
|
||||
|
||||
# Стандартная система сессий уже обрабатывает кэширование
|
||||
# Дополнительной проверки Redis кэша не требуется
|
||||
|
||||
# Отладка: детальная информация о запросе без токена в декораторе
|
||||
if not token:
|
||||
logger.warning(f"[decorators] ДЕКОРАТОР: ЗАПРОС БЕЗ ТОКЕНА: {request.method} {request.url.path}")
|
||||
logger.warning(f"[decorators] User-Agent: {request.headers.get('user-agent', 'НЕ НАЙДЕН')}")
|
||||
logger.warning(f"[decorators] Referer: {request.headers.get('referer', 'НЕ НАЙДЕН')}")
|
||||
logger.warning(f"[decorators] Origin: {request.headers.get('origin', 'НЕ НАЙДЕН')}")
|
||||
logger.warning(f"[decorators] Content-Type: {request.headers.get('content-type', 'НЕ НАЙДЕН')}")
|
||||
logger.warning(f"[decorators] Все заголовки: {list(request.headers.keys())}")
|
||||
|
||||
# Проверяем, есть ли активные сессии в Redis
|
||||
try:
|
||||
from services.redis import redis as redis_adapter
|
||||
|
||||
# Получаем все активные сессии
|
||||
session_keys = await redis_adapter.keys("session:*")
|
||||
logger.debug(f"[decorators] Найдено активных сессий в Redis: {len(session_keys)}")
|
||||
|
||||
if session_keys:
|
||||
# Пытаемся найти токен через активные сессии
|
||||
for session_key in session_keys[:3]: # Проверяем первые 3 сессии
|
||||
try:
|
||||
session_data = await redis_adapter.hgetall(session_key)
|
||||
if session_data:
|
||||
logger.debug(f"[decorators] Найдена активная сессия: {session_key}")
|
||||
# Извлекаем user_id из ключа сессии
|
||||
user_id = (
|
||||
session_key.decode("utf-8").split(":")[1]
|
||||
if isinstance(session_key, bytes)
|
||||
else session_key.split(":")[1]
|
||||
)
|
||||
logger.debug(f"[decorators] User ID из сессии: {user_id}")
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(f"[decorators] Ошибка чтения сессии {session_key}: {e}")
|
||||
else:
|
||||
logger.debug("[decorators] Активных сессий в Redis не найдено")
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"[decorators] Ошибка проверки сессий: {e}")
|
||||
|
||||
# 3. Проверяем наличие auth в scope
|
||||
if hasattr(request, "scope") and isinstance(request.scope, dict) and "auth" in request.scope:
|
||||
auth_info = request.scope.get("auth", {})
|
||||
if isinstance(auth_info, dict) and "token" in auth_info:
|
||||
token = auth_info.get("token")
|
||||
if token is not None:
|
||||
token_len = len(token) if hasattr(token, "__len__") else "unknown"
|
||||
logger.debug(f"[decorators] Токен получен из request.scope['auth']: {token_len}")
|
||||
return token
|
||||
|
||||
# 4. Проверяем заголовок Authorization
|
||||
headers = get_safe_headers(request)
|
||||
|
||||
# Сначала проверяем основной заголовок авторизации
|
||||
auth_header = headers.get(SESSION_TOKEN_HEADER.lower(), "")
|
||||
if auth_header:
|
||||
if auth_header.startswith("Bearer "):
|
||||
token = auth_header[7:].strip()
|
||||
token_len = len(token) if hasattr(token, "__len__") else "unknown"
|
||||
logger.debug(f"[decorators] Токен получен из заголовка {SESSION_TOKEN_HEADER}: {token_len}")
|
||||
return token
|
||||
token = auth_header.strip()
|
||||
if token:
|
||||
token_len = len(token) if hasattr(token, "__len__") else "unknown"
|
||||
logger.debug(f"[decorators] Прямой токен получен из заголовка {SESSION_TOKEN_HEADER}: {token_len}")
|
||||
return token
|
||||
|
||||
# Затем проверяем стандартный заголовок Authorization, если основной не определен
|
||||
if SESSION_TOKEN_HEADER.lower() != "authorization":
|
||||
auth_header = headers.get("authorization", "")
|
||||
if auth_header and auth_header.startswith("Bearer "):
|
||||
token = auth_header[7:].strip()
|
||||
if token:
|
||||
token_len = len(token) if hasattr(token, "__len__") else "unknown"
|
||||
logger.debug(f"[decorators] Токен получен из заголовка Authorization: {token_len}")
|
||||
return token
|
||||
|
||||
# 5. Проверяем cookie
|
||||
if hasattr(request, "cookies") and request.cookies:
|
||||
token = request.cookies.get(SESSION_COOKIE_NAME)
|
||||
if token:
|
||||
token_len = len(token) if hasattr(token, "__len__") else "unknown"
|
||||
logger.debug(f"[decorators] Токен получен из cookie {SESSION_COOKIE_NAME}: {token_len}")
|
||||
return token
|
||||
|
||||
# Если токен не найден ни в одном из мест
|
||||
logger.debug("[decorators] Токен авторизации не найден")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"[decorators] Ошибка при извлечении токена: {e}")
|
||||
return None
|
||||
# get_auth_token теперь импортирован из auth.utils
|
||||
|
||||
|
||||
async def validate_graphql_context(info: GraphQLResolveInfo) -> None:
|
||||
@@ -236,7 +66,7 @@ async def validate_graphql_context(info: GraphQLResolveInfo) -> None:
|
||||
return
|
||||
|
||||
# Если аутентификации нет в request.auth, пробуем получить ее из scope
|
||||
token: Optional[str] = None
|
||||
token: str | None = None
|
||||
if hasattr(request, "scope") and "auth" in request.scope:
|
||||
auth_cred = request.scope.get("auth")
|
||||
if isinstance(auth_cred, AuthCredentials) and getattr(auth_cred, "logged_in", False):
|
||||
@@ -337,7 +167,7 @@ def admin_auth_required(resolver: Callable) -> Callable:
|
||||
"""
|
||||
|
||||
@wraps(resolver)
|
||||
async def wrapper(root: Any = None, info: Optional[GraphQLResolveInfo] = None, **kwargs: dict[str, Any]) -> Any:
|
||||
async def wrapper(root: Any = None, info: GraphQLResolveInfo | None = None, **kwargs: dict[str, Any]) -> Any:
|
||||
# Подробное логирование для диагностики
|
||||
logger.debug(f"[admin_auth_required] Начало проверки авторизации для {resolver.__name__}")
|
||||
|
||||
@@ -483,7 +313,7 @@ def permission_required(resource: str, operation: str, func: Callable) -> Callab
|
||||
f"[permission_required] Пользователь с ролью администратора {author.email} имеет все разрешения"
|
||||
)
|
||||
return await func(parent, info, *args, **kwargs)
|
||||
if not ca or not ca.has_permission(resource, operation):
|
||||
if not ca or not ca.has_permission(f"{resource}:{operation}"):
|
||||
logger.warning(
|
||||
f"[permission_required] У пользователя {author.email} нет разрешения {operation} на {resource}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user