This article looks at how the built-in permission classes work in Django REST Framework (DRF).
--
Django REST Framework Permissions Series:
- Permissions in Django REST Framework
- Built-in Permission Classes in Django REST Framework (this article!)
- Custom Permission Classes in Django REST Framework
Contents
Objectives
By the end of this article, you should be able to:
- Explain the differences between the seven built-in permission classes in DRF
- Set permissions on a specific model and object
- Use the built-in permission classes to set a global permission policy
Built-in Classes
Although you can create your own permission classes, DRF comes with seven built-in classes intended to make your life easier:
- AllowAny
- IsAuthenticated
- IsAuthenticatedOrReadOnly
- IsAdminUser
- DjangoModelPermissions
- DjangoModelPermissionsOrAnonReadOnly
- DjangoObjectPermissions
Using them is as simple as including the class in the permission_classes
list of a specific API View. They stretch from entirely open (AllowAny
) to access granted only to admin users (IsAdminUser
). With very little additional work, you can use them to implement fine-grained access control -- either on a model or at the object level. You can also set permissions globally, for all API endpoints.
All of those classes, except the last one, DjangoObjectPermissions
, override just the has_permission
method and inherits the has_object_permission
from the BasePermission
class. has_object_permission
in the BasePermission
class always returns True
, so it has no impact on object-level access restriction:
Permission class | has_permission | has_object_permission |
---|---|---|
AllowAny | ✓ | ✗ |
IsAuthenticated | ✓ | ✗ |
IsAuthenticatedOrReadOnly | ✓ | ✗ |
IsAdminUser | ✓ | ✗ |
DjangoModelPermissions | ✓ | ✗ |
DjangoModelPermissionsOrAnonReadOnly | ✓ | ✗ |
DjangoObjectPermissions | by extending DjangoModelPermissions |
✓ |
For more on
has_permission
vshas_object_permission
, be sure to check out the first article in this series, Permissions in Django REST Framework.
AllowAny
The most open permission of all is AllowAny
. The has_permission
and has_object_permission
methods on AllowAny
always return True
without checking anything. Using it isn't necessary (by not setting the permission class, you implicitly set this one), but you still should since it makes the intent explicit and helps to maintain consistency throughout the app.
You specify it by including permission_classes
in your view:
from rest_framework import viewsets
from rest_framework.permissions import AllowAny
from .models import Message
from .serializers import MessageSerializer
class MessageViewSet(viewsets.ModelViewSet):
permission_classes = [AllowAny] # built-in permission class used
queryset = Message.objects.all()
serializer_class = MessageSerializer
Anyone, even unauthenticated users, can access the API endpoint using any HTTP request method:
IsAuthenticated
IsAuthenticated
checks if the request has a user and if that user is authenticated. Setting permission_classes
to IsAuthenticated
means that only authenticated users will be able to access the API endpoint with any of the request methods.
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from .models import Message
from .serializers import MessageSerializer
class MessageViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] # permission class changed
queryset = Message.objects.all()
serializer_class = MessageSerializer
The unauthenticated user now gets access denied:
IsAuthenticatedOrReadOnly
When permissions are set to IsAuthenticatedOrReadOnly
, the request must either have an authenticated user or use one of the safe/read-only HTTP request methods (GET, HEAD, OPTIONS). This means that every user will be able to see all the messages, but only logged-in users will be able to add, change, or delete objects.
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Message
from .serializers import MessageSerializer
class MessageViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticatedOrReadOnly] # ReadOnly added
queryset = Message.objects.all()
serializer_class = MessageSerializer
An unauthenticated user can view the message that was posted by an authenticated user, but they can't do anything with it or add their own:
IsAdminUser
Permissions set to IsAdminUser
means that the request needs to have a user and that user must have is_staff set to True
. This means that only admin users can see, add, change, or delete objects.
from rest_framework import viewsets
from rest_framework.permissions import IsAdminUser
from .models import Message
from .serializers import MessageSerializer
class MessageViewSet(viewsets.ModelViewSet):
permission_classes = [IsAdminUser] # only for admin users
queryset = Message.objects.all()
serializer_class = MessageSerializer
The interesting part here is that unauthenticated users and authenticated users without admin access will get different errors.
For unauthenticated users, a NotAuthenticated
exception is raised:
Meanwhile, for an authenticated user without admin access, a PermissionDenied
exception is raised:
DjangoModelPermissions
DjangoModelPermissions
allows us to set any combination of permissions to each of the users separately. The permission then checks if the user is authenticated and if they have add
, change
, or delete
user permissions on the model.
from rest_framework import viewsets
from rest_framework.permissions import DjangoModelPermissions
from .models import Message
from .serializers import MessageSerializer
class MessageViewSet(viewsets.ModelViewSet):
permission_classes = [DjangoModelPermissions]
queryset = Message.objects.all()
serializer_class = MessageSerializer
Unlike with other permissions, this is not the end of setting the permission. You need to set the permissions for the specific user:
If you check the single view for the post, you can see that this specific user can edit the post, but can't delete it:
DjangoModelPermissions
doesn't necessarily need to be assigned to a single user. You can use it for group permissions also. Here's a group that's allowed to delete the message:
And you can see here, that the member of that group can delete the message:
DjangoModelPermissions
must only be applied to views that have aqueryset
property or aget_queryset()
method.For example, the CreateAPIView generic view doesn't require a queryset, so if you set
DjangoModelPermissions
on it you'll get an assertion error. However, if you perform the querying, even though you're not using the queryset,DjangoModelPermissions
will work:class NewMessage(generics.CreateAPIView): queryset = Message.objects.all() permission_classes = [DjangoModelPermissions] serializer_class = MessageSerializer
DjangoModelPermissionsOrAnonReadOnly
DjangoModelPermissionsOrAnonReadOnly
extends the DjangoModelPermissions
and only changes one thing: It sets authenticated_users_only
to False
.
from rest_framework import viewsets
from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly
from .models import Message
from .serializers import MessageSerializer
class MessageViewSet(viewsets.ModelViewSet):
permission_classes = [DjangoModelPermissionsOrAnonReadOnly]
queryset = Message.objects.all()
serializer_class = MessageSerializer
Anonymous users can see the objects but can't interact with them:
DjangoObjectPermissions
While DjangoModelPermissions
limits the user's permission for interacting with a model (all the instances), DjangoObjectPermissions
limits the interaction to a single instance of the model (an object). To use DjangoObjectPermissions
you'll need a permission backend that supports object-level permissions. We'll look at django-guardian.
While there are quite a few packages that cover Django permissions, DRF explicitly mentions django-guardian, which is why we're working with it in this article.
Other packages that deal with object-level permissions:
Installing django-guardian
To use django-guardian, you first need to install it:
$ pip install django-guardian
Then, add it to the INSTALLED_APPS
and the AUTHENTICATION_BACKENDS
:
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'guardian',
]
# ...
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'guardian.backends.ObjectPermissionBackend',
)
Finally, run the migrations:
(venv)$ python manage.py migrate
If you want to see the permissions for a single object, you need to use GuardedModelAdmin instead of ModelAdmin. You can do that in an admin.py file like so:
# admin.py
from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from tutorial.models import Message
class MessageAdmin(GuardedModelAdmin):
pass
admin.site.register(Message, MessageAdmin)
Now, if you open a single object in the admin panel, on the top right there's a new button called "OBJECT PERMISSIONS". Upon clicking on it, the object permissions panel opens:
Using DjangoObjectPermissions
Now knowing the difference between has_permission
and has_object_permission
will come in handy. In short, DRF first checks if the request has permission to access the model. If it doesn't, DRF doesn't care about the object-level permissions.
That means that the user must have model permission if you want the object-level permission to be checked. A good use case for object-level permissions is for only allowing the owner of an object to change or delete it. Here's a view that allows only the creator of the object to delete it:
# views.py
from guardian.shortcuts import assign_perm
from rest_framework import viewsets
from rest_framework.permissions import DjangoObjectPermissions
from .models import Message
from .serializers import MessageSerializer
class MessageViewSet(viewsets.ModelViewSet):
permission_classes = [DjangoObjectPermissions] # class changed
queryset = Message.objects.all()
serializer_class = MessageSerializer
def perform_create(self, serializer): # new function
instance = serializer.save()
assign_perm("delete_message", self.request.user, instance)
As you can see, there are two important changes:
- First, the permission class is
DjangoObjectPermissions
. - Next, we tapped into the model instance creation. We can't assign permission to a non-existing object, so we first need to create the instance and after that, assign object permission to it via the assign_perm shortcut from django-guardian.
assign_perm
is a django-guardian function used for assigning permissions to certain users or groups. It takes three arguments:
- permission:
delete_message
- user_or_group:
self.request.user
- object (defaults to
None
):instance
Again, for the object permission to work, the user must have model-level permission for the corresponding model. Let's say you have two users with the same permissions on the model:
And you use the code above to assign permission only to the creator. user_1 -- the creator of the object -- can delete it, but user_2 can't delete it even though they have model-level permissions:
If we remove the permission to delete any object on the model for user_1:
They can't delete their message:
Even though they have the permission to delete this particular object:
Global Permissions
You can easily set global permission in your settings.py file, using built-in permission classes. For example:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
DEFAULT_PERMISSION_CLASSES
will only work for the views or objects that don't have permissions explicitly set.
You don't necessarily need to use built-in classes here. You can use your own custom classes as well.
Conclusion
That's it. You should now know how to use Django REST Framework's seven built-in permission classes. They vary from totally open (AllowAny
) to mostly closed (IsAdminUser
).
You can set permissions globally, on the model (DjangoModelPermissions
), or on a single object (DjangoObjectPermissions
).
There are also classes that allow you to limit "unsafe" HTTP methods, but allow anyone the safe ones (IsAuthenticatedOrReadOnly
, DjangoModelPermissionsOrAnonReadOnly
).
If you don't have any specific requirements, the built-in classes should suffice for most cases. On the other hand, if you have some special requirements, you should build your own permission class.
--
Django REST Framework Permissions Series: