- Создана структура docs/ с категориями: completed, guides, architecture, diagnostic, in-progress, reports - Перемещено 47+ MD файлов из корня проекта в docs/ - Удалено 6 устаревших/дублирующихся файлов - Создана структура backend/scripts/ для тестов и утилит - Перемещено 35 Python скриптов в backend/scripts/ - Создана структура backend/docs/ для технической документации - Перемещено 12 MD файлов backend в backend/docs/ - Перемещены LDAP тестовые данные в backend/tests/fixtures/ - Добавлены README файлы для навигации - Добавлены правила в .github/copilot-instructions.md для размещения документации Результат: - Корень проекта: только README.md - Корень backend/: только manage.py и Django приложения - Вся документация организована в структурированные папки
29 KiB
Тестирование Прав Доступа Через Должности (Position Permissions)
✅ РЕШЕНИЕ НАЙДЕНО И ПРИМЕНЕНО
Дата: 8 декабря 2025
Статус: ✅ Исправлено и протестировано
Корневая Причина
В шаблоне feed/feed_list.html проверялись несуществующие права:
{% if user.is_staff or perms.feed.publish_company_post or perms.feed.publish_department_post %}
Эти права никогда не создавались в Django! Правильное право для создания постов:
{% if user.is_staff or perms.feed.add_post %}
Диагностика Production
Запущен отладочный скрипт для пользователя maze3290@gmail.com:
User: maze3290@gmail.com
Position: СММ специалист
Position Groups: ['Дизайн СММ Реклама']
Group Permissions: ['add_post', 'change_post', 'delete_post', 'view_post', ...]
User Permissions (via PositionRoleBackend):
{'feed.add_post', 'feed.change_post', 'feed.delete_post', ...}
Has feed.publish_company_post: False # ❌ Несуществующее право!
Has feed.add_post: True # ✅ Реальное право!
Вывод: PositionRoleBackend работает правильно, проблема была в шаблоне!
Исправление
Файл: backend/templates/feed/feed_list.html (строка 19)
- {% if user.is_staff or perms.feed.publish_company_post or perms.feed.publish_department_post %}
+ {% if user.is_staff or perms.feed.add_post %}
Коммиты
769f2ed- test: add comprehensive position permissions tests66406d9- fix: add is_active check to PositionRoleBackend4a6b92f- docs: add production debugging guide6162d9f- fix: correct permission check in feed_list.html ← РЕШЕНИЕ
Описание Проблемы (Оригинал)
Исходная Жалоба Пользователя
"Ты говорил, что пользователь с должностью получает все права которые есть у групп в этой должности, но по факту кнопка создания публикаций не появляется"
Ожидаемое поведение:
- Пользователь имеет должность (Position)
- Должность связана с группами (Position.groups)
- Группы имеют права (Group.permissions)
- Пользователь должен получить все права из групп своей должности
Фактическое поведение:
- UI кнопки не появляются (например, "Создать публикацию")
- Пользователи с должностями не могут выполнять действия, несмотря на наличие прав в группах
Архитектура Системы Прав
Цепочка Наследования
Position → Position.groups (ManyToMany) → Group.permissions (ManyToMany) → Permission
↓
Employee.position (ForeignKey)
↓
User (связан через OneToOne)
Реализация Backend
Файл: backend/eusrr_backend/auth_backends.py
class PositionRoleBackend(BaseBackend):
"""
Custom authentication backend that grants permissions based on
Position.groups relationships.
"""
def get_all_permissions(self, user_obj, obj=None):
if not user_obj.is_active or user_obj.is_anonymous:
return set()
# Получаем права из должности
if hasattr(user_obj, 'employee') and user_obj.employee.position:
pos = user_obj.employee.position
return set(
Permission.objects.filter(
group__positions=pos
).select_related('content_type').values_list(
'content_type__app_label',
'codename'
)
)
return set()
Конфигурация: settings.py (строка 329)
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'eusrr_backend.auth_backends.LDAPAuthBackend',
'eusrr_backend.auth_backends.LDAPGroupAuthBackend',
'eusrr_backend.auth_backends.PositionRoleBackend', # ← Наш backend
]
Модели
Файл: backend/employees/models.py
class Position(models.Model):
"""Должность сотрудника"""
name = models.CharField(max_length=100, unique=True)
groups = models.ManyToManyField(
Group,
related_name='positions',
blank=True
)
class Employee(models.Model):
"""Сотрудник"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
position = models.ForeignKey(
Position,
on_delete=models.SET_NULL,
null=True,
blank=True
)
Тестовая Стратегия
Цель Тестов
Определить, является ли проблема:
- Логической ошибкой в
PositionRoleBackend→ Тесты упадут - Проблемой конфигурации (кэширование, LDAP sync, deployment) → Тесты пройдут
Структура Тестов
Файл: backend/tests/employees/test_position_permissions.py
Базовые Тесты (TestPositionPermissions)
class TestPositionPermissions:
"""Основная функциональность"""
✅ test_user_without_position_has_no_permissions
Проверяет: Пользователь без должности = нет прав
❌ test_user_with_position_gets_permissions
Проверяет: Position с Group → User получает права
❌ test_position_change_updates_permissions
Проверяет: Смена должности → права обновляются
❌ test_remove_position_removes_permissions
Проверяет: Удаление должности → права убираются
❌ test_multiple_users_same_position
Проверяет: Несколько user с одной Position → одинаковые права
❌ test_staff_user_keeps_permissions
Проверяет: Staff users сохраняют is_staff права
✅ test_position_group_permissions_are_additive
Проверяет: Несколько групп → права складываются
❌ test_get_all_permissions_includes_position_perms
Проверяет: get_all_permissions() возвращает права должности
Граничные Случаи (TestPositionPermissionsEdgeCases)
class TestPositionPermissionsEdgeCases:
"""Edge cases и особые ситуации"""
✅ test_position_without_groups_grants_no_permissions
Проверяет: Position без groups = нет прав
❌ test_inactive_user_has_no_permissions
Проверяет: Неактивный user = нет прав (даже с Position)
❌ test_direct_user_permissions_and_position_permissions
Проверяет: Прямые права user + права Position = аддитивны
Интеграционные Тесты (TestPositionPermissionsIntegration)
class TestPositionPermissionsIntegration:
"""Интеграция с реальными моделями"""
❌ test_feed_publish_permission_via_position
Проверяет: Право "feed.publish_company_post" работает
❌ test_document_add_permission_via_position
Проверяет: Право "documents.add_document" работает
Текущие Результаты
============== test session starts ==============
collected 13 items
✅ PASSED: 3 tests
❌ ERRORS: 10 tests
Passing Tests:
- test_user_without_position_has_no_permissions
- test_position_group_permissions_are_additive
- test_position_without_groups_grants_no_permissions
Failing Tests:
- All tests with IntegrityError or fixture issues
Обнаруженные Проблемы в Тестах
Проблема 1: IntegrityError на Permission Creation
Ошибка:
IntegrityError: UNIQUE constraint failed: auth_permission.content_type_id, auth_permission.codename
Причина:
Использование Permission.objects.get_or_create() в фикстурах приводит к попыткам создать дубликаты.
Решение: Изменить паттерн создания:
# ❌ Старый код
perm, _ = Permission.objects.get_or_create(
codename='publish_company_post',
content_type=feed_content_type,
defaults={'name': 'Can publish company post'}
)
# ✅ Новый код
perm = Permission.objects.filter(
codename='publish_company_post',
content_type=feed_content_type
).first()
if not perm:
perm = Permission.objects.create(
codename='publish_company_post',
content_type=feed_content_type,
name='Can publish company post'
)
Статус: Исправлено в 3 местах:
- ✅ Фикстура
group_with_publish_perm(строки 44-62) - ✅ Фикстура
group_with_document_perm(строки 64-82) - ✅ Тест
test_position_group_permissions_are_additive(строки 244-269)
Проблема 2: Fixture Visibility Between Test Classes
Ошибка:
fixture 'engineer_position' not found
fixture 'feed_content_type' not found
fixture 'documents_content_type' not found
Причина:
Фикстуры определены в TestPositionPermissions как методы класса, но дочерние классы TestPositionPermissionsEdgeCases и TestPositionPermissionsIntegration не имеют к ним доступа.
Возможные решения:
- Перенести фикстуры на уровень модуля (вне классов)
- Использовать
@pytest.fixture(scope="module") - Использовать наследование классов
- Создать фикстуры в
conftest.py
Статус: ❌ Не исправлено
Проблема 3: Permission Objects Already Exist in Database
Ошибка: При запуске тестов Django уже имеет базовые права из миграций.
Решение:
Использовать .filter().first() вместо .create() для всех Permission объектов.
Статус: ✅ Частично исправлено (нужна проверка всех 13 тестов)
План Исправления
Шаг 1: Рефакторинг Фикстур ✅ IN PROGRESS
- Заменить
get_or_createнаfilter().first()+ conditional create - Применить в
group_with_publish_perm - Применить в
group_with_document_perm - Применить в
test_position_group_permissions_are_additive - Перенести фикстуры на module level
Шаг 2: Запуск Тестов
cd /c/Users/Игорь/Dev/EUSRR/backend
pytest tests/employees/test_position_permissions.py -v --tb=short
Ожидаемый результат после исправлений:
- Все 13 тестов должны запуститься (не ERROR)
- Проверим, сколько PASS vs FAIL
Шаг 3: Анализ Результатов
Сценарий А: Все тесты PASS ✅
Вывод: Логика PositionRoleBackend корректна
Действия:
-
Проверить production конфигурацию:
AUTHENTICATION_BACKENDSorder- Кэширование прав (Django cache)
- LDAP синхронизация групп
-
Проверить данные в БД:
# Django shell user = User.objects.get(username='...') print(user.employee.position) print(user.employee.position.groups.all()) print(user.get_all_permissions()) # Должны быть права из Position -
Проверить middleware и template context
Сценарий Б: Некоторые тесты FAIL ❌
Вывод: Есть проблема в логике PositionRoleBackend
Действия:
- Проанализировать failing assertions
- Исправить
get_all_permissions()илиhas_perm() - Убедиться что query
Permission.objects.filter(group__positions=pos)корректен - Проверить returns в формате "app_label.codename"
Проверка UI Visibility
Текущая Реализация
Файл: backend/templates/feed/feed_list.html (строка 19)
{% if user.is_staff or perms.feed.publish_company_post or perms.feed.publish_department_post %}
<button class="btn btn-primary">Создать публикацию</button>
{% endif %}
Файл: backend/templates/documents/document_list.html (строки 17-21)
{% if user.is_staff or perms.documents.add_document %}
<a href="{% url 'documents:document_create' %}" class="btn btn-primary">
Добавить документ
</a>
{% endif %}
Логика Проверки в Templates
Django использует perms.app_label.codename в шаблонах:
- Вызывает
user.has_perm('app_label.codename') - Проходит по всем AUTHENTICATION_BACKENDS
- Если хотя бы один backend вернёт
True→ permission granted
Требования:
PositionRoleBackend.has_perm()должен вернутьTruePositionRoleBackend.get_all_permissions()должен включать это право- Формат: tuple
('app_label', 'codename')или строка'app_label.codename'
Отладочные Команды
Django Shell Testing
from django.contrib.auth.models import User, Permission, Group
from django.contrib.contenttypes.models import ContentType
from employees.models import Employee, Position
# Получить пользователя
user = User.objects.get(username='test_user')
# Проверить должность
print(f"Position: {user.employee.position}")
print(f"Position Groups: {user.employee.position.groups.all()}")
# Проверить права через backend
from eusrr_backend.auth_backends import PositionRoleBackend
backend = PositionRoleBackend()
all_perms = backend.get_all_permissions(user)
print(f"All Permissions: {all_perms}")
# Проверить has_perm
has_perm = backend.has_perm(user, 'feed.publish_company_post')
print(f"Has feed.publish_company_post: {has_perm}")
# Проверить через Django API
has_perm_django = user.has_perm('feed.publish_company_post')
print(f"User.has_perm('feed.publish_company_post'): {has_perm_django}")
SQL Query Debugging
from django.db import connection
from django.test.utils import CaptureQueriesContext
with CaptureQueriesContext(connection) as queries:
perms = backend.get_all_permissions(user)
for q in queries:
print(q['sql'])
LDAP Group Sync Check
from employees.ldap.services import PositionService
pos = Position.objects.get(name='Engineer')
dn = PositionService._ensure_position_group(pos)
print(f"LDAP Group DN: {dn}")
Ожидаемая SQL Query
При вызове get_all_permissions() должен выполниться query:
SELECT DISTINCT
django_content_type.app_label,
auth_permission.codename
FROM auth_permission
INNER JOIN django_content_type
ON auth_permission.content_type_id = django_content_type.id
INNER JOIN auth_group_permissions
ON auth_permission.id = auth_group_permissions.permission_id
INNER JOIN auth_group
ON auth_group_permissions.group_id = auth_group.id
INNER JOIN employees_position_groups
ON auth_group.id = employees_position_groups.group_id
WHERE employees_position_groups.position_id = ?
Контрольный Чеклист
Pre-Test Checklist
- Создан файл
tests/employees/test_position_permissions.py - 13 тестов покрывают все сценарии
- Фикстуры используют
.filter().first()для Permission - Фикстуры доступны во всех test классах
- Тесты запускаются без ERROR
Post-Test Checklist (если тесты проходят)
- Проверить
user.get_all_permissions()в production shell - Проверить
user.has_perm('feed.publish_company_post')в production - Проверить что
Position.groupsсодержит нужные группы - Проверить что
Group.permissionsсодержит нужные права - Проверить порядок AUTHENTICATION_BACKENDS
- Очистить Django permission cache
- Перезапустить сервер
Post-Test Checklist (если тесты не проходят)
- Проанализировать failing assertions
- Исправить логику в
PositionRoleBackend.get_all_permissions() - Исправить логику в
PositionRoleBackend.has_perm() - Убедиться что query возвращает правильный формат
- Проверить related_name в моделях
- Перезапустить тесты
Следующие Шаги
- Сейчас: Исправить fixture visibility (перенести на module level)
- Потом: Запустить pytest с verbose output
- Далее: Проанализировать PASS/FAIL результаты
- Финал: Исправить production или backend в зависимости от результатов
Дополнительная Информация
Связанные Файлы
backend/eusrr_backend/auth_backends.py- PositionRoleBackendbackend/employees/models.py- Position, Employeebackend/templates/feed/feed_list.html- UI для публикацийbackend/templates/documents/document_list.html- UI для документовbackend/tests/employees/test_position_permissions.py- Тесты
Связанные Коммиты
769f2ed- test: add comprehensive position permissions tests
Известные Ограничения
- Права кэшируются Django (может требовать перезапуска)
- LDAP синхронизация может задерживаться
- Staff users всегда имеют все права (is_staff=True)
Документ создан: 2025-01-XX
Последнее обновление: ✅ ВСЕ ТЕСТЫ ПРОШЛИ (13/13 PASSED)
Статус: ✅ Готово к production тестированию
ВАЖНО: Что Делать Дальше
✅ Что Мы Выяснили
Тесты показали, что логика PositionRoleBackend работает ПРАВИЛЬНО!
- ✅ Пользователи с должностями получают права из групп
- ✅ Права обновляются при смене должности
- ✅ Права удаляются при снятии должности
- ✅
has_perm()иget_all_permissions()работают корректно - ✅ Добавлена проверка
is_active(исправили баг)
🔍 Где Искать Проблему
Если кнопки всё ещё не появляются в production, проблема НЕ в коде, а в:
-
Кэширование прав Django
- Django кэширует права в
_perm_cacheи_user_perm_cache - После изменения должности нужен перелогин или очистка кэша
- Django кэширует права в
-
LDAP синхронизация групп
- Проверить что
Position.groupsсодержит правильные группы - Проверить что
Group.permissionsсодержит нужные права
- Проверить что
-
Порядок AUTHENTICATION_BACKENDS
PositionRoleBackendдолжен быть в списке (сейчас на 4 месте ✅)- Возможно другие backend'ы блокируют его работу
📋 Чеклист Отладки Production
Шаг 1: Проверить Права в Django Shell
from django.contrib.auth import get_user_model
User = get_user_model()
# Замените на реального пользователя
user = User.objects.get(username='имя_пользователя')
print(f"1. Position: {user.position}")
print(f"2. Position Groups: {user.position.groups.all() if user.position else 'NO POSITION'}")
if user.position:
for group in user.position.groups.all():
print(f" - Group: {group.name}")
print(f" Permissions: {list(group.permissions.values_list('codename', flat=True))}")
print(f"\n3. User Permissions:")
print(f" All Permissions: {user.get_all_permissions()}")
print(f" Has feed.publish_company_post: {user.has_perm('feed.publish_company_post')}")
print(f" Has documents.add_document: {user.has_perm('documents.add_document')}")
Ожидаемый результат:
Positionдолжен быть неNonePosition.groupsдолжны содержать группыGroup.permissionsдолжны включать нужные праваuser.get_all_permissions()должен возвращать права из должностиuser.has_perm('feed.publish_company_post')должен бытьTrue
Шаг 2: Проверить Базу Данных
-- Проверить связь User → Employee → Position
SELECT u.username, e.id, p.name
FROM auth_user u
JOIN employees_employee e ON e.user_id = u.id
LEFT JOIN employees_position p ON e.position_id = p.id
WHERE u.username = 'имя_пользователя';
-- Проверить связь Position → Groups
SELECT p.name AS position, g.name AS group_name
FROM employees_position p
JOIN employees_position_groups pg ON pg.position_id = p.id
JOIN auth_group g ON pg.group_id = g.id
WHERE p.id = <position_id>;
-- Проверить связь Group → Permissions
SELECT g.name AS group_name, p.codename
FROM auth_group g
JOIN auth_group_permissions gp ON gp.group_id = g.id
JOIN auth_permission p ON gp.permission_id = p.id
WHERE g.id IN (
SELECT group_id FROM employees_position_groups
WHERE position_id = <position_id>
);
Шаг 3: Очистить Кэш Прав
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.get(username='имя_пользователя')
# Очистить кэши прав
if hasattr(user, '_perm_cache'):
delattr(user, '_perm_cache')
if hasattr(user, '_user_perm_cache'):
delattr(user, '_user_perm_cache')
# Или просто получить fresh instance
user = User.objects.get(id=user.id)
# Проверить снова
print(user.has_perm('feed.publish_company_post'))
Шаг 4: Проверить Template Context
В шаблоне feed/feed_list.html:
{# DEBUG: Покажите права пользователя #}
{% if user.is_authenticated %}
<p>User: {{ user.username }}</p>
<p>Position: {{ user.position }}</p>
<p>Has publish_company_post: {{ perms.feed.publish_company_post }}</p>
<p>Has publish_department_post: {{ perms.feed.publish_department_post }}</p>
<p>Is Staff: {{ user.is_staff }}</p>
{% endif %}
Шаг 5: Проверить Middleware
В settings.py убедитесь что есть:
MIDDLEWARE = [
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
]
Шаг 6: Перезапустить Сервер
# Остановить
Ctrl+C
# Очистить кэш (если используется Redis/Memcached)
python manage.py shell
>>> from django.core.cache import cache
>>> cache.clear()
>>> exit()
# Запустить снова
python manage.py runserver
Шаг 7: Перелогинить Пользователя
- Попросите пользователя выйти из системы
- Войти заново
- Проверить видимость кнопок
🐛 Возможные Проблемы
Проблема 1: Position.groups пустые
Симптом: user.position.groups.all() возвращает пустой список
Решение:
from employees.models import Position
from django.contrib.auth.models import Group
# Найти или создать группу
pub_group = Group.objects.get(name="publishers")
# Назначить группу должности
position = Position.objects.get(name="Инженер")
position.groups.add(pub_group)
Проблема 2: Group.permissions пустые
Симптом: group.permissions.all() возвращает пустой список
Решение:
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
# Найти Content Type
ct = ContentType.objects.get(app_label="feed", model="post")
# Найти или создать Permission
perm = Permission.objects.filter(
codename="publish_company_post",
content_type=ct
).first()
if not perm:
perm = Permission.objects.create(
codename="publish_company_post",
name="Can publish company post",
content_type=ct
)
# Добавить в группу
pub_group = Group.objects.get(name="publishers")
pub_group.permissions.add(perm)
Проблема 3: LDAP не синхронизирует группы
Симптом: В LDAP нет группы POS_Инженер
Решение:
from employees.models import Position
from employees.ldap.services import PositionService
position = Position.objects.get(name="Инженер")
dn = PositionService._ensure_position_group(position)
print(f"LDAP Group DN: {dn}")
<EFBFBD> Итоговая Таблица Результатов Тестов
| Тест | Статус | Что проверяет |
|---|---|---|
| test_user_without_position_has_no_permissions | ✅ PASS | Без должности = нет прав |
| test_user_with_position_gets_permissions | ✅ PASS | С должностью = права есть |
| test_position_change_updates_permissions | ✅ PASS | Смена должности обновляет права |
| test_remove_position_removes_permissions | ✅ PASS | Удаление должности убирает права |
| test_multiple_users_with_same_position | ✅ PASS | Несколько user одна должность |
| test_superuser_has_all_permissions | ✅ PASS | Superuser имеет все права |
| test_position_group_permissions_are_additive | ✅ PASS | Несколько групп складываются |
| test_get_all_permissions_includes_position_perms | ✅ PASS | get_all_permissions() работает |
| test_position_without_groups_grants_no_permissions | ✅ PASS | Должность без групп = нет прав |
| test_inactive_user_has_no_permissions | ✅ PASS | Неактивный user = нет прав |
| test_position_permissions_with_direct_user_permissions | ✅ PASS | Прямые + должность аддитивны |
| test_feed_permissions_through_position | ✅ PASS | Реальные feed права работают |
| test_documents_permissions_through_position | ✅ PASS | Реальные document права работают |
🎯 Рекомендации
- Запустить отладочный скрипт (Шаг 1) чтобы увидеть текущее состояние
- Проверить LDAP синхронизацию группы для должности
- Очистить кэш и перелогинить пользователя
- Если проблема остаётся: Показать вывод отладочного скрипта
Результат должен быть: Кнопки появляются у пользователей с нужными должностями! 🎉
Документ создан: 2025-01-XX
Последнее обновление: ✅ ВСЕ ТЕСТЫ ПРОШЛИ (13/13 PASSED)
Статус: ✅ Готово к production тестированию