from rest_framework import permissions
class UserButEmailVerified(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated:
return True
def has_object_permission(self, request, view):
if request.user.email_is_verified:
return True
return False
== Persmission class
from .permissions import UserButEmailVerified
#api_view(["POST"])
#permission_classes([UserButEmailVerified])
def sendMessage(request):
print(request.user.email_is_verified,"emai")
== also gets called even if email_is_verified returns False=?
I got the feeling that UserButEmailVerified isnt called at all, print statements are not getting executed, if I try to login unauthorized at all, I do not get access....
I know i could achive this without the permission class, but I want to learn it, so what am I doing wrong?
Related
TLDR: How do you permit specific Groups access to views using Django Rest Framework?
I'm in the process of building a web service with the Django Rest Framework. Only a (proper) subset of the views are intended to be available to customers.
So far, I've:
set the default permission to rest_framework.permissions.IsAdminUser
created a permission called is_customer (using the Django admin website)
created a Group called customers and added all relevant users to this group (using the admin website)
added the is_customer permission to customers (again using the admin website)
All of my views are function-based. To provide the appropriate permission to the customers group, I've tried
from rest_framework.decorators import api_view
from django.contrib.auth.decorators import permission_required
#api_view(["POST"])
#permission_required(["is_customer"])
def my_func(request):
# calculations
return Response(...)
EDIT: also tried the below method
from rest_framework import permissions
from rest_framework.decorators import api_view
class IsCustomer(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_customer:
return True
return False
def has_object_permission(self, request, view, obj):
if request.user.is_customer:
return True
return False
#api_view(["POST"])
#permission_required([IsCustomer])
def my_func(request):
# calculations
return Response(...)
and
from rest_framework.decorators permission_classes, api_view
#api_view(["POST"])
#permission_classes(["is_customer"])
def my_func(request):
# calculations
return Response(...)
and neither seems to be working. What is the proper way to go about this? Thanks in advance for any advice!
The last block should work if you reference the class directly:
from rest_framework.decorators permission_classes, api_view
#api_view(["POST"])
#permission_classes([IsCustomer])
def my_func(request):
# calculations
return Response(...)
You can view the documentation with a similar example here:
https://www.django-rest-framework.org/api-guide/permissions/
The IsCustomer class was not set up properly. This is what the class should look like:
class IsCustomer(permissions.BasePermission):
def has_permission(self, request, view):
if 'auth.is_customer' in request.user.get_all_permissions():
return True
return False
def has_object_permission(self, request, view, obj):
if 'auth.is_customer' in request.user.get_all_permissions():
return True
return False
From there, using the #permission_classes([IsCustomer]) works as intended. Therefore the final form of the view is:
#api_view(["POST"])
#permission_classes([IsCustomer])
def testing_post(request):
try:
someData = someCalculations()
return Response(data=someData, status=status.HTTP_200_OK)
except ValueError as e:
return Response(e.args[0], status.HTTP_400_BAD_REQUEST)
I have created a decorator 'unauthenticated_user(view_func)' in my 'decorators.py' file which restricts a user from accessing a view if the current user is not authenticated.
In the 'unauthenticated_user()' function, there is a conditional 'request.user.is_authenticated' which should return true if a user attached to the token is authenticated. However, upon testing this function, I have noticed that 'request.user' in 'decorators.py' is not always equal to the correct value returned by 'self.request.user' in 'views.py'. Instead, it often returns previously logged in users, or 'AnonymousUser' which ruins functionality.
decorators.py:
def unauthenticated_user(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
return view_func(request, *args, **kwargs)
else:
return HttpResponse('No user logged in')
return wrapper_func
views.py:
#method_decorator(unauthenticated_user, name = 'dispatch')
class UserLogout(APIView):
...
This is the views.py function I have been using to test that there is in fact an authenticated user when the decorators.py function returns 'AnonymousUser':
class Current(APIView):
def get(self, request, format = None):
try:
return Response(self.request.user.is_authenticated)
except:
return Response("no user currently logged in")
How to I ensure that 'decorators.py' 'unauthenticated_user' function has access to the user in the current request like the 'Current' view does in 'views.py'?
Any help would be much appreciated. To my knowledge there is no way to call 'self' in the 'decorators.py' function.
Thanks, Grae
Since you are using the DRF, you should make use of the IsAuthenticated permission class of the view
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class MyAPIView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
return Response({"msg": "Some message"})
When using class-based views, you can use the UserPassesTestMixin in the following way (docs):
from django.contrib.auth.mixins import UserPassesTestMixin
class MyView(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.email.endswith('#example.com')
The docs also state:
Furthermore, you can set any of the parameters of AccessMixin to customize the handling of unauthorized users
However, no example of the syntax is provided as to how to do this. For example, how would I set raise_exception and permission_denied_message so authenticated users get an exception error and not a 403?
This does not work, as users are still returned a 403 error:
class MyView(UserPassesTestMixin, View):
raise_exception = True
permission_denied_message = 'You have been denied'
def test_func(self):
return self.request.user.email.endswith('#example.com')
You should override the handle_no_permission method to handle what the user sees when it does not pass the test:
class MyView(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.email.endswith('#example.com')
def handle_no_permission(self):
""" Do whatever you want here if the user doesn't pass the test """
return HttpResponse('You have been denied')
Class-based view UserPassesTestMixin inherited from the AccessMixin mixin which has denoted options. From the documentation of raise_exception.
If this attribute is set to True, a PermissionDenied exception is
raised when the conditions are not met. When False (the default),
anonymous users are redirected to the login page.
As you set it to True and a user wasn't authenticated the 403 status would be returned by the server. Option permission_denied_message only controls the message that would be passed to the PermissionDenied exception. It does not return any messages to a user (of course you can override it). A little bit simplified version from the source code:
def handle_no_permission(self):
if self.raise_exception or self.request.user.is_authenticated:
raise PermissionDenied(self.permission_denied_message)
return redirect_to_login(...)
Where PermissionDenied class as simple as that:
class PermissionDenied(Exception):
"""The user did not have permission to do that"""
pass
I don't seem to be triggering the has_object_permission function.
class MyPermission(DefaultPermission):
def has_permission(self, request, view):
return request.user.is_superuser
def has_object_permission(self, request, view, obj):
import pdb;pdb.set_trace()
return request.user == obj.submitter
Going to mydomain.com/api/mymodel/100, this should be considered accessing the object, correct? I am viewing object 100 here. Why isn't my trace() being picked up?
View
class MyModelViewSet(viewsets.ModelViewSet):
serializer_class = MyModelSerializer
permission_classes = (MyPermission,)
def get_queryset(self):
queryset = MyModel.objects.all()
return queryset
In a DRF generic views check_permissions(request) is always called in every request since it is inside the dispatch method.
check_permissions method collects all the permissions from the permission classes and checks each permission. If any permission return false the method raises an exception.
So if your has_permission method returns false has_object_permission is not called.
check_object_permissions(request, obj) calls has_object_permission method on each permission class. It is called inside get_object method.
So the rule is if has_permission for all permission classes returns true then and only then has_object_permission is checked.
class MyPermission(DefaultPermission):
def has_permission(self, request, view):
# check if the request is for a single object
if view.lookup_url_kwarg in view.kwargs:
return True
return request.user.is_superuser
def has_object_permission(self, request, view, obj):
import pdb;pdb.set_trace()
return request.user == obj.submitter
Permission won't work because check_object_permissions method is called only in get_object function. So you should call that function in your views:
def check(self, request, pk=None):
obj = self.get_object()
Or you could add permissions directly in the decorator,
Example
class MyViewSet(ModelViewSet):
queryset = MyModel.objects.all()
....
permission_classes = (MyPermission, )
#detail_route(methods=['GET', ])
def custom(self, request, pk=None):
my_obj = self.get_object() # do this and your permissions shall be checked
return Response('whatever')
From the docs(django-rest-framework)
Note: The instance-level has_object_permission method will only be called if the view-level has_permission checks have already passed. Also note that in order for the instance-level checks to run, the view code should explicitly call .check_object_permissions(request, obj). If you are using the generic views then this will be handled for you by default.
In django rest framework, whenever a permission is denied, it returns 401 to the client.
However this is very bad for items that are hidden. By sending 401 you acknowledge the user that infact, there is something there.
How can I instead return 404 in specific permissions? This one for example:
class IsVisibleOrSecretKey(permissions.BasePermission):
"""
Owner can view no matter what, everyone else must specify secret_key if private
"""
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
key = request.query_params.get('secret_key')
return (
obj.visibility != 'P'
or
request.user == obj.user
or
obj.secret_key == key
)
Going off of the DjangoObjectPermissions in the Django Rest Framework GitHub, you can raise an Http404. So instead of returning True or False, you return True if everything is good and then raise Http404 if not:
from django.http import Http404
class IsVisibleOrSecretKey(permissions.BasePermission):
"""
Owner can view no matter what, everyone else must specify secret_key if private
"""
def has_permission(self, request, view):
return True
def has_object_permission(self, request, view, obj):
key = request.query_params.get('secret_key')
if obj.visibility != 'P' or request.user == obj.user or obj.secret_key == key:
return True
else:
raise Http404
Since DjangoObjectPermissions extends BasePermission (a few steps back) this shouldn't do anything unexpected - I've just tested to make sure it returns 404 though, I haven't used it outside of this context. Just a head's up to do some testing.