Detect first time user has authenticated using Django Rest Framework - django

I'm using DRF to allow users of my mobile app to authenticate to my web application.
I want to create a model instance associated with this user the first time a user "logs in" using the client.
I'm using token-based authentication in DRF, and for my /api/authenticate/ endpoint I'm pointing at url(r'^authenticate/', restviews.obtain_auth_token),
It seems like the best way to handle this is to override ObtainAuthToken(APIView), by adding this class to my api/views.py. This class looks like this:
class ObtainAuthTokenCustomized(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key})
obtain_auth_token = ObtainAuthTokenCustomized.as_view()
It looks like I would want to insert a test prior to get_or_create for whether a token has been created previously for this user. And if so, perform the model instance creation I have planned.
Is this there a better way to handle this?

From what I can tell this is the best place to handle this.
The reason is that DRF does not currently have a token expiration capability. So once a token is created with the above class it does not go away.
This means created will return True if it is the user's first time logging in:
token, created = Token.objects.get_or_create(user=user)
Thus you'd simply test created on the following line and perform the model creation or other actions necessary.
Additional logic may be necessary to handle a situation if tokens were removed. For example, if you used created an API logout method like the one given in this answer.

Related

DRF Protect user registration view

I have a following user view:
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
permission_classes = [NotSafeMethodAndAllowAny | permissions.IsAuthenticated]
def get_queryset(self):
if self.request.user.is_superuser:
return User.objects.all()
else:
return User.objects.filter(id=self.request.user.id)
#action(detail=False, methods=["get"])
def current(self, request, *args, **kwargs):
return Response(self.get_serializer(request.user).data)
As this is a ModelViewSet, it allows API users to create, list, update, delete the users.
Don't mind the NotSafeMethodAndAllowAny permission.
Now I want to somehow protect my API from the users using it like in a script. For example, when I submit the user registration form on my frontend (a separate React app), it should be accepted, but if someone posts to my api/user/ with random registration data like from Postman/curl/etc., I want it to be discarded.
Is there a way for DRF to distinguish between those two types of requests? And possibly via some setting, so I can test it via Postman when in development, but when it runs in production, it would not accept automated requests. I want to prevent a situation where someone would create like a bazillion users in a matter of minutes.
BTW: I use JWT for authentication, but for obvious reasons the registration/login endpoints do not require authentication.
If i understand maybe you can create a CustomPermission and only allow requests from specific IP ADDRESS.
from rest_framework import permissions
class IpAddressPermission(permissions.BasePermission):
"""
Global permission check for IPs.
"""
def has_permission(self, request, view):
remote_address = request.META['REMOTE_ADDR']
if remote_addr == settings.ALLOWED_IP_ADDRESS:
return True
return False

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.

Django: Best practice for showing list of Models (as a text) based on group permissions given to specific user

My main concern is to show a list of Models that user can interact with based on given permissions.
I am thinking of doing this by way of user's group permissions. I wanna mimic how the Django admin is able to list the models when you give any permissions to the user related to that model like this:
For example I have model called Posts with permissions can_add_post and can_delete_post. I am assigned to a Group called Writer that has been assigned with both permissions. In my view, I want to be able to retrieve the name of 'Posts' model as a text so I can do something with it like set an onClick function to call a respective React component like this:
I have no clear business logic in mind to do this though. However, I am using DRF and I have a Login API and I'm thinking I might put the logic inside this API so that I can fetch all permissions of this logged in user and show those modules that he has access on.
UPDATE:
Here is my Login API View:
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
permission_classes = ()
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
return Response(
{
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1],
"authenticated": True
}
)
I guess you need this?
from django.contrib.auth.models import Permission
#for user permisions
set(Permission.objects.filter(user=your_user).values_list('content_type__model', flat = True))
#for user groups permisions
set(Permission.objects.filter(group__user=your_user).values_list('content_type__model', flat = True))
This is concept, so you should adapt it to your needs
p.s.: using set() because i don't know how to distinct() on content_type__model field, perhaps a more elegant solution exists.

Django do not ask user from database for request

In my Django project I have a public API endpoint built with Django Rest Framework's APIView. It does not need to know anything about the user. Still, Django automatically fetches the session and the user from the database. Is there a way to not do this since it causes two unnecessary DB hits?
Here is the code:
class TermListView(APIView):
permission_classes = ()
def get(self, request, format=None):
qs = Term.objects.all().only('original_word')
return Response([term.original_word for term in qs])
You need to add authentication_classes = () to the View class. This tells Django not to worry about the user. Or you can also configure this option globally for all your endpoints.

is_authenticated returns True for logged out user

I'm writing a server app using Django, Django REST framework, Django-rest-auth and Django-allauth. I have a method that's used to pass messages between users, and this should only happen when the receiver is logged in.
However, it seems that the user object's is_authenticated() method returns True even though the user has logged out (called rest-auth/logout/, which should in turn call Django's logout). What could cause this? Is there something I've missed here?
Here's the code I have:
class SendMessage(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = MessageSerializer
def perform_create(self, serializer):
m = self.request.data['msg']
targetUser = User.objects.get(pk = self.request.data['user'])
if targetUser.is_authenticated():
# Send message
else:
# Don't send message
Unfortunately, the is_authenticated() method always returns true.
def is_authenticated(self):
"""
Always return True. This is a way to tell if the user has been
authenticated in templates.
"""
return True
It is meant to discern between a User instance and an AnonymousUser instance, which is what the User is set as when they do not pass authentication.
Try fetching all authenticated users, then check if the target user is among them or not:
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
from django.utils import timezone
def get_all_logged_in_users_ids():
# Query all non-expired sessions
# use timezone.now() instead of datetime.now() in latest versions of Django
sessions = Session.objects.filter(expire_date__gte=timezone.now())
uid_list = []
# Build a list of user ids from that query
for session in sessions:
data = session.get_decoded()
uid_list.append(data.get('_auth_user_id', None))
return uid_list
class SendMessage(generics.CreateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = MessageSerializer
def perform_create(self, serializer):
m = self.request.data['msg']
if (self.request.data['user'] in get_all_logged_in_users_ids()):
# Send message
else:
# Don't send message
Reference.
You've misunderstood what is_authenticated is checking.
Remember that the web is a stateless environment. There is no server-level 'logged-in' status: the only record of whether a user is logged in or not is whether the cookie exists on that user's machine.
Since it is always possible for the user to close their browser, or turn of their machine, without notifying the server, there is no reliable way to know whether or not a user is really logged in. The only way you can approach that is to have some kind of repeated Ajax call every few seconds, posting to a view that updates the user's record with the current time. Then you can check that the user did indeed update within the last few seconds. This won't be totally reliable, though.
Alternatively you can look into using some kind of long-polling system to keep a channel open.