Files
octopus/myproject/user_roles/auth_backend.py
Andrey Smakotin d90b0162c5 fix: Исправлена вторая ошибка - отключен ModelBackend для CustomUser
Проблема:
После первого исправления ошибка продолжалась, но теперь в другом месте.
Django's ModelBackend пытался проверить permissions для CustomUser через
Permission.objects.filter(group__user=user_obj), что вызывало ошибку
"Cannot query 'chupa@chus.by': Must be 'PlatformAdmin' instance"

Причина:
RoleBasedPermissionBackend наследует ModelBackend, и для CustomUser
все равно вызывался super().has_perm(), который обращался к Django
Permission таблице в public schema, ожидая PlatformAdmin.

Решение:
Полностью отключен вызов super().has_perm() и super().has_module_perms()
для CustomUser. Теперь для CustomUser используется только role-based
permission checking, а для PlatformAdmin - стандартный ModelBackend.

Изменения в user_roles/auth_backend.py:
- has_perm(): добавлена ветка if is_tenant, которая полностью обрабатывает
  CustomUser без вызова super()
- has_module_perms(): аналогичная логика
- Для PlatformAdmin сохранена проверка через super() (ModelBackend)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-09 23:50:19 +03:00

189 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).
"""
from django.contrib.auth.backends import ModelBackend
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(ModelBackend):
"""
Backend, который предоставляет права на основе роли пользователя в текущем тенанте.
Расширяет стандартный ModelBackend, добавляя проверку прав на основе ролей из tenant schema.
Как это работает:
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 проверяем стандартные permissions через ModelBackend
# Суперпользователь имеет все права
if user_obj.is_superuser:
return True
# Проверяем через родительский ModelBackend
return super().has_perm(user_obj, perm, obj)
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 проверяем стандартные permissions через ModelBackend
# Суперпользователь имеет все права
if user_obj.is_superuser:
return True
# Проверяем через родительский ModelBackend
return super().has_module_perms(user_obj, app_label)