How to limit view to authenticated user in Django Rest Framework - django

I have a Django Rest Framework application.
Authentication is performed through a login method:
def login(self, request):
user = find_my_user(request)
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
return Response({"status": "ok"})
Authentication works fin.
I have a ViewSet having a list_route() that need authenticated user to be used.
Here is the code:
class CommonView(viewsets.ViewSet):
#list_route()
#authentication_classes(SessionAuthentication)
#permission_classes(IsAuthenticated)
def connected(self, request):
return Response({"status": "ok"})
Even if the user is not authenticated (no session cookie), the action is performed.
As a work around, I've performed it like that :
class CommonView(viewsets.ViewSet):
#list_route()
def connected(self, request):
if request.user.is_authenticated():
return Response({"status": "ok"})
else:
return Response({"status": "ko", "message": "Unauthenticated"})
But I feel it could be cleaner, any idea ?

You can create a custom ListRouteIsAuthenticated permission class inheriting from BasePermission class which will deny any permission to unauthenticated users for any request in the list route.
For detail route requests, it will allow unrestricted access, regardless of if the request was authenticated or unauthenticated.
from rest_framework.permissions import BasePermission
class ListRouteIsAuthenticated(BasePermission):
"""
Custom Permission Class which authenticates a request for `list` route
"""
def has_permission(self, request, view):
if view.action == 'list':
return request.user and request.user.is_authenticated() # check user is authenticated for 'list' route requests
return True # no authentication check otherwise
Then in your viewset, you need to define this permission class.
class CommonView(viewsets.ViewSet):
permission_classes = [ListRouteIsAuthenticated]
...

According to the documentation, add an attribute:
class CommonView(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]

Related

How to invoke DRF has_object_permission() method with a function based view and Token auth?

I am using DRF with authentication. I have implemented both Session and Token auth. The first is used by the browsable API that I use to debug and the second is the auth that will be used by the actual client. I have the following detail view:
#api_view(['GET', 'PUT', 'DELETE'])
#permission_classes([permissions.IsAuthenticated, IsTrainingOwner]) # this view is only available to authenticated users
def training_detail(request, pk, format=None):
"""
Retrieve, update or delete a training. Need to be authenticated
"""
print(request.user)
try:
training = Training.objects.get(pk=pk)
except Training.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
if request.method == 'GET':
serializer = TrainingSerializer(training)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = TrainingSerializer(training, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
training.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
As you cas see I am using a custom permission:
class IsTrainingOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to see and edit it
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
print("Object owner: ", obj.owner)
print("Request user: ", request.user)
return True
# Instance must have an attribute named `owner`.
print("Object owner: ", obj.owner)
print("Request user: ", request.user)
return obj.owner == request.user
This custom permission is being correctly called when I use the DRF browsable API, with the prints of object owner and request user being displayed in my console.
However, when I use Postman to test the Token permission, the 'has_object_permission' method is not called at all (I don't see any prints in my console) and I can modify the training of a user A when sending the token of a user B in the header.
I have read the DRF doc that talks about this and then decided to try a generic view:
class TrainingDetail(generics.RetrieveUpdateAPIView):
permission_classes = [permissions.IsAuthenticated, IsTrainingOwner]
queryset = Training.objects.all()
serializer_class = TrainingSerializer
Using the generic view works well and the custom permission is invoked correctly.
So, my question is:
Why does the custom permission works with the function based view version when I use the browsable API which uses session auth and I need to switch to the generic view version for the custom permission to work with POSTMAN?
Thank you for your help :)
Have a look at the DRF documentation:
https://www.django-rest-framework.org/api-guide/permissions/#object-level-permissions
If you're writing your own views and want to enforce object level permissions, or if you override the get_object method on a generic view, then you'll need to explicitly call the .check_object_permissions(request, obj) method on the view at the point at which you've retrieved the object.
I think that in case of interacting with the API via the HTML interface, DRF is calling check_object_permissions while rendering the HTML form while your view isn't touching it at all.
Please try pulling the session cookie via browser dev tools and try using POSTMAN/curl to send the same request to fire JSONRenderer rather than BrowsableAPIRenderer (you can also force that by adding ?format=json in the url).

Authentication on specific method for generic API views

I have used ListCreateAPIView and RetrieveUpdateDestroyAPIView for a model. Now I want to add JWT authentication to only the Update and Destroy part in the RetrieveUpdateDestroyAPIView. How can I do that?
Let me make my question a bit more clear. I have a model named Post. Now All users are allowed to view the post but update, delete is only available to the user who created it. And I want to use JWT Authentication.
You can write custom permission class for this:
from rest_framework import permissions
class CustomPermission(permissions.BasePermission):
def has_permission(self, request, view):
if view.action in ('update', 'destroy'):
return request.user.is_authenticated
return True
And use in in your view:
class ExampleView(RetrieveUpdateDestroyAPIView):
permission_classes = (CustomPermission,)
we can override the method get_authenticators and don't forget to add authentication_classes to api view.
def get_authenticators(self):
if self.request.method in ['PUT', 'DELETE']:
return [auth() for auth in self.authentication_classes]
else:
return []
For your question update we need to add object level permissions like below
class OwnerRequiredPermission(object):
def has_object_permission(self, request, obj):
return obj.created_by == request.user
add above permission class to permission_classes

How to add django rest framework permissions on specific method only ?

I have following functions in rest API for User model. I want to set AllowAny permission on only POST request. Can someone help me out.
class UserList(APIView):
"""Get and post users data."""
def get(self, request, format=None):
"""Get users."""
users = User.objects.all()
serialized_users = UserSerializer(users, many=True)
return Response(serialized_users.data)
def post(self, request, format=None):
"""Post users."""
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
You can write a custom Permission class IsPostOrIsAuthenticated which will allow unrestricted access to POST requests but will allow only authenticated GET requests.
To implement the custom permission IsPostOrIsAuthenticated, override the BasePermission class and implement .has_permission(self, request, view) method. The method should return True if the request should be granted access, and False otherwise.
from rest_framework import permissions
class IsPostOrIsAuthenticated(permissions.BasePermission):
def has_permission(self, request, view):
# allow all POST requests
if request.method == 'POST':
return True
# Otherwise, only allow authenticated requests
# Post Django 1.10, 'is_authenticated' is a read-only attribute
return request.user and request.user.is_authenticated
So, all POST requests will be granted unrestricted access. For other requests, authentication will be required.
Now, you need to include this custom permission class in your global settings.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'my_app.permissions.IsPostOrIsAuthenticated',
)
}
http://www.django-rest-framework.org/api-guide/permissions/
as per above URL you have to write one custom permission class
class ExampleView(APIView):
permission_classes = (MyCustomAuthenticated,)
Write your own logic using AllowAny or IsAuthenticated inside MyCUstomAuthenticated based on POST and GET

