Composite permissions DRF - django

I have a view class inherited from RetrieveUpdateDestroyAPIView. I need to have different permission classes for different method so I am over writing get_permissions method but I'm getting error
TypeError: unsupported operand type(s) for |: 'IsSuperAdmin' and 'IsOwner.
views.py
class UserView(RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
http_method_names = ['patch', 'get', 'delete']
def get_permissions(self):
if self.request.method == 'GET':
return [IsAuthenticated(), IsSuperAdmin()|IsOwner()|IsAdmin(), ]
elif self.request.method == 'DELETE':
return [IsAuthenticated(), IsSuperAdmin()|IsAdmin()]
else:
return [IsAuthenticated(), IsSuperAdmin()|IsAdmin()|IsOwner(), ]
permissions.py
class IsSuperAdmin(BasePermission):
message = "You must be super admin to perform requested operation"
def has_permission(self, request, view):
if request.user.role == "super_admin":
return True
return False
class IsAdmin(BasePermission):
message = "You must be admin to perform requested operation"
def has_permission(self, request, view):
if request.user.role == "admin":
return True
return False
class IsOwner(BasePermission):
message = "You must be owner of resource to perform requested operaton"
def has_object_permission(self, request, view, obj):
if obj.id == request.user.id:
return True
return False

You don't need to instanciate permissions classes, so the code
return [IsAuthenticated(), IsSuperAdmin()|IsOwner()|IsAdmin(), ]
Should be like
return [IsAuthenticated, IsSuperAdmin |IsOwner | IsAdmin ]
Cf the rest framework doc: https://www.django-rest-framework.org/api-guide/permissions/#setting-the-permission-policy

Related

Django Rest Framework Custom Permission does not recognize user

I am trying to enforce a custom permission in DRF. Specifically, I am trying to see whether a user is authorized to access an object. I adapted this solution from another post here but it does not quite work for me. The permission class always assumes that request.user is an Anonymous user. What am I missing?
permissions.py
class CanSeeWishlist(permissions.BasePermission):
def has_permission(self, request, view):
try:
wishlist = Wishlist.objects.get(
pk=view.kwargs['pk'])
except:
return False
if wishlist.private:
print(request.user) # Prints Anonymous User
if request.user.id == wishlist.owner.id or request.user.id in wishlist.members.all():
return True
return False
return True
api.py
class WishlistViewset(viewsets.ModelViewSet):
serializer_class = WishlistSerializer
queryset = Wishlist.objects.all()
authentication_classes = (TokenAuthentication,)
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
def get_permissions(self):
if self.request.method == 'GET':
self.permission_classes = (CanSeeWishlist,)
return super(WishlistViewset, self).get_permissions()
I suspect that doing super(WishlistViewset, self).get_permissions() didn't pass request to the class so it resulted in Anonymous User or as you dropped IsAuthenticatedOrReadOnly something went wrong.
Anyway, I think it's better to override get_permissions() with the same way it was implemented, it will fix the problem I hope:
What get_permissions() actually do:
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
So It should be:
def get_permissions(self):
permissions_list = [permissions.IsAuthenticatedOrReadOnly] #the base one that should be there for all request actions
if self.request.method == 'GET': # this will add the permission for all GET requests, If you want it to only apply on retrive you can do self.action == 'retrive':
permissions_list.append(CanSeeWishlist)
return [permission() for permission in permissions_list]

permission doesn't work in view method [Django restframework]

I'm using django restframework to do some permission requiring work. What I want to do is make a whole permission require in my view and a different permission require in my specified method. And below is my trial with some main codes.
1 basic view
class VSAccount(viewsets.ModelViewSet):
queryset = MAccount.objects.all().filter(active=True)
serializer_class = DEFAULT_ACCOUNT_SERIALIZER_CLASS
filter_backends = (SearchFilter, DjangoFilterBackend)
permission_classes = [IsAuthenticated, BaseDataPermission, ]
filter_class = FAccount
search_fields = []
module_perm = 'account.get_account'
# 1) add module_perm account.get_account required for whole view.
#action(methods=['get'], url_path='list-with-daily-spends', detail=False)
def list_daily_spend(self, request, *args, **kwargs):
self.module_perm = 'account.get_account-list-with-daily-spend'
# 2) add module_perm for this method only but doesn't work here
self.permission_classes = [BaseDataPermission, ]
self.serializer_class = SAccountListItemDaily
ret_data = super().list(request, *args, **kwargs)
self.serializer_class = DEFAULT_ACCOUNT_SERIALIZER_CLASS
return ret_data
2 customer permission
class BaseDataPermission(BasePermission):
authenticated_users_only = True
def has_perms(self, user, perm):
user_perms = user.get_all_permissions()
print(perm) # it's always what I write in viewset?
if perm in user_perms:
return True
return False
def has_permission(self, request, view):
if request.user.is_superuser:
return True
assert hasattr(view, "module_perm"), "need module_perm"
assert isinstance(view.module_perm, str), "module_perm should be a string"
if getattr(view, '_ignore_model_permissions', False):
return True
if hasattr(view, 'get_queryset'):
queryset = view.get_queryset()
else:
queryset = getattr(view, 'queryset', None)
assert queryset is not None, (
'Cannot apply DjangoModelPermissions on a view that '
'does not set `.queryset` or have a `.get_queryset()` method.'
)
return (
request.user and
(request.user.is_authenticated or not self.authenticated_users_only) and
self.has_perms(request.user, view.module_perm)
)
My question is why I rewrite the moudle_perm in method list_daily_spend, the permission required is still account.get_account which I write in VSAccount and how can I get the expected result?
Thanks
Changing the value of self.permission_classes will not get you there, you need to override the get_permissions(...) method of ModelViewSet as,
class VSAccount(viewsets.ModelViewSet):
# rest of your code
def get_permissions(self):
if self.action == 'list_daily_spend':
self.module_perm = 'account.get_account-list-with-daily-spends'
permission_classes = [BaseDataPermission, ]
return [permission() for permission in permission_classes]
return super().get_permissions()
Alternatively, you can set the permission classes in your action decorator as,
class VSAccount(viewsets.ModelViewSet):
#action(methods=['get'],
url_path='list-with-daily-spends',
detail=False,
permission_classes=[BaseDataPermission, ], module_perm = 'account.get_account-list-with-daily-spends')
def list_daily_spend(self, request, *args, **kwargs):
# your code

