Elio Maisonneuve
Elio Maisonneuve

Reputation: 369

DRF - How to Authentify an application with Oauth Toolkit?

In my application, I use a modified User model with 3 more field that I need.

from django.contrib.auth.models import AbstractUser
from django.db import models

import ldapdb.models


class User(AbstractUser):
    cotisant = models.BooleanField(default=False)
    nbSessions = models.IntegerField(default=0)
    tel = models.CharField(max_length=20, default="")

I want people to be able to change their accounts settings, (like their password, email, tel,...).

To do so I have a serializer like this :

from rest_framework import serializers
from django.contrib.auth.hashers import make_password
from coreapp.models import User


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name', 'email', 'password', 'cotisant', 'tel')
        extra_kwargs = {
            # Allow to set pwd, but disallow getting the hash from database
            'password': {'write_only': True}
        }

    def validate_password(self, value: str):
        return make_password(value)

And a view like this :

class UserViewSet(viewsets.ModelViewSet):
    serializer_class = UserSerializer
    permission_classes = (IsSelfOrStaffPermission, TokenHasReadWriteScopeOrCreate,)
    lookup_field = 'username'

    def get_queryset(self):
        current_user = self.request.user
        if current_user.is_staff:
            user_set = User.objects.all()
        else:
            user_set = User.objects.filter(username=current_user.username)

        query = self.request.query_params.get('q', None)

        if not query:
            return user_set

        return user_set.filter(
            Q(username__icontains=query) |
            Q(first_name__icontains=query) |
            Q(last_name__icontains=query)
        )

(This allow access to a user only to himself, unless he is staff)

The problem is, to update nbSessions parameter, the user have to pay something on one of my apps.

How can I allow the app to set the parameter, but disallow user to update it directly ?

NOTE : I have other apps who use Password Credential Flow and are client side, so someone can get an app token through it.

Upvotes: 4

Views: 161

Answers (2)

theguru42
theguru42

Reputation: 3378

You have to use a client secret to identify who is calling your api. You will have to generate api keys for your clients. The clients will send these api keys as query params to prove they are your approved clients.

for example you generated a token abcd for your ios app. Your ios app will send the request as: https://your-endpoint.com/api/method/?token=abcd

To check if the token is correct you can create a custom permission class.

class IsApprovedClientPermission(permissions.BasePermission):

    def has_permission(self, request, view):
        token = request.query_params.get('token', None)
        if request.method not in permissions.SAFE_METHODS:
            # custom checks
            return token in approved_tokens_list:
        return True

You can apply this permission class to any view you want to verify before letting the request modify your data.

Upvotes: 1

Andrea Corbellini
Andrea Corbellini

Reputation: 17781

If I understood the question correctly, you want a certain field (nbSessions) to be writable through the same API endpoint by certain third-party apps, but not by regular users.

Here's how I would do it: create two distinct serializers, one for third-party apps, another one for regular users.

class UserSerializer(serializers.ModelSerializer):
    """
    Serializer used by third-party apps.
    All fields, including nbSessions, are writeable.
    """

    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name', 'email', 'password', 'cotisant', 'nbSessions', 'tel')
        extra_kwargs = {
            'password': {'write_only': True}
        }

    # ...

class RestrictedUserSerializer(UserSerializer):
    """
    Serializer used by regular users.
    All fields except nbSessions are writeable.
    It inherits from UserSerializer so that common code is not duplicated.
    """

    class Meta(BaseUserSerializer.Meta):
        read_only_fields = ('nbSessions',)

Here, as you can see, the only difference between the two serializers is the presence of the read_only_fields attribute in RestrictedUserSerializer.Meta.

Then in your view you can inspect your request to see what serializer class to use:

class UserViewSet(viewsets.ModelViewSet):

    def get_serializer_class(self):
        if is_third_party_app(self.request.user):
            return UserSerializer
        return RestrictedUserSerializer

    # ...

You can expand this concept even further and have different serializer classes (possibly inheriting from the same base class) for regular users, third-party apps, staff members, admins, and so on. Each serializer can have their specific set of fields, validation rules and update logic.

Upvotes: 1

Related Questions