Files
core/docs/rbac-system.md
Untone 1b48675b92
Some checks failed
Deploy on push / deploy (push) Failing after 2m22s
[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

555 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Система ролей и разрешений (RBAC)
## Общее описание
Система управления доступом на основе ролей (Role-Based Access Control, RBAC) обеспечивает гибкое управление правами пользователей в рамках сообществ платформы. Система поддерживает иерархическое наследование разрешений и автоматическое кеширование для оптимальной производительности.
## Архитектура системы
### Принципы работы
1. **Иерархия ролей**: Роли наследуют права друг от друга с рекурсивным вычислением
2. **Контекстная проверка**: Права проверяются в контексте конкретного сообщества
3. **Системные администраторы**: Пользователи из `ADMIN_EMAILS` автоматически получают роль `admin` в любом сообществе
4. **Динамическое определение community_id**: Система автоматически определяет `community_id` из аргументов GraphQL мутаций
5. **Рекурсивное наследование**: Разрешения автоматически включают все унаследованные права от родительских ролей
### Получение community_id
Система RBAC автоматически определяет `community_id` для проверки прав:
- **Из аргументов мутации**: Для мутаций типа `delete_community(slug: String!)` система получает `slug` и находит соответствующий `community_id`
- **По умолчанию**: Если `community_id` не может быть определен, используется значение `1`
- **Логирование**: Все операции получения `community_id` логируются для отладки
### Основные компоненты
1. **Community** - сообщество, контекст для ролей
2. **CommunityAuthor** - связь пользователя с сообществом и его ролями
3. **Role** - роль пользователя (reader, author, editor, admin)
4. **Permission** - разрешение на выполнение действия
5. **RBAC Service** - сервис управления ролями и разрешениями с рекурсивным наследованием
### Модель данных
```sql
-- Основная таблица связи пользователя с сообществом
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. Регистрация пользователя
При регистрации пользователя:
```python
# 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`:
```python
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. Проверка разрешений
При выполнении действий проверяются разрешения:
```python
@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. Управление ролями
#### Назначение ролей
```python
# Назначить роль пользователю
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"])
```
#### Проверка ролей
```python
# Получить роли пользователя
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
)
```
## Конфигурация сообщества
### Дефолтные роли
Каждое сообщество может настроить свои дефолтные роли для новых пользователей:
```python
# Получить дефолтные роли
default_roles = community.get_default_roles() # ["reader", "author"]
# Установить дефолтные роли
community.set_default_roles(["reader"]) # Только reader по умолчанию
```
### Доступные роли
Сообщество может ограничить список доступных ролей:
```python
# Все роли доступны по умолчанию
available_roles = ["reader", "author", "artist", "expert", "editor", "admin"]
# Ограничить только базовыми ролями
community.set_available_roles(["reader", "author", "editor"])
```
## Миграция данных
### Проблемы существующих пользователей
1. **Пользователи без роли `reader`** - не могут войти в систему
2. **Старая система ролей** - данные в `Author.roles` устарели
3. **Отсутствие связей `CommunityAuthor`** - новые пользователи без ролей
### Решения
#### 1. Автоматическое добавление роли `reader`
```python
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. Массовое исправление ролей
```python
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 мутации
```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 запросы
```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
}
}
}
```
## Безопасность
### Принципы безопасности
1. **Принцип минимальных привилегий** - пользователь получает только необходимые права
2. **Разделение обязанностей** - разные роли для разных функций
3. **Аудит действий** - логирование всех изменений ролей
4. **Проверка на каждом уровне** - валидация разрешений в API и UI
### Защита от атак
1. **Privilege Escalation** - проверка прав на изменение ролей
2. **Mass Assignment** - валидация входных данных
3. **CSRF** - использование токенов для изменения ролей
4. **XSS** - экранирование данных ролей в UI
### Логирование
```python
# Логирование изменений ролей
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}")
```
## Тестирование
### Тестовые сценарии
1. **Регистрация пользователя** - проверка назначения дефолтных ролей
2. **Вход в систему** - проверка требования роли `reader`
3. **Назначение ролей** - проверка прав администратора
4. **Проверка разрешений** - валидация доступа к ресурсам
5. **Иерархия ролей** - наследование прав
### Пример тестов
```python
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")
```
## Производительность
### Оптимизации
1. **Кеширование ролей** - хранение ролей пользователя в Redis
2. **Индексы БД** - быстрый поиск по `community_id` и `author_id`
3. **Batch операции** - массовое назначение ролей
4. **Ленивая загрузка** - загрузка разрешений по требованию
### Мониторинг
```python
# Метрики для 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')
```
## Новые возможности системы
### Рекурсивное наследование разрешений
Система теперь поддерживает автоматическое вычисление всех унаследованных разрешений:
```python
# Получить разрешения для конкретной роли с учетом наследования
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)
# Возвращает полный словарь всех ролей с их разрешениями
```
### Автоматическая инициализация
При создании нового сообщества система автоматически инициализирует права с учетом иерархии:
```python
# Автоматически создает расширенные разрешения для всех ролей
await rbac_ops.initialize_community_permissions(community_id=123)
# Система рекурсивно вычисляет все наследованные разрешения
# и сохраняет их в Redis для быстрого доступа
```
### Улучшенная производительность
- **Кеширование в Redis**: Все разрешения кешируются с ключом `community:roles:{community_id}`
- **Рекурсивное вычисление**: Разрешения вычисляются один раз при инициализации
- **Быстрая проверка**: Проверка разрешений происходит за O(1) из кеша
### Обновленный API
```python
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
```
## Миграция на новую систему
### Обновление существующего кода
Если в вашем коде используются старые методы, обновите их:
```python
# Старый код
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` работают без изменений
- Существующие тесты проходят без модификации