Setting different permission classes for POST and GET while using djangorestframework's default router

I was just watching some online tutorial on how to use Django Rest Framework to create a basic REST API using their default router. Link to Docs
but then because he used a model viewset he had to add permission_classes to them which means all different types of requests whether its post or get or others it'll all take the same permission.
I was wondering if there's a way to give them different permission_classes depending on the type of request.
Yes, you can write your own permissions.
Just create some python file. I named it permissions.py:
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import BasePermission
from company.models import Company
class IsGetOrIsAuthenticated(BasePermission):
def has_permission(self, request, view):
if request.method == 'GET':
return True
return request.user and request.user.is_authenticated
class IsGetOrIsCompanyOwner(BasePermission):
def has_permission(self, request, view):
if request.method == 'GET' or 'OPTIONS':
return True
elif request.method == 'DELETE':
company = get_object_or_404(Company, id=view.kwargs['pk'])
return request.user.user_type == 'moder' or company.owner == request.user
else:
company = get_object_or_404(Company, id=view.kwargs['pk'])
return company.owner == request.user or request.user.user_type == 'moder'
class IsModer(BasePermission):
def has_permission(self, request, view):
return request.user.user_type == 'moder'
After that you can use them in views.py
from company.permissions import IsGetOrIsAuthenticated, IsGetOrIsCompanyOwner, IsModer
class ActiveCompanyShortView(ModelViewSet):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsGetOrIsAuthenticated,)
you can read more there
To achieve what you need, one possible solution is to override the get_permissions() of ViewSet.
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
if self.action == 'list':
return [objects of permissions_u_need_in_list_view]
elif self.action == 'create':
return [objects of permissions_u_need_in_create_view]
This is what DRF provides the definition of get_permissions().
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]

