Files
octopus/myproject/user_roles/auth_backend.py
Andrey Smakotin eb6a3c1874 Исправлена ошибка public admin для мультитенантной архитектуры
Проблема: при входе в localhost/admin/ (public схема) возникала ошибка
"relation user_roles_userrole does not exist", так как tenant-only
таблицы не существуют в public схеме.

Решение:
- Создан TenantAdminOnlyMixin для скрытия tenant-only моделей от public admin
- Применён миксин ко всем ModelAdmin классам в tenant-only приложениях:
  user_roles, customers, orders, inventory, products
- Добавлена проверка _is_public_schema() в RoleBasedPermissionBackend
  для предотвращения запросов к tenant-only таблицам в public схеме

Теперь:
- localhost/admin/ показывает только public модели (Client, Domain, User)
- shop.localhost/admin/ показывает все модели магазина

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 01:05:47 +03:00

166 lines
8.0 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()
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 если пользователь имеет разрешение
"""
# Сначала проверяем стандартные permissions через ModelBackend
# (для superuser, staff с Django permissions и т.д.)
if super().has_perm(user_obj, perm, obj):
return True
# Если пользователь не аутентифицирован, нет доступа
if not user_obj.is_authenticated:
return False
# Суперпользователь имеет все права
if user_obj.is_superuser:
return True
# ВАЖНО: В public схеме нет таблиц UserRole/Role!
# Используем только стандартные Django permissions.
if _is_public_schema():
return False
# Получаем роль пользователя в текущем тенанте
# ВАЖНО: RoleService работает с текущей tenant schema!
user_role = RoleService.get_user_role(user_obj)
if not user_role:
return False
# Парсим разрешение: 'app_label.action_modelname'
# Например: 'products.add_product' -> app_label='products', action='add'
try:
app_label, codename = perm.split('.')
# Извлекаем действие из codename (add_product -> add, change_order -> change)
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
def has_module_perms(self, user_obj, app_label):
"""
Проверяет, имеет ли пользователь какие-либо разрешения для приложения.
Используется в Django admin для определения, показывать ли модуль в навигации.
Args:
user_obj: Пользователь
app_label: Название приложения (например, 'products', 'orders')
Returns:
bool: True если пользователь имеет хотя бы одно разрешение для приложения
"""
# Сначала проверяем стандартные permissions через ModelBackend
if super().has_module_perms(user_obj, app_label):
return True
# Если пользователь не аутентифицирован, нет доступа
if not user_obj.is_authenticated:
return False
# Суперпользователь имеет все права
if user_obj.is_superuser:
return True
# ВАЖНО: В public схеме нет таблиц UserRole/Role!
# Используем только стандартные Django permissions.
if _is_public_schema():
return False
# Получаем роль пользователя в текущем тенанте
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