# Тестирование Прав Доступа Через Должности (Position Permissions) ## ✅ РЕШЕНИЕ НАЙДЕНО И ПРИМЕНЕНО **Дата:** 8 декабря 2025 **Статус:** ✅ Исправлено и протестировано ### Корневая Причина В шаблоне `feed/feed_list.html` проверялись **несуществующие** права: ```django {% if user.is_staff or perms.feed.publish_company_post or perms.feed.publish_department_post %} ``` Эти права **никогда не создавались** в Django! Правильное право для создания постов: ```django {% if user.is_staff or perms.feed.add_post %} ``` ### Диагностика Production Запущен отладочный скрипт для пользователя `maze3290@gmail.com`: ```python 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) ```diff - {% 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 %} ``` ### Коммиты 1. `769f2ed` - test: add comprehensive position permissions tests 2. `66406d9` - fix: add is_active check to PositionRoleBackend 3. `4a6b92f` - docs: add production debugging guide 4. `6162d9f` - **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` ```python 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) ```python 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` ```python 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 ) ``` ## Тестовая Стратегия ### Цель Тестов Определить, является ли проблема: 1. **Логической ошибкой** в `PositionRoleBackend` → Тесты упадут 2. **Проблемой конфигурации** (кэширование, LDAP sync, deployment) → Тесты пройдут ### Структура Тестов **Файл:** `backend/tests/employees/test_position_permissions.py` #### Базовые Тесты (TestPositionPermissions) ```python 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) ```python 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) ```python 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()` в фикстурах приводит к попыткам создать дубликаты. **Решение:** Изменить паттерн создания: ```python # ❌ Старый код 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` не имеют к ним доступа. **Возможные решения:** 1. Перенести фикстуры на уровень модуля (вне классов) 2. Использовать `@pytest.fixture(scope="module")` 3. Использовать наследование классов 4. Создать фикстуры в `conftest.py` **Статус:** ❌ Не исправлено ### Проблема 3: Permission Objects Already Exist in Database **Ошибка:** При запуске тестов Django уже имеет базовые права из миграций. **Решение:** Использовать `.filter().first()` вместо `.create()` для всех Permission объектов. **Статус:** ✅ Частично исправлено (нужна проверка всех 13 тестов) ## План Исправления ### Шаг 1: Рефакторинг Фикстур ✅ IN PROGRESS - [x] Заменить `get_or_create` на `filter().first()` + conditional create - [x] Применить в `group_with_publish_perm` - [x] Применить в `group_with_document_perm` - [x] Применить в `test_position_group_permissions_are_additive` - [ ] Перенести фикстуры на module level ### Шаг 2: Запуск Тестов ```bash cd /c/Users/Игорь/Dev/EUSRR/backend pytest tests/employees/test_position_permissions.py -v --tb=short ``` **Ожидаемый результат после исправлений:** - Все 13 тестов должны запуститься (не ERROR) - Проверим, сколько PASS vs FAIL ### Шаг 3: Анализ Результатов #### Сценарий А: Все тесты PASS ✅ **Вывод:** Логика `PositionRoleBackend` корректна **Действия:** 1. Проверить production конфигурацию: - `AUTHENTICATION_BACKENDS` order - Кэширование прав (Django cache) - LDAP синхронизация групп 2. Проверить данные в БД: ```python # Django shell user = User.objects.get(username='...') print(user.employee.position) print(user.employee.position.groups.all()) print(user.get_all_permissions()) # Должны быть права из Position ``` 3. Проверить middleware и template context #### Сценарий Б: Некоторые тесты FAIL ❌ **Вывод:** Есть проблема в логике `PositionRoleBackend` **Действия:** 1. Проанализировать failing assertions 2. Исправить `get_all_permissions()` или `has_perm()` 3. Убедиться что query `Permission.objects.filter(group__positions=pos)` корректен 4. Проверить returns в формате "app_label.codename" ## Проверка UI Visibility ### Текущая Реализация **Файл:** `backend/templates/feed/feed_list.html` (строка 19) ```html {% if user.is_staff or perms.feed.publish_company_post or perms.feed.publish_department_post %} {% endif %} ``` **Файл:** `backend/templates/documents/document_list.html` (строки 17-21) ```html {% if user.is_staff or perms.documents.add_document %} Добавить документ {% endif %} ``` ### Логика Проверки в Templates Django использует `perms.app_label.codename` в шаблонах: 1. Вызывает `user.has_perm('app_label.codename')` 2. Проходит по всем AUTHENTICATION_BACKENDS 3. Если хотя бы один backend вернёт `True` → permission granted **Требования:** - `PositionRoleBackend.has_perm()` должен вернуть `True` - `PositionRoleBackend.get_all_permissions()` должен включать это право - Формат: tuple `('app_label', 'codename')` или строка `'app_label.codename'` ## Отладочные Команды ### Django Shell Testing ```python 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 ```python 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 ```python 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: ```sql 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 - [x] Создан файл `tests/employees/test_position_permissions.py` - [x] 13 тестов покрывают все сценарии - [x] Фикстуры используют `.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 в моделях - [ ] Перезапустить тесты ## Следующие Шаги 1. **Сейчас:** Исправить fixture visibility (перенести на module level) 2. **Потом:** Запустить pytest с verbose output 3. **Далее:** Проанализировать PASS/FAIL результаты 4. **Финал:** Исправить production или backend в зависимости от результатов ## Дополнительная Информация ### Связанные Файлы - `backend/eusrr_backend/auth_backends.py` - PositionRoleBackend - `backend/employees/models.py` - Position, Employee - `backend/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, проблема НЕ в коде, а в: 1. **Кэширование прав Django** - Django кэширует права в `_perm_cache` и `_user_perm_cache` - После изменения должности нужен перелогин или очистка кэша 2. **LDAP синхронизация групп** - Проверить что `Position.groups` содержит правильные группы - Проверить что `Group.permissions` содержит нужные права 3. **Порядок AUTHENTICATION_BACKENDS** - `PositionRoleBackend` должен быть в списке (сейчас на 4 месте ✅) - Возможно другие backend'ы блокируют его работу ### 📋 Чеклист Отладки Production #### Шаг 1: Проверить Права в Django Shell ```python 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` должен быть не `None` - `Position.groups` должны содержать группы - `Group.permissions` должны включать нужные права - `user.get_all_permissions()` должен возвращать права из должности - `user.has_perm('feed.publish_company_post')` должен быть `True` #### Шаг 2: Проверить Базу Данных ```sql -- Проверить связь 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 = ; -- Проверить связь 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 = ); ``` #### Шаг 3: Очистить Кэш Прав ```python 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`: ```django {# DEBUG: Покажите права пользователя #} {% if user.is_authenticated %}

