diff --git a/drf_spectacular/templates/drf_spectacular/swagger_ui.html b/drf_spectacular/templates/drf_spectacular/swagger_ui.html index 5ba225a9..1a99c98b 100644 --- a/drf_spectacular/templates/drf_spectacular/swagger_ui.html +++ b/drf_spectacular/templates/drf_spectacular/swagger_ui.html @@ -12,6 +12,12 @@
+ {% if script_url %} + {% else %} + + {% endif %} diff --git a/drf_spectacular/templates/drf_spectacular/swagger_ui.js b/drf_spectacular/templates/drf_spectacular/swagger_ui.js index 978945cf..2ca3308d 100644 --- a/drf_spectacular/templates/drf_spectacular/swagger_ui.js +++ b/drf_spectacular/templates/drf_spectacular/swagger_ui.js @@ -2,7 +2,7 @@ const swagger_settings = {{settings|safe}} const ui = SwaggerUIBundle({ url: "{{schema_url|safe}}", - dom_id: '#swagger-ui', + dom_id: "#swagger-ui", presets: [ SwaggerUIBundle.presets.apis, ], @@ -11,7 +11,7 @@ const ui = SwaggerUIBundle({ ], layout: "BaseLayout", requestInterceptor: (request) => { - request.headers['X-CSRFToken'] = "{{csrf_token}}" + request.headers["X-CSRFToken"] = "{{csrf_token}}" return request; }, ...swagger_settings diff --git a/drf_spectacular/views.py b/drf_spectacular/views.py index adc61d03..bb43d0af 100644 --- a/drf_spectacular/views.py +++ b/drf_spectacular/views.py @@ -81,6 +81,31 @@ class SpectacularSwaggerView(APIView): template_name = 'drf_spectacular/swagger_ui.html' template_name_js = 'drf_spectacular/swagger_ui.js' + @extend_schema(exclude=True) + def get(self, request, *args, **kwargs): + schema_url = self.url or get_relative_url(reverse(self.url_name, request=request)) + return Response( + data={ + 'dist': spectacular_settings.SWAGGER_UI_DIST, + 'favicon_href': spectacular_settings.SWAGGER_UI_FAVICON_HREF, + 'schema_url': set_query_parameters( + url=schema_url, + lang=request.GET.get('lang') + ), + 'settings': json.dumps(spectacular_settings.SWAGGER_UI_SETTINGS), + 'template_name_js': self.template_name_js + }, + template_name=self.template_name, + ) + + +class SpectacularSwaggerSplitView(SpectacularSwaggerView): + """ + Alternate Swagger UI implementation that separates the html request from the + javascript request to cater to web servers with stricter CSP policies. + """ + url_self = None + @extend_schema(exclude=True) def get(self, request, *args, **kwargs): if request.GET.get('script') is not None: @@ -97,7 +122,7 @@ def get(self, request, *args, **kwargs): content_type='application/javascript', ) else: - script_url = request.get_full_path() + script_url = self.url_self or request.get_full_path() return Response( data={ 'dist': spectacular_settings.SWAGGER_UI_DIST, diff --git a/tests/test_i18n.py b/tests/test_i18n.py index 8599fca2..a5c6d60d 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -91,6 +91,4 @@ def test_i18n_schema(no_warnings, url, header, translated): @pytest.mark.urls(__name__) def test_i18n_schema_ui(no_warnings): response = APIClient().get('/api/schema/swagger-ui/?lang=de') - assert b'/api/schema/swagger-ui/?lang=de&script=' in response.content - response = APIClient().get('/api/schema/swagger-ui/?lang=de&script=') assert b'/api/schema/?lang=de' in response.content diff --git a/tests/test_view.py b/tests/test_view.py index 03a8b91c..ac5f167e 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -8,7 +8,9 @@ from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema from drf_spectacular.validation import validate_schema -from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView +from drf_spectacular.views import ( + SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerSplitView, SpectacularSwaggerView, +) @extend_schema(responses=OpenApiTypes.FLOAT) @@ -25,7 +27,8 @@ def pi(request): urlpatterns_v2 = [ path('api/v2/pi/', pi), path('api/v2/pi-fast/', pi), - path('api/v2/schema/swagger-ui/', SpectacularSwaggerView.as_view(), name='swagger-ui'), + path('api/v2/schema/swagger-ui/', SpectacularSwaggerView.as_view(), name='swagger'), + path('api/v2/schema/swagger-ui-alt/', SpectacularSwaggerSplitView.as_view(), name='swagger-alt'), path('api/v2/schema/redoc/', SpectacularRedocView.as_view(), name='redoc'), ] urlpatterns_v2.append( @@ -95,12 +98,17 @@ def test_spectacular_ui_view(no_warnings, ui): response = APIClient().get(f'/api/v2/schema/{ui}/') assert response.status_code == 200 assert response.content.startswith(b'') + assert b'"/api/v2/schema/"' in response.content + - if ui == 'redoc': - assert b'"/api/v2/schema/"' in response.content - else: - assert b'"/api/v2/schema/swagger-ui/?script="' in response.content - # second request to obtain swagger config (CSP self) - response = APIClient().get('/api/v2/schema/swagger-ui/?script=') - assert response.status_code == 200 - assert b'"/api/v2/schema/"' in response.content +@pytest.mark.urls(__name__) +def test_spectacular_swagger_ui_alternate(no_warnings): + # first request for the html + response = APIClient().get('/api/v2/schema/swagger-ui-alt/') + assert response.status_code == 200 + assert response.content.startswith(b'') + # assert b'"/api/v2/schema/swagger-ui-alt/?script="' in response.content + # second request to obtain js swagger config (CSP self) + response = APIClient().get('/api/v2/schema/swagger-ui-alt/?script=') + assert response.status_code == 200 + assert b'"/api/v2/schema/"' in response.content