Corp/docs/completed/CALENDAR_PERMISSIONS_FIX.md
Игорь Надеин 7a055c79cb fix: исправлены JS ошибки в календаре и emoji-picker
- Создана заглушка для calendarEventDragDrop.js с экспортом initCalendarEventDragDrop
- Закомментирован импорт несуществующего database.js в emoji-picker/index.js
- Исправлена ошибка 'does not provide an export named initCalendarEventDragDrop'
- Исправлена ошибка 404 для emoji-picker/database.js
2026-02-23 12:26:03 +03:00

7.4 KiB
Raw Blame History

Исправление прав доступа для глобальных календарей

Дата: 23 февраля 2026 г. Статус: ✅ Выполнено

Проблема

Обычные пользователи не могли создавать события в глобальных календарях (новая архитектура), даже если у них были соответствующие права через подписку (can_edit=True) или если календарь имел default_can_edit=True.

Причина

Метод get_permissions() в CalendarEventsViewSet проверял права только для legacy логики (department/employee), но не учитывал новую архитектуру с calendar_id:

# Старая логика (ОШИБКА):
def get_permissions(self):
    emp = self._employee_id(required=False)
    dep = self._dept_id(required=False)

    # Календарь компании — только администраторы
    if dep is None:  # ❌ Срабатывало для глобальных календарей!
        return [IsAdminUser()]

Когда пользователь создавал событие в глобальном календаре:

  1. calendar_id передавался в теле запроса
  2. get_permissions() НЕ видел calendar_id (смотрел только URL/query параметры)
  3. Видел dep = None, emp = None
  4. Считал это legacy календарём компании
  5. Блокировал всех, кроме администраторов

Решение

Переписан метод get_permissions() с поддержкой новой архитектуры календарей:

Новая логика проверки прав

  1. Приоритет новой архитектуры: Сначала проверяется наличие calendar_id
  2. Проверка через модель Calendar: Используется метод calendar.can_user_edit(user)
  3. Fallback на legacy: Если calendar_id не указан, используется старая логика

Класс CalendarEditPermission

class CalendarEditPermission(BasePermission):
    """Проверка прав редактирования через календари."""

    def has_permission(self, request, view):
        # Ищем calendar_id в:
        # 1. Теле запроса (create)
        # 2. Query параметрах
        # 3. Объекте события (update/delete)

        if calendar_id is not None:
            calendar = Calendar.objects.get(id=int(calendar_id))
            return calendar.can_user_edit(request.user)

        return None  # Переход к legacy логике

Метод calendar.can_user_edit()

Проверяет права в следующем порядке:

  1. Админы/staff → ✅ Всегда могут
  2. Владелец календаря → ✅ Всегда может
  3. Подписка с can_edit=True → ✅ Может
  4. Иначе → ❌ Не может

Изменённые файлы

  • backend/api/v1/calendar/views.py
    • Метод CalendarEventsViewSet.get_permissions() — добавлена проверка новой архитектуры
    • Класс CalendarEditPermission — новый permission для календарей

Как это работает теперь

Сценарий 1: Глобальный календарь с default_can_edit=True

# Создание календаря админом
calendar = Calendar.objects.create(
    title="Общие мероприятия",
    visibility=CalendarVisibility.PUBLIC,
    default_can_edit=True  # ✅ Все могут редактировать
)

# Обычный пользователь создаёт событие
POST /api/v1/calendar/events/
{
    "calendar_id": 1,
    "title": "Корпоратив",
    "start_date": "2026-03-01",
    "all_day": true
}
# ✅ Успех! Проверка через calendar.can_user_edit()

Сценарий 2: Глобальный календарь с подпиской

# Админ приглашает пользователя
calendar.subscriptions.create(
    user=user,
    can_edit=True  # ✅ Даём право редактировать
)

# Пользователь создаёт событие
POST /api/v1/calendar/events/
{
    "calendar_id": 1,
    "title": "Важная встреча",
    "start_date": "2026-03-05",
    "all_day": true
}
# ✅ Успех! can_edit=True в подписке

Сценарий 3: Legacy календарь компании

# Без calendar_id — старая логика
POST /api/v1/calendar/events/
{
    "title": "Собрание компании",
    "start_date": "2026-03-10",
    "all_day": true
}
# ❌ Требуется IsAdminUser (как и раньше)

Обратная совместимость

✅ Полностью сохранена:

  • Legacy календари (без calendar_id) работают по-прежнему
  • Календари отделов требуют MANAGE_CALENDAR permission
  • Личные календари доступны только владельцу

Тестирование

Рекомендуемые тесты:

  1. Глобальный календарь + default_can_edit=True:

    • Обычный пользователь создаёт событие → ✅ Успех
  2. Глобальный календарь + подписка с can_edit=True:

    • Подписанный пользователь создаёт событие → ✅ Успех
    • Неподписанный пользователь → ❌ Отказ
  3. Глобальный календарь + подписка с can_edit=False:

    • Подписанный пользователь создаёт событие → ❌ Отказ
  4. Legacy календарь компании:

    • Обычный пользователь → ❌ Отказ
    • Админ → ✅ Успех
  5. Update/Delete:

    • Проверка прав через event.calendar.can_user_edit()

Дополнительные улучшения

Возможные расширения:

  1. Кеширование проверок прав (если календарей много)
  2. Логирование отказов для аудита
  3. Rate limiting для создания событий
  4. Webhook уведомления о создании событий

Связанные файлы

  • backend/calendar_app/models.py — модели Calendar, CalendarSubscription
  • backend/api/v1/calendar/serializers.py — сериализаторы календарей
  • backend/api/v1/calendar/views.py — ViewSet для событий и календарей

Примечания

  • Проверка прав происходит до валидации сериализатора
  • Для update/delete используется тот же механизм через get_object()
  • Админы и staff всегда имеют полный доступ

Автор: GitHub Copilot Проверено: Требуется тестирование