I've setup DRF-YASG but it seems not able to hide APIs that require Authentication.
Below is the configuration.
public_apis = [
# sample-end-point
url(f'{URL_VERSION}/sample-end-point/', include(sample_end_point.urls)), # requires authentication
]
schema_view = get_schema_view(
openapi.Info(
title="Swagger Doc",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="[email protected]"),
license=openapi.License(name="BSD License"),
),
validators=['flex', 'ssv'],
permission_classes=(permissions.AllowAny,),
public=False,
patterns=public_apis,
)
urlpatterns = public_apis + [
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
]
With the above configuration, the sample-end-point does not appear on the SwaggerUi. It only shows the Authorize Button and text that says No operations defined in spec!. But if I change thepublic=False to public=True setting, the sample-end-point appears. But that is not what I want as this URL should be accessible only after one logs in.
Is there something am not understanding or a setting am missing?
My Swagger settings are as below:
SWAGGER_SETTINGS = {
'USE_SESSION_AUTH': False,
'SECURITY_DEFINITIONS': {
'api_key': {
'type': 'apiKey',
'in': 'header',
'name': 'Authorization'
}
},
'APIS_SORTER': 'alpha',
'SUPPORTED_SUBMIT_METHODS': ['get', 'post', 'put', 'delete', 'patch'],
'OPERATIONS_SORTER': 'alpha'
}
I'd posted the issue on Stack Overflow
Hello!
The public parameter is used to hide APIs from the generated specification according to the permissions of the user who views the API documentation page. With public=True, anyone who has access to view the swagger page can see ALL endpoints in the api, while with False, they would only see the ones they can actually call according to their current credentials.
If you want to restrict who has access to the page itself, that would be achived, as with any regular view, via permission_classes. The get_schema_view method also provides it as a convenience argument, so you could achieve what you want simply by replacing permission_classes=(permissions.AllowAny,), with, say, permission_classes=(permissions.IsAuthenticated,),, or whatever floats your boat.
The main thing to take away is that the permission_classes on the swagger view are unrelated to the public argument to the spec generator - one controls who can VIEW the spec, while the other controls what endpoints are included in it.
Just now read the stack overflow question more closely - the error you are getting happens because you try to access the endpoint without authentication. I see that the error handling is not exactly on point for this case.
You could wrap the views in login_required:
https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/#decorating-class-based-views
from django.contrib.auth.decorators import login_required,
.. login_required(schema_view.with_ui('swagger', cache_timeout=None)) ... # same for the other two
Great explanation, i stumbled upon this too.
👍✨
@axnsan12 thanks for the guide. However, I tried the following...
from django.contrib.auth.decorators import login_required
schema_view = get_schema_view(
openapi.Info(
title="Swagger Doc",
default_version='v1',
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="[email protected]"),
license=openapi.License(name="BSD License"),
),
# validators=['flex', 'ssv'],
permission_classes=(permissions.IsAuthenticated,), # If I change the permission it throws an exception
public=False,
patterns=public_apis,
)
And URLs, I added the login_required...
url(r'^docs(?P<format>.json|.yaml)$', login_required(schema_view.without_ui(cache_timeout=None)), name='schema-json'),
url(r'^docs/$', login_required(schema_view.with_ui('swagger', cache_timeout=None)), name='schema-swagger-ui'),
url(r'^redoc/$', login_required(schema_view.with_ui('redoc', cache_timeout=None)), name='schema-redoc'),
permissions.AllowAny:
With permissions.AllowAny and public=false, the APIs that require to be hidden before someone has authenticated do not show but the Swagger view loads. Just that it does not have any API on it.
Any other Permission Type:
With permissions.IsAuthenticated or _-any other permission for that matter-_ , the swagger view fails with the error below.
Internal Server Error: /swagger/
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 41, in inner
response = get_response(request)
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 217, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/usr/local/lib/python3.6/site-packages/django/core/handlers/base.py", line 215, in _get_response
response = response.render()
File "/usr/local/lib/python3.6/site-packages/django/template/response.py", line 107, in render
self.content = self.rendered_content
File "/usr/local/lib/python3.6/site-packages/rest_framework/response.py", line 72, in rendered_content
ret = renderer.render(self.data, accepted_media_type, context)
File "/usr/local/lib/python3.6/site-packages/drf_yasg/renderers.py", line 54, in render
self.set_context(renderer_context, swagger)
File "/usr/local/lib/python3.6/site-packages/drf_yasg/renderers.py", line 62, in set_context
renderer_context['title'] = swagger.info.title
AttributeError: 'dict' object has no attribute 'info'
Internal Server Error: /swagger/
So am not sure what am doing wrong..
@chgad any tips?
So, the 'dict' object has no attribute 'info' error you are getting happens because the renderer receives a plain dict and fails to check for non-200 staus code (here), so any outcome SchemaView#get except success would trigger the error.
My assumption was that your error happened because of lacking authentication, but it might also happen for other reasons, running with a debugger might help - the error details probably arrive as the swagger parameter of render.
I'll try to fix the error handling this weekend to avoid burying the error like this.
@axnsan12 I also got the same error. In my case it's permissions.IsAdminUser.
Analysing it with my Debugger lead to the following statement : the local variable swagger is dictionary which only contains {'detail'."Authentication credentials were not provided."}.
After seeing this i first loggedin the admin panel and the tried again.
Aaaaaaaaaaaaaaaaaaaaaaand it worked, so my first guess is, that the swagger Renderer does not comunicate with the Authenticationbackend correctly.
I'd like to help further but i don't see where the swagger variable enters and where it's origin is.
@chgad it enters via the response object returned by the view, as is normal for django rest framework renderers (http://www.django-rest-framework.org/api-guide/renderers/).
The request and response are passed in renderer_context, and the problem is that swagger is expected to be a Swagger object, which is not the case for a non-200 response. I don't yet know exactly what would be the correct treatment, should probably somehow render an error page.
Finally worked.
Turned out the issue was that I had only rest_framework_jwt.authentication.JSONWebTokenAuthentication in REST_FRAMEWORK's DEFAULT_AUTHENTICATION_CLASSES. So I guess the request object sent after logging in via the Django Admin login view did not have a user so all permission classes kept failing and it would then lead to the above error?
So to solve it I added 'rest_framework.authentication.SessionAuthentication', so the login works okay and drf-yasg shows all its glory.
@axnsan12 good job on this. Really solved a big documentation gap.
If i add rest_framework.authentication.SessionAuthentication, my apis start working without auth token in the header
@ihd2911 same here. Did you find a solution?
No, it didn't. I had to change package
Most helpful comment
Hello!
The
publicparameter is used to hide APIs from the generated specification according to the permissions of the user who views the API documentation page. Withpublic=True, anyone who has access to view the swagger page can see ALL endpoints in the api, while withFalse, they would only see the ones they can actually call according to their current credentials.If you want to restrict who has access to the page itself, that would be achived, as with any regular view, via
permission_classes. Theget_schema_viewmethod also provides it as a convenience argument, so you could achieve what you want simply by replacingpermission_classes=(permissions.AllowAny,),with, say,permission_classes=(permissions.IsAuthenticated,),, or whatever floats your boat.The main thing to take away is that the
permission_classeson the swagger view are unrelated to thepublicargument to the spec generator - one controls who can VIEW the spec, while the other controls what endpoints are included in it.