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.41k stars 266 forks source link

Specify different security for each endpoint #1294

Open stefanofusai opened 2 months ago

stefanofusai commented 2 months ago

Hello, I was wondering if there was a way to specify a different security for each endpoint, so that some endpoints can have the security look like this:

"security": [
    {
        "ApiKeyAuth": []
    }
]

And others like this:

"security": [
    {
        "ApiKeyAuth": [],
        "jwtAuth": []
    }
]

Thank you!

tfranzel commented 2 months ago

By having your permission_classes and authentication_classes properly set. In case some they are not detected, you might have to tweak a OpenApiAuthenticationExtension.

The 3rd party auth plugins listed here should work out of the box: https://github.com/tfranzel/drf-spectacular/tree/master/drf_spectacular/contrib

Some extensions are not enabled to trigger on subclasses. In that case you might have to create a extension subclass and put your class as target or enable match_subclasses.

stefanofusai commented 2 months ago

That's interesting, thank you. It seems like the integration with https://florimondmanca.github.io/djangorestframework-api-key/ doesn't work perfectly (as you had mentioned in this issue)

For example, the following code results in the following security:

# Generates: [{jwtAuth: []}, {ApiKeyAuth: []}, {ApiKeyAuth: [], jwtAuth: []}]
# Should be: [{ApiKeyAuth: [], jwtAuth: []}]
class AuthViewSet(ReadOnlyModelViewSet):
    authentication_classes = [
        rest_framework_simplejwt.authentication.JWTAuthentication,
        rest_framework.authentication.SessionAuthentication,
    ]
    permission_classes = [rest_framework_api_key.permission.HasAPIKey]
    ...

# Generates: [{jwtAuth: []}, {ApiKeyAuth: []}, {ApiKeyAuth: [], jwtAuth: []}]
# Should be: [{ApiKeyAuth: []}]
class NoAuthViewSet(ReadOnlyModelViewSet):
    authentication_classes = []
    permission_classes = [rest_framework_api_key.permission.HasAPIKey]
    ...

djoser endpoints look like this:

My SPECTACULAR_SETTINGS looks like this:

SPECTACULAR_SETTINGS = {
    "APPEND_COMPONENTS": {
        "securitySchemes": {
            "ApiKeyAuth": {"type": "apiKey", "in": "header", "name": "X-API-Key"},
            "jwtAuth": {
                "type": "http",
                "scheme": "bearer",
                "bearerFormat": "JWT",
            },
        }
    },
    "AUTHENTICATION_WHITELIST": [
        "rest_framework_api_key.permissions.HasAPIKey",
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ],
    ...
    "SECURITY": [
        {"ApiKeyAuth": []},
        {"ApiKeyAuth": [], "jwtAuth": []},
    ],
    ...
}

And my DJOSER settings look like this:

DJOSER = {
    ...
    "PERMISSIONS": {
        "activation": ["rest_framework_api_key.permissions.HasAPIKey"],
        "password_reset": ["rest_framework_api_key.permissions.HasAPIKey"],
        "password_reset_confirm": ["rest_framework_api_key.permissions.HasAPIKey"],
        "set_password": [
            "rest_framework_api_key.permissions.HasAPIKey",
            "djoser.permissions.CurrentUserOrAdmin",
        ],
        "username_reset": ["rest_framework_api_key.permissions.HasAPIKey"],
        "username_reset_confirm": ["rest_framework_api_key.permissions.HasAPIKey"],
        "set_username": [
            "rest_framework_api_key.permissions.HasAPIKey",
            "djoser.permissions.CurrentUserOrAdmin",
        ],
        "user_create": ["rest_framework_api_key.permissions.HasAPIKey"],
        "user_delete": [
            "rest_framework_api_key.permissions.HasAPIKey",
            "djoser.permissions.CurrentUserOrAdmin",
        ],
        "user": [
            "rest_framework_api_key.permissions.HasAPIKey",
            "djoser.permissions.CurrentUserOrAdmin",
        ],
        "user_list": [
            "rest_framework_api_key.permissions.HasAPIKey",
            "djoser.permissions.CurrentUserOrAdmin",
        ],
        "token_create": ["rest_framework_api_key.permissions.HasAPIKey"],
        "token_destroy": [
            "rest_framework_api_key.permissions.HasAPIKey",
            "rest_framework.permissions.IsAuthenticated",
        ],
    },
    ...
}

What I need is basically to hide all endpoints behind HasAPIKey, and only require authentication in few of them. Thanks for the help