How to avoid checking authorization header for ReadOnly Modelviewset - django

I have a view with the following permission
permission_classes = [IsOwnerOrReadOnly]
the permission definition is
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
# the object has a owner attribute
return obj.owner == request.user
It listing of the objects works as expected if the user authenticated and pass correct access token. it also works if I do not add Authorization in header.
The only problem happens when the authorization token is expired or wrong. It sends 401 response.
Invalid token header. No credentials provided.
How can I ignore authorization token in django code for right or wrong authorization header.

I found a way to achieve what I intend. However, I am not sure whether this is the standard or not. override perform_authentication in Modelviewset does the work and we can strip the authorization from the header.
def perform_authentication(self, request):
if 'HTTP_AUTHORIZATION' in request.META:
request.META.pop('HTTP_AUTHORIZATION')

Related

Conditionally authenticate users to DRF view

I have a DRF view as such:
class DocumentsView(generics.ListCreateAPIView):
permission_classes = ()
authentication_classes = ()
# I typically enforce authentication with
# authentication_classes = (JWTCookieAuthentication,)
def list(self, request):
pagination_class = None
if request.user.is_authenticated:
... return protected data ...
else:
... return generic data ...
I want to allow both users sending a valid token and those not sending a valid token to both get a response from this endpoint. However, request.user.is_authenticated returns False, even when a valid token is sent (I understand why). How can I try to authenticate the user, but still allow them to proceed even if not presenting a token?
Or is better practice to not have the same view to authenticated an unauthenticated users?
What you want is to have no permissions, but still perform authentication.
Authentication validates tokens, gets the user from the database, etc. If you disable it, then no checks of tokens/etc will be performed. This means request.user will always equal AnonymousUser.
class MyView(ListCreateApiView):
permissions_classes = [AllowAny]
It's worth considering using AllowAny as your permission. It is the same as using a blank list [], but makes it clear your intention is to make the view public, rather than a mistake.

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).

DRF django-rest-framework-simplejwt JWTAuthentication not working

Ideally using django-rest-framework-simplejwt and the authentication class JWTAuthentication, the API should give 403 when I pass the token incorrectly.
Instead, when I am making my API request it is executing successfully even without the Authentication token.
This is a dummy API, my concern is the Authentication should work.
My code looks like this:
class ViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = SomeSerializer
http_method_names = ("post", "patch")
authentication_classes = (JWTAuthentication,)
When I debug I see that it is executing JWTAuthentication, which in turn returns None.
Which is expected since I am not passing the Token in the header.
def authenticate(self, request):
header = self.get_header(request)
if header is None:
return None
Now I think the View should give Permission Denied, which is not happening.
Not able to understand what is missing here.
If you pass incorrect token, it'll return 401 status response.
But if you don't put authorization header on your request, django will not return 401 response and behave with request as AnonymousUser request.
If you want only authenticated users have access to your ViewSet, you should put permission_classes = [IsAuthenticated,] in your ViewSet.
IsAuthenticated permission class can be imported from rest_framework.permissions

Django Rest API with okta OAUTH token authentication

I have a problem with Okta token authentication, I know how to authenticate with drf token and jwt token auth.
In my project, I have to use okta token which is a type of jwt as well, however, this token is generated by front-end and send back to me in the request
so here you can see how I authenticate the okta token with okta_jwt package:
def post(self, request, *args, **kwargs):
access_token = request.META.get('HTTP_AUTHORIZATION')
try:
validate_token(access_token, config.issuer, config.aud, config.client_id)
except Exception as e:
return JsonResponse({"result": e.args[0]}, status=400)
..........
Basically I have to take the token out from the header and check with okta_jwt to see if it's legal
Obviously, I don't think it's a good solution and it's hard to do unit test
Can anyone provide a better solution for this?
Thanks
I found the solution:
I just created the custom authentication inherit from BaseAuthentication. In the Custom authentication, you can do whatever authenticating process you want:
class OktaAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
access_token = request.META.get('HTTP_AUTHORIZATION')
if not access_token:
return None
payload = validate_token(access_token, config.issuer, config.aud, config.client_id)
try:
user = get_user_model().objects.get(email=payload['sub'])
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return user, None
In the setting.py, making sure you have the custom authentication added as the Default:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'core.authentication.OktaAuthentication',
)}
In the views:
authentication_classes = (OktaAuthentication,)
permission_classes = (IsAuthenticated,)

Django REST Framework Deep Dive - Where is it determined that an enpoint needs an auth token

general django question for those who are more experienced than myself,
I'm reading through the code posted for a tutorial on thinkster.io:
https://github.com/howardderekl/conduit-django/tree/master/conduit/apps
There's an endpoint pertaining to the User model authenticion/models.py that requires an Authorization header for it to return user information defined here in authentication/views.py:
class UserRetrieveUpdateAPIView(RetrieveUpdateAPIView):
permission_classes = (IsAuthenticated,)
renderer_classes = (UserJSONRenderer,)
serializer_class = UserSerializer
def retrieve(self, request, *args, **kwargs):
serializer = self.serializer_class(request.user)
return Response(serializer.data, status=status.HTTP_200_OK)
My question is how/where is it (supposed to be) determined that an endpoint requires this Authorization. My thought is that it is tied to the permission_classes variable stated in the UserRetrieveUpdateAPIVIiew class above. I dug into the package location where this was imported from (from rest_framework.permissions import IsAuthenticated), but that doesn't appear to contain anything pertaining to an HTTP header:
class BasePermissionMetaclass(OperationHolderMixin, type):
pass
class BasePermission(metaclass=BasePermissionMetaclass):
"""
A base class from which all permission classes should inherit.
"""
def has_permission(self, request, view):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return True
def has_object_permission(self, request, view, obj):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return True
...
...
...
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
I'm looking for best practices on how to structure headers like this for HTTP methods in my backend. Any ideas on where I should look, somewhere in settings.py maybe?
Thanks!
Bonus question:
This header requires two strings in your request. First being 'Token', followed by a space, then the actual JWT for that user. Is this standard practice to use two strings like this? If so, what's the reasoning. I've seen this before with other seemingly arbitrary words used for the first string, 'Token'
As shown in the documentation :
REST framework will attempt to authenticate with each class in the list, and will set request.user and request.auth using the return value of the first class that successfully authenticates.
So if you use a token authentication scheme, the header Authorization: Token ... will result in the setup of the request.user and request.user.is_autenticated will be set to true for the view.
By combining it with the IsAuthenticated permission class, the view will only be accessible if the authorization token is set (and if you don't allow other authentication schemes)
For your second question, you can indeed put any string you want. Here DRF uses Token to make it clearer for which authentication scheme it is used. Since a lot of apps also uses a "Token", a lot of them use the same word. You can also often find Bearer.
Django REST Framework comes with a package for Token Authentication, which is probably what you are looking for:
https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
With it, you can assign tokens to individual users, which they can use in lieu of a username and password to authenticate against endpoints. This token is provided in the HTTP header. As for your second question, by providing Token as a convention, it would allow for other custom authentication types:
https://www.django-rest-framework.org/api-guide/authentication/#custom-authentication
The permission_classes are what determine what, if any, authentication is necessary. Good luck!