206 lines
8.8 KiB
Python
206 lines
8.8 KiB
Python
"""
|
||
Реализация RBAC операций для использования через интерфейс.
|
||
|
||
Этот модуль предоставляет конкретную реализацию RBAC операций,
|
||
не импортирует ORM модели напрямую, используя dependency injection.
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from auth.orm import Author
|
||
from auth.rbac_interface import CommunityAuthorQueries, RBACOperations, get_community_queries
|
||
from services.db import local_session
|
||
from services.redis import redis
|
||
from settings import ADMIN_EMAILS
|
||
from utils.logger import root_logger as logger
|
||
|
||
# --- Загрузка каталога сущностей и дефолтных прав ---
|
||
|
||
with Path("services/permissions_catalog.json").open() as f:
|
||
PERMISSIONS_CATALOG = json.load(f)
|
||
|
||
with Path("services/default_role_permissions.json").open() as f:
|
||
DEFAULT_ROLE_PERMISSIONS = json.load(f)
|
||
|
||
role_names = list(DEFAULT_ROLE_PERMISSIONS.keys())
|
||
|
||
|
||
class RBACOperationsImpl(RBACOperations):
|
||
"""Конкретная реализация RBAC операций"""
|
||
|
||
async def get_permissions_for_role(self, role: str, community_id: int) -> list[str]:
|
||
"""
|
||
Получает список разрешений для конкретной роли в сообществе.
|
||
Иерархия уже применена при инициализации сообщества.
|
||
|
||
Args:
|
||
role: Название роли
|
||
community_id: ID сообщества
|
||
|
||
Returns:
|
||
Список разрешений для роли
|
||
"""
|
||
role_perms = await self._get_role_permissions_for_community(community_id)
|
||
return role_perms.get(role, [])
|
||
|
||
async def initialize_community_permissions(self, community_id: int) -> None:
|
||
"""
|
||
Инициализирует права для нового сообщества на основе дефолтных настроек с учетом иерархии.
|
||
|
||
Args:
|
||
community_id: ID сообщества
|
||
"""
|
||
key = f"community:roles:{community_id}"
|
||
|
||
# Проверяем, не инициализировано ли уже
|
||
existing = await redis.execute("GET", key)
|
||
if existing:
|
||
logger.debug(f"Права для сообщества {community_id} уже инициализированы")
|
||
return
|
||
|
||
# Создаем полные списки разрешений с учетом иерархии
|
||
expanded_permissions = {}
|
||
|
||
def get_role_permissions(role: str, processed_roles: set[str] | None = None) -> set[str]:
|
||
"""
|
||
Рекурсивно получает все разрешения для роли, включая наследованные
|
||
|
||
Args:
|
||
role: Название роли
|
||
processed_roles: Список уже обработанных ролей для предотвращения зацикливания
|
||
|
||
Returns:
|
||
Множество разрешений
|
||
"""
|
||
if processed_roles is None:
|
||
processed_roles = set()
|
||
|
||
if role in processed_roles:
|
||
return set()
|
||
|
||
processed_roles.add(role)
|
||
|
||
# Получаем прямые разрешения роли
|
||
direct_permissions = set(DEFAULT_ROLE_PERMISSIONS.get(role, []))
|
||
|
||
# Проверяем, есть ли наследование роли
|
||
for perm in list(direct_permissions):
|
||
if perm in role_names:
|
||
# Если пермишен - это название роли, добавляем все её разрешения
|
||
direct_permissions.remove(perm)
|
||
direct_permissions.update(get_role_permissions(perm, processed_roles))
|
||
|
||
return direct_permissions
|
||
|
||
# Формируем расширенные разрешения для каждой роли
|
||
for role in role_names:
|
||
expanded_permissions[role] = list(get_role_permissions(role))
|
||
|
||
# Сохраняем в Redis уже развернутые списки с учетом иерархии
|
||
await redis.execute("SET", key, json.dumps(expanded_permissions))
|
||
logger.info(f"Инициализированы права с иерархией для сообщества {community_id}")
|
||
|
||
async def user_has_permission(
|
||
self, author_id: int, permission: str, community_id: int, session: Any = None
|
||
) -> bool:
|
||
"""
|
||
Проверяет, есть ли у пользователя конкретное разрешение в сообществе.
|
||
|
||
Args:
|
||
author_id: ID автора
|
||
permission: Разрешение для проверки
|
||
community_id: ID сообщества
|
||
session: Опциональная сессия БД (для тестов)
|
||
|
||
Returns:
|
||
True если разрешение есть, False если нет
|
||
"""
|
||
community_queries = get_community_queries()
|
||
user_roles = community_queries.get_user_roles_in_community(author_id, community_id, session)
|
||
return await self._roles_have_permission(user_roles, permission, community_id)
|
||
|
||
async def _get_role_permissions_for_community(self, community_id: int) -> dict:
|
||
"""
|
||
Получает права ролей для конкретного сообщества.
|
||
Если права не настроены, автоматически инициализирует их дефолтными.
|
||
|
||
Args:
|
||
community_id: ID сообщества
|
||
|
||
Returns:
|
||
Словарь прав ролей для сообщества
|
||
"""
|
||
key = f"community:roles:{community_id}"
|
||
data = await redis.execute("GET", key)
|
||
|
||
if data:
|
||
return json.loads(data)
|
||
|
||
# Автоматически инициализируем, если не найдено
|
||
await self.initialize_community_permissions(community_id)
|
||
|
||
# Получаем инициализированные разрешения
|
||
data = await redis.execute("GET", key)
|
||
if data:
|
||
return json.loads(data)
|
||
|
||
# Fallback на дефолтные разрешения если что-то пошло не так
|
||
return DEFAULT_ROLE_PERMISSIONS
|
||
|
||
async def _roles_have_permission(self, role_slugs: list[str], permission: str, community_id: int) -> bool:
|
||
"""
|
||
Проверяет, есть ли у набора ролей конкретное разрешение в сообществе.
|
||
|
||
Args:
|
||
role_slugs: Список ролей для проверки
|
||
permission: Разрешение для проверки
|
||
community_id: ID сообщества
|
||
|
||
Returns:
|
||
True если хотя бы одна роль имеет разрешение
|
||
"""
|
||
role_perms = await self._get_role_permissions_for_community(community_id)
|
||
return any(permission in role_perms.get(role, []) for role in role_slugs)
|
||
|
||
|
||
class CommunityAuthorQueriesImpl(CommunityAuthorQueries):
|
||
"""Конкретная реализация запросов CommunityAuthor через поздний импорт"""
|
||
|
||
def get_user_roles_in_community(
|
||
self, author_id: int, community_id: int = 1, session: Any = None
|
||
) -> list[str]:
|
||
"""
|
||
Получает роли пользователя в сообществе через новую систему CommunityAuthor
|
||
"""
|
||
# Поздний импорт для избежания циклических зависимостей
|
||
from orm.community import CommunityAuthor
|
||
|
||
try:
|
||
if session:
|
||
ca = (
|
||
session.query(CommunityAuthor)
|
||
.where(CommunityAuthor.author_id == author_id, CommunityAuthor.community_id == community_id)
|
||
.first()
|
||
)
|
||
return ca.role_list if ca else []
|
||
|
||
# Используем local_session для продакшена
|
||
with local_session() as db_session:
|
||
ca = (
|
||
db_session.query(CommunityAuthor)
|
||
.where(CommunityAuthor.author_id == author_id, CommunityAuthor.community_id == community_id)
|
||
.first()
|
||
)
|
||
return ca.role_list if ca else []
|
||
except Exception as e:
|
||
logger.error(f"[get_user_roles_in_community] Ошибка при получении ролей: {e}")
|
||
return []
|
||
|
||
|
||
# Создаем экземпляры реализаций
|
||
rbac_operations = RBACOperationsImpl()
|
||
community_queries = CommunityAuthorQueriesImpl()
|