Files
octopus/myproject/user_roles/auth_backend.py
Andrey Smakotin b63162b1cb Рефакторинг: убрана зависимость от Django Groups/Permissions для CustomUser
- CustomUser теперь наследуется от AbstractBaseUser (вместо AbstractUser)
- Удалены поля groups и user_permissions из CustomUser
- Все authentication backends (TenantUserBackend, PlatformAdminBackend, RoleBasedPermissionBackend) больше НЕ наследуются от ModelBackend
- Добавлены методы has_perm() и has_module_perms() в CustomUser для делегирования проверки прав кастомным backends
- Полная изоляция: CustomUser использует только систему ролей (UserRole), PlatformAdmin использует только is_superuser
- Удалён весь старый код, связанный с Django permissions
- Нет обратной совместимости (не требуется)
- Чистая архитектура для multi-tenant приложения
2026-01-10 00:10:25 +03:00

186 lines
9.1 KiB
Python
Raw 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.
"""
Кастомный backend аутентификации для связывания ролей с Django permissions API.
ВАЖНО: Этот backend НЕ использует таблицы Django permissions из public schema!
Он только эмулирует API has_perm(), читая роли из текущей tenant schema.
Это безопасно для мультитенантной архитектуры.
ВАЖНО: Backend проверяет текущую схему перед обращением к tenant-only таблицам.
В public схеме ролевые проверки пропускаются (fallback на стандартные Django permissions).
ВАЖНО: Этот backend НЕ наследуется от ModelBackend!
Он реализует только has_perm/has_module_perms для проверки прав на основе ролей.
Аутентификацию (authenticate/get_user) выполняют другие backends.
"""
from django.db import connection
from django_tenants.utils import get_public_schema_name
from user_roles.services import RoleService
from user_roles.models import Role
def _is_public_schema():
"""
Проверяет, находимся ли мы в public схеме.
В public схеме нет таблиц tenant-only моделей (UserRole, Role).
"""
return connection.schema_name == get_public_schema_name()
def _is_tenant_user(user_obj):
"""
Проверяет, является ли пользователь CustomUser (пользователь тенанта).
PlatformAdmin не имеет ролей в тенантах.
"""
from accounts.models import CustomUser
return isinstance(user_obj, CustomUser)
class RoleBasedPermissionBackend:
"""
Backend, который предоставляет права на основе роли пользователя в текущем тенанте.
НЕ наследуется от ModelBackend! Реализует только проверку прав (has_perm/has_module_perms).
Аутентификацию (authenticate/get_user) выполняют TenantUserBackend и PlatformAdminBackend.
Как это работает:
1. Django вызывает user.has_perm('products.add_product')
2. Backend проверяет роль пользователя в ТЕКУЩЕЙ tenant schema (через RoleService)
3. На основе роли возвращает True/False
4. Никакие данные из public schema не используются!
"""
# Маппинг ролей на наборы разрешений
# Формат: 'app_label': ['action1', 'action2', ...]
# где action - это префикс permission: add_product -> add, change_order -> change
ROLE_PERMISSIONS = {
Role.OWNER: {
# Владелец: полный доступ ко всем модулям
'products': ['add', 'change', 'delete', 'view'],
'inventory': ['add', 'change', 'delete', 'view'],
'orders': ['add', 'change', 'delete', 'view'],
'clients': ['add', 'change', 'delete', 'view'],
'suppliers': ['add', 'change', 'delete', 'view'],
'user_roles': ['add', 'change', 'delete', 'view'],
'payments': ['add', 'change', 'delete', 'view'],
},
Role.MANAGER: {
# Менеджер: доступ к основным операционным модулям (без настроек)
'products': ['add', 'change', 'delete', 'view'],
'inventory': ['add', 'change', 'delete', 'view'],
'orders': ['add', 'change', 'delete', 'view'],
'clients': ['add', 'change', 'delete', 'view'],
'suppliers': ['add', 'change', 'view'], # только просмотр и изменение, без удаления
'payments': ['view'],
},
Role.FLORIST: {
# Флорист: работа с заказами и просмотр товаров
'products': ['view'],
'inventory': ['view', 'change'], # может обновлять остатки при сборке заказов
'orders': ['change', 'view'],
},
Role.COURIER: {
# Курьер: только просмотр и обновление статуса заказов
'orders': ['change', 'view'],
},
}
def has_perm(self, user_obj, perm, obj=None):
"""
Проверяет, имеет ли пользователь указанное разрешение.
Формат разрешения: 'app_label.action_modelname'
Например: 'products.add_product', 'orders.change_order'
Args:
user_obj: Пользователь
perm: Строка разрешения в формате 'app.action_model'
obj: Опциональный объект для object-level permissions
Returns:
bool: True если пользователь имеет разрешение
"""
# Если пользователь не аутентифицирован, нет доступа
if not user_obj.is_authenticated:
return False
# Определяем тип пользователя
is_tenant = _is_tenant_user(user_obj)
# Для CustomUser (пользователи тенантов) НЕ вызываем super().has_perm()
# т.к. AUTH_USER_MODEL = PlatformAdmin, и ModelBackend будет пытаться
# искать permissions в Django Permission таблице для PlatformAdmin
if is_tenant:
# Суперпользователь имеет все права
if user_obj.is_superuser:
return True
# ВАЖНО: В public схеме нет таблиц UserRole/Role!
if _is_public_schema():
return False
# Для CustomUser получаем роль пользователя в текущем тенанте
user_role = RoleService.get_user_role(user_obj)
if not user_role:
return False
# Парсим разрешение: 'app_label.action_modelname'
try:
app_label, codename = perm.split('.')
action = codename.split('_')[0]
except (ValueError, IndexError):
return False
# Проверяем, есть ли у роли это разрешение
role_perms = self.ROLE_PERMISSIONS.get(user_role.code, {})
app_perms = role_perms.get(app_label, [])
return action in app_perms
else:
# Для PlatformAdmin - этот backend НЕ обрабатывает PlatformAdmin
# Возвращаем None, чтобы Django перешёл к другому backend
# (PlatformAdminBackend должен обработать это)
return None
def has_module_perms(self, user_obj, app_label):
"""
Проверяет, имеет ли пользователь какие-либо разрешения для приложения.
Используется в Django admin для определения, показывать ли модуль в навигации.
Args:
user_obj: Пользователь
app_label: Название приложения (например, 'products', 'orders')
Returns:
bool: True если пользователь имеет хотя бы одно разрешение для приложения
"""
# Если пользователь не аутентифицирован, нет доступа
if not user_obj.is_authenticated:
return False
# Определяем тип пользователя
is_tenant = _is_tenant_user(user_obj)
# Для CustomUser (пользователи тенантов) НЕ вызываем super().has_module_perms()
if is_tenant:
# Суперпользователь имеет все права
if user_obj.is_superuser:
return True
# ВАЖНО: В public схеме нет таблиц UserRole/Role!
if _is_public_schema():
return False
# Для CustomUser получаем роль пользователя в текущем тенанте
user_role = RoleService.get_user_role(user_obj)
if not user_role:
return False
# Проверяем, есть ли у роли какие-либо разрешения для этого приложения
role_perms = self.ROLE_PERMISSIONS.get(user_role.code, {})
return app_label in role_perms and len(role_perms[app_label]) > 0
else:
# Для PlatformAdmin - этот backend НЕ обрабатывает PlatformAdmin
# Возвращаем None, чтобы Django перешёл к другому backend
return None