SECURITY: Ограничен доступ владельцев тенантов к админке Django

Исправлена критическая уязвимость безопасности, которая потенциально позволяла
владельцам тенантов получить доступ к админ-панели Django.

Изменения:
- Добавлены явные setdefault для is_staff=False и is_superuser=False в CustomUserManager.create_user()
- Добавлены явные флаги безопасности при создании владельца тенанта
- Добавлены явные флаги безопасности при создании пользователей через систему ролей
- Создан TenantAdminAccessMiddleware для защиты /admin/ на уровне middleware
- Создана миграция данных для исправления флагов у существующих пользователей

Реализована трёхуровневая защита (Defense-in-Depth):
1. Уровень модели: явные дефолты в create_user()
2. Уровень views: явные флаги при создании
3. Уровень middleware: runtime блокировка доступа

Файлы:
- accounts/models.py: явные флаги в create_user()
- tenants/admin.py: явные флаги при создании владельца
- user_roles/views.py: явные флаги при создании через роли
- myproject/admin_access_middleware.py: новый middleware
- myproject/settings.py: регистрация middleware
- accounts/migrations/0002_fix_owner_staff_flags.py: миграция данных

ВАЖНО: После применения этого коммита необходимо выполнить:
1. python manage.py migrate accounts
2. python manage.py migrate_schemas

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-02 10:57:39 +03:00
parent 0c64aac570
commit 0d3f07ad25
6 changed files with 151 additions and 2 deletions

View File

@@ -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),
]

View File

@@ -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)