jazzband / django-rest-knox

Authentication Module for django rest auth
MIT License
1.17k stars 213 forks source link

OpenAPI schema generation #292

Open rbuffat opened 1 year ago

rbuffat commented 1 year ago

What is the recommended way to automatically generate an OpenAPI schema of the knox API endpoints? Neither the in the Django REST framework integrated schema builder (python manage.py generateschema) nor drf-spectacular seem to work out of the box.

``` Django==4.1.4 psycopg2-binary==2.9.5 djangorestframework==3.14.0 django-cors-headers==3.13.0 django-rest-knox==4.2.0 cryptography==38.0.4 markdown==3.4.1 django-filter==22.1 pyyaml==6.0 uritemplate==4.1.1 coreapi==2.3.3 drf-spectacular==0.25.1 ``` generateschema: ``` /api/login/: post: operationId: createLogin description: '' parameters: [] requestBody: content: application/json: schema: {} application/x-www-form-urlencoded: schema: {} multipart/form-data: schema: {} responses: '201': content: application/json: schema: {} description: '' tags: - api /api/logout/: post: operationId: createLogout description: '' parameters: [] requestBody: content: application/json: schema: {} application/x-www-form-urlencoded: schema: {} multipart/form-data: schema: {} responses: '201': content: application/json: schema: {} description: '' tags: - api /api/logoutall/: post: operationId: createLogoutAll description: 'Log the user out of all sessions I.E. deletes all auth tokens for the user' parameters: [] requestBody: content: application/json: schema: {} application/x-www-form-urlencoded: schema: {} multipart/form-data: schema: {} responses: '201': content: application/json: schema: {} description: '' tags: - api ``` spectacular ``` /home/worker/django/api/views.py:8: Error [LoginView]: unable to guess serializer. This is graceful fallback handling for APIViews. Consider using GenericAPIView as view base class, if view is under your control. Either way you may want to add a serializer_class (or method). Ignoring view for now. /home/worker/django/api/views.py:8: Warning [LoginView]: could not resolve authenticator . There was no OpenApiAuthenticationExtension registered for that class. Try creating one by subclassing it. Ignoring for now. /usr/local/lib/python3.11/site-packages/knox/views.py:70: Error [LogoutView]: unable to guess serializer. This is graceful fallback handling for APIViews. Consider using GenericAPIView as view base class, if view is under your control. Either way you may want to add a serializer_class (or method). Ignoring view for now. /usr/local/lib/python3.11/site-packages/knox/views.py:70: Warning [LogoutView]: could not resolve authenticator . There was no OpenApiAuthenticationExtension registered for that class. Try creating one by subclassing it. Ignoring for now. /usr/local/lib/python3.11/site-packages/knox/views.py:81: Error [LogoutAllView]: unable to guess serializer. This is graceful fallback handling for APIViews. Consider using GenericAPIView as view base class, if view is under your control. Either way you may want to add a serializer_class (or method). Ignoring view for now. /usr/local/lib/python3.11/site-packages/knox/views.py:81: Warning [LogoutAllView]: could not resolve authenticator . There was no OpenApiAuthenticationExtension registered for that class. Try creating one by subclassing it. Ignoring for now. ... /api/login/: post: operationId: api_login_create tags: - api security: - {} responses: '200': description: No response body /api/logout/: post: operationId: api_logout_create tags: - api responses: '200': description: No response body /api/logoutall/: post: operationId: api_logoutall_create description: |- Log the user out of all sessions I.E. deletes all auth tokens for the user tags: - api responses: '200': description: No response body ```
ianepperson commented 1 year ago

I was able to do this with the Login view by overriding it. (Should probably make this a pull request). First, needed to build a LoginResponseSerializer class (Knox doesn't use one for the login view):

from knox.serializers import UserSerializer as LoginUserSerializer # or whatever serializer you use

# Serializer to mimic the response from a Knox login
class LoginResponseSerializer(serializers.Serializer):
    token = serializers.CharField()
    expiry = serializers.DateTimeField()
    user = LoginUserSerializer()

Then extend the schema for the login view:

from drf_spectacular.utils import extend_schema
from rest_framework.authtoken.serializers import AuthTokenSerializer

class LoginView(KnoxLoginView):
    """
    Login to the API
    This endpoint permits a user to login and use the API.
    """
    @extend_schema(
        request=AuthTokenSerializer, responses={200: LoginResponseSerializer}
    )
    def post(self, request, format=None):
        return super().post(request, format=format)

I've added quite a bit more to my login view so I'm not sure if this works exactly as I've written it, but it should get you going in the right direction at least.

giovannicimolin commented 1 year ago

Moving my comments from #310 here.

I've recently started using this with drf-spetacular and had to describe some of the API using extensions. I'll evaluate if it's possible to modify this library in a way that the schema is automatically generated without needing any overrides. (it looks like it's possible and simple from https://github.com/jazzband/django-rest-knox/issues/292#issuecomment-1406866139).

Ping me if you want to take a stab at this! I can review/merge.

If anyone is also trying to use this with drf-spetacular and code generators, this is what I'm using now:

from drf_spectacular.extensions import (
    OpenApiAuthenticationExtension,
    OpenApiViewExtension,
)
from rest_framework import serializers

class KnoxAuthentication(OpenApiAuthenticationExtension):
    """
    Knox authentication Open API definition.
    """

    target_class = "knox.auth.TokenAuthentication"
    name = "TokenAuthentication"

    def get_security_definition(self, auto_schema):
        """
        Custom definition for APIView.
        """
        return {
            "type": "apiKey",
            "in": "header",
            "name": "Authorization",
        }

class LogoutResponseSerializer(serializers.Serializer):
    """
    Empty logout response serializer
    """

class FixLogoutView(OpenApiViewExtension):
    target_class = "knox.views.LogoutView"

    def view_replacement(self):
        """
        Fix view
        """

        class Fixed(self.target_class):
            serializer_class = LogoutResponseSerializer

        return Fixed

class FixLogoutAllView(OpenApiViewExtension):
    target_class = "knox.views.LogoutAllView"

    def view_replacement(self):
        """
        Fix view
        """

        class Fixed(self.target_class):
            serializer_class = LogoutResponseSerializer

        return Fixed

This doesn't yield a 1 to 1 to the API output response, but it doesn't matter for my usage.

Note: be mindful of the settings if you are re-using this, they are specific to my projects.