How to add custom permission in viewset

How to add custom permission in viewset in django rest framework other than the default permission while creating a module?
I have a permission "fix_an_appointment". In the below viewset, how to include this permission? Those who have this permission has only able to create.
My views.py file:
class settingsViewSet(viewsets.ModelViewSet):
serializer_class = SettingsSerializer
queryset = Setting.objects.all()
Can anyone help?
I can't use a decorator like: #permission_classes(IsAuthenticated, ) in extra actions within ViewSet
To use different permissions in actions, instead, put it into the #action() as a parameter.
#action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
...
drf doc
simply create a custom permission class
class FixAnAppointmentPermssion(permissions.BasePermission):
def has_permission(self, request, view):
return True or False
then the in your view set class use your custom permission
class settingsViewSet(viewsets.ModelViewSet):
serializer_class = SettingsSerializer
queryset = Setting.objects.all()
permission_classes = (FixAnAppointmentPermssion,)
by docs custom-permissions, list of view actions actions
my_permissions.py
from rest_framework import permissions
class FixPermission(permissions.BasePermission):
"""
fix_an_appointment
"""
def has_permission(self, request, view):
if request.user.is_authenticated :
if view.action == 'retrieve':
return request.user.has_perms('fix_list_perm')
if view.action == 'retrieve':
return request.user.has_perms('fix_an_appointment')
return False
in views.py
from my_permissions import FixPermission
class settingsViewSet(viewsets.ModelViewSet):
serializer_class = SettingsSerializer
queryset = Setting.objects.all()
permission_classes = (FixPermission,)
We can set permission for each functions like create, retrive, update, delete(add,edit,delete and update)
from my_permissions import FixPermission
class FixAnAppointmentPermssion(permissions.BasePermission):
def has_permission(self, request, view):
return True or False
class YourViewSet(viewsets.ModelViewSet):
serializer_class = SettingsSerializer
queryset = Your.objects.all()
#permission_classes(FixAnAppointmentPermssion,)
def create(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
#permission_classes(FixAnAppointmentPermssion,)
def retrive(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)

Custom permissions on viewset

Im trying to find a way to create custom permissions on a viewset.
Im want to implement somthing like this :
class ActivityViewSet(viewsets.ModelViewSet):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
if request.method == 'PUT':
permission_classes = (permissions.IsOwner)
elif request.method == 'LIST':
permission_classes = (permissions.IsAdmin)
else :
permission_classes = (permissions.AllowAny)
IE : sorting permissions by method. (the above code doesnt work cause "request" isnt recognized)
Couldn't find any use in the "Custom permissions" section of the documentation, but maybe Im just blind. (overriding BasePermission? how? who? where?)
Thanks :)
Viewsets use action to determine HTTP a verb and permissions invoked from get_permissions method, so you can override it:
class ActivityViewSet(viewsets.ModelViewSet):
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
def get_permissions(self):
if self.action == 'update':
return IsOwner()
elif self.action == 'list':
return IsAdminUser()
else :
return AllowAny()
Spent some amount on the same problem. Here is the solution for DRF 3+
class ActivityViewSet(viewsets.ModelViewSet):
"""ViewSet for viewing and editing client's accounts."""
queryset = Activity.objects.all()
serializer_class = ActivitySerializer
def get_permissions(self):
"""Set custom permissions for each action."""
if self.action in ['update', 'partial_update', 'destroy', 'list']:
self.permission_classes = [IsAuthenticated, ]
elif self.action in ['create']:
self.permission_classes = [AllowAny, ]
return super().get_permissions()
Make sure that permission_classes is a list.