How To Change Permissions In Renaissance Place
This article looks at how to build custom permission classes in Django Residual Framework (DRF).
--
Django REST Framework Permissions Serial:
- Permissions in Django REST Framework
- Congenital-in Permission Classes in Django REST Framework
- Custom Permission Classes in Django Residue Framework (this article!)
- Objectives
- Custom Permission Classes
- Custom Permission Examples
- Combining and Excluding Permission Classes
- Conclusion
Objectives
By the end of this commodity, you should be able to:
- Create custom permission classes
- Explicate when to use
has_permission
andhas_object_permission
in your custom permission classes - Return a custom error message when a permission is denied
- Combine and exclude permission classes using AND, OR, and Non operators
Custom Permission Classes
If your application has some special requirements and the built-in permission classes don't meet those requirements, it's fourth dimension to kickoff building your own custom permissions.
Creating custom permissions allows you to set up permissions based on whether the user is authenticated or non, the request method, the group that the user belongs to, object attributes, the IP address... or whatever of their combinations.
All permission classes, either custom or built-in, extend from the BasePermission
class:
form BasePermission ( metaclass = BasePermissionMetaclass ): """ A base of operations class from which all permission classes should inherit. """ def has_permission ( self , request , view ): """ Return `True` if permission is granted, `False` otherwise. """ return Truthful def has_object_permission ( self , asking , view , obj ): """ Return `True` if permission is granted, `False` otherwise. """ return True
BasePermission
has ii methods, has_permission
and has_object_permission
, that both return Truthful
. Permission classes override one or both of those methods to conditionally render True
. If you don't override the methods, they volition always return True
, granting unlimited access.
For more on
has_permission
vshas_object_permission
, be sure to check out the offset article in this series, Permissions in Django Rest Framework.
By convention, you should put custom permissions in a permissions.py file. This is only a convention then y'all don't have to do this if you need to organize your permissions differently.
Every bit with the born permissions, if whatever of the permission classes used in a view returns Simulated
from either has_permission
or has_object_permission
, a PermissionDenied
exception is raised. To alter the error message associated with the exception, you can set a bulletin aspect directly on your custom permission form.
With that, let's await at some examples.
Custom Permission Examples
User Properties
You may desire to give different levels of access to dissimilar users, based on their backdrop -- i.e., are they the creators of the object or are they a staff member?
Let'southward say you don't want staff members to exist able to edit objects. Hither's how a custom permission class for that case might look like:
# permissions.py from rest_framework import permissions course AuthorAllStaffAllButEditOrReadOnly ( permissions . BasePermission ): edit_methods = ( "PUT" , "PATCH" ) def has_permission ( cocky , request , view ): if request . user . is_authenticated : return True def has_object_permission ( cocky , request , view , obj ): if request . user . is_superuser : return True if request . method in permissions . SAFE_METHODS : return True if obj . author == asking . user : return True if request . user . is_staff and asking . method not in self . edit_methods : return True return False
Here, the AuthorAllStaffAllButEditOrReadOnly
class extends BasePermission
and overrides both has_permission
and has_object_permission
.
has_permission:
In has_permission
just one thing gets checked: If the user is authenticated. If not, the NotAuthenticated
exception is raised and access is denied.
has_object_permission:
Since you should never limit the superuser'due south access, the first check -- request.user.is_superuser
-- grants access to the superuser.
Next, we check if the request method is ane of the "prophylactic" ones -- request.method in permissions.SAFE_METHODS
. Safe methods are defined in rest_framework/permissions.py:
SAFE_METHODS = ( 'GET' , 'HEAD' , 'OPTIONS' )
These methods take no affect on the object; they can only read information technology.
At first glance, it may seem like the SAFE_METHODS
check should exist in the has_permission
method. If you're only checking the asking method then that's the place it should exist. But in this instance, other checks wouldn't be executed:
Since we want to grant access when the method is one of the safe ones or when the user is the author of the object or when the user is a staff fellow member, we demand to check that on the same level. In other words, since we can't bank check for the owner on the has_permission
level, we need to check everything on the has_object_permission
level.
The last possibility is that the user is a staff member: They are allowed all methods but the ones nosotros defined every bit edit_methods
.
Finally, turn back to the class name: AuthorAllStaffAllButEditOrReadOnly
. You should always try to name the permission class as informative equally possible.
Continue in mind that
has_object_permission
is never executed for listing views (regardless of the view yous're extending from) or when the asking method is
You lot apply the custom permission class the same fashion as the congenital-in 1:
# views.py from rest_framework import viewsets from .models import Bulletin from .permissions import AuthorAllStaffAllButEditOrReadOnly from .serializers import MessageSerializer grade MessageViewSet ( viewsets . ModelViewSet ): permission_classes = [ AuthorAllStaffAllButEditOrReadOnly ] # Custom permission grade used queryset = Message . objects . all () serializer_class = MessageSerializer def perform_create ( self , serializer ): serializer . save ( author = cocky . request . user )
The author of the object has full access to it. A staff member, meanwhile, can delete the object, just tin't edit it:
And an authenticated user tin view the object, but can't edit or delete it:
Object Properties
Although nosotros briefly touched on the object's property in the previous example, the emphasis was much more than on the user's properties (e.g., the object'southward author). In this example, we'll focus on the object's properties.
How tin one or more of the object'southward backdrop have an bear upon on the permissions?
- Every bit in the previous example, you can limit the access only to the possessor of the object. You can also limit admission to the group the owner belongs to.
- Objects may have an expiration date, so you lot can limit the access to objects older than n to but some users.
- You can have DELETE implemented as a flag (and then that it's not actually removed from the database). You tin then prevent access to objects with a delete flag.
Let's say y'all desire to restrict access to objects older than ten minutes for everyone except superusers:
# permissions.py from datetime import datetime , timedelta from django.utils import timezone from rest_framework import permissions class ExpiredObjectSuperuserOnly ( permissions . BasePermission ): def object_expired ( self , obj ): expired_on = timezone . make_aware ( datetime . now () - timedelta ( minutes = 10 )) return obj . created < expired_on def has_object_permission ( self , request , view , obj ): if self . object_expired ( obj ) and not request . user . is_superuser : return Simulated else : return True
In this permission class, the has_permission
method is non overridden -- so it volition always return True
.
Since the simply of import property is the object's creation time, the check happens in has_object_permission
(since we don't take access to an object'southward properties in has_permission
).
So, if a user wants to access the expired object, the exception PermissionDenied
is raised:
Once again, as with the previous instance, we could cheque if the user is a superuser in has_permission
, but if they're non, the object's property would never get checked.
Take notation of the mistake message. It'southward not very informative. The user has no idea why their access was denied. We can create a custom error bulletin by adding a message
attribute to our permission class:
class ExpiredObjectSuperuserOnly ( permissions . BasePermission ): message = "This object is expired." # custom mistake message def object_expired ( cocky , obj ): expired_on = timezone . make_aware ( datetime . at present () - timedelta ( minutes = 10 )) return obj . created < expired_on def has_object_permission ( self , request , view , obj ): if self . object_expired ( obj ) and not request . user . is_superuser : return False else : return True
Now the user sees exactly why the permission was denied:
Combining and Excluding Permission Classes
Typically, when using more i permission grade, you'd define them in the view like so:
permission_classes = [ IsAuthenticated , IsStaff , SomeCustomPermissionClass ]
This approach combines them then that permission is granted only if all of the classes return Truthful
.
Since DRF version 3.9.0, you can besides combine multiple classes using the AND (&
) or OR (|
) logical operators. Also, since iii.ix.2, the Non (~
) operator is supported.
These operators are non limited to custom permission classes. They tin can also be used with the built-in ones.
Instead of creating many complicated permission classes that are similar to each other, you lot could create simpler classes and combine them with the same operators.
For instance, y'all may have dissimilar permissions for different combinations of groups. Let'south say you desire the post-obit permissions:
- Permission for groups A or B
- Permission for groups B or C
- Permission for members of both B and C
- Permission for all groups but A
While four permission classes doesn't seem similar much this won't scale well. What if you lot had eight dissimilar groups -- A, B, C, D, Eastward, F, G? It would apace ballon to a point where information technology'south impossible to understand and maintain.
Y'all could simplify information technology and combine them with operators past first creating permission classes for groups A, B, and C. And then, yous can implement them like so:
-
permission_classes = [PermGroupA | PermGroupB]
-
permission_classes = [PermGroupB | PermGroupC]
-
permission_classes = [PermGroupB & PermGroupC]
-
permission_classes = [~PermGroupA]
When OR (
|
) is involved, things tin can get a fiddling more complicated. Errors tin can often fall through the cracks. For more, review the discussion on the permissions: Let permissions to exist composed pull request.
AND Operator
AND is the default behavior of permission classes, achieved past using ,
:
permission_classes = [ IsAuthenticated , IsStaff , SomeCustomPermissionClass ]
It tin can also be written with &
:
permission_classes = [ IsAuthenticated & IsStaff & SomeCustomPermissionClass ]
OR Operator
With the OR (|
), when whatsoever of the permission classes return True
, the permission is granted. You tin can use the OR operator to offer multiple possibilities in which the user gets granted permission.
Let's await at an example where either the owner of the object or a staff member tin can edit or delete the object.
We'll need ii classes:
-
IsStaff
returnsTrue
if the useris_staff
-
IsOwner
returnsTruthful
if the user is the same as theobj.author
Code:
grade IsStaff ( permissions . BasePermission ): def has_permission ( cocky , request , view ): if request . user . is_staff : return Truthful return Imitation def has_object_permission ( self , asking , view , obj ): if request . user . is_staff : render True return False form IsOwner ( 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 == asking . user : return True return False
There's quite a bit of redundancy here, only it'south necessary.
Why?
-
For covering list views
Again, the list view doesn't bank check for
has_object_permission
. However, each of the created permissions needs to exist standalone. You shouldn't create a permission course that needs to exist combined with some other permission class to cover the list view.IsOwner
limits access to authenticated users inhas_permission
-- and then that if theIsOwner
is the merely class used, admission to API is still controlled. -
Both methods by default render
True
When using OR, if you lot don't provide the
has_object_permission
method, the user will take admission to the object, even though they shouldn't.Notes:
-
if you omit
has_permission
on theIsOwner
form, anyone volition be able to see or create on the list. -
if you omit
has_object_permission
onIsStaff
and combine information technology withIsOwner
withor
, either one or the other will returnTrue
. That style, a registered user that'south neither an owner nor a staff member, would exist able to change the content.
-
At present, when we take our permission classes well designed, it's easy to combine them:
from rest_framework import viewsets from .models import Message from .permissions import IsStaff , IsOwner from .serializers import MessageSerializer form MessageViewSet ( viewsets . ModelViewSet ): permission_classes = [ IsStaff | IsOwner ] # or operator used queryset = Message . objects . all () serializer_class = MessageSerializer
Here, nosotros allow either a staff member or the owner of the object to change or delete it.
The only requirement IsOwner
has for the list views, is that the user is authenticated. That means that the authenticated user that'due south non a staff fellow member, will be able to create objects.
Non Operator
The Non operator results in the exact opposite to the defined permission grade. In other words, permission is granted to all users except the ones from the permission class.
Let'south say you have three groups of users:
- Tech
- Management
- Finances
Each of these groups should have admission to API endpoints meant only for their specific grouping.
Hither'due south a permission class that grants access to only members of the Finances group:
class IsFinancesMember ( permissions . BasePermission ): def has_permission ( self , request , view ): if request . user . groups . filter ( name = "Finances" ) . exists (): return True
Now, say you take a new view that'southward meant for all users who are not role of the Finances grouping. You can utilise the NOT operator to implement this:
from rest_framework import viewsets from .models import Message from .permissions import IsFinancesMember from .serializers import MessageSerializer course MessageViewSet ( viewsets . ModelViewSet ): permission_classes = [ ~ IsFinancesMember ] # using non operator queryset = Message . objects . all () serializer_class = MessageSerializer
So, simply members of the Finances group won't have access.
Be careful! If you lot only utilize the NOT operator, everybody else volition be allowed access, including unauthenticated users! If that's not what you meant to do, you tin can fix that by adding another grade like so:
permission_classes = [ ~ IsFinancesMember & IsAuthenticated ]
Parentheses
Inside permission_classes
you can also use parentheses (()
) to control which expression gets resolved offset.
Quick example:
class MessageViewSet ( viewsets . ModelViewSet ): permission_classes = [( IsFinancesMember | IsTechMember ) & IsOwner ] # using parentheses queryset = Message . objects . all () serializer_class = MessageSerializer
In this example, (IsFinancesMember | IsTechMember)
will be resolved offset. And then, the event of that volition be used with & IsOwner
-- e.g., ResultsFromFinancesOrTech & IsOwner
. This ways that a user that'southward either a member of the Tech OR Finances groups AND is the owner of the object will be granted access.
Conclusion
Despite a wide range of built-in permission classes, at that place will be situations where they won't fit your needs. That's when custom permission classes come up in handy.
With custom permission classes you must override one or both of the following methods:
-
has_permission
-
has_object_permission
If permission isn't granted in the has_permission
method, it doesn't affair what's written in has_object_permission
-- the permission is denied. If you don't override one (or both) of them, you lot need to take into account that by default, the method will e'er return True
.
You can combine and exclude permission classes with the AND, OR, and Not operators. Y'all tin even decide the order in which permissions resolve in with parenthesis.
--
Django REST Framework Permissions Serial:
- Permissions in Django REST Framework
- Congenital-in Permission Classes in Django Remainder Framework
- Custom Permission Classes in Django REST Framework (this article!)
Source: https://testdriven.io/blog/custom-permission-classes-drf/
Posted by: holderbray1962.blogspot.com
0 Response to "How To Change Permissions In Renaissance Place"
Post a Comment