Django Rest Framework. Custom permission with JSONWebTokenAuthentication - django

I have a need to write a custom permission (deriving from BasePermission) for one of my endpoints where:
If the method is POST, it's open for everyone (e.g. returns true)..
however, if the method if PUT or GET it should be authenticated with JSONWebTokenAuthentication to figure out if to clear or reject the request.
Typically, I know how to add this into my APIView class
authentication_classes = ([JSONWebTokenAuthentication])
But how do I check whether the user is already authenticated with JSONWebTokenAuthentication in case the HTTP method is PUT or GET in my Custom Permission class? Is there something like IsJSONWebTokenAuthenticated somewhere?

You need to write a custom permission for your view,
permissions.py
class CustomPermission(BasePermission):
def has_permission(self, request, view):
if (request.method =='POST' or (request.user and request.user.is_authenticated())):
return True
return False
Then, you need to add to your settings.py,
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES':
('rest_framework.permissions.IsAuthenticated', ),
'DEFAULT_AUTHENTICATION_CLASSES':
('rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication', ),
}
Then add the permission to your permission_classes of the view,
from .permissions import CustomPermission
class YourView(APIView):
permission_classes = (CustomPermission, )

Related

Getting "Authentication credentials were not provided." message when trying to access my ModelViewSet URL

I am creating an application using Django Rest Framework and using Token based authentication. I have a PlaceViewSet class inheriting from ModelViewSet. I want both list and retrieve to work even if there is no token sent by the user whereas create, destroy and update should be allowed only if the user has sent the Token. But I am getting Authentication credentials were not provided. for requests of all types. I also want to stick to the REST standard so that the list, retrieve, update, create, destroy comes under the same model view set.
In case if DRF does not allow any requests without Token if I have set my default authentication as TokenAuthentication then why I am able to access the signin and signup views?
You can change permission policy in settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny'
],
}
If you want to add special permissions for methods.
Permission:
from rest_framework.permissions import IsAuthenticated, IsAdminUser
class SpecialPermissionMixin(object):
def get_permissions(self):
user_permission_list = ['list', 'retrieve', 'update']
admin_permission_list = ['destroy', 'create']
if self.action in user_permission_list:
permission_classes = [
IsAuthenticated,
]
elif self.action in admin_permission_list:
permission_classes = [
IsAdminUser,
]
else:
permission_classes = []
return [permission() for permission in permission_classes]
View:
class BlogViewSet(SpecialPermissionMixin, viewsets.ModelViewSet):
...

How do I alter my API request based on a field from the user?

What I would like is an API request that gives me data based on a field from the User.
i.e. If the User is premium, give all of the data, otherwise, give a subset of that data.
This is easy enough, as shown in the DRF
filtering documentation:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
This view should return a list of all the purchases
for the currently authenticated user.
"""
user = self.request.user
return Purchase.objects.filter(purchaser=user)
The problem:
I currently have it set up so anyone trying to access the API needs a Token. But, I'd like a 'Guest' user (someone who is not logged in) to still be able to see some data from my API.
I am using these Django Rest Framework settings:
REST_FRAMEWORK = {
"TEST_REQUEST_DEFAULT_FORMAT": "json",
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
}
You can override the permissions for individual views.
from rest_framework import permissions
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Then you might want to alter the behaviour in get_queryset if the user is not logged in
if request.user.is_anonymous:
# do something different

Django not recognizing my Token?

I'm using Django Rest Framework on the backend and an ember-cli app on the frontend. The authentication is working correctly, but there seems to be a hole somewhere in authorization.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
}
# views.py
class CurrentUserView(APIView):
"get the data for the current authenticatd user"
permission_classes = (IsAuthenticated,)
def get_queryset(self, request):
queryset = User.objects.filter(username=request.user.username)
return queryset
def get(self, request):
serializer = UserSerializer(request.user)
return Response(serializer.data)
When I make a request to this endpoint /v1/me/ it responds back with a 403. When I take off the permission class, I get back {"id":null,"username":"","is_active":false} because it doesn't know who I am.
Also, when I use the browsable API, the /v1/me URL works correctly.
On the Ember side of things, I login with my account and correctly get back my Token. In the request the Authorization: Token asdf1234asdf1234asdf1234 is being passed. I would think Django takes that token and knows who I am? Am I missing something?
Try something like
from rest_framework import authentication
class TokenAuthView(APIView):
authentication_classes = (authentication.TokenAuthentication,)
Then,
class CurrentUserView(TokenAuthView)
In the setting you need to add auth_token.
# settings.py
INSTALLED_APP = ('rest_framework.authtoken',)
You don't need to add the authentication_classes on every view.

Cannot apply DjangoModelPermissions when rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly is enabled

I am trying to make MongoEngine work with Django REST framework. By following this link Getting mongoengine and django rest framework to play nice, I manage to get everything working, but have to disable "PERMISSION CLASSES" in REST framework, like below
'DEFAULT_PERMISSION_CLASSES': [
#'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
Otherwise, I get this error "Cannot apply DjangoModelPermissions on a view that does not have .model or .queryset property.". The reason seems to be that the returned value from "Collection.objects" or "Collection.objects.all()" can not pass "has_permission" function in permission.py in REST framework.
Could anyone help to look at this?
Or you can just add:
from rest_framework import permissions
and in the view classes add
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
In your views.py import the following models:
from rest_framework.permissions import AllowAny
from rest_framework.decorators import api_view, permission_classes
Before declaring the function (the view function) add:
#api_view(['GET', 'POST'])
#permission_classes((AllowAny, ))
or
#api_view(['GET', 'PUT', 'DELETE'])
#permission_classes((AllowAny, ))
As per Django REST docs:
This permission class ties into Django's standard django.contrib.auth
model permissions. This permission must only be applied to views
that have a .queryset property or get_queryset() method.
This means that on your view, you must have the queryset properly set, e.g:
queryset = SampleModel.objects.all()
Best approach will be to authenticate at a view-base level. So your view must look like this:
from rest_framework.views import APIView
from rest_framework.permissions import DjangoModelPermissionsOrAnonReadOnly
class SampleAPIView(APIView):
queryset = SampleModel.objects.all()
serializer_class = SampleSerializer
permission_classes = [DjangoModelPermissionsOrAnonReadOnly,]
And just in case you are using JWT authentication, you just need to add this to settings.py:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}

How to add permissions in Django Rest Framework for specific requests

I am creating a class based api for a documentation app, but I want to add specific permissions to the post and patch definitions in my APIView. For example,
class DocumentList(APIView):
def get(self,request,format=None):
... blah
def post(self,request,format=None):
only allow administrators to create new documents
... blah
By default permissions are unrestricted. In your settings.py you can specify a different set of defaults to where users have to be authenticated and have the correct Django model permission. You will need to specify a model attribute on your view class for the DjangoModelPermissions to take effect.
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.DjangoModelPermissions'
)
}
# views.py
class DocumentList(APIView):
model = Document
...
The DjangoModelPermissions permission map can be found in the source.
GET, OPTIONS and HEAD don't require a permission but since we specified IsAuthenticated we're still requiring that
POST maps to add
PUT and PATCH map to change
DELETE maps to delete
This is what I did. Reference from the documentation
Created permission class for the project
project/permissions.py
from rest_framework import permissions
class IsAuthenticatedOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Read permission - always allow for GET request
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions - only if authenticated
return request.user and request.user.is_authenticated()
Now use this PermissionClass in the view
#permission_classes((IsAuthenticatedOrReadOnly, ))
class ShopViewSet(viewsets.ModelViewSet):
queryset = Shop.objects.all()
serializer_class = ShopSerializer