0% found this document useful (0 votes)
22 views9 pages

Permissions in Django REST Framework

Uploaded by

constrictor36209
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
22 views9 pages

Permissions in Django REST Framework

Uploaded by

constrictor36209
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 9

Permissions in Django REST Framework

Django REST Framework Permissions Series:

Objectives
By the end of this article, you should be able to explain:

1. How DRF permissions work


2. The similarities and differences between has_permission and has_object_permission
3. When to use has_permission and has_object_permission

DRF Permissions
In DRF, permissions, along with authentication and throttling, are used to grant or deny access for
different classes of users to different parts of an API.

Authentication and authorization work hand in hand. Authentication is always executed before
authorization.

While authentication is the process of checking a user's identity (the user the request came from, the
token that it was signed with), authorization is a process of checking if the request user has the
necessary permissions for executing the request (are they a super user, are they the creators of the
object).

The authorization process in DRF is covered by permissions.

View Permissions
APIView has two methods that check for permissions:

1. check_permissions checks if the request should be permitted based on request data


2. check_object_permissions checks if the request should be permitted based on the
combination of the request and object data

# rest_framework/views.py

class APIView(View):
# other methods
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""

1/9
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)

def check_object_permissions(self, request, obj):


"""
Check if the request should be permitted for a given object.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_object_permission(request, self, obj):
self.permission_denied(
request,
message=getattr(permission, 'message', None),
code=getattr(permission, 'code', None)
)

When the request comes in, authentication is performed. If the authentication isn't successful, a
NotAuthenticated error is raised. After that, the permissions get checked in a loop, and if any of them
fail, a PermissionDenied error is raised. Finally, a throttling check is performed against the request.

check_permissions is called before the view handler is executed while


check_object_permissions is not executed unless you explicitly call it. For example:

class MessageSingleAPI(APIView):

def get(self, request, pk):


message = get_object_or_404(Message.objects.all(), pk=pk)
self.check_object_permissions(request, message) # explicitly called
serializer = MessageSerializer(message)
return Response(serializer.data)

With ViewSets and Generic Views, check_object_permissions is called after the object is retrieved
from the database for all detail views.

# rest_framework/generics.py

class GenericAPIView(views.APIView):
# other methods
def get_object(self):
"""

2/9
Returns the object the view is displaying.

You may want to override this if you need to provide non-standard


queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())

# Perform the lookup filtering.


lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field

assert lookup_url_kwarg in self.kwargs, (


'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)

filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}


obj = get_object_or_404(queryset, **filter_kwargs)

# May raise a permission denied


self.check_object_permissions(self.request, obj) # HERE

return obj

Permission is checked for all permissions and if any one of them return False, a PermissionDenied
error is raised.

Permission classes
Permissions in DRF are defined as a list of permission classes. You can either create your own or use
one of the seven built-in classes. All permission classes, either custom or built-in, extend from the
BasePermission class:

class BasePermission(metaclass=BasePermissionMetaclass):

def has_permission(self, request, view):


return True

def has_object_permission(self, request, view, obj):


return True

As you can see, BasePermission has two methods, has_permission and


has_object_permission, that both return True. The permission classes override one or both of the

3/9
methods to conditionally return True.

Turn back to the check_permissions and check_object_permissions methods from the


beginning of the article:

check_permissions calls has_permission for each of the permissions


check_object_permissions calls has_object_permission for each of the permissions as
well

has_permission
has_permission is used to decide whether a request and a user are allowed to access a specific view

For example:

Is the request method allowed?


Is the user authenticated?
Is the user an admin or super user?

has_permission possesses knowledge about the request, but not about the object of the request.

As explained at the beginning, has_permission (called by check_permissions) gets executed


before the view handler is executed, without explicitly calling it.

has_object_permission
has_object_permission is used to decide whether a specific user is allowed to interact with a
specific object

For example:

Who created the object?


When was it created?
In which group does the object belong to?

Besides the knowledge of the request, has_object_permission also possesses data about the object
of the request. The method executes after the object is retrieved from the database.

Unlike has_permission, has_object_permission isn't always executed by default:

With an APIView, you must explicitly call check_object_permission to execute


