diff --git a/myproject/accounts/migrations/0002_fix_owner_staff_flags.py b/myproject/accounts/migrations/0002_fix_owner_staff_flags.py new file mode 100644 index 0000000..06a54eb --- /dev/null +++ b/myproject/accounts/migrations/0002_fix_owner_staff_flags.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +""" +Миграция данных для исправления флагов is_staff/is_superuser у существующих пользователей. + +SECURITY FIX: Убираем флаги is_staff и is_superuser у владельцев тенантов и обычных пользователей, +которые могли быть созданы до внедрения явных проверок безопасности. + +Эта миграция должна быть запущена: +1. На public схеме: python manage.py migrate accounts +2. На всех tenant схемах: python manage.py migrate_schemas +""" +from django.db import migrations + + +def fix_owner_staff_flags(apps, schema_editor): + """ + Исправляет флаги is_staff/is_superuser у существующих владельцев тенантов и обычных пользователей. + """ + User = apps.get_model('accounts', 'CustomUser') + + # Пытаемся получить модели системы ролей (могут не существовать на момент миграции) + try: + UserRole = apps.get_model('user_roles', 'UserRole') + Role = apps.get_model('user_roles', 'Role') + + # Находим роль owner + try: + owner_role = Role.objects.get(code='owner') + + # Получаем всех пользователей с ролью owner + owner_user_ids = UserRole.objects.filter( + role=owner_role, + is_active=True + ).values_list('user_id', flat=True) + + # Убираем is_staff и is_superuser у всех владельцев + updated_staff = User.objects.filter( + id__in=owner_user_ids, + is_staff=True + ).update(is_staff=False) + + updated_super = User.objects.filter( + id__in=owner_user_ids, + is_superuser=True + ).update(is_superuser=False) + + if updated_staff > 0 or updated_super > 0: + print(f"[SECURITY FIX] Исправлено владельцев: is_staff={updated_staff}, is_superuser={updated_super}") + + except Role.DoesNotExist: + print("[SECURITY FIX] Роль 'owner' не найдена, пропускаем исправление владельцев") + + except LookupError: + # Модели user_roles еще не существуют - это нормально для новых инсталляций + print("[SECURITY FIX] Модели user_roles не найдены (это нормально для новых инсталляций)") + + # Убираем is_staff у всех НЕ-суперпользователей (дополнительная безопасность) + # Только суперпользователи должны иметь is_staff=True + updated = User.objects.filter( + is_staff=True, + is_superuser=False + ).update(is_staff=False) + + if updated > 0: + print(f"[SECURITY FIX] Убран is_staff у {updated} НЕ-суперпользователей") + + # Итоговая статистика + total_staff = User.objects.filter(is_staff=True).count() + total_super = User.objects.filter(is_superuser=True).count() + print(f"[SECURITY FIX] Текущее состояние: is_staff={total_staff}, is_superuser={total_super}") + + +def reverse_fix(apps, schema_editor): + """ + Откат не требуется - мы только исправляем безопасность, не меняем структуру данных. + """ + pass + + +class Migration(migrations.Migration): + """ + Миграция данных для исправления флагов безопасности. + + ВАЖНО: Эту миграцию нужно запустить на всех тенантах командой: + python manage.py migrate_schemas + """ + + dependencies = [ + ('accounts', '0001_initial'), + ] + + operations = [ + migrations.RunPython(fix_owner_staff_flags, reverse_fix), + ] diff --git a/myproject/accounts/models.py b/myproject/accounts/models.py index c6d3c16..b68f58f 100644 --- a/myproject/accounts/models.py +++ b/myproject/accounts/models.py @@ -11,6 +11,12 @@ class CustomUserManager(BaseUserManager): email = self.normalize_email(email) # Generate a unique username based on email to satisfy the AbstractUser constraint username = email + + # SECURITY FIX: Явно устанавливаем флаги безопасности в False по умолчанию + # Обычные пользователи НЕ должны иметь доступ к админке + extra_fields.setdefault('is_staff', False) + extra_fields.setdefault('is_superuser', False) + user = self.model(email=email, name=name, username=username, **extra_fields) user.set_password(password) user.save(using=self._db) diff --git a/myproject/myproject/admin_access_middleware.py b/myproject/myproject/admin_access_middleware.py new file mode 100644 index 0000000..151cba9 --- /dev/null +++ b/myproject/myproject/admin_access_middleware.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +""" +Middleware для ограничения доступа к админке Django на поддоменах тенантов. + +SECURITY: Этот middleware обеспечивает дополнительный слой защиты, +блокируя доступ владельцев тенантов к /admin/ даже если у них +случайно установлен is_staff=True. + +Правила доступа: +- На tenant поддоменах (shop1.localhost): только is_superuser=True может входить в /admin/ +- На public схеме (localhost): обычные правила Django (is_staff=True достаточно) +- Суперпользователи имеют доступ везде +""" +from django.http import HttpResponseForbidden + + +class TenantAdminAccessMiddleware: + """ + Дополнительный слой безопасности: блокирует доступ владельцев тенантов к /admin/ + даже если у них случайно установлен is_staff=True. + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + # Проверяем, это admin URL? + if request.path.startswith('/admin/'): + from django.db import connection + + # Если мы в tenant схеме (не public) + if hasattr(connection, 'tenant') and connection.tenant: + # Проверяем: это не public схема? + if connection.tenant.schema_name != 'public': + # Если пользователь авторизован, но НЕ суперпользователь - блокируем + if request.user.is_authenticated and not request.user.is_superuser: + return HttpResponseForbidden( + "Доступ запрещен. Только системные администраторы могут " + "заходить в админ-панель на поддоменах тенантов. " + "Используйте панель управления тенанта." + ) + + response = self.get_response(request) + return response diff --git a/myproject/myproject/settings.py b/myproject/myproject/settings.py index 4a097e3..1d97197 100644 --- a/myproject/myproject/settings.py +++ b/myproject/myproject/settings.py @@ -95,6 +95,7 @@ SHOW_PUBLIC_IF_NO_TENANT_FOUND = True MIDDLEWARE = [ 'django_tenants.middleware.main.TenantMainMiddleware', # ОБЯЗАТЕЛЬНО ПЕРВЫМ! + 'myproject.admin_access_middleware.TenantAdminAccessMiddleware', # SECURITY: Ограничение доступа к админке 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', diff --git a/myproject/tenants/admin.py b/myproject/tenants/admin.py index 9c6234f..29bffb2 100644 --- a/myproject/tenants/admin.py +++ b/myproject/tenants/admin.py @@ -340,7 +340,9 @@ class TenantRegistrationAdmin(admin.ModelAdmin): owner = User.objects.create_user( email=registration.owner_email, name=registration.owner_name, - password=None # Пароль будет установлен через ссылку + password=None, # Пароль будет установлен через ссылку + is_staff=False, # SECURITY: Владелец НЕ может входить в админку + is_superuser=False # SECURITY: Владелец НЕ суперпользователь ) # Помечаем email как подтвержденный, так как владелец регистрировался с ним owner.is_email_confirmed = True diff --git a/myproject/user_roles/views.py b/myproject/user_roles/views.py index 4759006..ecf18ee 100644 --- a/myproject/user_roles/views.py +++ b/myproject/user_roles/views.py @@ -39,7 +39,9 @@ def user_role_create(request): email=email, name=name, password=password, - is_email_confirmed=True + is_email_confirmed=True, + is_staff=False, # SECURITY: Пользователи ролей НЕ имеют доступа к админке + is_superuser=False # SECURITY: Пользователи ролей НЕ суперпользователи ) # Назначаем роль