Custom authorization in Django Rest - django

I am just a newbie with Django, python. I try to build 1 simple API Collection include CRUD basic, and authentication, authorization.
I have 1 simple Views like:
#api_view(['GET'])
#permission_classes([IsUser])
def get_test(request : Request):
return JsonResponse({"message": "success"}, status = status.HTTP_200_OK)
and IsUser is:
class IsUser(IsAuthenticated):
def has_permission(self, request : Request, view):
token = request.query_params.get('token')
if token:
role = jwt.decode(token.split(" ").__getitem__(1), key="secret_key",algorithms="HS256").get('role')
if role == 'User':
return True
else:
return False
return False
My purpose wants to parse the JWT token and authorization based on that. I wonder don't know if my way is really correct? Can anyone give me some comments as well as documentation to better understand this issue? I don't use any other lib because I want to code by myself at first to understand the flow.
Thanks for helping me.

If you are using the default JWT authentication of DRF then permission class IsAuthenticated will verify the the token for you and instead of specifying token in query parameter specify in headers.
However, if you want to allow a specific type(role) of user to access your endpoint. Then create a subclass of BasePermission class as show in the example.
Then simply add IsAuthenticated in #permission_classes([IsUser & IsAuthenticated]) to make it work.
Again, this would work only if you are using the default Group model.
Here is how you can import it from django.contrib.auth.models import Group
from rest_framework.permissions import BasePermission
USER = 'USER'
class IsUser(BasePermission):
def has_permission(self, request, view, user=None):
if not user:
user = request.user
return USER in [role.name for role in user.groups.filter()]

Related

Using external API for token based authentication in Django (DRF)

Context
My API accepts a jwt-token that I can pass to an external endpoint which will return 401 if invalid/expired or user information if still valid. Based on this I will either return 401 or filtered data belonging to the user. Also, POST request's need to involve writing who this resource belongs to for the GET to work. I tried to do this by overriding the get_queryset and perform_create methods.
My viewset looks something like this:
class ReportViewSet(AuthorizedUserBasedFilteredViewset):
queryset = Report.objects.all()
serializer_class = ReportSerializer
def perform_create(self, serializer):
try:
username = self.get_authorized_user()['user']['username']
except Exception as e:
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
serializer.save(creatd_by=username)
def get_queryset(self):
try:
username = self.get_authorized_user()['user']['username']
except Exception as e:
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
return Report.objects.filter(created_by=username)
This doesn't work because get_queryset expects a queryset as response.
Questions
How do I bubble up the authorize exception in get_queryset? Is there some other method I should be overriding to the authentication entirely? I still need to pass the username recieved to get_queryset for the authentication to be successful
Is there a better way to do this? I feel that the call to the external authentication API and setting the user to be accessible by the get_queryset and perform_create methods would ideally go somewhere else in the code. I looked at the RemoteUserAuthenticationBackend but that still involved creation of a User object but for this use case the user model is entirely external
Instead of
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
use this
raise NotAuthenticated(detail='Token does not exist')
Hope above line has addressed your 1st question.
For 2nd question.
You can extend TokenAuthentication and then implement def authenticate_credentials(self, key): method. It is not a good idea to call external API to fetch user each time. Instead, you should get JTW token one time from external source and then pass JWT token in header like Authorization : Bearer cn389ncoiwuencr for each API call. Then you should decode JWT token in current system.
from rest_framework.authentication import TokenAuthentication
from django.contrib.auth import get_user_model
class CustomTokenAuthentication(TokenAuthentication):
keyword = 'Bearer' # token type
def authenticate_credentials(self, key):
#decode JWT.
username = get_username()
User = get_user_model()
user = User(username=username)
return user, key
Finally, add this class to settings.py file.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'path.of.CustomTokenAuthentication',
)
}
That will apply to all of your views. or you can use view specific auth class.
class Sample(ViewSet):
authentication_classes = (CustomTokenAuthentication,)
From now you can access user by request.user at views or viewset. That's it.

Django Rest Framework User and UserRole and Permission Handling using Token Authentication

I am newbie to Django Rest Framework. I googled a lot and stuck on How to set User Role to User and strict to Permission.
My question is very basic but I don't know how to do it.
I want to create 2 types of Users:
1) Admin (Full Access)
2) Normal User (Limited Access)
I found some links but still on getting what to do:
Django rest-framework per action permission
django-rest-framework : setting per user permissions
you can separate users by is_staff property and use, standart rest permission isadminuser, for test you try this simple view
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = (IsAdminUser,)
def get(self, request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)

