drf-spectacular icon indicating copy to clipboard operation
drf-spectacular copied to clipboard

Problem with authorization through SwaggerUI

Open Danfs64 opened this issue 2 years ago • 3 comments

Describe the bug If I setup a Django project using ClientCredentials Oauth2 for authentication, and have a view using oauth2_provider.contrib.rest_framework.OAuth2Authentication as authentication_class and a permission class that is, or specializes, oauth2_provider.contrib.rest_framework.TokenHasScope, I can authorize myself using SwaggerUI, and the request will properly have the Authorization header.

However, if my permission_class is a logic OR ( | ) between 2 permissions, of which at least one is or specializes from oauth2_provider.contrib.rest_framework.TokenHasScope, authorizing myself through SwaggerUI won't result in the Authorization header being passed when I make the request. This behavior happens even if both permissions are instances of TokenHasScope.

To Reproduce Just have any view's permission_class be a OR containing a TokenHasScope specialization. I can easily upload a repository demonstrating it, if you guys want.

Expected behavior Since a OR containing a TokenHasScope Permission will still need the Authorization header, it should be passed when doing the request using SwaggerUI, after using the Authorize button.

Notes I think I tracked down this behavior all the way to drf_spectacular.contrib.django_oauth_toolkit.DjangoOAuthToolkitScheme.get_security_requirement(). I think that this function should also check if a permission is an instance of rest_framework.permissions.OR (I don't know how to treat AND or NOT) and, if so, recursively verify each side of it.

Danfs64 avatar Oct 26 '23 19:10 Danfs64

thanks for the detailed explanation. could you also add am example (mock) view that shows this problem? it is easier to talk about.

tfranzel avatar Oct 27 '23 09:10 tfranzel

Sure thing!

class BookView(APIView):
    authentication_classes = [OAuth2Authentication]
    permission_classes = [TokenHasScope1 | TokenHasScope2]

    def get(self, request):
        return Response({"message": "success"}, 200)

where TokenHasScope1 and 2 are like:

class TokenHasScope1(TokenHasScope):
    def get_scopes(self, request, view):
        return ['scope1']

Danfs64 avatar Oct 27 '23 11:10 Danfs64

If you're interested, I'd like to make a PR fixing this, although I'm not sure what is the expected behaviour on ORs: adding the Authorization header if either side of a OR has a TokenHasScope, or if both sides should be TokenHasScope. An AND would definitely require just one side having a TokenHasScope.

Danfs64 avatar Oct 27 '23 17:10 Danfs64