Skip to content

Commit

Permalink
consolidate bearer scheme generation & bugfix #515
Browse files Browse the repository at this point in the history
  • Loading branch information
tfranzel committed Sep 17, 2021
1 parent 36251a8 commit f43d7d3
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 45 deletions.
20 changes: 5 additions & 15 deletions drf_spectacular/authentication.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.conf import settings
from django.utils.translation import gettext_lazy as _

from drf_spectacular.extensions import OpenApiAuthenticationExtension
from drf_spectacular.plumbing import build_bearer_security_scheme_object


class SessionScheme(OpenApiAuthenticationExtension):
Expand Down Expand Up @@ -36,17 +36,7 @@ class TokenScheme(OpenApiAuthenticationExtension):
priority = -1

def get_security_definition(self, auto_schema):
if self.target.keyword == 'Bearer':
return {
'type': 'http',
'scheme': 'bearer',
}
else:
return {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization',
'description': _(
'Token-based authentication with required prefix "%s"'
) % self.target.keyword
}
return build_bearer_security_scheme_object(
header_name='Authorization',
token_prefix=self.target.keyword,
)
11 changes: 6 additions & 5 deletions drf_spectacular/contrib/rest_framework_jwt.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from drf_spectacular.extensions import OpenApiAuthenticationExtension
from drf_spectacular.plumbing import build_bearer_security_scheme_object


class JWTScheme(OpenApiAuthenticationExtension):
Expand All @@ -8,8 +9,8 @@ class JWTScheme(OpenApiAuthenticationExtension):
def get_security_definition(self, auto_schema):
from rest_framework_jwt.settings import api_settings

return {
'type': 'http',
'scheme': 'bearer',
'bearerFormat': api_settings.JWT_AUTH_HEADER_PREFIX,
}
return build_bearer_security_scheme_object(
header_name='AUTHORIZATION',
token_prefix=api_settings.JWT_AUTH_HEADER_PREFIX,
bearer_format='JWT'
)
31 changes: 7 additions & 24 deletions drf_spectacular/contrib/rest_framework_simplejwt.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers

from drf_spectacular.drainage import warn
from drf_spectacular.extensions import OpenApiAuthenticationExtension, OpenApiSerializerExtension
from drf_spectacular.plumbing import build_bearer_security_scheme_object
from drf_spectacular.utils import inline_serializer


Expand Down Expand Up @@ -71,29 +71,12 @@ def get_security_definition(self, auto_schema):
f'OpenAPI3 can only have one "bearerFormat". JWT Settings specify '
f'{api_settings.AUTH_HEADER_TYPES}. Using the first one.'
)
header_name = getattr(api_settings, 'AUTH_HEADER_NAME', 'HTTP_AUTHORIZATION')

if (
api_settings.AUTH_HEADER_TYPES[0] == 'Bearer'
and header_name == 'HTTP_AUTHORIZATION'
):
return {
'type': 'http',
'scheme': 'bearer',
'bearerFormat': "JWT",
}
else:
if header_name.startswith('HTTP_'):
header_name = header_name[5:]
header_name = header_name.replace('_', '-').capitalize()
return {
'type': 'apiKey',
'in': 'header',
'name': header_name,
'description': _(
'Token-based authentication with required prefix "%s"'
) % api_settings.AUTH_HEADER_TYPES[0]
}

return build_bearer_security_scheme_object(
header_name=getattr(api_settings, 'AUTH_HEADER_NAME', 'HTTP_AUTHORIZATION'),
token_prefix=api_settings.AUTH_HEADER_TYPES[0],
bearer_format='JWT'
)


class SimpleJWTTokenUserScheme(SimpleJWTScheme):
Expand Down
25 changes: 25 additions & 0 deletions drf_spectacular/plumbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
)
from django.utils.functional import Promise, cached_property
from django.utils.module_loading import import_string
from django.utils.translation import gettext_lazy as _
from django.utils.version import PY38
from rest_framework import exceptions, fields, mixins, serializers, versioning
from rest_framework.settings import api_settings
Expand Down Expand Up @@ -327,6 +328,30 @@ def build_choice_field(field):
return schema


def build_bearer_security_scheme_object(header_name, token_prefix, bearer_format=None):
""" Either build a bearer scheme or a fallback due to OpenAPI 3.0.3 limitations """
# normalize Django header quirks
if header_name.startswith('HTTP_'):
header_name = header_name[5:]
header_name = header_name.replace('_', '-').capitalize()

if token_prefix == 'Bearer' and header_name == 'Authorization':
return {
'type': 'http',
'scheme': 'bearer',
**({'bearerFormat': bearer_format} if bearer_format else {}),
}
else:
return {
'type': 'apiKey',
'in': 'header',
'name': header_name,
'description': _(
'Token-based authentication with required prefix "%s"'
) % token_prefix
}


def build_root_object(paths, components, version):
settings = spectacular_settings
if settings.VERSION and version:
Expand Down
16 changes: 16 additions & 0 deletions tests/contrib/test_drf_jwt.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from unittest import mock

import pytest
from django.urls import path
from rest_framework import mixins, routers, serializers, viewsets
Expand Down Expand Up @@ -35,3 +37,17 @@ def test_drf_jwt(no_warnings):
schema = generate_schema(None, patterns=urlpatterns)

assert_schema(schema, 'tests/contrib/test_drf_jwt.yml')


@pytest.mark.contrib('rest_framework_jwt')
@mock.patch('rest_framework_jwt.settings.api_settings.JWT_AUTH_HEADER_PREFIX', 'JWT')
def test_drf_jwt_non_bearer_keyword(no_warnings):
schema = generate_schema('/x', XViewset)
assert schema['components']['securitySchemes'] == {
'jwtAuth': {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization',
'description': 'Token-based authentication with required prefix "JWT"'
},
}
2 changes: 1 addition & 1 deletion tests/contrib/test_drf_jwt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ components:
jwtAuth:
type: http
scheme: bearer
bearerFormat: Bearer
bearerFormat: JWT

0 comments on commit f43d7d3

Please sign in to comment.