""" Кастомный 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