I'm trying to check permissions for some API requests.
I've already set auth users and auth_user_user_permissions and auth_permissions tables like view_company add_company bla bla, but the problem is not that. The problem is when I'm trying yo use decorator which
#permission_required('API.view_company', raise_exception=True)
it said to me
AttributeError: 'CompanyDetailView' object has no attribute 'user'
Most probably it's looking for the user because its gonna check user_permission is it available to view companies or not but my view which I declared in urls.py (path('companies//', CompanyDetailView.as_view()),) has not have user object that's why error message returned attribute error, how can I solve this, thanks a lot
I tried to set example user in view class, in the beginning, it worked because view was looking for user object, i can not use that way because every request has different user
import rest_framework
from rest_framework import status
from django.contrib.auth.models import User
from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth.decorators import permission_required
class CompanyDetailView(APIView):
#permission_required('api.view_company', raise_exception=True)
def get(self, request, id):
try:
request_data = {}
request_data['request_method'] = request.method
request_data['id'] = id
companies = Company.objects.get(id=id)
status = rest_framework.status.HTTP_200_OK
return Response(companies, status)
bla bla bla
url line was =
path('companies/<int:id>/', CompanyDetailView.as_view()),
my error message was : AttributeError: 'CompanyDetailView' object has no attribute 'user'
when i debug and i see request.user.has_perm('view_company')returned false but still api give responses, it suppose to say you are not allow to view companies
The mechanism of Django Views and Django Rest Framework Views are a bit different, that's why you've got that error message. permission_required will try to access user field of your view to check user permission using has_perm method. But APIView didn't have user field inside of it.
To get rid of this, you might want to use permissions which provided by Django Rest Framework to restrict the access.
But if you still want to use built-in permission of Django to restrict the access to your view, you could create a Permission class which will use has_perm to check user permission. Like so:
from rest_framework import permissions
from rest_framework import exceptions
class ViewCompanyPermission(permissions.BasePermission):
def has_permission(self, request, view):
if not request.user.has_perm('api.view_company'):
raise exceptions.PermissionDenied("Don't have permission")
return True
and use it on your view via permission_classes field:
class CompanyDetailView(APIView):
permission_classes = (ViewCompanyPermission, )
def get(self, request, id):
try:
request_data = {}
request_data['request_method'] = request.method
request_data['id'] = id
companies = Company.objects.get(id=id)
status = rest_framework.status.HTTP_200_OK
return Response(companies, status)
In case you want to replicas the permission_required behavior, you could do something like this:
from rest_framework import permissions
from rest_framework import exceptions
def permission_required(permission_name, raise_exception=False):
class PermissionRequired(permissions.BasePermission):
def has_permission(self, request, view):
if not request.user.has_perm(permission_name):
if raise_exception:
raise exceptions.PermissionDenied("Don't have permission")
return False
return True
return PermissionRequired
Then you can use it like:
class CompanyDetailView(APIView):
permission_classes = (permission_required("api.view_company", raise_exception=True), )
# ...
You can't easily use django permissions with django rest framework.
there is a tutorial about django-rest-framework permissions at:
https://www.django-rest-framework.org/api-guide/permissions/
Based on the description permission_required this decorator should be used for a function view, where first argument is request and you try to apply it for the class method where the first argument is self in your case instanse of the CompanyDetailView so you get the error. And you should use another way to check the permissions.
You can read some examples in here: decorators-on-django-class-based-views
Related
I know that there are answers regarding Django Rest Framework, but I couldn't find a solution to my problem.
I have an application which has authentication and some functionality.
I added a new app to it, which uses Django Rest Framework. I want to use the library only in this app. Also I want to make POST request, and I always receive this response:
{
"detail": "CSRF Failed: CSRF token missing or incorrect."
}
I have the following code:
# urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns(
'api.views',
url(r'^object/$', views.Object.as_view()),
)
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
class Object(APIView):
#csrf_exempt
def post(self, request, format=None):
return Response({'received data': request.data})
I want add the API without affecting the current application.
So my questions is how can I disable CSRF only for this app ?
Note: Disabling CSRF is unsafe from security point of view. Please use your judgement to use the below method.
Why this error is happening?
This is happening because of the default SessionAuthentication scheme used by DRF. DRF's SessionAuthentication uses Django's session framework for authentication which requires CSRF to be checked.
When you don't define any authentication_classes in your view/viewset, DRF uses this authentication classes as the default.
'DEFAULT_AUTHENTICATION_CLASSES'= (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
),
Since DRF needs to support both session and non-session based authentication to the same views, it enforces CSRF check for only authenticated users. This means that only authenticated requests require CSRF tokens and anonymous requests may be sent without CSRF tokens.
If you're using an AJAX style API with SessionAuthentication, you'll need to include a valid CSRF token for any "unsafe" HTTP method calls, such as PUT, PATCH, POST or DELETE requests.
What to do then?
Now to disable csrf check, you can create a custom authentication class CsrfExemptSessionAuthentication which extends from the default SessionAuthentication class. In this authentication class, we will override the enforce_csrf() check which was happening inside the actual SessionAuthentication.
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
class CsrfExemptSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
In your view, then you can define the authentication_classes to be:
authentication_classes = (CsrfExemptSessionAuthentication, BasicAuthentication)
This should handle the csrf error.
Easier solution:
In views.py, use django-braces' CsrfExemptMixin and authentication_classes:
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
from braces.views import CsrfExemptMixin
class Object(CsrfExemptMixin, APIView):
authentication_classes = []
def post(self, request, format=None):
return Response({'received data': request.data})
Modify urls.py
If you manage your routes in urls.py, you can wrap your desired routes with csrf_exempt() to exclude them from the CSRF verification middleware.
import views
from django.conf.urls import patterns, url
from django.views.decorators.csrf import csrf_exempt
urlpatterns = patterns('',
url(r'^object/$', csrf_exempt(views.ObjectView.as_view())),
...
)
Alternatively, as a Decorator
Some may find the use of the #csrf_exempt decorator more suitable for their needs
for instance,
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
#csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
should get the Job Done!
For all who did not find a helpful answer. Yes DRF automatically removes CSRF protection if you do not use SessionAuthentication AUTHENTICATION CLASS, for example, many developers use only JWT:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
But issue CSRF not set may be occurred from some another reason, for exmple you not correctly added path to you view:
url(r'^api/signup/', CreateUserView), # <= error! DRF cant remove CSRF because it is not as_view that does it!
instead of
url(r'^api/signup/', CreateUserView.as_view()),
I tried a few of the answers above and felt creating a separate class was a little overboard.
For reference, I ran into this problem when trying to update a function based view method to a class based view method for user registration.
When using class-based-views (CBVs) and Django Rest Framework (DRF), Inherit from the ApiView class and set permission_classes and authentication_classes to an empty tuple. Find an example below.
class UserRegistrationView(APIView):
permission_classes = ()
authentication_classes = ()
def post(self, request, *args, **kwargs):
# rest of your code here
If you do not want to use session based authentication, you can remove Session Authentication from REST_AUTHENTICATION_CLASSES and that would automatically remove all csrf based issues. But in that case Browseable apis might not work.
Besides this error should not come even with session authentication. You should use custom authentication like TokenAuthentication for your apis and make sure to send Accept:application/json and Content-Type:application/json(provided you are using json) in your requests along with authentication token.
You need to add this to prevent default session authentication: (settings.py)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
Then: (views.py)
from rest_framework.permissions import AllowAny
class Abc(APIView):
permission_classes = (AllowAny,)
def ...():
You need to be absolutely sure, that you want to switch off CSRF protection.
Create file authentication.py and place it wherever you want in your project. For example, in folder session_utils.
Place this code in the file:
from rest_framework.authentication import SessionAuthentication
class SessionCsrfExemptAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
pass
When you want to make POST, PUT, PATCH or DELETE requests to your view be sure that you've changed SessionAuthentication to SessionCsrfExemptAuthentication from the new file. View example:
#api_view(["POST"])
#authentication_classes([SessionCsrfExemptAuthentication])
#permission_classes([IsAuthenticated])
def some_view(request) -> "Response":
# some logic here
return Response({})
This trick allow you to override method (pass) enforce_csrf and the new session authentication class will skip CSRF check.
✌️
I am struck with the same problem. I followed this reference and it worked.
Solution is to create a middleware
Add disable.py file in one of your apps (in my case it is 'myapp')
class DisableCSRF(object):
def process_request(self, request):
setattr(request, '_dont_enforce_csrf_checks', True)
And add the middileware to the MIDDLEWARE_CLASSES
MIDDLEWARE_CLASSES = (
myapp.disable.DisableCSRF,
)
My Solution is shown blow. Just decorate my class.
from django.views.decorators.csrf import csrf_exempt
#method_decorator(csrf_exempt, name='dispatch')
#method_decorator(basic_auth_required(
target_test=lambda request: not request.user.is_authenticated
), name='dispatch')
class GenPedigreeView(View):
pass
When using REST API POSTs, absence of X-CSRFToken request header may cause that error.
Django docs provide a sample code on getting and setting the CSRF token value from JS.
As pointed in answers above, CSRF check happens when the SessionAuthentication is used. Another approach is to use TokenAuthentication, but keep in mind that it should be placed first in the list of DEFAULT_AUTHENTICATION_CLASSES of REST_FRAMEWORK setting.
If you are using an exclusive virtual environment for your application, you can use the following approach without effective any other applications.
What you observed happens because rest_framework/authentication.py has this code in the authenticate method of SessionAuthentication class:
self.enforce_csrf(request)
You can modify the Request class to have a property called csrf_exempt and initialize it inside your respective View class to True if you do not want CSRF checks. For example:
Next, modify the above code as follows:
if not request.csrf_exempt:
self.enforce_csrf(request)
There are some related changes you'd have to do it in the Request class
This could also be a problem during a DNS Rebinding attack.
In between DNS changes, this can also be a factor. Waiting till DNS is fully flushed will resolve this if it was working before DNS problems/changes.
For me, using django 3.1.5 and django rest framework 3.12 the solution was way easier.
It happened to me that on a views.py file I had defined this two methods:
#api_view(['POST'])
#permission_classes((IsAuthenticated, ))
def create_transaction(request):
return Response(status=status.HTTP_200_OK)
def create_transaction(initial_data):
pass
On my urls.py:
urlpatterns = [
path('transaction', views.create_transaction, name='transaction'),
]
Django was picking the latest and throwing the error. Renaming one of the two solved the issue.
Code bellow would remove demand for CSRF. Even anon user would be able to send request.
from typing import List, Any
class Object(APIView):
authentication_classes: List = []
permission_classes: List[Any] = [AllowAny]
...
...
Removing CSRF check is not always the only (or best) solution. Actually, it's an important security mechanism for SessionAuthentication.
I was having the same issue when trying to authenticate with JWT and doing a POST request.
My initial setup looked like this:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"django_cognito_jwt.JSONWebTokenAuthentication",
),
...
}
As SessionAuthentication was checked first in the list, the CSRF error was raised. My solution was as simple as changing the order to always check JWT auth first. Like this:
"DEFAULT_AUTHENTICATION_CLASSES": (
"django_cognito_jwt.JSONWebTokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
At the end, SessionAuthentication for me is only used in the django admin panel and 99% of the requests goes to the API that uses JWT auth.
Im using get_queryset, in ListAPIView
I want to check the user's access token, before providing the list, I done the below but the issue is that get_query set does not return a Response, is there a way to return a response, or I should use an alternative :
this my class in the views.py :
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
def get_queryset(self):
# I get the token here from the headers
token = self.request.META.get("HTTP_TOKEN", "")
if not token:
return Response(
data={
"message": "no token!"
},
status=status.HTTP_400_BAD_REQUEST
)
if not UserAccess.objects.filter(accessToken=token).exists():
return Response(
data={
"message": "invalid token!"
},
status=status.HTTP_400_BAD_REQUEST
)
category_id = self.kwargs.get(self.lookup_url_kwarg)
return Product.objects.filter(category_id=category_id)
note that everything is working perfect If I removed the token related part.
thanks in advance.
after last update this is the repsonse :
I'd suggest you to move check token logic into dispatch() method. It's a better place than get_queryset. Or even better to write your own authentication class in order to share it between views.
With some fixes (see updated get_queryset()) it can be:
UPDATE
I think you can go with built-in restframework.exceptions.AuthenticationFailed.
If you are not satisfied with default DRF exceptions you can create your own custom exceptions. For example somewhere in exceptions.py:
from rest_framework.exceptions import APIException
class MissingTokenException(APIException):
status_code = 400
default_detail = 'Your request does not contain token'
default_code = 'no_token'
class InvalidTokenException(APIException):
status_code = 400
default_detail = 'Your request contain invalid token'
default_code = 'invalid_token'
Then you can use them in views.py:
from rest_framework import serializers
from .exceptions import MissingTokenException, InvalidTokenException
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
def dispatch(self, *args, **kwargs):
token = self.request.META.get("HTTP_TOKEN", "")
if not token:
raise MissingTokenException
if not UserAccess.objects.filter(accessToken=token).exists():
raise InvalidTokenException
return super().dispatch(*args, **kwargs)
def get_queryset(self):
qs = super().get_queryset()
category_id = self.kwargs.get(self.lookup_url_kwarg)
return qs.filter(category_id=category_id)
I'm not 100% if I'm getting this right, but I believe you can just use the regular authentication mechanisms that DRF provides. In this particular example, I think this section of the DRF docs should show you how to do it the "DRF" way: Setting Authentication Scheme
If you add the TokenAuthentication scheme to your application, you don't need to verify the token in your get_queryset method, but you can just use decorators to restrict access for function-based views or permission_classes for class-based views:
View-based
I guess this is what you'd be most interested in.
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
authentication_classes = (TokenAuthentication, ) # Add others if desired
permission_classes = (IsAuthenticated,)
Route-based
If you only want to restrict access for some of your routes (e.g. only post or detail views), then you can write your own permission class. For example, see this question here: How to add django rest framework permissions on specific method only ?
I understand that we can set up authentication classes in class based viewsets like this:
class ExampleViewSet(ModelViewSet):
authentication_classes = (SessionAuthentication, BasicAuthentication)
However, is there a way to dynamically change the authentication class based on the request method? I tried overriding this function in my ExampleViewSet:
def get_authenticators(self): # Found in
if self.request.method == "POST":
authentication_classes.append(authentication.MyCustomAuthentication)
return authentication_classes
However, django rest does not have the request object setup at this point:
'ExampleViewSet' object has no attribute 'request'
Note: not real variable names - just for example purpose.
Based on the previous answer, it works on django 1.10
#detail_route(methods=['post', 'get'])
def get_authenticators(self):
if self.request.method == "GET":
self.authentication_classes = [CustomAuthenticationClass]
return [auth() for auth in self.authentication_classes]
You can use detail_route decorator from rest_framework like this for getting requests,
detail_route can be used to define post as well as get,options or delete options
So,the updated code should be like :
from rest_framework.decorators import detail_route
class ExampleViewSet(ModelViewSet):
authentication_classes = (SessionAuthentication, BasicAuthentication)
#detail_route(methods=['post','get'])
def get_authenticators(self, request, **kwargs): # Found in
if request.method == "POST":
authentication_classes.append(authentication.MyCustomAuthentication)
return authentication_classes
For further reading,Read from here.
I have defined the following models
class Flight(models.Model):
...
class FlightUpdate(models.Model):
flight = models.ForeignKey('Flight', related_name='updates')
...
and the following viewset using the NestedViewsetMixin in the REST Framework Extensions
class FlightUpdateViewSet(mixins.ListModelMixin,
mixins.CreateModelMixin,
NestedViewSetMixin,
viewsets.GenericViewSet):
"""
API Endpoint for Flight Updates
"""
queryset = FlightUpdate.objects.all()
serializer_class = FlightUpdateSerializer
def create(self, request, *args, **kwargs):
flight = Flight.objects.get(pk=self.get_parents_query_dict()['flight'])
...
So, to access the FlightUpdates associated with a Flight, the URL is /flights/1/updates/.
I want to ensure that people can only create FlightUpdates if they have the permissions to change the Flight object with which the FlightUpdate is associated.
How would I go about performing the extra check when adding a FlightUpdate? I've tried adding something like this in the viewset, but I'm not sure if it's the best way.
if not request.user.has_perm('flights.change_flight', flight):
raise PermissionError()
Note: I'm using django-rules for the object-level permissions implementation.
I solved this problem by implementing a custom permissions class.
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.permissions import BasePermission, SAFE_METHODS
from .models import Flight
class FlightPermission(BasePermission):
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
try:
flight = Flight.objects.get(pk=view.kwargs['parent_lookup_flight'])
except ObjectDoesNotExist:
return False
return request.user.has_perm('flights.change_flight', flight)
It seems that permission classes are ANDed when REST framework checks permissions. That is every permission class needs to return True for permission to be granted. This makes things like "if you are a superuser, you can access anything, but if you are a regular user you need explicit permissions" a bit hard to implement, you cannot just return False, it will fail the whole stack. Is there a way to maybe short-circuit permissions? Something like "if this permission is granted, stop checking?" or some other way to deal with cases like that?
Now DRF allows permissions to be composed using bitwise operators: & -and- and | -or-.
From the docs:
Provided they inherit from rest_framework.permissions.BasePermission, permissions can be composed using standard Python bitwise operators. For example, IsAuthenticatedOrReadOnly could be written:
from rest_framework.permissions import BasePermission, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class ReadOnly(BasePermission):
def has_permission(self, request, view):
return request.method in SAFE_METHODS
class ExampleView(APIView):
permission_classes = (IsAuthenticated|ReadOnly,)
def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
Edited: Please note there is a comma after IsAuthenticated|ReadOnly.
I think you might be able to use django-rules library here. Link
It is a rule based engine very similar to decision trees and it can be easily integrated with permissions_class framework of DRF.
The best part is you can perform set operations on simple permissions and create complex permissions from them.
Example
>>> #rules.predicate
>>> def is_admin(user):
... return user.is_staff
...
>>> #rules.predicate
>>> def is_object_owner(user, object):
return object.owner == user
Predicates can do pretty much anything with the given arguments, but must always return True if the condition they check is true, False otherwise.
Now combining these two predicates..
is_object_editable = is_object_owner | is_admin
You can use this new predicate rule is_object_editable inside your has_permissions method of permission class.
You need to build your own custom http://www.django-rest-framework.org/api-guide/permissions/#custom-permissions as described in the docs.
Something like:
from rest_framework import permissions
class IsAdminOrStaff(permissions.BasePermission):
message = 'None of permissions requirements fulfilled.'
def has_permission(self, request, view):
return request.user.is_admin() or request.user.is_staff()
Then in your view:
permission_classes = (IsAdminOrStaff,)
Aside from the custom permission which is simpler approach mentioned in the earlier answer, you can also look for an existing 3rd party that handle a much complex permission handling if necessary.
As of Feb 2016, those handling complex condition permission includes:
rest_condition
djangorestframework-composed-permissions
One way would be to add another permission class which combines existing classes the way you want it, e.g.:
class IsAdmin(BasePermission):
"""Allow access to admins"""
def has_object_permission(self, request, view, obj):
return request.user.is_admin()
class IsOwner(BasePermission):
"""Allow access to owners"""
def has_object_permission(self, request, view, obj):
request.user.is_owner(obj)
class IsAdminOrOwner(BasePermission):
"""Allow access to admins and owners"""
def has_object_permission(*args):
return (IsAdmin.has_object_permission(*args) or
IsOwner.has_object_permission(*args))
Here is a generic solution:
from functools import reduce
from rest_framework.decorators import permission_classes
from rest_framework.permissions import BasePermission
def any_of(*perm_classes):
"""Returns permission class that allows access for
one of permission classes provided in perm_classes"""
class Or(BasePermission):
def has_permission(*args):
allowed = [p.has_permission(*args) for p in perm_classes]
return reduce(lambda x, y: x or y, allowed)
return Or
class IsAdmin(BasePermission):
"""Allow access to admins"""
def has_object_permission(self, request, view, obj):
return request.user.is_admin()
class IsOwner(BasePermission):
"""Allow access to owners"""
def has_object_permission(self, request, view, obj):
request.user.is_owner(obj)
"""Allow access to admins and owners"""
#permission_classes((any_of(IsAdmin, IsOwner),))
def you_function(request):
# Your logic
...
The easiest way would be to separate them out with | in permission_classes attribute or get_permissions method, but if you have complex rules, you can specify those rules in the check_permissions method in the viewsets class that you are defining. Something like this:
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = ProfileSerializerBase
def create(self, request, *args, **kwargs):
# Create rules here
def get_permissions(self):
if self.action == "destroy":
# Only Super User or Org Admin can delete record
permission_classes = [SAPermission, OAPermission]
def check_permissions(self, request):
"""
Original check_permissions denies access if any one of the permission
classes returns False, changing it so that it would deny access only if
all classes returns False
"""
all_permissions = []
messages = []
code = []
for permission in self.get_permissions():
all_permissions.append(permission.has_permission(request, self))
messages.append(getattr(permission, "message", None))
code.append(getattr(permission, "code", None))
if True in all_permissions:
return
message = ",".join(i for i in messages if i)
self.permission_denied(
request,
message=message if message else None,
code=code[0],
)