80👍
Permissions are applied to the entire View class, but you can take into account aspects of the request (like the method such as GET or POST) in your authorization decision.
See the built-in IsAuthenticatedOrReadOnly
as an example:
SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
class IsAuthenticatedOrReadOnly(BasePermission):
"""
The request is authenticated as a user, or is a read-only request.
"""
def has_permission(self, request, view):
if (request.method in SAFE_METHODS or
request.user and
request.user.is_authenticated()):
return True
return False
55👍
I’ve come across the same problem when using CBV’s, as i have fairly complex permissions logic depending on the request method.
The solution i came up with was to use the third party ‘rest_condition’ app listed at the bottom of this page
http://www.django-rest-framework.org/api-guide/permissions
https://github.com/caxap/rest_condition
I just split the permissions flow logic so that each branch will run, depending on the request method.
from rest_condition import And, Or, Not
class MyClassBasedView(APIView):
permission_classes = [Or(And(IsReadOnlyRequest, IsAllowedRetrieveThis, IsAllowedRetrieveThat),
And(IsPostRequest, IsAllowedToCreateThis, ...),
And(IsPutPatchRequest, ...),
And(IsDeleteRequest, ...)]
So the ‘Or’ determines which branch of the permissions should run depending on the request method and the ‘And’ wraps the permissions relating to the accepted request method, so all must pass for permission to be granted. You can also mix ‘Or’, ‘And’ and ‘Not’ within each flow to create even more complex permissions.
The permission classes to run each branch simply look like this,
class IsReadyOnlyRequest(permissions.BasePermission):
def has_permission(self, request, view):
return request.method in permissions.SAFE_METHODS
class IsPostRequest(permissions.BasePermission):
def has_permission(self, request, view):
return request.method == "POST"
... #You get the idea
- [Django]-How can I filter a date of a DateTimeField in Django?
- [Django]-How to view corresponding SQL query of the Django ORM's queryset?
- [Django]-Django : How can I find a list of models that the ORM knows?
21👍
Update 30 March 2020: My original solution only patched object permissions, not request permissions. I’ve included an update below to make this work with request permissions as well.
I know this is an old question but I recently ran into the same problem and wanted to share my solution (since the accepted answer wasn’t quite what I needed). @GDorn’s answer put me on the right track, but it only works with ViewSet
s because of the self.action
I’ve solved it creating my own decorator:
def method_permission_classes(classes):
def decorator(func):
def decorated_func(self, *args, **kwargs):
self.permission_classes = classes
# this call is needed for request permissions
self.check_permissions(self.request)
return func(self, *args, **kwargs)
return decorated_func
return decorator
Instead of setting the permission_classes
property on the function, like the built-in decorator does, my decorator wraps the call and sets the permission classes on the view instance that is being called. This way, the normal get_permissions()
doesn’t need any changes, since that simply relies on self.permission_classes
.
To work with request permissions, we do need to call check_permission()
from the decorator, because the it’s orginally called in initial()
so before the permission_classes
property is patched.
Note The permissions set through the decorator are the only ones called for object permissions, but for request permissions they are in addition to the class wide permissions, because those are always checked before the request method is even called. If you want to specify all permissions per method only, set permission_classes = []
on the class.
Example use case:
from rest_framework import views, permissions
class MyView(views.APIView):
permission_classes = (permissions.IsAuthenticatedOrReadOnly,) # used for default APIView endpoints
queryset = MyModel.objects.all()
serializer_class = MySerializer
@method_permission_classes((permissions.IsOwnerOfObject,)) # in addition to IsAuthenticatedOrReadOnly
def delete(self, request, id):
instance = self.get_object() # ...
Hope this helps someone running into the same problem!
- [Django]-Error: "dictionary update sequence element #0 has length 1; 2 is required" on Django 1.4
- [Django]-Django class-based view: How do I pass additional parameters to the as_view method?
- [Django]-Django : How can I find a list of models that the ORM knows?
14👍
I ran into this problem and really wanted to use the @permission_classes
decorator to mark some custom view methods with specific permissions. I ended up coming up with a mixin:
class PermissionsPerMethodMixin(object):
def get_permissions(self):
"""
Allows overriding default permissions with @permission_classes
"""
view = getattr(self, self.action)
if hasattr(view, 'permission_classes'):
return [permission_class() for permission_class in view.permission_classes]
return super().get_permissions()
An example use case:
from rest_framework.decorators import action, permission_classes # other imports elided
class MyViewset(PermissionsPerMethodMixin, viewsets.ModelViewSet):
permission_classes = (IsAuthenticatedOrReadOnly,) # used for default ViewSet endpoints
queryset = MyModel.objects.all()
serializer_class = MySerializer
@action(detail=False, methods=['get'])
@permission_classes((IsAuthenticated,)) # overrides IsAuthenticatedOrReadOnly
def search(self, request):
return do_search(request) # ...
- [Django]-How do I access the request object or any other variable in a form's clean() method?
- [Django]-Factory-boy create a list of SubFactory for a Factory
- [Django]-Many-To-Many Fields View on Django Admin
7👍
I ran into a similar issue.
I wanted to allow unauthenticated POSTs but disallow unauthenticated GETs.
An unauthenticated member of the public can submit an item but only an authenticated admin user can retrieve the list of items submitted.
So I built a custom permission class – UnauthenticatedPost
– for the POST and then set the list of permission clases to be IsAuthentictaed
or UnauthenticatedPost
.
Note I only allow gets and posts by setting the allowable methods with http_method_names = ['get', 'post']
.
from django.http import HttpResponse
from rest_framework import viewsets
from rest_framework.permissions import BasePermission, IsAuthenticated
from MyAPI.serializers import MyAPISerializer
from MyAPI.models import MyAPI
class UnauthenticatedPost(BasePermission):
def has_permission(self, request, view):
return request.method in ['POST']
class MyAPIViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated|UnauthenticatedPost]
queryset = MyAPI.objects.all().order_by('-TimeSubmitted')
serializer_class = MyAPISerializer
http_method_names = ['get', 'post']
- [Django]-How to pass information using an HTTP redirect (in Django)
- [Django]-How can one use enums as a choice field in a Django model?
- [Django]-How do I create multiple model instances with Django Rest Framework?
6👍
If you use ViewSet
s or ModelViewSet
s, I think overwriting get_permissions
will do the trick.
Take a look at how djoser handles this.
Example:
class UserViewSet(viewsets.ModelViewSet):
permission_classes = settings.PERMISSIONS.user # default
def get_permissions(self):
if self.action == "activation": # per action
self.permission_classes = settings.PERMISSIONS.activation
return super().get_permissions()
@action(["post"], detail=False) # action
def activation(self, request, *args, **kwargs):
pass
- [Django]-Django migration strategy for renaming a model and relationship fields
- [Django]-OneToOneField() vs ForeignKey() in Django
- [Django]-Django – why is the request.POST object immutable?
6👍
This question is about APIView
instances, but for anyone landing here looking for a per-method permissions override using the @action
decorator within ViewSets
:
class SandwichViewSet(ModelViewSet):
permission_classes = [IsAuthenticated]
@action(..., permission_classes=[CanSeeIngredients])
def retrieve__ingredients(self, request):
...
- [Django]-Can't install via pip because of egg_info error
- [Django]-What is the path that Django uses for locating and loading templates?
- [Django]-How to merge consecutive database migrations in django 1.9+?
2👍
We’re having the same kind of challenges when it comes to having different permissions for GET, PUT and POST, and have solved this using a customized permission-class:
from rest_framework import permissions
class HasRequiredPermissionForMethod(permissions.BasePermission):
get_permission_required = None
put_permission_required = None
post_permission_required = None
def has_permission(self, request, view):
permission_required_name = f'{request.method.lower()}_permission_required'
if not request.user.is_authenticated:
return False
if not hasattr(view, permission_required_name):
view_name = view.__class__.__name__
self.message = f'IMPLEMENTATION ERROR: Please add the {permission_required_name} variable in the API view class: {view_name}.'
return False
permission_required = getattr(view, permission_required_name)
if not request.user.has_perm(permission_required):
self.message = f'Access denied. You need the {permission_required} permission to access this service with {request.method}.'
return False
return True
We use this in our API’s like this:
class MyAPIView(APIView):
permission_classes = [HasRequiredPermissionForMethod]
get_permission_required = 'permission_to_read_this'
put_permission_required = 'permission_to_update_this'
post_permission_required = 'permission_to_create_this'
def get(self, request):
# impl get
def put(self, request):
# impl put
def post(self, request):
# impl post
- [Django]-Distributed task queues (Ex. Celery) vs crontab scripts
- [Django]-Specifying a mySQL ENUM in a Django model
- [Django]-How to do SELECT COUNT(*) GROUP BY and ORDER BY in Django?
1👍
I write my solution with hope that it helps someone.
As much as I understand, There are two ways to handle permissions in APIView class
- Statically assign a proper
Permission
classes toAPIView.permission_classes
(like extendBasePermission
) - Dynamically decide
Permission
instances inAPIView
(overrideAPIView.get_permission()
)
APIView
checks permissions returned from .get_permission()
.
And .get_permission()
instantiates Permission
s from .permission_classes
.
In my situation, Only I needed a predefined Permission
but depending on method.
So I chose latter approach.
class TokenView(APIView):
authentication_classes = [TokenAuthentication]
// return instances of Permission classes
def get_permissions(self, *args, **kwargs):
if self.request.method in ['DELETE']:
return [IsAuthenticated()]
else:
return []
def post(self, request, *args, **kwargs):
username = request.data["username"]
password = request.data["password"]
user = authenticate(username=username, password=password)
token, created = Token.objects.get_or_create(user=user)
return Response({"token": token.key}, status=status.HTTP_200_OK)
def delete(self, request, *args, **kwargs):
user = request.user
user.auth_token.delete()
return Response({"success", status.HTTP_200_OK})
- [Django]-What is reverse()?
- [Django]-Django F() division – How to avoid rounding off
- [Django]-Filter Queryset on empty ImageField
1👍
So, I’ve made for that a mixin. You just have to inherit this mixin.
class FooViewSet(ModelViewSet, PermissionByAction):
queryset = Foo.objects.all()
serializer_class = FooSerializer
permission_classes_by_action = {
'create': [IsAuthenticated],
'list': [AllowAny],
'retrieve': [AllowAny],
'destroy': [IsOwner | IsAdmin,],
}
This is a mixin:
from rest_framework.settings import api_settings
class PermissionByAction(object):
permission_classes_by_action : dict = {
'create': api_settings.DEFAULT_PERMISSION_CLASSES,
'list': api_settings.DEFAULT_PERMISSION_CLASSES,
'retrieve': api_settings.DEFAULT_PERMISSION_CLASSES,
'destroy': api_settings.DEFAULT_PERMISSION_CLASSES,
}
def get_permissions(self):
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
if self.action == "list":
permission_classes = self.permission_classes_by_action['list']
elif self.action == "create":
permission_classes = self.permission_classes_by_action['create']
elif self.action == "retrieve":
permission_classes = self.permission_classes_by_action['retrieve']
elif self.action == "destroy":
permission_classes = self.permission_classes_by_action['destroy']
return [permission() for permission in permission_classes]
- [Django]-How to access a dictionary element in a Django template?
- [Django]-Iterate over model instance field names and values in template
- [Django]-Django admin make a field read-only when modifying obj but required when adding new obj
1👍
You need to check the permissions explicitly, or annotate with your custom method.
Once the request entered into method, there’s no way that the permission will be checked from the global value of permission classes. You need a wrapper condition like this.
class TestView(generics.GenericAPIView):
queryset = TestModel.objects.all()
serializer_class = TestSerializer
def perms_wrapper(self, request, permissions, view):
for _ in permissions:
obj = _()
if not obj.has_permission(request, view):
return False
return True
def get(self, request, *args, **kwargs):
self.permission_classes = [IsAdminUser, IsAuthenticated, ]
if self.perms_wrapper(request, self.permission_classes, super):
test_qs = TestModel.objects.all()
serializer = TestSerializer(test_qs, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response({'detail' : 'permissions denied'}, status=status.HTTP_403_FORBIDDEN)
- [Django]-Numeric for loop in Django templates
- [Django]-Django delete FileField
- [Django]-Only accept a certain file type in FileField, server-side