From f94af70f7f2f5017e4b4951025e0e3283c4bd7b2 Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Thu, 8 Jan 2026 22:16:26 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B2=20myproject:=20=D0=BD=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B9=D0=BA=D0=B8,=20URL=20=D0=B8=20middlewa?= =?UTF-8?q?re=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF=D0=B0=20=D0=BA=20?= =?UTF-8?q?=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../myproject/admin_access_middleware.py | 69 +++++++++++++------ myproject/myproject/settings.py | 46 +++++++------ myproject/myproject/urls_public.py | 19 ++++- 3 files changed, 89 insertions(+), 45 deletions(-) diff --git a/myproject/myproject/admin_access_middleware.py b/myproject/myproject/admin_access_middleware.py index 17625a6..7c9e970 100644 --- a/myproject/myproject/admin_access_middleware.py +++ b/myproject/myproject/admin_access_middleware.py @@ -1,23 +1,26 @@ # -*- coding: utf-8 -*- """ -Middleware для ограничения доступа к админке Django на поддоменах тенантов. +Middleware для ограничения доступа к админке Django. -SECURITY: Этот middleware обеспечивает дополнительный слой защиты, -блокируя доступ владельцев тенантов к /admin/ даже если у них -случайно установлен is_staff=True. +SECURITY: Этот middleware обеспечивает изоляцию аутентификации между +PlatformAdmin (public schema) и CustomUser (tenant schemas). -Правила доступа: -- На tenant поддоменах (shop1.localhost): только is_superuser=True может входить в /admin/ -- На public схеме (localhost): обычные правила Django (is_staff=True достаточно) -- Суперпользователи имеют доступ везде +Правила доступа к /admin/: +- На PUBLIC домене: только PlatformAdmin может входить +- На TENANT доменах: только PlatformAdmin с is_superuser=True (для поддержки) + или CustomUser с is_superuser=True (системный пользователь тенанта) """ from django.http import HttpResponseForbidden +from django.db import connection class TenantAdminAccessMiddleware: """ - Дополнительный слой безопасности: блокирует доступ владельцев тенантов к /admin/ - даже если у них случайно установлен is_staff=True. + Middleware для контроля доступа к Django Admin. + + Обеспечивает разделение между: + - PlatformAdmin (администраторы платформы) на public домене + - CustomUser (пользователи тенантов) на tenant доменах """ def __init__(self, get_response): @@ -26,20 +29,42 @@ class TenantAdminAccessMiddleware: def __call__(self, request): # Проверяем, это admin URL? if request.path.startswith('/admin/'): - from django.db import connection + # Импортируем здесь чтобы избежать circular imports + from platform_admin.models import PlatformAdmin - # Если мы в tenant схеме (не public) - if hasattr(connection, 'tenant') and connection.tenant: - # Проверяем: это не public схема? - if connection.tenant.schema_name != 'public': - # Проверяем наличие атрибута user (добавляется AuthenticationMiddleware) - if hasattr(request, 'user'): - # Если пользователь авторизован, но НЕ суперпользователь - блокируем - if request.user.is_authenticated and not request.user.is_superuser: + # Получаем текущую schema + schema_name = getattr(connection, 'schema_name', 'public') + is_public = schema_name == 'public' + + # Проверяем наличие атрибута user (добавляется AuthenticationMiddleware) + if hasattr(request, 'user') and request.user.is_authenticated: + user = request.user + is_platform_admin = isinstance(user, PlatformAdmin) + + if is_public: + # PUBLIC DOMAIN: только PlatformAdmin + if not is_platform_admin: + return HttpResponseForbidden( + "Доступ запрещен. Админка платформы доступна только " + "для администраторов платформы. " + "Если вы владелец магазина, перейдите на домен вашего магазина." + ) + else: + # TENANT DOMAIN: PlatformAdmin (superuser) или CustomUser (superuser) + if is_platform_admin: + # PlatformAdmin на tenant домене - только суперадмин + if not user.is_superuser: return HttpResponseForbidden( - "Доступ запрещен. Только системные администраторы могут " - "заходить в админ-панель на поддоменах тенантов. " - "Используйте панель управления тенанта." + "Доступ запрещен. Только суперадминистраторы платформы " + "могут входить в админку тенантов." + ) + else: + # CustomUser на tenant домене + if not user.is_superuser: + return HttpResponseForbidden( + "Доступ запрещен. Только системные администраторы тенанта " + "могут входить в Django Admin. " + "Используйте панель управления магазином." ) response = self.get_response(request) diff --git a/myproject/myproject/settings.py b/myproject/myproject/settings.py index 880cf89..9adae66 100644 --- a/myproject/myproject/settings.py +++ b/myproject/myproject/settings.py @@ -58,6 +58,7 @@ CSRF_USE_SESSIONS = True # Рекомендуется для мультихос SHARED_APPS = [ 'django_tenants', # ОБЯЗАТЕЛЬНО ПЕРВЫМ! 'tenants', # Приложение с моделями тенантов + 'platform_admin', # Администраторы платформы (отдельная модель от CustomUser) # Django встроенные приложения 'django.contrib.contenttypes', @@ -67,12 +68,8 @@ SHARED_APPS = [ 'django.contrib.admin', 'django.contrib.staticfiles', - # Accounts должен быть в shared для CustomUser (используется в админке) - 'accounts', - # Celery results (для сохранения статуса асинхронных задач) 'django_celery_results', - 'simple_history', # Перенесли сюда для корректной инициализации моделей ] # Tenant apps: создаются в отдельной схеме для каждого тенанта (изолированные данные) @@ -80,6 +77,11 @@ TENANT_APPS = [ 'django.contrib.contenttypes', # Дублируем для tenant схем 'django.contrib.auth', # Дублируем для tenant схем + # CustomUser - TENANT-ONLY модель! + # Каждый тенант имеет свою таблицу accounts_customuser в своей схеме + # Один email в разных тенантах = разные пользователи (полная изоляция) + 'accounts', + # Приложения с бизнес-логикой (изолированные для каждого магазина) 'nested_admin', 'django_filters', # Фильтрация данных @@ -90,6 +92,7 @@ TENANT_APPS = [ 'inventory', # Складской учет 'pos', # POS Terminal 'system_settings', # Системные настройки компании (только для владельца) + # TODO: 'simple_history' - вернуть позже для истории изменений ] # Объединяем для INSTALLED_APPS @@ -137,11 +140,19 @@ if DEBUG_TOOLBAR_ENABLED: # AUTHENTICATION BACKENDS # ============================================ -# Кастомный backend для связи ролей с Django permissions API -# ВАЖНО: Этот backend работает с ролями из tenant schema, НЕ трогая public schema! +# Authentication backends для изолированной аутентификации +# ВАЖНО: Порядок важен! Django пробует backends по очереди до первого успеха AUTHENTICATION_BACKENDS = [ - 'user_roles.auth_backend.RoleBasedPermissionBackend', # Наш кастомный backend для ролей - 'django.contrib.auth.backends.ModelBackend', # Стандартный backend (для superuser и т.д.) + # 1. PlatformAdmin - администраторы платформы (public schema) + # Работает на public домене, а также на tenant доменах для суперадминов (поддержка) + 'platform_admin.backends.PlatformAdminBackend', + + # 2. CustomUser - пользователи тенантов (tenant schema) + # Работает ТОЛЬКО на tenant доменах, ищет пользователя в текущей schema + 'accounts.backends.TenantUserBackend', + + # 3. Ролевой backend для проверки permissions (работает с RoleService) + 'user_roles.auth_backend.RoleBasedPermissionBackend', ] @@ -248,6 +259,9 @@ TENANT_DOMAIN_BASE = env('TENANT_DOMAIN_BASE', default='localhost:8000') # Использовать HTTPS для ссылок (в проде True, локально False) USE_HTTPS = env.bool('USE_HTTPS', default=False) +# Email техподдержки платформы (создаётся скрытый аккаунт в каждом тенанте) +PLATFORM_SUPPORT_EMAIL = env('PLATFORM_SUPPORT_EMAIL', default=None) + # ============================================ # SESSION CONFIGURATION @@ -491,18 +505,10 @@ DEFAULT_FROM_EMAIL = 'noreply@inventory.by' # AUTHENTICATION # ============================================ -AUTH_USER_MODEL = 'accounts.CustomUser' - - -# ============================================ -# TENANT ADMIN AUTO-CREATION -# ============================================ - -# При создании нового тенанта автоматически создается суперпользователь -# с указанными credentials для доступа к админке тенанта -TENANT_ADMIN_EMAIL = env('TENANT_ADMIN_EMAIL') -TENANT_ADMIN_PASSWORD = env('TENANT_ADMIN_PASSWORD') -TENANT_ADMIN_NAME = env('TENANT_ADMIN_NAME') +# AUTH_USER_MODEL указывает на PlatformAdmin (public schema) +# Это нужно для django.contrib.admin.LogEntry и других Django-компонентов +# CustomUser живёт в TENANT_APPS и НЕ является AUTH_USER_MODEL +AUTH_USER_MODEL = 'platform_admin.PlatformAdmin' # ============================================ diff --git a/myproject/myproject/urls_public.py b/myproject/myproject/urls_public.py index 2bda779..7f98f89 100644 --- a/myproject/myproject/urls_public.py +++ b/myproject/myproject/urls_public.py @@ -3,8 +3,12 @@ URL configuration for the PUBLIC schema (inventory.by domain). This is the main domain where: -- Super admin can access admin panel +- PlatformAdmin can access admin panel and dashboard - Tenant registration is available +- Password setup for new tenant owners + +IMPORTANT: На public домене НЕ работает аутентификация CustomUser! +CustomUser живёт в tenant schemas и доступен только на tenant доменах. """ from django.contrib import admin from django.urls import path, include @@ -14,9 +18,18 @@ from django.urls import re_path from django.views.static import serve urlpatterns = [ + # Django Admin для PlatformAdmin path('admin/', admin.site.urls), - path('', include('tenants.urls')), # Роуты для регистрации тенантов (/, /register/, /register/success/) - path('accounts/', include('accounts.urls')), # Роуты для установки пароля владельцами тенантов + + # Панель управления платформой (login/logout/dashboard для PlatformAdmin) + path('platform/', include('platform_admin.urls')), + + # Регистрация тенантов (/, /register/, /register/success/) + path('', include('tenants.urls')), + + # Установка пароля владельцами тенантов (только setup-password) + # НЕ включаем полный accounts.urls - на public домене нельзя логиниться как CustomUser + path('accounts/', include('accounts.urls')), ] # Serve media files in development