Django Rest Framework authentication - how to implement custom decorator

I am trying to implement TokenAuthentication using the Rest Framework, but it seems that I can't add my own custom decorators to my ViewSets because they are evaluated BEFORE the authentication. Consider this:
from django.utils.decorators import method_decorator
from django.http.response import HttpResponseForbidden
def require_staff(View):
def staffOnly(function):
def wrap(request, *args, **kwargs):
if request.user.is_active and request.user.is_staff:
return function(request, *args, **kwargs)
else:
return HttpResponseForbidden()
return wrap
View.dispatch = method_decorator(staffOnly)(View.dispatch)
return View
When I try to implement this, it seems the decorator code fires first, so the authentication is never run.
#require_staff
class CustomerViewSet(ModelViewSet):
model = Customer
filter_class = CustomerFilter
filter_backends = (DjangoFilterBackend,)
Since request.user is never set, introducing the decorator breaks authentication.
I think the issue is that Authentication is occuring the rest_frameworks dispatch() function and it is not clear to me how I could add additional (say) custom security if authentication is done that late in the game.
Am I missing something here, or what is the proper way to implement this customization?
Someone suggested using Permissions for this instead. I assume they mean custom DRF Permissions, right?
Everything you need to know is DRF permissions is here: http://www.django-rest-framework.org/api-guide/permissions
DRF provides a built in permission that is similar to yours, called IsAdminUser
The IsAdminUser permission class will deny permission to any user,
unless user.is_staff is True in which case permission will be allowed.
This permission is suitable is you want your API to only be accessible
to a subset of trusted administrators.
To use this permission in a Class Based View:
class ExampleView(APIView):
permission_classes = (IsAdminUser,)
Now you have two options to do an extra check for user.is_active.
The first is override the IsAdminUser permission, like so:
from rest_framework import permissions
class IsActiveAndAdminUser(permissions.IsAdminUser):
"""Only allow a user who is Admin and Active to view this endpoint. """
def has_permission(self, request, view):
is_admin = super(IsAdminAndActiveUser, self).has_permission(request, view)
return request.user.is_active and is_admin
The second is to create an IsActiveUser permission, and chain them in your view.
IsActiveUser Permission:
from rest_framework import permissions
class IsActiveUser(permissions.BasePermission):
""" Only Active Users have permission """
def has_permission(self, request, view):
return request.user.is_active
Your new permission list in your class based view:
class ExampleView(APIView):
permission_classes = (IsActiveUser, IsAdminUser,)