Key based access for the Django Rest Framework

Lets say the API endpoint to GET a list of users is this
/api_auth/user/
But I want to restrict access to this list only to people with an api_key
/api_auth/user/?access_key=$omeRandomHash3252532
How do I implement such an access system using the Django Rest Framework?
Should I use Permissions to implement this?
This is not supported out of the box for django-rest-framework, however it can easily be implemented:
If you take a look at http://www.django-rest-framework.org/api-guide/authentication/ you'll see an Example of a custom authentication method. Baased on that, you'll need to implement something like this:
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
class APIKeyAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
api_key = request.GET.get('api_key')
if not api_key:
return None
try:
user = get_user_from_api_key(api_key)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No user for API KEY')
return (user, None)
The APIKeyAuthentication should be put on an authentication.py module and be configured with REST_FRAMEWORK setting on settings.py, like this
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'my_custom_package.authentication.APIKeyAuthentication',
)
}
Now, what the above does is that it checks if the api_key parameter is present (if not it will return None to check if the request can be authenticated differently -- if you don't want to check any other authentication classes then just raise an exceptions.AuthenticationFailed exception like we do when we dont find the user below. Now, we need to implement a get_user_from_api_key function that will return a User instance from an API_KEY. If the user that is correlated with the passed api_key is found then it will be returned, if not an exceptions.AuthenticationFailedexception will be thrown.
Concerning the get_user_from_api_key function, its implementation depends on your requirements. For instance, if you want to create a new api key for each user, you should create an APIKey model that will have an api_key CharField and a ForeignKey to the User that has this api_key. The get_user_from_api_key function then will query the APIKey model to get the user with the provided api_key.
Update
If you want to use the django-rest-framework permissions instead of the authentication, you may create an APIKeyPermission class like this:
from rest_framework import permissions
class APIKeyPermission(permissions.BasePermission):
def has_permission(self, request, view):
api_key = request.GET.get('api_key')
return check_permission(api_key, request)
Where the check_permission function will check if the api_key passed has permissions for that specific request. Please check the examples on http://www.django-rest-framework.org/api-guide/permissions/ for more info - you may instead choose to implement has_object_permission to implement object-level permissions instead of view-level permissions.
If you are able to set a header in your request you can use Rest Framework's Token Authentication.
Otherwise, if you need to put it in the URL as a GET-paramter you could make your own custom authentication class:
from rest_framework.authentication import TokenAuthentication
class MyAuthentication(TokenAuthentication):
def authenticate(self, request):
token = request.GET.get('api-key', None)
if token is None:
return None
return self.authenticate_credentials(token)

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

Custom authentication with django?

Because I didn't want to use Django's in-build authentication system (maybe I should do, please tell me if this is the case), I created a simple little auth class:
import random
import hashlib
from myapp import models
class CustomerAuth:
key = 'customer'
def __init__(self, session):
self.session = session
def attempt(self, email_address, password):
password_hash = hashlib.sha1(password).hexdigest()
try:
return models.Customer.objects.get(
email_address=email_address,
password_hash=password_hash)
except models.Customer.DoesNotExist:
return None
def login(self, customer):
self.session[self.key] = customer
def logout(self):
if self.session.has_key(self.key):
self.session[self.key] = None
def is_logged_in(self):
return self.session.has_key(self.key)
and self.session[self.key] != None
def get_active(self):
if self.is_logged_in():
return self.session[self.key]
else:
raise Exception('No user is logged in.')
def redirect_to_login(self):
return HttpResponseRedirect('/login/')
def redirect_from_login(self):
return HttpResponseRedirect('/account/')
The problem is, that when I want to use it to stop unauthorized access, I have to use this code snippet in every single view method:
def example(req):
auth = CustomerAuth(req.session)
if not auth.is_logged_in():
return auth.redirect_to_login()
As you can imagine, this yields fairly ugly and repetitive code. What is a better way of doing this? Should I be using Django's auth framework?
Firstly, yes you should use Django's authentication framework, and build your own custom auth backend.
Secondly, however you do it, you'll need to have something in the views that you want to restrict access to. The best way to do that is via a decorator on the view. Again, Django's built-in framework gives you access to the #login_required decorator, which does exactly what you want.