auth-fix
All checks were successful
Deploy on push / deploy (push) Successful in 13s

This commit is contained in:
2025-07-23 13:29:49 +03:00
parent 3826797317
commit 867232e48f
2 changed files with 504 additions and 353 deletions

View File

@@ -1,403 +1,477 @@
# Система RBAC (Role-Based Access Control) # Система ролей и разрешений (RBAC)
## Обзор ## Общее описание
Система управления доступом на основе ролей для платформы Discours. Роли хранятся в CSV формате в таблице `CommunityAuthor` и могут быть назначены пользователям в рамках конкретного сообщества. Система управления доступом на основе ролей (Role-Based Access Control, RBAC) обеспечивает гибкое управление правами пользователей в рамках сообществ платформы.
> **v0.6.11: Важно!** Наследование разрешений между ролями происходит **только при инициализации** прав для сообщества. В Redis хранятся уже развернутые (полные) списки разрешений для каждой роли. При запросе прав никакого on-the-fly наследования не происходит — только lookup по роли. ## Архитектура системы
## Архитектура ### Основные компоненты
### Основные принципы 1. **Community** - сообщество, контекст для ролей
- **CSV хранение**: Роли хранятся как CSV строка в поле `roles` таблицы `CommunityAuthor` 2. **CommunityAuthor** - связь пользователя с сообществом и его ролями
- **Простота**: Один пользователь может иметь несколько ролей в одном сообществе 3. **Role** - роль пользователя (reader, author, editor, admin)
- **Привязка к сообществу**: Роли существуют в контексте конкретного сообщества 4. **Permission** - разрешение на выполнение действия
- **Иерархия ролей**: `reader``author``artist``expert``editor``admin` 5. **RBAC Service** - сервис управления ролями и разрешениями
- **Наследование прав**: Каждая роль наследует все права предыдущих ролей **только при инициализации**
### Схема базы данных ### Модель данных
#### Таблица `community_author`
```sql ```sql
-- Основная таблица связи пользователя с сообществом
CREATE TABLE community_author ( CREATE TABLE community_author (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
community_id INTEGER REFERENCES community(id) NOT NULL, community_id INTEGER REFERENCES community(id),
author_id INTEGER REFERENCES author(id) NOT NULL, author_id INTEGER REFERENCES author(id),
roles TEXT, -- CSV строка ролей ("reader,author,expert") roles TEXT, -- CSV строка ролей: "reader,author,editor"
joined_at INTEGER NOT NULL, -- Unix timestamp присоединения joined_at INTEGER NOT NULL,
UNIQUE(community_id, author_id)
CONSTRAINT uq_community_author UNIQUE (community_id, author_id)
); );
```
#### Индексы -- Индексы для производительности
```sql
CREATE INDEX idx_community_author_community ON community_author(community_id); CREATE INDEX idx_community_author_community ON community_author(community_id);
CREATE INDEX idx_community_author_author ON community_author(author_id); CREATE INDEX idx_community_author_author ON community_author(author_id);
``` ```
## Работа с ролями ## Роли в системе
### Модель CommunityAuthor ### Базовые роли
#### Основные методы #### 1. `reader` (Читатель)
```python - **Обязательная роль для всех пользователей**
from orm.community import CommunityAuthor - **Права:**
- Чтение публикаций
- Просмотр комментариев
- Подписка на сообщества
- Базовая навигация по платформе
# Получение списка ролей #### 2. `author` (Автор)
ca = session.query(CommunityAuthor).first() - **Права:**
roles = ca.role_list # ['reader', 'author', 'expert'] - Все права `reader`
- Создание публикаций (шаутов)
- Редактирование своих публикаций
- Комментирование
- Создание черновиков
# Установка ролей #### 3. `artist` (Художник)
ca.role_list = ['reader', 'author'] - **Права:**
- Все права `author`
- Может быть указан как credited artist
- Загрузка и управление медиафайлами
# Проверка роли #### 4. `expert` (Эксперт)
has_author = ca.has_role('author') # True - **Права:**
- Все права `author`
- Добавление доказательств (evidence)
- Верификация контента
- Экспертная оценка публикаций
# Добавление роли #### 5. `editor` (Редактор)
ca.add_role('expert') - **Права:**
- Все права `expert`
- Модерация контента
- Редактирование чужих публикаций
- Управление тегами и категориями
- Модерация комментариев
# Удаление роли #### 6. `admin` (Администратор)
ca.remove_role('author') - **Права:**
- Все права `editor`
# Установка полного списка ролей - Управление пользователями
ca.set_roles(['reader', 'editor']) - Управление ролями
- Настройка сообщества
# Получение всех разрешений - Полный доступ к административной панели
permissions = await ca.get_permissions() # ['shout:read', 'shout:create', ...]
# Проверка разрешения
can_create = await ca.has_permission('shout:create') # True
```
### Вспомогательные функции
#### Основные функции из `orm/community.py`
```python
from orm.community import (
get_user_roles_in_community,
check_user_permission_in_community,
assign_role_to_user,
remove_role_from_user,
get_all_community_members_with_roles,
bulk_assign_roles
)
# Получение ролей пользователя
roles = get_user_roles_in_community(author_id=123, community_id=1)
# Возвращает: ['reader', 'author']
# Проверка разрешения
has_perm = await check_user_permission_in_community(
author_id=123,
permission='shout:create',
community_id=1
)
# Назначение роли
success = assign_role_to_user(
author_id=123,
role='expert',
community_id=1
)
# Удаление роли
success = remove_role_from_user(
author_id=123,
role='author',
community_id=1
)
# Получение всех участников с ролями
members = get_all_community_members_with_roles(community_id=1)
# Возвращает: [{'author_id': 123, 'roles': ['reader', 'author'], ...}, ...]
# Массовое назначение ролей
bulk_assign_roles([
{'author_id': 123, 'roles': ['reader', 'author']},
{'author_id': 456, 'roles': ['expert', 'editor']}
], community_id=1)
```
## Система разрешений
### Иерархия ролей ### Иерархия ролей
``` ```
reader → author → artist → expert → editor → admin admin > editor > expert > artist/author > reader
``` ```
Каждая роль наследует все права предыдущих ролей в дефолтной иерархии **только при создании сообщества**. Каждая роль автоматически включает права всех ролей ниже по иерархии.
### Стандартные роли и их права ## Разрешения (Permissions)
| Роль | Базовые права | Дополнительные права |
|------|---------------|---------------------|
| `reader` | `*:read`, базовые реакции | `chat:*`, `message:*`, `bookmark:*` |
| `author` | Наследует `reader` + `*:create`, `*:update_own`, `*:delete_own` | `draft:*` |
| `artist` | Наследует `author` | `reaction:CREDIT:accept`, `reaction:CREDIT:decline` |
| `expert` | Наследует `author` | `reaction:PROOF:*`, `reaction:DISPROOF:*`, `reaction:AGREE:*`, `reaction:DISAGREE:*` |
| `editor` | `*:read`, `*:create`, `*:update_any`, `*:delete_any` | `community:read`, `community:update_own`, `topic:merge`, `topic:create`, `topic:update_own`, `topic:delete_own` |
| `admin` | Все права (`*`) | Полный доступ ко всем функциям |
### Формат разрешений ### Формат разрешений
- Базовые: `<entity>:<action>` (например: `shout:create`, `topic:create`)
- Реакции: `reaction:<type>:<action>` (например: `reaction:LIKE:create`)
- Специальные: `topic:merge` (слияние топиков)
- Wildcard: `<entity>:*` или `*` (только для admin)
### Права на топики Разрешения записываются в формате `resource:action`:
- `topic:create` - создание новых топиков (роли: `author`, `editor`)
- `topic:read` - чтение топиков (роли: `reader` и выше)
- `topic:update_own` - обновление собственных топиков (роли: `author`)
- `topic:update_any` - обновление любых топиков (роли: `editor`)
- `topic:delete_own` - удаление собственных топиков (роли: `author`)
- `topic:delete_any` - удаление любых топиков (роли: `editor`)
- `topic:merge` - слияние топиков (роли: `editor`)
## GraphQL API - `shout:create` - создание публикаций
- `shout:edit` - редактирование публикаций
- `shout:delete` - удаление публикаций
- `comment:create` - создание комментариев
- `comment:moderate` - модерация комментариев
- `user:manage` - управление пользователями
- `community:settings` - настройки сообщества
### Запросы ### Категории разрешений
#### Получение участников сообщества с ролями #### Контент (Content)
```graphql - `shout:create` - создание шаутов
query AdminGetCommunityMembers( - `shout:edit_own` - редактирование своих шаутов
$community_id: Int! - `shout:edit_any` - редактирование любых шаутов
$page: Int = 1 - `shout:delete_own` - удаление своих шаутов
$limit: Int = 50 - `shout:delete_any` - удаление любых шаутов
) { - `shout:publish` - публикация шаутов
adminGetCommunityMembers( - `shout:feature` - продвижение шаутов
community_id: $community_id
page: $page
limit: $limit
) {
success
error
members {
id
name
slug
email
roles
is_follower
created_at
}
total
page
limit
has_next
}
}
```
### Мутации #### Комментарии (Comments)
- `comment:create` - создание комментариев
- `comment:edit_own` - редактирование своих комментариев
- `comment:edit_any` - редактирование любых комментариев
- `comment:delete_own` - удаление своих комментариев
- `comment:delete_any` - удаление любых комментариев
- `comment:moderate` - модерация комментариев
#### Назначение ролей пользователю #### Пользователи (Users)
```graphql - `user:view_profile` - просмотр профилей
mutation AdminSetUserCommunityRoles( - `user:edit_own_profile` - редактирование своего профиля
$author_id: Int! - `user:manage_roles` - управление ролями пользователей
$community_id: Int! - `user:ban` - блокировка пользователей
$roles: [String!]!
) {
adminSetUserCommunityRoles(
author_id: $author_id
community_id: $community_id
roles: $roles
) {
success
error
author_id
community_id
roles
}
}
```
#### Обновление настроек ролей сообщества #### Сообщество (Community)
```graphql - `community:view` - просмотр сообщества
mutation AdminUpdateCommunityRoleSettings( - `community:settings` - настройки сообщества
$community_id: Int! - `community:manage_members` - управление участниками
$default_roles: [String!]! - `community:analytics` - просмотр аналитики
$available_roles: [String!]!
) {
adminUpdateCommunityRoleSettings(
community_id: $community_id
default_roles: $default_roles
available_roles: $available_roles
) {
success
error
community_id
default_roles
available_roles
}
}
```
## Использование декораторов RBAC ## Логика работы системы
### 1. Регистрация пользователя
При регистрации пользователя:
### Импорт декораторов
```python ```python
from resolvers.rbac import ( # 1. Создается запись в Author
require_permission, require_role, admin_only, user = Author(email=email, name=name, ...)
authenticated_only, require_any_permission,
require_all_permissions, RBACError # 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 ```python
@mutation.field("createShout") def login(email, password):
@require_permission("shout:create") # 1. Найти пользователя
async def create_shout(self, info: GraphQLResolveInfo, **kwargs): author = Author.get_by_email(email)
# Только пользователи с правом создания статей
return await self._create_shout_logic(**kwargs)
@mutation.field("create_topic") # 2. Проверить пароль
@require_permission("topic:create") if not verify_password(password, author.password):
async def create_topic(self, info: GraphQLResolveInfo, topic_input: dict): return error("Неверный пароль")
# Только пользователи с правом создания топиков (author, editor)
return await self._create_topic_logic(topic_input)
@mutation.field("merge_topics") # 3. Получить роли в дефолтном сообществе
@require_permission("topic:merge") user_roles = get_user_roles_in_community(author.id, community_id=1)
async def merge_topics(self, info: GraphQLResolveInfo, merge_input: dict):
# Только пользователи с правом слияния топиков (editor) # 4. Проверить наличие роли reader
return await self._merge_topics_logic(merge_input) if "reader" not in user_roles and author.email not in ADMIN_EMAILS:
return error("Нет прав для входа. Требуется роль 'reader'.")
# 5. Создать сессию
return create_session(author)
``` ```
#### Проверка любого из разрешений (OR логика) ### 3. Проверка разрешений
При выполнении действий проверяются разрешения:
```python ```python
@mutation.field("updateShout") @login_required
@require_any_permission(["shout:update_own", "shout:update_any"]) async def create_shout(info, input):
async def update_shout(self, info: GraphQLResolveInfo, shout_id: int, **kwargs): user_id = info.context["author"]["id"]
# Может редактировать свои статьи ИЛИ любые статьи
return await self._update_shout_logic(shout_id, **kwargs)
@mutation.field("update_topic") # Проверяем разрешение на создание шаутов
@require_any_permission(["topic:update_own", "topic:update_any"]) has_permission = await check_user_permission_in_community(
async def update_topic(self, info: GraphQLResolveInfo, topic_input: dict): user_id,
# Может редактировать свои топики ИЛИ любые топики "shout:create",
return await self._update_topic_logic(topic_input) community_id=1
)
@mutation.field("delete_topic") if not has_permission:
@require_any_permission(["topic:delete_own", "topic:delete_any"]) raise GraphQLError("Недостаточно прав для создания публикации")
async def delete_topic(self, info: GraphQLResolveInfo, topic_id: int):
# Может удалять свои топики ИЛИ любые топики # Создаем шаут
return await self._delete_topic_logic(topic_id) return Shout.create(input)
``` ```
#### Проверка конкретной роли ### 4. Управление ролями
#### Назначение ролей
```python ```python
@mutation.field("verifyEvidence") # Назначить роль пользователю
@require_role("expert") assign_role_to_user(user_id=123, role="editor", community_id=1)
async def verify_evidence(self, info: GraphQLResolveInfo, **kwargs):
# Только эксперты могут верифицировать доказательства # Убрать роль
return await self._verify_evidence_logic(**kwargs) remove_role_from_user(user_id=123, role="editor", community_id=1)
# Установить все роли
community.set_user_roles(user_id=123, roles=["reader", "author", "editor"])
``` ```
#### Только для администраторов #### Проверка ролей
```python ```python
@mutation.field("deleteAnyContent") # Получить роли пользователя
@admin_only roles = get_user_roles_in_community(user_id=123, community_id=1)
async def delete_any_content(self, info: GraphQLResolveInfo, content_id: int):
# Только администраторы # Проверить конкретную роль
return await self._delete_content_logic(content_id) has_role = "editor" in roles
# Проверить разрешение
has_permission = await check_user_permission_in_community(
user_id=123,
permission="shout:edit_any",
community_id=1
)
``` ```
### Обработка ошибок ## Конфигурация сообщества
### Дефолтные роли
Каждое сообщество может настроить свои дефолтные роли для новых пользователей:
```python ```python
from resolvers.rbac import RBACError # Получить дефолтные роли
default_roles = community.get_default_roles() # ["reader", "author"]
try: # Установить дефолтные роли
result = await some_rbac_protected_function() community.set_default_roles(["reader"]) # Только reader по умолчанию
except RBACError as e:
return {"success": False, "error": str(e)}
``` ```
## Настройка сообщества ### Доступные роли
Сообщество может ограничить список доступных ролей:
### Управление ролями в сообществе
```python ```python
from orm.community import Community # Все роли доступны по умолчанию
available_roles = ["reader", "author", "artist", "expert", "editor", "admin"]
community = session.query(Community).filter(Community.id == 1).first() # Ограничить только базовыми ролями
community.set_available_roles(["reader", "author", "editor"])
# Установка доступных ролей
community.set_available_roles(['reader', 'author', 'expert', 'admin'])
# Установка дефолтных ролей для новых участников
community.set_default_roles(['reader'])
# Получение настроек
available = community.get_available_roles() # ['reader', 'author', 'expert', 'admin']
default = community.get_default_roles() # ['reader']
``` ```
### Автоматическое назначение дефолтных ролей ## Миграция данных
При создании связи пользователя с сообществом автоматически назначаются роли из `default_roles`.
## Интеграция с GraphQL контекстом ### Проблемы существующих пользователей
1. **Пользователи без роли `reader`** - не могут войти в систему
2. **Старая система ролей** - данные в `Author.roles` устарели
3. **Отсутствие связей `CommunityAuthor`** - новые пользователи без ролей
### Решения
#### 1. Автоматическое добавление роли `reader`
### Middleware для установки ролей
```python ```python
async def rbac_middleware(request, call_next): async def ensure_user_has_reader_role(user_id: int) -> bool:
# Получаем автора из контекста """Убеждается, что у пользователя есть роль 'reader'"""
author = getattr(request.state, 'author', None) existing_roles = get_user_roles_in_community(user_id, community_id=1)
if author:
# Устанавливаем роли в контекст для текущего сообщества
community_id = get_current_community_id(request)
if community_id:
user_roles = get_user_roles_in_community(author.id, community_id)
request.state.user_roles = user_roles
response = await call_next(request) if "reader" not in existing_roles:
return response success = assign_role_to_user(user_id, "reader", community_id=1)
if success:
logger.info(f"Роль 'reader' добавлена пользователю {user_id}")
return True
return True
``` ```
### Получение ролей в resolver'ах #### 2. Массовое исправление ролей
```python ```python
def get_user_roles_from_context(info): async def fix_all_users_reader_role() -> dict[str, int]:
"""Получение ролей пользователя из GraphQL контекста""" """Проверяет всех пользователей и добавляет роль 'reader'"""
# Из middleware stats = {"checked": 0, "fixed": 0, "errors": 0}
user_roles = getattr(info.context, "user_roles", [])
if user_roles:
return user_roles
# Из author'а напрямую all_authors = session.query(Author).all()
author = getattr(info.context, "author", None)
if author and hasattr(author, "roles"):
return author.roles.split(",") if author.roles else []
return [] 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
``` ```
## Миграция и обновления #### 3. Миграция из старой системы
### Миграция с предыдущей системы ролей ```python
Если в проекте была отдельная таблица ролей, необходимо: def migrate_old_roles_to_community_author():
"""Переносит роли из старой системы в CommunityAuthor"""
1. Создать миграцию для добавления поля `roles` в `CommunityAuthor` # Получаем все старые роли из Author.roles
2. Перенести данные из старых таблиц в CSV формат old_roles = session.query(AuthorRole).all()
3. Удалить старые таблицы ролей
```bash for role in old_roles:
alembic revision --autogenerate -m "Add CSV roles to CommunityAuthor" # Создаем запись CommunityAuthor
alembic upgrade head ca = CommunityAuthor(
community_id=role.community,
author_id=role.author,
roles=role.role
)
session.add(ca)
session.commit()
``` ```
### Обновление CHANGELOG.md ## API для работы с ролями
После внесения изменений в RBAC систему обновляется `CHANGELOG.md` с новой версией.
### 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")
```
## Производительность ## Производительность
### Оптимизация ### Оптимизации
- CSV роли хранятся в одном поле, что снижает количество JOIN'ов
- Индексы на `community_id` и `author_id` ускоряют запросы
- Кеширование разрешений на уровне приложения
### Рекомендации 1. **Кеширование ролей** - хранение ролей пользователя в Redis
- Избегать частых изменений ролей 2. **Индексы БД** - быстрый поиск по `community_id` и `author_id`
- Кешировать результаты `get_role_permissions_for_community()` 3. **Batch операции** - массовое назначение ролей
- Использовать bulk операции для массового назначения ролей 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')
```

View File

@@ -9,7 +9,6 @@ import time
from functools import wraps from functools import wraps
from typing import Any, Callable, Optional from typing import Any, Callable, Optional
from sqlalchemy import exc
from starlette.requests import Request from starlette.requests import Request
from auth.email import send_auth_email from auth.email import send_auth_email
@@ -80,24 +79,39 @@ class AuthService:
f"[check_auth] Результат verify_internal_auth: user_id={user_id}, roles={user_roles}, is_admin={is_admin}" f"[check_auth] Результат verify_internal_auth: user_id={user_id}, roles={user_roles}, is_admin={is_admin}"
) )
# Если в ролях нет админа, но есть ID - проверяем в БД # Если в ролях нет админа, но есть ID - проверяем через новую систему RBAC
if user_id and not is_admin: if user_id and not is_admin:
try: try:
with local_session() as session: # Преобразуем user_id в число
# Преобразуем user_id в число try:
try: if isinstance(user_id, str):
if isinstance(user_id, str): user_id_int = int(user_id.strip())
user_id_int = int(user_id.strip())
else:
user_id_int = int(user_id)
except (ValueError, TypeError):
logger.error(f"Невозможно преобразовать user_id {user_id} в число")
else: else:
# Проверяем наличие админских прав через новую RBAC систему user_id_int = int(user_id)
from orm.community import get_user_roles_in_community except (ValueError, TypeError):
logger.error(f"Невозможно преобразовать user_id {user_id} в число")
return 0, [], False
# Получаем роли через новую систему CommunityAuthor
from orm.community import get_user_roles_in_community
user_roles_in_community = get_user_roles_in_community(user_id_int, community_id=1)
logger.debug(f"[check_auth] Роли из CommunityAuthor: {user_roles_in_community}")
# Обновляем роли из новой системы
user_roles = user_roles_in_community
is_admin = any(role in ["admin", "super"] for role in user_roles_in_community)
# Проверяем админские права через email если нет роли админа
if not is_admin:
with local_session() as session:
author = session.query(Author).filter(Author.id == user_id_int).first()
if author and author.email in ADMIN_EMAILS.split(","):
is_admin = True
logger.debug(
f"[check_auth] Пользователь {author.email} определен как админ через ADMIN_EMAILS"
)
user_roles_in_community = get_user_roles_in_community(user_id_int, community_id=1)
is_admin = any(role in ["admin", "super"] for role in user_roles_in_community)
except Exception as e: except Exception as e:
logger.error(f"Ошибка при проверке прав администратора: {e}") logger.error(f"Ошибка при проверке прав администратора: {e}")
@@ -112,26 +126,30 @@ class AuthService:
logger.info(f"Adding roles {roles} to user {user_id}") logger.info(f"Adding roles {roles} to user {user_id}")
from orm.community import assign_role_to_user try:
user_id_int = int(user_id)
except (ValueError, TypeError):
logger.error(f"Невозможно преобразовать user_id {user_id} в число")
return None
logger.debug("Using local authentication with new RBAC system") from orm.community import assign_role_to_user, get_user_roles_in_community
with local_session() as session:
try:
author = session.query(Author).filter(Author.id == user_id).one()
# Добавляем роли через новую систему RBAC в дефолтное сообщество (ID=1) # Проверяем существующие роли
for role_name in roles: existing_roles = get_user_roles_in_community(user_id_int, community_id=1)
success = assign_role_to_user(int(user_id), role_name, community_id=1) logger.debug(f"Существующие роли пользователя {user_id}: {existing_roles}")
if success:
logger.debug(f"Роль {role_name} добавлена пользователю {user_id}")
else:
logger.warning(f"Не удалось добавить роль {role_name} пользователю {user_id}")
return user_id # Добавляем новые роли через новую систему RBAC
for role_name in roles:
if role_name not in existing_roles:
success = assign_role_to_user(user_id_int, role_name, community_id=1)
if success:
logger.debug(f"Роль {role_name} добавлена пользователю {user_id}")
else:
logger.warning(f"Не удалось добавить роль {role_name} пользователю {user_id}")
else:
logger.debug(f"Роль {role_name} уже есть у пользователя {user_id}")
except exc.NoResultFound: return user_id
logger.error(f"Author {user_id} not found")
return None
def create_user(self, user_dict: dict[str, Any], community_id: int | None = None) -> Author: def create_user(self, user_dict: dict[str, Any], community_id: int | None = None) -> Author:
"""Создает нового пользователя с дефолтными ролями""" """Создает нового пользователя с дефолтными ролями"""
@@ -332,17 +350,22 @@ class AuthService:
logger.warning(f"Пользователь {email} не найден") logger.warning(f"Пользователь {email} не найден")
return {"success": False, "token": None, "author": None, "error": "Пользователь не найден"} return {"success": False, "token": None, "author": None, "error": "Пользователь не найден"}
# Проверяем роли (упрощенная версия) # Проверяем роли через новую систему CommunityAuthor
has_reader_role = False from orm.community import get_user_roles_in_community
if hasattr(author, "roles") and author.roles:
for role in author.roles: user_roles = get_user_roles_in_community(author.id, community_id=1)
if role.id == "reader": has_reader_role = "reader" in user_roles
has_reader_role = True
break logger.debug(f"Роли пользователя {email}: {user_roles}")
if not has_reader_role and author.email not in ADMIN_EMAILS.split(","): if not has_reader_role and author.email not in ADMIN_EMAILS.split(","):
logger.warning(f"У пользователя {email} нет роли 'reader'") logger.warning(f"У пользователя {email} нет роли 'reader'. Текущие роли: {user_roles}")
return {"success": False, "token": None, "author": None, "error": "Нет прав для входа"} return {
"success": False,
"token": None,
"author": None,
"error": "Нет прав для входа. Требуется роль 'reader'.",
}
# Проверяем пароль # Проверяем пароль
try: try:
@@ -610,6 +633,60 @@ class AuthService:
logger.error(f"Ошибка отмены смены email: {e}") logger.error(f"Ошибка отмены смены email: {e}")
return {"success": False, "error": str(e), "author": None} return {"success": False, "error": str(e), "author": None}
async def ensure_user_has_reader_role(self, user_id: int) -> bool:
"""
Убеждается, что у пользователя есть роль 'reader'.
Если её нет - добавляет автоматически.
Args:
user_id: ID пользователя
Returns:
True если роль была добавлена или уже существует
"""
from orm.community import assign_role_to_user, get_user_roles_in_community
existing_roles = get_user_roles_in_community(user_id, community_id=1)
if "reader" not in existing_roles:
logger.warning(f"У пользователя {user_id} нет роли 'reader'. Добавляем автоматически.")
success = assign_role_to_user(user_id, "reader", community_id=1)
if success:
logger.info(f"Роль 'reader' добавлена пользователю {user_id}")
return True
logger.error(f"Не удалось добавить роль 'reader' пользователю {user_id}")
return False
return True
async def fix_all_users_reader_role(self) -> dict[str, int]:
"""
Проверяет всех пользователей и добавляет роль 'reader' тем, у кого её нет.
Returns:
Статистика операции: {"checked": int, "fixed": int, "errors": int}
"""
stats = {"checked": 0, "fixed": 0, "errors": 0}
with local_session() as session:
# Получаем всех пользователей
all_authors = session.query(Author).all()
for author in all_authors:
stats["checked"] += 1
try:
had_reader = await self.ensure_user_has_reader_role(author.id)
if not had_reader:
stats["fixed"] += 1
except Exception as e:
logger.error(f"Ошибка при исправлении ролей для пользователя {author.id}: {e}")
stats["errors"] += 1
logger.info(f"Исправление ролей завершено: {stats}")
return stats
def login_required(self, f: Callable) -> Callable: def login_required(self, f: Callable) -> Callable:
"""Декоратор для проверки авторизации пользователя. Требуется наличие роли 'reader'.""" """Декоратор для проверки авторизации пользователя. Требуется наличие роли 'reader'."""