banner



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:

  1. Permissions in Django REST Framework
  2. Congenital-in Permission Classes in Django REST Framework
  3. 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:

  1. Create custom permission classes
  2. Explicate when to use has_permission and has_object_permission in your custom permission classes
  3. Return a custom error message when a permission is denied
  4. 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 vs has_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:

DRF Permissions Execution

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 Mail (since the object doesn't exist however).

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:

DRF Permissions Execution

And an authenticated user tin view the object, but can't edit or delete it:

DRF Permissions Execution

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?

  1. 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.
  2. Objects may have an expiration date, so you lot can limit the access to objects older than n to but some users.
  3. 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:

DRF Permissions Execution

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:

DRF Permissions Execution

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:

  1. Permission for groups A or B
  2. Permission for groups B or C
  3. Permission for members of both B and C
  4. 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:

  1. permission_classes = [PermGroupA | PermGroupB]
  2. permission_classes = [PermGroupB | PermGroupC]
  3. permission_classes = [PermGroupB & PermGroupC]
  4. 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:

  1. IsStaff returns True if the user is_staff
  2. IsOwner returns Truthful if the user is the same as the obj.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?

  1. 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 in has_permission -- and then that if the IsOwner is the merely class used, admission to API is still controlled.

  2. 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 the IsOwner form, anyone volition be able to see or create on the list.

    • if you omit has_object_permission on IsStaff and combine information technology with IsOwner with or, either one or the other will return True. 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:

  1. Tech
  2. Management
  3. 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:

  1. Permissions in Django REST Framework
  2. Congenital-in Permission Classes in Django Remainder Framework
  3. 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

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel