login by one user and accesstoken of another user - django-rest-framework

I have implemented simpleJWT for token based authentication. I created a simple hello world test API.
While testing, I am logging with /rest-auth/login/ and for generating use /api/token/ - both working fine.
Now for testing, I am logging in with say user XYZ (having access rights for helloworld api) and generating token using another user ABC (not having access rights for helloworld api).
So now user XYZ is authenticated (ok) but I am having token of user ABC (ok).
Now, when I call the API with token generated for use ABC, I am able to access the helloworld api even if user ABC has no rights for the API !! Because user XYZ who has rights already logged in.
Problem is this will always be case when multiple users will be using the site. How to resolve ? Few code snippets also presented below :
My settings.py snipped
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}
code basically a decorator which authenticate for users is as below
def user_is_ADM(fun_c):
#wraps(fun_c)
def wrapped(request, *args, **kwargs):
# 1 = ADM
if(request.user and request.user.is_authenticated) : <--- here is the issue
user_data = UserProfile.objects.get(user_id = request.user.id)
# user profile as as a user type
u = user_data.user_type
if u == 1:
return fun_c(request, *args, **kwargs)
else:
raise PermissionDenied
return wrapped
what should be my strategy in this case ?
EDIT
Modified my decorator as follows and it is working. Someone please comment if I am doing something wrong
def user_is_ADM(fun_c):
#wraps(fun_c)
def wrapped(request, *args, **kwargs):
juser = get_user(request)
if juser.is_authenticated:
user_jwt = JWTAuthentication().authenticate(Request(request))
if user_jwt is not None:
if request.user == user_jwt[0]:
k = user_jwt[0].userprofile.get_user_type_display()
if k == 'ADM':
return fun_c(request,*args,**kwargs)
else:
raise PermissionDenied
else:
raise PermissionDenied
else:
raise PermissionDenied
else:
raise PermissionDenied
return wrapped

check out this documentation https://www.django-rest-framework.org/api-guide/permissions/ (Custom permissions)
when setting a general permission setting (IsAuthenticated).
It is really authenticating users but not verifying their permissions at any time
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
if basic administrator and user authentication is not enough. you can implement a custom permission
from rest_framework import permissions
class CustomerAccessPermission(permissions.BasePermission):
"""
extracted from the documentation
"""
message = 'Adding customers not allowed.'
def has_permission(self, request, view):
"""
add authentication logic and return a boolean value
"""
# ...
# return bool()
in the views
from rest_framework.views import APIView
from modulename.permissions import CustomerAccessPermission
class ExampleView(APIView):
"""
...
"""
permission_classes = (CustomerAccessPermission,)
def get(self, request, format=None):
"""
...
"""
Here is an example with django authentication permissions
from typing import Tuple
from rest_framework.permissions import BasePermission
class CustomPermission(BasePermission):
"""
...
"""
list_permissions: Tuple[str] = (
'modelname.view_modelname',
'modelname.add_modelname',
'modelname.change_modelname',
'modelname.delete_modelname',
)
def has_permission(self, request, view) -> bool:
return bool(
request.user.has_perms(self.list_permissions)
or
request.user and request.user.is_staff
or
request.user and request.user.is_superuser
)
summary
With regard to the syntax in general, you will not see any changes, you must import your permission or decorator in the view, the difference lies in the runtime and the way django will evaluate the permissions
Remembering that a decorator is nothing more than a summary way of saying func(func()).
so you should always evaluate the view and call your method modified by the decorator
instead, permissions in this framework are always defined as a list of permission classes. Before executing the main body of the view, each permission in the list is verified. If any permission verification fails, exceptions will be generated. Permission denied or exceptions. An unauthenticated exception will be generated and the main body of the view will not be executed. (Consult the documentation for a complete explanation)
You should evaluate which method is most appropriate for your case (performance, syntax, etc.).
# you can set your permission in the general settings to avoid importing into each file
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'modulename.permissions.CustomPermission',
),
# ...
}

Related

Django rest framework authentication issue

I have custom token creating and decoding methods. Now everytime I get request I need to decode token to get request user or I get anonymous user. How can I decode it without repeating every time these lines?
auth = get_authorization_header(request).split()
if auth and len(auth) == 2:
token = auth[1].decode('utf-8')
id = decode_access_token(token)
You can create a middleware, where you decode the token and pass the info about the user to the class/function view
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
auth = get_authorization_header(request).split()
user = None
if auth and len(auth) == 2:
token = auth[1].decode('utf-8')
id = decode_access_token(token)
user = ... # get user
response = self.get_response(request, user)
# Code to be executed for each request/response after
# the view is called.
return response
And in your view
#method_decorator(SimpleMiddleware, name='dispatch')
class SimpleView(View):
def post(self, request, user):
pass

How to validate JWT token using identity server 4 in DRF?

I have an idenityserver4, a front-end angular app, and a Django rest framework resource API. The Angular app is unaccessible if not logged, and redirect to the identityserver4. The user has to log in there and is redirected to the front-end. So far so good, the provider gives the front-end a JWT access token.
Then the frontend application asks for resources to the DRF API providing the JWT. That's where I'm stuck, all the tutorials I find on google explain how to create your own provider. But I just want to get the token, check with the identity server 4 that it's valid, authenticate the user, provide the resources
Any code snippet or library will be highly helpful.
Thanks.
I had a similar problem that I haven't fully solved, but maybe this can help you.
It is a permission class that only checks if a given jwt is properly encoded and signed using authlib, from there you can access the token internal information to do further validation.
permissions.py:
from authlib.jose import jwt
from rest_framework import permissions
from rest_framework.exceptions import APIException
class TokenNotValid(APIException):
status_code = 403
default_detail = "Invalid or absent JWT token field."
class NoAuthHeader(APIException):
status_code = 403
default_detail = "Absent 'Authorization' header."
class ValidJWTPermission(permissions.BasePermission):
"""
Global permission check for JWT token.
"""
def _get_pubkey(self):
# You will probably need to change this to get the
# public key from your identity server and not from a file.
with open("../public.pem", "rb") as key_file:
pubk = key_file.read()
return pubk
def has_permission(self, request, view):
auth_header = request.META.get('HTTP_AUTHORIZATION')
# print("Received header:")
# print(auth_header)
if auth_header is None:
raise NoAuthHeader
try:
token = auth_header.split()[1]
# print("Encoded Token:")
# print(token)
dec_token = jwt.decode(token,self._get_pubkey())
except Exception:
# Probably should add proper exception handling.
raise TokenNotValid
# print("Decoded token:")
# print(dec_token)
return True
views.py:
from .permissions import ValidJWTPermission
class HelloView(APIView):
permission_classes = [ValidJWTPermission]
def get(self, request):
return HttpResponse("Hello, world. You're at the gerenciador index.")

Can we Use Django GraphQL JWT For authentification and also Django rest framework for other apis together?

I feel very difficult to cover DRF_jwt/DRF_oauth2 but Django GraphQL
JWT seems easy....
Can i use both of them together for my ease
I am new in Rest Framework
You can create a custom authentication backend for DRF that extends rest_framework.authentication.BaseAuthentication and uses graphql_jwt.utils to authenticate the user in DRF the exact same way django-graphql-jwt does it.
This is what I have:
from graphql_jwt.exceptions import JSONWebTokenError
from graphql_jwt.utils import get_payload, get_user_by_payload
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework import exceptions
class TokenAuthentication(BaseAuthentication):
keyword = 'JWT'
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None
if len(auth) == 1:
msg = 'Invalid token header. No credentials provided.'
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = 'Invalid token header. Token string should not contain spaces.'
raise exceptions.AuthenticationFailed(msg)
try:
token = auth[1].decode()
except UnicodeError:
msg = 'Invalid token header. Token string should not contain invalid characters.'
raise exceptions.AuthenticationFailed(msg)
try:
payload = get_payload(token)
user = get_user_by_payload(payload)
except JSONWebTokenError as e:
raise exceptions.AuthenticationFailed(str(e))
if user is None or not user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted.')
return (user, None)
def authenticate_header(self, request):
return self.keyword
If you mean using django-graphql-jwt to authentication and using DRF for other user account related actions, like updating, retrieving, deleting, reset password...
You can use django-graphql-auth.
It extends django-graphql-jwt, and provide a full api to handle user account.

How to check if the current user is logged in django rest framework? how to notify other django app that the current user is logged?

I use ListCreateAPIView for POST and GET requests. I want to check if current user is logged in GET request.
How to get current user (if he logged) in GET methods ?
To make it work, i have to send with token , t's not what I want because if user is logout, user can not access listView.
I thought django signals,or to rewrite authorization.
I thought django signals, or to rewrite Permissions or Authorization.
class PropertyList(generics.ListCreateAPIView):
"""To create a property"""
permission_classes = [permissions.IsAuthenticatedOrReadOnly, ]
queryset = Property.objects.filter(published=True)
serializer_class = PropertySerializer
filterset_class = PropertyFilter
pagination_class = LimitOffsetPagination
#
# def perform_create(self, serializer):
# serializer.save(created_by=self.request.user)
# for _ in range(100):
# logger.info("Your log message is here")
def get_serializer_context(self):
context = super().get_serializer_context()
context['is_create'] = True
print(self.request.user)
if self.request.user.is_authenticated:
print(self.request.user)
current_user = self.request.user
context['user_favs'] = (Bookmark.objects.filter(
bookUser = current_user
).values(
))
else:
context['user_favs'] = False
return context
In get_serializer_context(self) , i want to get current user because i return properties that user has bookmarked.
I need to add token in my Get request to have current user but that's mean , we have to login to see properties , it's not what I want
settings
REST_FRAMEWORK = {
"DATE_INPUT_FORMATS": ["%d-%m-%Y"],
# 'DATETIME_FORMAT': "%d-%m-%Y %H:%M:%S",
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
# 'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
'EXCEPTION_HANDLER': 'dooba.utils.custom_exception_handler',
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
# 'DEFAULT_PARSER_CLASSES': (
# 'rest_framework.parsers.JSONParser',
# 'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
# )
}
As you can see
You can access user object in your APIView methods by self.request.user if there is no logged-in user, it should be AnonymousUser or else you should get the logged in user.
EDIT: Further research revealed that when you use JSONWebTokenAuthentication with IsAuthenticatedOrReadOnly returns 401 with expired tokens even if you make GET request. I recommend you to never put Authenticate header when making a GET request to your PropertyList view, that will solve your problem.

DjangoRestFramework - Where do I raise a 404 error when `get_queryset()` cannot get an object because it does not exist?

This is my view:
class PostListByUsername(generics.ListAPIView):
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, IsLikeOrOwnerDeleteOrReadOnly, IsFromSameLocation,)
def get_queryset(self):
username = self.kwargs['username']
user = User.objects.get(username=username)
return Post.objects.filter(owner__username=username).order_by('-createdAt')
This is my IsFromSameLocation permission:
class IsFromSameLocation(permissions.BasePermission):
"""
Permission.
Allow permissions to authenticated users from the same
location as the user.
"""
message = 'Not from the same location.'
def has_permission(self, request, view):
username = view.kwargs.get('username', None)
try:
userProfile = User.objects.get(username=username)
except:
return False
return request.user.userextended.location == userProfile.userextended.location
With that said, in my get_queryset() method, I do user = User.objects.get(username=username) but if the user does not exist, I want to raise a 404 error. I know that get_queryset() is supposed to return a queryset, so I'm guessing I shouldn't raise a 404 in that method. So where exactly should I do the check to see if the user exists or not? Note that in my permission, I do do a try and except to see if the user exists, but permissions should only return True or False from my understanding (not raise 404 errors).
You actually can raise an exception from a permission and it will be correctly handled by Django. For example you can do it with get_object_or_404() shortcut function:
def has_permission(self, request, view):
username = view.kwargs.get('username', None)
userProfile = get_object_or_404(User, username=username)
return request.user.userextended.location == userProfile.userextended.location
In fact, while the code that throws an exception is executed in a view, it will be handled by Django, so it should not matter where you are raising it from -- from view, serializer, permission, model etc. methods.
The easiest way is to raise a Http404 exception:
In your try/except block, raise the 404 in the exception, but its probably worth limiting the exception to just DoesNotExist errors:
try:
userProfile = User.objects.get(username=username)
except User.DoesNotExist:
from django.http import Http404
raise Http404("User does not exist")

Resources