2025-08-17 16:33:54 +03:00
|
|
|
|
"""
|
|
|
|
|
|
Интерфейс для RBAC операций, исключающий циркулярные импорты.
|
|
|
|
|
|
|
|
|
|
|
|
Этот модуль содержит только типы и абстрактные интерфейсы,
|
|
|
|
|
|
не импортирует ORM модели и не создает циклических зависимостей.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from typing import Any, Protocol
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RBACOperations(Protocol):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Протокол для RBAC операций, позволяющий ORM моделям
|
2025-08-17 17:56:31 +03:00
|
|
|
|
выполнять операции с правами без прямого импорта rbac.api
|
2025-08-17 16:33:54 +03:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
async def get_permissions_for_role(self, role: str, community_id: int) -> list[str]:
|
|
|
|
|
|
"""Получает разрешения для роли в сообществе"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
async def initialize_community_permissions(self, community_id: int) -> None:
|
|
|
|
|
|
"""Инициализирует права для нового сообщества"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
async def user_has_permission(
|
|
|
|
|
|
self, author_id: int, permission: str, community_id: int, session: Any = None
|
|
|
|
|
|
) -> bool:
|
|
|
|
|
|
"""Проверяет разрешение пользователя в сообществе"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
[0.9.7] - 2025-08-18
### 🔄 Изменения
- **SQLAlchemy KeyError** - исправление ошибки `KeyError: Reaction` при инициализации
- **Исправлена ошибка SQLAlchemy**: Устранена проблема `InvalidRequestError: When initializing mapper Mapper[Shout(shout)], expression Reaction failed to locate a name (Reaction)`
### 🧪 Тестирование
- **Исправление тестов** - адаптация к новой структуре моделей
- **RBAC инициализация** - добавление `rbac.initialize_rbac()` в `conftest.py`
- **Создан тест для getSession**: Добавлен комплексный тест `test_getSession_cookies.py` с проверкой всех сценариев
- **Покрытие edge cases**: Тесты проверяют работу с валидными/невалидными токенами, отсутствующими пользователями
- **Мокирование зависимостей**: Использование unittest.mock для изоляции тестируемого кода
### 🔧 Рефакторинг
- **Упрощена архитектура**: Убраны сложные конструкции с отложенными импортами, заменены на чистую архитектуру
- **Перемещение моделей** - `Author` и связанные модели перенесены в `orm/author.py`: Вынесены базовые модели пользователей (`Author`, `AuthorFollower`, `AuthorBookmark`, `AuthorRating`) из `orm.author` в отдельный модуль
- **Устранены циклические импорты**: Разорван цикл между `auth.core` → `orm.community` → `orm.author` через реструктуризацию архитектуры
- **Создан модуль `utils/password.py`**: Класс `Password` вынесен в utils для избежания циклических зависимостей
- **Оптимизированы импорты моделей**: Убран прямой импорт `Shout` из `orm/community.py`, заменен на строковые ссылки
### 🔧 Авторизация с cookies
- **getSession теперь работает с cookies**: Мутация `getSession` теперь может получать токен из httpOnly cookies даже без заголовка Authorization
- **Убрано требование авторизации**: `getSession` больше не требует декоратор `@login_required`, работает автономно
- **Поддержка dual-авторизации**: Токен может быть получен как из заголовка Authorization, так и из cookie `session_token`
- **Автоматическая установка cookies**: Middleware автоматически устанавливает httpOnly cookies при успешном `getSession`
- **Обновлена GraphQL схема**: `SessionInfo` теперь содержит поля `success`, `error` и опциональные `token`, `author`
- **Единообразная обработка токенов**: Все модули теперь используют централизованные функции для работы с токенами
- **Улучшена обработка ошибок**: Добавлена детальная валидация токенов и пользователей в `getSession`
- **Логирование операций**: Добавлены подробные логи для отслеживания процесса авторизации
### 📝 Документация
- **Обновлена схема GraphQL**: `SessionInfo` тип теперь соответствует новому формату ответа
- Обновлена документация RBAC
- Обновлена документация авторизации с cookies
2025-08-18 14:25:25 +03:00
|
|
|
|
async def get_role_permissions_for_community(self, community_id: int, role: str) -> dict:
|
|
|
|
|
|
"""Получает права для конкретной роли в сообществе"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
async def get_all_permissions_for_community(self, community_id: int) -> dict:
|
|
|
|
|
|
"""Получает все права ролей для конкретного сообщества"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
async def roles_have_permission(self, role_slugs: list[str], permission: str, community_id: int) -> bool:
|
2025-08-17 16:33:54 +03:00
|
|
|
|
"""Проверяет, есть ли у набора ролей конкретное разрешение в сообществе"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
2025-08-20 18:33:58 +03:00
|
|
|
|
def assign_role_to_user(self, author_id: int, role: str, community_id: int, session: Any = None) -> bool:
|
|
|
|
|
|
"""Назначает роль пользователю в сообществе"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
def get_user_roles_in_community(self, author_id: int, community_id: int, session: Any = None) -> list[str]:
|
|
|
|
|
|
"""Получает роли пользователя в сообществе"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
def remove_role_from_user(self, author_id: int, role: str, community_id: int, session: Any = None) -> bool:
|
|
|
|
|
|
"""Удаляет роль у пользователя в сообществе"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
2025-08-17 16:33:54 +03:00
|
|
|
|
|
|
|
|
|
|
class CommunityAuthorQueries(Protocol):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Протокол для запросов CommunityAuthor, позволяющий RBAC
|
|
|
|
|
|
выполнять запросы без прямого импорта ORM моделей
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2025-08-17 17:56:31 +03:00
|
|
|
|
def get_user_roles_in_community(self, author_id: int, community_id: int, session: Any = None) -> list[str]:
|
2025-08-17 16:33:54 +03:00
|
|
|
|
"""Получает роли пользователя в сообществе"""
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Глобальные переменные для dependency injection
|
|
|
|
|
|
_rbac_operations: RBACOperations | None = None
|
|
|
|
|
|
_community_queries: CommunityAuthorQueries | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_rbac_operations(ops: RBACOperations) -> None:
|
|
|
|
|
|
"""Устанавливает реализацию RBAC операций"""
|
2025-08-17 17:56:31 +03:00
|
|
|
|
global _rbac_operations # noqa: PLW0603
|
2025-08-17 16:33:54 +03:00
|
|
|
|
_rbac_operations = ops
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_community_queries(queries: CommunityAuthorQueries) -> None:
|
|
|
|
|
|
"""Устанавливает реализацию запросов сообщества"""
|
2025-08-17 17:56:31 +03:00
|
|
|
|
global _community_queries # noqa: PLW0603
|
2025-08-17 16:33:54 +03:00
|
|
|
|
_community_queries = queries
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_rbac_operations() -> RBACOperations:
|
|
|
|
|
|
"""Получает реализацию RBAC операций"""
|
|
|
|
|
|
if _rbac_operations is None:
|
|
|
|
|
|
raise RuntimeError("RBAC operations не инициализированы. Вызовите set_rbac_operations()")
|
|
|
|
|
|
return _rbac_operations
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_community_queries() -> CommunityAuthorQueries:
|
|
|
|
|
|
"""Получает реализацию запросов сообщества"""
|
|
|
|
|
|
if _community_queries is None:
|
|
|
|
|
|
raise RuntimeError("Community queries не инициализированы. Вызовите set_community_queries()")
|
|
|
|
|
|
return _community_queries
|