Перейти до змісту

Дозволи та Групи — Permissions & Groups

Для кого: Студенти, що вивчають як контролювати доступ до ресурсів у Django.

Головне питання цього документа: Як зробити так, щоб Аліса могла редагувати лише свої нотатки, а не нотатки Боба?


1. Що таке Дозволи (Permissions)?

Django автоматично створює 4 базових дозволи для кожної моделі:

# Для моделі Note Django автоматично створює:
'notes_app.add_note'     # може створювати нотатки
'notes_app.view_note'    # може переглядати
'notes_app.change_note'  # може редагувати
'notes_app.delete_note'  # може видаляти

# Приклад використання:
from django.contrib.auth.decorators import permission_required

@permission_required('notes_app.delete_note')  # ← перевіряє дозвіл
def admin_delete_note(request, pk):
    # Тільки юзери з явним дозволом delete_note потрапляють сюди
    note = get_object_or_404(Note, pk=pk)
    note.delete()

2. Object-Level Permissions — Перевірка на рівні конкретного об'єкта

Проблема: @permission_required('notes_app.change_note') перевіряє лише "чи може цей юзер редагувати нотатки взагалі". Але не перевіряє "чи належить ця конкретна нотатка йому".

# НЕБЕЗПЕЧНО — IDOR (Insecure Direct Object Reference):
@login_required
def note_edit(request, pk):
    note = get_object_or_404(Note, pk=pk)   # pk=42 → Аліса редагує нотатку Боба!
    # ...

# БЕЗПЕЧНО — перевіряємо власника:
@login_required
def note_edit(request, pk):
    note = get_object_or_404(Note, pk=pk, user=request.user)   # pk=42 і user=Alice?
    # Якщо нотатка 42 належить Бобу → Django поверне 404 для Аліси ✓

Що таке IDOR (Insecure Direct Object Reference)?

Аліса знає що URL нотатки — /notes/42/edit/. Вона змінює число на /notes/43/edit/ і намагається редагувати нотатку Боба. Якщо view не перевіряє власника — атака вдається!

# Правило: завжди додавай user=request.user до get_object_or_404

# Для ВЛАСНИХ об'єктів:
note = get_object_or_404(Note, pk=pk, user=request.user)

# Для СПІЛЬНИХ об'єктів (своє АБО в групі):
from django.core.exceptions import PermissionDenied
from django.db.models import Q

note = get_object_or_404(Note, pk=pk)
user_groups = request.user.groups.all()
has_access = (note.user == request.user) or (note.group and note.group in user_groups)
if not has_access:
    raise PermissionDenied   # → 403 Forbidden

3. Групи (Groups) — управління доступом через ролі

Проблема: У тебе є 50 модераторів. Призначати кожному по 10 дозволів вручну — погана ідея. При зміні правил треба оновлювати 50 юзерів.

Рішення: Групи (Groups).

Замість:
  Alice → [can_delete_note, can_edit_note, can_view_reports, ...]
  Bob   → [can_delete_note, can_edit_note, can_view_reports, ...]
  ...

Краще:
  Group "Moderators" → [can_delete_note, can_edit_note, can_view_reports]
  Alice → Group "Moderators"
  Bob   → Group "Moderators"

  При зміні прав → оновлюєш тільки групу ✓

Архітектура груп та дозволів

graph TD
    A[Юзер: Аліса] --> B[Група: Модератори]
    A --> C[Група: Редактори]
    B --> D[Дозвіл: delete_note]
    C --> E[Дозвіл: add_note]
    C --> F[Дозвіл: change_note]

    style B fill:#fff3e0,stroke:#e65100
    style C fill:#e8f5e9,stroke:#2e7d32

4. Групи у нашому проєкті — Спільний доступ до нотаток

У crispy_notes_chat_app ми використовуємо Django вбудовану модель Group не для дозволів, а для спільного доступу до нотаток і списків покупок.

Ідея: Аліса створює групу "Сімя" і додає Боба. Коли Аліса створює нотатку і вказує групу "Сімя" — Боб теж бачить цю нотатку.

# models.py
from django.contrib.auth.models import Group

class Note(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)  # особистий власник
    group = models.ForeignKey(                                  # опціональна група
        Group, on_delete=models.SET_NULL, null=True, blank=True
    )
    title = models.CharField(max_length=200)
    content = models.TextField(blank=True)

selectors.py — запит: "показати всі нотатки юзера та його груп":

from django.db.models import Q

def get_user_notes(user):
    user_groups = user.groups.all()
    return Note.objects.filter(
        Q(user=user) |                   # власні нотатки
        Q(group__in=user_groups)         # нотатки груп
    ).distinct()
    # Аліса в групах "Сімя" і "Робота" → бачить і свої і групові нотатки


5. Управління групами — Group Views

У проєкті є views для управління групами:

URL View Дія
/groups/ group_list Список груп юзера
/groups/new/ group_create Створити нову групу
/groups/<pk>/ group_detail Перегляд учасників, додавання/видалення
/groups/<pk>/delete/ group_delete Видалення групи

Приклад: створення групи "Сімя":

# services.py
def create_group(*, name, creator):
    group = Group.objects.create(name=name)
    group.user_set.add(creator)   # ← creator автоматично стає першим учасником
    return group

# Додавання учасника:
def add_user_to_group(group, username):
    from django.contrib.auth.models import User
    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        return False, f'Юзера «{username}» не знайдено.'
    group.user_set.add(user)
    return True, ''


6. ER-діаграма: Юзер, Група, Нотатка

erDiagram
    USER ||--o{ NOTE : "створив (особиста)"
    GROUP ||--o{ NOTE : "групова нотатка"
    USER }o--o{ GROUP : "є учасником"

    USER {
        int id PK
        string username
    }
    GROUP {
        int id PK
        string name
    }
    NOTE {
        int id PK
        string title
        text content
        int user_id FK
        int group_id FK
    }

Читання діаграми: - USER ||--o{ NOTE → один юзер може мати багато нотаток (|| = один, o{ = багато або нуль) - GROUP ||--o{ NOTE → одна група може мати багато нотаток - USER }o--o{ GROUP → юзер може бути в кількох групах, група може мати кількох юзерів (M:N)


Де це в нашому проєкті?

Файл Що реалізовано
notes_app/models.py Note.group FK, ShoppingList.group FK
notes_app/views.py group_list, group_create, group_detail, group_delete
notes_app/services.py create_group, add_user_to_group, remove_user_from_group
notes_app/selectors.py get_user_notes (Q filter), get_user_groups, get_group_with_members
notes_app/forms.py GroupCreateForm, GroupAddMemberForm
notes_app/templates/notes_app/ group_list.html, group_form.html, group_detail.html