Back to Rules
Django89% popularity

Django REST Framework Permissions

Implement granular permission systems with DRF permission classes and custom permission logic.

Tom ChristieUpdated Mar 25, 2024

Overview

Django REST Framework's permission system provides layered access control, from basic safety checks to object-level granularity. Understanding how permissions compose and evaluate enables building secure APIs that properly handle complex authorization scenarios. DRF evaluates permissions in order, short-circuiting on the first deny. The DefaultRouter and ViewSets automatically provide SAFE_METHODS permissions (GET, HEAD, OPTIONS) while requiring authentication for unsafe methods. Custom permissions can be created by extending BasePermission and implementing has_permission for view-level and has_object_permission for object-level checks. Object-level permissions are evaluated manually in ViewSets using self.check_object_permissions(). DRF doesn't automatically enforce object permissions—ViewSets must explicitly call this for each retrieved object. This design allows flexibility in when object permissions are checked, but forgetting to call it creates security vulnerabilities. Custom permission logic should follow the principle of explicit denial. Default to denying access, requiring explicit grants for each action. Async permission classes can be created for permissions requiring async operations, though most permissions are synchronous checks against the request and model objects. For complex permission requirements, consider using django Guardian for object-level permissions or rules for permission composition. These libraries integrate with DRF's permission system while providing more sophisticated rule definitions than custom permission classes alone.

Code Example

.djangorules
from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from guardian.shortcuts import get_perms, assign_perm
from .models import Project, Task, TeamMembership


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed for any authenticated request
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner
        return obj.owner == request.user


class IsProjectMember(permissions.BasePermission):
    """
    Permission to check project membership
    """

    message = 'You must be a member of this project to access it.'

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return obj.is_public or request.user in obj.members.all()

        return request.user in obj.members.all()


class IsTaskAssigneeOrProjectMember(permissions.BasePermission):
    """
    Tasks can be modified by assignees or project members
    """

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True

        user = request.user
        task_project = obj.project

        if task_project.owner == user:
            return True

        if obj.assignee == user:
            return True

        return user in task_project.members.all()


class ProjectPermission(permissions.BasePermission):
    """
    Granular project permissions based on role
    """

    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True

        return request.user and request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        user = request.user

        # Public read access
        if request.method in permissions.SAFE_METHODS:
            return obj.is_public or user in obj.members.all()

        # Admin can do anything
        if user == obj.owner or user.is_superuser:
            return True

        # Check role-based permissions
        try:
            membership = TeamMembership.objects.get(
                project=obj,
                user=user
            )

            if request.method == 'DELETE':
                return membership.role == 'admin'

            if request.method in ['PATCH', 'PUT']:
                return membership.role in ['admin', 'editor']

            return True
        except TeamMembership.DoesNotExist:
            return False


class NotificationPermission(permissions.BasePermission):
    """
    Users can only see their own notifications
    """

    def has_object_permission(self, request, view, obj):
        return obj.recipient == request.user


class ProjectViewSet(ModelViewSet):
    queryset = Project.objects.all()
    serializer_class = ProjectSerializer
    permission_classes = [permissions.IsAuthenticated, ProjectPermission]

    def get_queryset(self):
        user = self.request.user

        if user.is_superuser:
            return Project.objects.all()

        return Project.objects.filter(
            models.Q(owner=user) |
            models.Q(members=user) |
            models.Q(is_public=True)
        ).distinct()

    def perform_create(self, serializer):
        project = serializer.save(owner=self.request.user)
        assign_perm('view_project', self.request.user, project)
        assign_perm('change_project', self.request.user, project)
        assign_perm('delete_project', self.request.user, project)


class TaskViewSet(ModelViewSet):
    queryset = Task.objects.all()
    serializer_class = TaskSerializer
    permission_classes = [
        permissions.IsAuthenticated,
        IsTaskAssigneeOrProjectMember
    ]

    def get_queryset(self):
        user = self.request.user
        return Task.objects.filter(
            models.Q(project__owner=user) |
            models.Q(project__members=user) |
            models.Q(assignee=user)
        ).distinct()

More Django Rules

DJANGO
89%

Django REST Framework Serializer Validation

Implement multi-layer validation in DRF serializers for robust API input handling and security.

djangorest-frameworkvalidation
from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.utils import timezone
from .models import Project, T...
Mar 5, 2024by Tom Christie
View Rule
DJANGO
87%

Django QuerySet Optimization

Use select_related, prefetch_related, and only() to minimize database queries.

djangoormqueryset
from django.db.models import Prefetch, Count, Q
from django.shortcuts import render
from .models import Author, Book, Publisher

# Problematic: N+1 qu...
Feb 25, 2024by Aymeric Augustin
View Rule
DJANGO
91%

Django Security Best Practices

Implement comprehensive security measures including CSRF, XSS prevention, and SQL injection protection.

djangosecuritycsrf
from django.http import HttpResponse, HttpResponseForbidden
from django.views.decorators.http import require_http_methods
from django.views.decorators...
Mar 2, 2024by Django Software Foundation
View Rule