Усилена безопасность: запрет доступа владельцев тенантов к Django админке
Реализовано три уровня защиты от доступа владельцев тенантов к /admin/: 1. Явные флаги безопасности в CustomUserManager.create_user() - Добавлены setdefault(is_staff=False, is_superuser=False) - Устраняет зависимость от неявных дефолтов Django 2. Явные флаги при создании владельцев тенантов (tenants/admin.py) - Владельцы создаются с is_staff=False, is_superuser=False - Явная документация намерений в коде 3. Middleware защита на уровне HTTP (TenantAdminAccessMiddleware) - Блокирует доступ к /admin/ на поддоменах тенантов - Только is_superuser=True может войти в админку тенанта - Последний рубеж обороны (defense-in-depth) Дополнительно: - Исправлена видимость alert-уведомлений на странице регистрации (добавлены явные цвета для всех типов alert) Суперпользователи НЕ затронуты: create_superuser() работает как прежде. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
# -*- 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),
|
|
||||||
]
|
|
||||||
@@ -98,6 +98,29 @@
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
padding: 0.65rem 0.875rem;
|
padding: 0.65rem 0.875rem;
|
||||||
|
color: #1f2937 !important;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.alert-success {
|
||||||
|
background-color: #d1fae5 !important;
|
||||||
|
border-color: #10b981 !important;
|
||||||
|
color: #065f46 !important;
|
||||||
|
}
|
||||||
|
.alert-error, .alert-danger {
|
||||||
|
background-color: #fee2e2 !important;
|
||||||
|
border-color: #ef4444 !important;
|
||||||
|
color: #991b1b !important;
|
||||||
|
}
|
||||||
|
.alert-warning {
|
||||||
|
background-color: #fef3c7 !important;
|
||||||
|
border-color: #f59e0b !important;
|
||||||
|
color: #92400e !important;
|
||||||
|
}
|
||||||
|
.alert-info {
|
||||||
|
background-color: #dbeafe !important;
|
||||||
|
border-color: #3b82f6 !important;
|
||||||
|
color: #1e40af !important;
|
||||||
}
|
}
|
||||||
small.text-muted {
|
small.text-muted {
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
@@ -119,12 +142,14 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
<div class="mb-3">
|
||||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
{% for message in messages %}
|
||||||
{{ message }}
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
{{ message }}
|
||||||
</div>
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
{% endfor %}
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user