django-oauth/django-oauth-toolkit

Lack of dynamic scope handling in TokenHasScope

Closed this issue · 1 comments

The Current Limitation

In the current implementation of TokenHasScope, the get_scopes method retrieves the required scopes using this method:

def get_scopes(self, request, view):
    try:
        return getattr(view, "required_scopes")
    except AttributeError:
        raise ImproperlyConfigured(
            "TokenHasScope requires the view to define the required_scopes attribute"
        )

This approach assumes a static required_scopes attribute on the view. However, this design doesn't support cases where the required scope changes dynamically such as different scopes for GET, PUT, PATCH, or DELETE methods in RetrieveUpdateDestroyAPIView like following example:

class StudentRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = [TokenHasScope]

    def get_required_scopes(self):
        if self.request.method in ["PUT", "PATCH"]:
            return ["student_data_update"]
        elif self.request.method == "GET":
            return ["student_data_read"]
        elif self.request.method == "DELETE":
            return ["student_data_delete"]

Solutions

  1. Modifying TokenHasScope to support a get_required_scopes method in addition to the existing required_scopes attribute. The new behavior could be:

    1. Check if the view has a get_required_scopes method.
    2. If the method exists, call it and use its return value as the required scopes.
    3. If the method does not exist, fall back to the static required_scopes attribute.

Here’s how the updated get_scopes implementation might look:

def get_scopes(self, request, view):
    if hasattr(view, "get_required_scopes"):
        return view.get_required_scopes()
    try:
        return getattr(view, "required_scopes")
    except AttributeError:
        raise ImproperlyConfigured(
            "TokenHasScope requires the view to define either the required_scopes attribute or the get_required_scopes method"
        )
  1. Another solution is to adjust TokenHasScope to check for get_scopes() in the view, if the view uses ScopedResourceMixin, Instead of directly checking for the required_scopes attribute