Django, request.user is always Anonymous User

I am using a custom authentication backend for Django (which runs off couchdb). I have a custom user model.
As part of the login, I am doing a request.user = user and saving the user id in session.
However, on subsequent requests, I am not able to retrieve the request.user. It is always an AnonymousUser. I can, however, retrieve the user id from the session and can confirm that the session cookie is being set correctly.
What am I missing?
I do not want to use a relational db as I want to maintain all my user data in couchdb.
Edit: I have written a class which does not inherit from Django's auth User. It, however, has the username and email attributes. For this reason, my backend does not return a class which derives from auth User.
The request.user is set by the django.contrib.auth.middleware.AuthenticationMiddleware.
Check django/contrib/auth/middleware.py:
class LazyUser(object):
def __get__(self, request, obj_type=None):
if not hasattr(request, '_cached_user'):
from django.contrib.auth import get_user
request._cached_user = get_user(request)
return request._cached_user
class AuthenticationMiddleware(object):
def process_request(self, request):
request.__class__.user = LazyUser()
return None
Then look at the get_user function in django/contrib/auth/__init__.py:
def get_user(request):
from django.contrib.auth.models import AnonymousUser
try:
user_id = request.session[SESSION_KEY]
backend_path = request.session[BACKEND_SESSION_KEY]
backend = load_backend(backend_path)
user = backend.get_user(user_id) or AnonymousUser()
except KeyError:
user = AnonymousUser()
return user
Your backend will need to implement the get_user function.
I too have custom authentication backend and always got AnonymousUser after successful authentication and login. I had the get_user method in my backend. What I was missing was that get_user must get the user by pk only, not by email or whatever your credentials in authenticate are:
class AccountAuthBackend(object):
#staticmethod
def authenticate(email=None, password=None):
try:
user = User.objects.get(email=email)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
#staticmethod
def get_user(id_):
try:
return User.objects.get(pk=id_) # <-- tried to get by email here
except User.DoesNotExist:
return None
Its easy to miss this line in the docs:
The get_user method takes a user_id – which could be a username,
database ID or whatever, but has to be the primary key of your User
object – and returns a User object.
It so happened that email is not primary key in my schema. Hope this saves somebody some time.
You say you've written a custom authentication backend, but in fact what you seem to have written is a complete custom authentication app, which doesn't interface with Django's contrib.auth.
If you want to use a non-relational database for your authentication data, all you need to do is create a class that provides two methods: get_user(user_id) and authenticate(**credentials). See the documentation. Once you have authenticated a user, you simply call Django's normal login methods. There should be no reason to manually set request.user or put anything into the session.
Update after edit That has nothing to do with it. There's no requirement that the user class derives from auth.models.User. You still just need to define a get_user method that will return an instance of your user class.
Please elaborate. If you are using a custom user model (which is different from a custom user PROFILE model), then you are basically on your own and the django.contrib.auth framework can not help you with authentication. If you are writing your own authentication system and are not using django.contrib.auth, then you need to turn that off because it seem to be interfering with your system.
In case you are using an API (Django-rest-framework) and accessing a view using a get, post, etc. methods.
You can get a user by sending the Bearer/JWT token corresponding to that user.
Wrong
# prints Anonymous User
def printUser(request):
print(request.user)
Correct
# using decorators
# prints username of the user
#api_view(['GET']) # or ['POST'] .... according to the requirement
def printUser()
print(request.user)
I had similar problem when I used custom authentication backend. I used field different than the primary key in the method get_user.
It directly solved after using primary key which must be number (not str)
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id) # <-- must be primary key and number
except User.DoesNotExist:
return None
After sending Token using Authorization header, the token will be gotten in dispatch function as bellow:
'''
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
So you are using django_role_permission's HasRoleMixin, the dispatch method of this mixin will hide dispatch of the view.
I think that the solution is to redefine the mixin of roles-permissions
user = authenticate(username=username, password=password)
if user is not None:
return render(request, 'home.html',{'user_id':user.id})
Added these in my view
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
and started getting original user