has_object_permission for all permission classes.
With ViewSets (like ModelViewSet) or Generic Views (like RetrieveAPIView),
has_object_permission is executed via check_object_permission inside a get_object
method out of the box.
has_object_permission is never executed for list views (regardless of the view you're
extending from) or when the request method is POST (since the object doesn't exist yet).

4/9
When any has_permission returns False, the has_object_permission doesn't get
checked. The request is immediately rejected.

has_permission vs has_object_permission
What's the difference between has_permission and has_object_permission in Django REST
Framework?

Again, for:

List views, only has_permission is executed and the request is either granted or refused access.
If access is refused, the objects never get retrieved.
Detail views, has_permission is executed and then only if permission is granted,
has_object_permission is executed after the object is retrieved.

Built-in DRF Permission Classes

5/9
With regard to the built-in DRF permission classes, all of them override has_permission while only
DjangoObjectPermissions overrides has_object_permission:

Permission class has_permission has_object_permission


AllowAny ✓ ✗
IsAuthenticated ✓ ✗
IsAuthenticatedOrReadOnly ✓ ✗
IsAdminUser ✓ ✗
DjangoModelPermissions ✓ ✗
DjangoModelPermissionsOrAnonReadOnly ✓ ✗
DjangoObjectPermissions by extending ✓
DjangoModelPermissions

For more on the built-in permission classes, be sure to check out the second article in this
series, Built-in Permission Classes in Django REST Framework.

Custom Permission Classes


For custom permission classes, you can override one or both of the methods. If you override only one of
them you need to be careful, especially if the permissions you're using are complicated or you're
combining multiple permissions. Both has_permission and has_object_permission defaults to
True, so if you don't set one of them explicitly, the denial of the request is dependent on the one you've
explicitly set.

For more on custom permission classes, be sure to check out the second article in this
series, Custom Permission Classes in Django REST Framework.

Correct Usage

Let's look at a quick example:

from rest_framework import permissions

class AuthorOrReadOnly(permissions.BasePermission):

def has_permission(self, request, view):


if request.user.is_authenticated:
return True
return False

def has_object_permission(self, request, view, obj):


if obj.author == request.user:
return True
return False

6/9
This permission class allows only the author of the object access to it:

1. In has_permission, we refuse permission only to the unauthenticated users. At this point we


don't have access to the object so we don't know if the user making the request is the author of the
desired object.
2. If the user is authenticated, after the object is retrieved, has_object_permission is called
where we check if the object's author is the same as the user.

Results:

List view Detail view


Grants permission to the Grants permission to the authenticated
has_permission
authenticated user user
Grants permission to the author of the
has_object_permission Has no impact
object
Access granted for the Access granted to the owner of the
Result
authenticated users object if they are authenticated

Incorrect Usage

Let's look at a permission that won't do what we want, to better understand what's going on:

from rest_framework import permissions

class AuthenticatedOnly(permissions.BasePermission):

def has_object_permission(self, request, view, obj):


if request.user.is_authenticated:
return True
return False

This permission denies access to the unauthenticated user, but the check is done in
has_object_permission instead of has_permission.

Detail view for unauthenticated user:

7/9
Even though the auto-generated browsable API shows the delete button, the unauthenticated user can't
delete the message.

And the list view for unauthenticated user:

What's going on?

1. The list view only checks has_permission. So, since the custom class doesn't have one, it
checks has_permission from the BasePermission, which unconditionally returns True.
2. The detail view first checks the has_permission (again, always True). Then it checks
has_object_permission, which denies access to unauthenticated users.

That's why in this example unauthenticated requests don't have access to detail views, but they do have
access to list views.

List view Detail view


Uses the default function that Uses the default function that
has_permission grants permission without any grants permission without any
condition condition
has_object_permission Has no impact Grants permission to the

8/9
authenticated user
Permission is granted for the
Result Permission is always granted
authenticated users

This permission class was created only to show how the two methods work. You should use
the built-in class IsAuthenticated class rather than creating your own.

Conclusion
All permissions, either custom or built-in, in Django REST Framework leverage either has_permission
or has_object_permission or both to restrict access to API endpoints.

While has_permission has no restrictions as to when it can be used, it doesn't have access to the
desired object. Because of this, it's more of a "generic" permission check to ensure that the request and
user can access the view. On the other hand, since has_object_permission has access to the
object, the criteria can be much more specific, but it has many limitations as to when it can be used.

Keep in mind, that if you don't override the methods, they will always return True, granting unlimited
access. Only has_permission impacts the access to list views while they both impact the access to
detail view.

Knowing and understanding how both of these methods work is especially important when creating
custom permission classes.

9/9

You might also like