circular-fix
Some checks failed
Deploy on push / deploy (push) Failing after 17s

This commit is contained in:
2025-08-17 16:33:54 +03:00
parent bc8447a444
commit e78e12eeee
65 changed files with 3304 additions and 1051 deletions

View File

@@ -9,27 +9,15 @@ RBAC: динамическая система прав для ролей и со
"""
import asyncio
import json
from functools import wraps
from pathlib import Path
from typing import Callable
from typing import Any, Callable
from auth.orm import Author
from auth.rbac_interface import get_community_queries, get_rbac_operations
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())
async def initialize_community_permissions(community_id: int) -> None:
"""
@@ -38,117 +26,8 @@ async def initialize_community_permissions(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 get_role_permissions_for_community(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 initialize_community_permissions(community_id)
# Получаем инициализированные разрешения
data = await redis.execute("GET", key)
if data:
return json.loads(data)
# Fallback на дефолтные разрешения если что-то пошло не так
return DEFAULT_ROLE_PERMISSIONS
async def set_role_permissions_for_community(community_id: int, role_permissions: dict) -> None:
"""
Устанавливает кастомные права ролей для сообщества.
Args:
community_id: ID сообщества
role_permissions: Словарь прав ролей
"""
key = f"community:roles:{community_id}"
await redis.execute("SET", key, json.dumps(role_permissions))
logger.info(f"Обновлены права ролей для сообщества {community_id}")
async def update_all_communities_permissions() -> None:
"""
Обновляет права для всех существующих сообществ с новыми дефолтными настройками.
"""
from orm.community import Community
with local_session() as session:
communities = session.query(Community).all()
for community in communities:
# Удаляем старые права
key = f"community:roles:{community.id}"
await redis.execute("DEL", key)
# Инициализируем новые права
await initialize_community_permissions(community.id)
logger.info(f"Обновлены права для {len(communities)} сообществ")
rbac_ops = get_rbac_operations()
await rbac_ops.initialize_community_permissions(community_id)
async def get_permissions_for_role(role: str, community_id: int) -> list[str]:
@@ -163,42 +42,54 @@ async def get_permissions_for_role(role: str, community_id: int) -> list[str]:
Returns:
Список разрешений для роли
"""
role_perms = await get_role_permissions_for_community(community_id)
return role_perms.get(role, [])
rbac_ops = get_rbac_operations()
return await rbac_ops.get_permissions_for_role(role, community_id)
async def update_all_communities_permissions() -> None:
"""
Обновляет права для всех существующих сообществ на основе актуальных дефолтных настроек.
Используется в админ-панели для применения изменений в правах на все сообщества.
"""
rbac_ops = get_rbac_operations()
# Поздний импорт для избежания циклических зависимостей
from orm.community import Community
try:
with local_session() as session:
# Получаем все сообщества
communities = session.query(Community).all()
for community in communities:
# Сбрасываем кеш прав для каждого сообщества
from services.redis import redis
key = f"community:roles:{community.id}"
await redis.execute("DEL", key)
# Переинициализируем права с актуальными дефолтными настройками
await rbac_ops.initialize_community_permissions(community.id)
logger.info(f"Обновлены права для {len(communities)} сообществ")
except Exception as e:
logger.error(f"Ошибка при обновлении прав всех сообществ: {e}", exc_info=True)
raise
# --- Получение ролей пользователя ---
def get_user_roles_in_community(author_id: int, community_id: int = 1, session=None) -> list[str]:
def get_user_roles_in_community(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 []
community_queries = get_community_queries()
return community_queries.get_user_roles_in_community(author_id, community_id, session)
async def user_has_permission(author_id: int, permission: str, community_id: int, session=None) -> bool:
async def user_has_permission(author_id: int, permission: str, community_id: int, session: Any = None) -> bool:
"""
Проверяет, есть ли у пользователя конкретное разрешение в сообществе.
@@ -211,8 +102,8 @@ async def user_has_permission(author_id: int, permission: str, community_id: int
Returns:
True если разрешение есть, False если нет
"""
user_roles = get_user_roles_in_community(author_id, community_id, session)
return await roles_have_permission(user_roles, permission, community_id)
rbac_ops = get_rbac_operations()
return await rbac_ops.user_has_permission(author_id, permission, community_id, session)
# --- Проверка прав ---
@@ -228,8 +119,8 @@ async def roles_have_permission(role_slugs: list[str], permission: str, communit
Returns:
True если хотя бы одна роль имеет разрешение
"""
role_perms = await get_role_permissions_for_community(community_id)
return any(permission in role_perms.get(role, []) for role in role_slugs)
rbac_ops = get_rbac_operations()
return await rbac_ops._roles_have_permission(role_slugs, permission, community_id)
# --- Декораторы ---
@@ -352,8 +243,7 @@ def get_community_id_from_context(info) -> int:
if "slug" in variables:
slug = variables["slug"]
try:
from orm.community import Community
from services.db import local_session
from orm.community import Community # Поздний импорт
with local_session() as session:
community = session.query(Community).filter_by(slug=slug).first()