User: {{ user.username }}

Position: {{ user.position }}

Has publish_company_post: {{ perms.feed.publish_company_post }}

Has publish_department_post: {{ perms.feed.publish_department_post }}

Is Staff: {{ user.is_staff }}

{% endif %} ``` #### Шаг 5: Проверить Middleware В `settings.py` убедитесь что есть: ```python MIDDLEWARE = [ ... 'django.contrib.auth.middleware.AuthenticationMiddleware', ... ] ``` #### Шаг 6: Перезапустить Сервер ```bash # Остановить 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()` возвращает пустой список **Решение:** ```python 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()` возвращает пустой список **Решение:** ```python 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_Инженер` **Решение:** ```python 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}") ``` ### � Итоговая Таблица Результатов Тестов | Тест | Статус | Что проверяет | |------|--------|---------------| | 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. **Запустить отладочный скрипт** (Шаг 1) чтобы увидеть текущее состояние 2. **Проверить LDAP синхронизацию** группы для должности 3. **Очистить кэш** и перелогинить пользователя 4. **Если проблема остаётся:** Показать вывод отладочного скрипта **Результат должен быть:** Кнопки появляются у пользователей с нужными должностями! 🎉 --- **Документ создан:** 2025-01-XX **Последнее обновление:** ✅ ВСЕ ТЕСТЫ ПРОШЛИ (13/13 PASSED) **Статус:** ✅ Готово к production тестированию