- **🔍 Comprehensive authentication documentation refactoring**: Полная переработка документации аутентификации
- Обновлена таблица содержания в README.md
- Исправлены архитектурные диаграммы - токены хранятся только в Redis
- Добавлены практические примеры кода для микросервисов
- Консолидирована OAuth документация
23 KiB
Система ролей и разрешений (RBAC)
Общее описание
Система управления доступом на основе ролей (Role-Based Access Control, RBAC) обеспечивает гибкое управление правами пользователей в рамках сообществ платформы. Система поддерживает иерархическое наследование разрешений и автоматическое кеширование для оптимальной производительности.
Архитектура системы
Принципы работы
- Иерархия ролей: Роли наследуют права друг от друга с рекурсивным вычислением
- Контекстная проверка: Права проверяются в контексте конкретного сообщества
- Системные администраторы: Пользователи из
ADMIN_EMAILSавтоматически получают рольadminв любом сообществе - Динамическое определение community_id: Система автоматически определяет
community_idиз аргументов GraphQL мутаций - Рекурсивное наследование: Разрешения автоматически включают все унаследованные права от родительских ролей
Получение community_id
Система RBAC автоматически определяет community_id для проверки прав:
- Из аргументов мутации: Для мутаций типа
delete_community(slug: String!)система получаетslugи находит соответствующийcommunity_id - По умолчанию: Если
community_idне может быть определен, используется значение1 - Логирование: Все операции получения
community_idлогируются для отладки
Основные компоненты
- Community - сообщество, контекст для ролей
- CommunityAuthor - связь пользователя с сообществом и его ролями
- Role - роль пользователя (reader, author, editor, admin)
- Permission - разрешение на выполнение действия
- RBAC Service - сервис управления ролями и разрешениями с рекурсивным наследованием
Модель данных
-- Основная таблица связи пользователя с сообществом
CREATE TABLE community_author (
id INTEGER PRIMARY KEY,
community_id INTEGER REFERENCES community(id),
author_id INTEGER REFERENCES author(id),
roles TEXT, -- CSV строка ролей: "reader,author,editor"
joined_at INTEGER NOT NULL,
UNIQUE(community_id, author_id)
);
-- Индексы для производительности
CREATE INDEX idx_community_author_community ON community_author(community_id);
CREATE INDEX idx_community_author_author ON community_author(author_id);
Роли в системе
Базовые роли
1. reader (Читатель)
- Обязательная роль для всех пользователей
- Права:
- Чтение публикаций
- Просмотр комментариев
- Подписка на сообщества
- Базовая навигация по платформе
2. author (Автор)
- Права:
- Все права
reader - Создание публикаций (шаутов)
- Редактирование своих публикаций
- Комментирование
- Создание черновиков
- Все права
3. artist (Художник)
- Права:
- Все права
author - Может быть указан как credited artist
- Загрузка и управление медиафайлами
- Все права
4. expert (Эксперт)
- Права:
- Все права
author - Добавление доказательств (evidence)
- Верификация контента
- Экспертная оценка публикаций
- Все права
5. editor (Редактор)
- Права:
- Все права
expert - Модерация контента
- Редактирование чужих публикаций
- Управление тегами и категориями
- Модерация комментариев
- Все права
6. admin (Администратор)
- Права:
- Все права
editor - Управление пользователями (
author:delete_any,author:update_any) - Управление ролями
- Настройка сообщества (
community:delete_any,community:update_any) - Управление чатами и сообщениями (
chat:delete_any,chat:update_any,message:delete_any,message:update_any) - Полный доступ к административной панели
- Все права
Иерархия ролей
admin > editor > expert > artist/author > reader
Каждая роль автоматически включает права всех ролей ниже по иерархии. Система рекурсивно вычисляет все унаследованные разрешения при инициализации сообщества.
Разрешения (Permissions)
Формат разрешений
Разрешения записываются в формате resource:action:
shout:create- создание публикацийshout:edit- редактирование публикацийshout:delete- удаление публикаций
Централизованная проверка прав
Система RBAC использует централизованную проверку прав через декораторы:
@require_permission("permission")- проверка конкретного разрешения@require_any_permission(["permission1", "permission2"])- проверка наличия любого из разрешений@require_all_permissions(["permission1", "permission2"])- проверка наличия всех разрешений
Важно: В resolvers не должна быть дублирующая логика проверки прав - вся проверка осуществляется через систему RBAC.
Категории разрешений
Контент (Content)
shout:create- создание шаутовshout:edit_own- редактирование своих шаутовshout:edit_any- редактирование любых шаутовshout:delete_own- удаление своих шаутовshout:delete_any- удаление любых шаутовshout:publish- публикация шаутовshout:feature- продвижение шаутов
Комментарии (Comments)
comment:create- создание комментариевcomment:edit_own- редактирование своих комментариевcomment:edit_any- редактирование любых комментариевcomment:delete_own- удаление своих комментариевcomment:delete_any- удаление любых комментариевcomment:moderate- модерация комментариев
Пользователи (Users)
user:view_profile- просмотр профилейuser:edit_own_profile- редактирование своего профиляuser:manage_roles- управление ролями пользователейuser:ban- блокировка пользователей
Сообщество (Community)
community:view- просмотр сообществаcommunity:settings- настройки сообществаcommunity:manage_members- управление участникамиcommunity:analytics- просмотр аналитики
Логика работы системы
1. Регистрация пользователя
При регистрации пользователя:
# 1. Создается запись в Author
user = Author(email=email, name=name, ...)
# 2. Создается связь с дефолтным сообществом (ID=1)
community_author = CommunityAuthor(
community_id=1,
author_id=user.id,
roles="reader,author" # Дефолтные роли
)
# 3. Создается подписка на сообщество
follower = CommunityFollower(
community=1,
follower=user.id
)
2. Проверка авторизации
При входе в систему проверяется наличие роли reader:
def login(email, password):
# 1. Найти пользователя
author = Author.get_by_email(email)
# 2. Проверить пароль
if not verify_password(password, author.password):
return error("Неверный пароль")
# 3. Получить роли в дефолтном сообществе
user_roles = get_user_roles_in_community(author.id, community_id=1)
# 4. Проверить наличие роли reader
if "reader" not in user_roles and author.email not in ADMIN_EMAILS:
return error("Нет прав для входа. Требуется роль 'reader'.")
# 5. Создать сессию
return create_session(author)
3. Проверка разрешений
При выполнении действий проверяются разрешения:
@login_required
async def create_shout(info, input):
user_id = info.context["author"]["id"]
# Проверяем разрешение на создание шаутов
has_permission = await check_user_permission_in_community(
user_id,
"shout:create",
community_id=1
)
if not has_permission:
raise GraphQLError("Недостаточно прав для создания публикации")
# Создаем шаут
return Shout.create(input)
4. Управление ролями
Назначение ролей
# Назначить роль пользователю
assign_role_to_user(user_id=123, role="editor", community_id=1)
# Убрать роль
remove_role_from_user(user_id=123, role="editor", community_id=1)
# Установить все роли
community.set_user_roles(user_id=123, roles=["reader", "author", "editor"])
Проверка ролей
# Получить роли пользователя
roles = get_user_roles_in_community(user_id=123, community_id=1)
# Проверить конкретную роль
has_role = "editor" in roles
# Проверить разрешение
has_permission = await check_user_permission_in_community(
user_id=123,
permission="shout:edit_any",
community_id=1
)
Конфигурация сообщества
Дефолтные роли
Каждое сообщество может настроить свои дефолтные роли для новых пользователей:
# Получить дефолтные роли
default_roles = community.get_default_roles() # ["reader", "author"]
# Установить дефолтные роли
community.set_default_roles(["reader"]) # Только reader по умолчанию
Доступные роли
Сообщество может ограничить список доступных ролей:
# Все роли доступны по умолчанию
available_roles = ["reader", "author", "artist", "expert", "editor", "admin"]
# Ограничить только базовыми ролями
community.set_available_roles(["reader", "author", "editor"])
Миграция данных
Проблемы существующих пользователей
- Пользователи без роли
reader- не могут войти в систему - Старая система ролей - данные в
Author.rolesустарели - Отсутствие связей
CommunityAuthor- новые пользователи без ролей
Решения
1. Автоматическое добавление роли reader
async def ensure_user_has_reader_role(user_id: int) -> bool:
"""Убеждается, что у пользователя есть роль 'reader'"""
existing_roles = get_user_roles_in_community(user_id, community_id=1)
if "reader" not in existing_roles:
success = assign_role_to_user(user_id, "reader", community_id=1)
if success:
logger.info(f"Роль 'reader' добавлена пользователю {user_id}")
return True
return True
2. Массовое исправление ролей
async def fix_all_users_reader_role() -> dict[str, int]:
"""Проверяет всех пользователей и добавляет роль 'reader'"""
stats = {"checked": 0, "fixed": 0, "errors": 0}
all_authors = session.query(Author).all()
for author in all_authors:
stats["checked"] += 1
try:
await ensure_user_has_reader_role(author.id)
stats["fixed"] += 1
except Exception as e:
logger.error(f"Ошибка для пользователя {author.id}: {e}")
stats["errors"] += 1
return stats
API для работы с ролями
GraphQL мутации
# Назначить роль пользователю
mutation AssignRole($userId: Int!, $role: String!, $communityId: Int) {
assignRole(userId: $userId, role: $role, communityId: $communityId) {
success
message
}
}
# Убрать роль
mutation RemoveRole($userId: Int!, $role: String!, $communityId: Int) {
removeRole(userId: $userId, role: $role, communityId: $communityId) {
success
message
}
}
# Установить все роли пользователя
mutation SetUserRoles($userId: Int!, $roles: [String!]!, $communityId: Int) {
setUserRoles(userId: $userId, roles: $roles, communityId: $communityId) {
success
message
}
}
GraphQL запросы
# Получить роли пользователя
query GetUserRoles($userId: Int!, $communityId: Int) {
userRoles(userId: $userId, communityId: $communityId) {
roles
permissions
community {
id
name
}
}
}
# Получить всех участников сообщества с ролями
query GetCommunityMembers($communityId: Int!) {
communityMembers(communityId: $communityId) {
authorId
roles
permissions
joinedAt
author {
id
name
email
}
}
}
Безопасность
Принципы безопасности
- Принцип минимальных привилегий - пользователь получает только необходимые права
- Разделение обязанностей - разные роли для разных функций
- Аудит действий - логирование всех изменений ролей
- Проверка на каждом уровне - валидация разрешений в API и UI
Защита от атак
- Privilege Escalation - проверка прав на изменение ролей
- Mass Assignment - валидация входных данных
- CSRF - использование токенов для изменения ролей
- XSS - экранирование данных ролей в UI
Логирование
# Логирование изменений ролей
logger.info(f"Role {role} assigned to user {user_id} by admin {admin_id}")
logger.warning(f"Failed login attempt for user without reader role: {user_id}")
logger.error(f"Permission denied: user {user_id} tried to access {resource}")
Тестирование
Тестовые сценарии
- Регистрация пользователя - проверка назначения дефолтных ролей
- Вход в систему - проверка требования роли
reader - Назначение ролей - проверка прав администратора
- Проверка разрешений - валидация доступа к ресурсам
- Иерархия ролей - наследование прав
Пример тестов
def test_user_registration_assigns_default_roles():
"""Проверяет назначение дефолтных ролей при регистрации"""
user = create_user(email="test@test.com")
roles = get_user_roles_in_community(user.id, community_id=1)
assert "reader" in roles
assert "author" in roles
def test_login_requires_reader_role():
"""Проверяет требование роли reader для входа"""
user = create_user_without_roles(email="test@test.com")
result = login(email="test@test.com", password="password")
assert result["success"] == False
assert "reader" in result["error"]
def test_role_hierarchy():
"""Проверяет иерархию ролей"""
user = create_user(email="admin@test.com")
assign_role_to_user(user.id, "admin", community_id=1)
# Админ должен иметь все права
assert check_permission(user.id, "shout:create")
assert check_permission(user.id, "user:manage")
assert check_permission(user.id, "community:settings")
Производительность
Оптимизации
- Кеширование ролей - хранение ролей пользователя в Redis
- Индексы БД - быстрый поиск по
community_idиauthor_id - Batch операции - массовое назначение ролей
- Ленивая загрузка - загрузка разрешений по требованию
Мониторинг
# Метрики для Prometheus
role_checks_total = Counter('rbac_role_checks_total')
permission_checks_total = Counter('rbac_permission_checks_total')
role_assignments_total = Counter('rbac_role_assignments_total')
🔗 Связанные системы
- Authentication System - Система аутентификации
- Security System - Управление паролями и email
- Redis Schema - Схема данных и кеширование
Новые возможности системы
Рекурсивное наследование разрешений
Система теперь поддерживает автоматическое вычисление всех унаследованных разрешений:
# Получить разрешения для конкретной роли с учетом наследования
role_permissions = await rbac_ops.get_role_permissions_for_community(
community_id=1,
role="editor"
)
# Возвращает: {"editor": ["shout:edit_any", "comment:moderate", "draft:create", "shout:read", ...]}
# Получить все разрешения для сообщества
all_permissions = await rbac_ops.get_all_permissions_for_community(community_id=1)
# Возвращает полный словарь всех ролей с их разрешениями
Автоматическая инициализация
При создании нового сообщества система автоматически инициализирует права с учетом иерархии:
# Автоматически создает расширенные разрешения для всех ролей
await rbac_ops.initialize_community_permissions(community_id=123)
# Система рекурсивно вычисляет все наследованные разрешения
# и сохраняет их в Redis для быстрого доступа
Улучшенная производительность
- Кеширование в Redis: Все разрешения кешируются с ключом
community:roles:{community_id} - Рекурсивное вычисление: Разрешения вычисляются один раз при инициализации
- Быстрая проверка: Проверка разрешений происходит за O(1) из кеша
Обновленный API
class RBACOperations(Protocol):
# Получить разрешения для конкретной роли с наследованием
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
Миграция на новую систему
Обновление существующего кода
Если в вашем коде используются старые методы, обновите их:
# Старый код
permissions = await rbac_ops._get_role_permissions_for_community(community_id)
# Новый код
permissions = await rbac_ops.get_all_permissions_for_community(community_id)
# Или для конкретной роли
role_permissions = await rbac_ops.get_role_permissions_for_community(community_id, "editor")
Обратная совместимость
Новая система полностью совместима с существующим кодом:
- Все публичные API методы сохранили свои сигнатуры
- Декораторы
@require_permissionработают без изменений - Существующие тесты проходят без модификации