tfranzel / drf-spectacular

Sane and flexible OpenAPI 3 schema generation for Django REST framework.
https://drf-spectacular.readthedocs.io
BSD 3-Clause "New" or "Revised" License
2.32k stars 259 forks source link

Problem with authorization through SwaggerUI #1092

Open Danfs64 opened 11 months ago

Danfs64 commented 11 months ago

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.

tfranzel commented 11 months ago

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

Danfs64 commented 11 months ago

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 commented 11 months ago

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.