django oauth toolkit `or` in required scopes in view

1.7k Views Asked by At

I'm using drf and oauth toolkit with IsAuthenticatedOrTokenHasScope permissions as default. I have a view that contains scopes required_scopes = ['mod', 'admin'] When users logs into the app he have special groups which define his permission scope. So when the moderator logs into the app he gets mod scope. When he calls my view he gets 403 because allow_scopes in AccessToken model returns False. That is because the resource_scopes is ['mod', 'admin'] and provided_scopes is 'mod'. When method allow_scopes checks resource_scopes.issubset(provided_scopes) she returns False which is not intentional in my case.

Is there any other option without overwriting allow_scopes in AccessToken model to define that this view needs scope mod or scope admin. ?

1

There are 1 best solutions below

0
On

I think I found a way to get this to work. The oauth2_provider doesn't provided any function to achieve this. So, what I did was I defined my own custom permission which is similar to the TokenHasScope. So, create a file called permissions.py and paste the code

from rest_framework import permissions 
from django.core.exceptions import ImproperlyConfigured
from rest_framework.exceptions import PermissionDenied
from oauth2_provider.settings import oauth2_settings

class TokenHasAtLeastOneScope(permissions.BasePermission):
    """
    The request is authenticated as a user and the token used has at least one of the right scope
    """

    def has_permission(self, request, view):
        token = request.auth

        if not token:
            return False

        if hasattr(token, "scope"):  # OAuth 2
            required_scopes = self.get_scopes(request, view)
            log.debug("Required scopes to access resource: {0}".format(required_scopes))

            # If any scope in the list of required_scopes is valid, return True.
            for given_scope in required_scopes:
                if token.is_valid([given_scope]):
                    return True


            # Provide information about required scope?
            include_required_scope = (
                oauth2_settings.ERROR_RESPONSE_WITH_SCOPES
                and required_scopes
                and not token.is_expired()
                and not token.allow_scopes(required_scopes)
            )

            if include_required_scope:
                self.message = {
                    "detail": PermissionDenied.default_detail,
                    "required_scopes": list(required_scopes),
                }

            return False

        assert False, (
            "TokenHasAtLeastOneScope requires the"
            "`oauth2_provider.rest_framework.OAuth2Authentication` authentication "
            "class to be used."
        )

Then in your view, import permissions and set it accordingly

permission_classes = (permissions.TokenHasAtLeastOneScope)
required_scopes = ['mod', 'admin']

In the custom TokenHasAtLeastOneScope above, the code is similar to TokenHasScope. The only line that changes is

for given_scope in required_scopes:
    if token.is_valid([given_scope]):
        return True

Which loop through the items in your required_scopes list and if it finds a valid scope, it returns True.