Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions ansible_base/api_documentation/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView

from ansible_base.api_documentation.apps import ApiDocumentationConfig
from ansible_base.api_documentation.views import DocsRootView

app_name = ApiDocumentationConfig.label
api_version_urls = [
path('docs/schema/', SpectacularAPIView.as_view(), name='schema'),
path('docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('docs/', DocsRootView.as_view(), name='docs-root'),
path('docs/swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('docs/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
path('docs/schema/', SpectacularAPIView.as_view(), name='schema'),
]
19 changes: 19 additions & 0 deletions ansible_base/api_documentation/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from collections import OrderedDict

from rest_framework import permissions
from rest_framework.response import Response

from ansible_base.lib.utils.response import get_relative_url
from ansible_base.lib.utils.views.django_app_api import AnsibleBaseDjangoAppApiView


class DocsRootView(AnsibleBaseDjangoAppApiView):
permission_classes = [permissions.AllowAny]

def get(self, request, format=None):
'''Index of API documentation endpoints'''
data = OrderedDict()
data['swagger'] = get_relative_url('swagger-ui')
data['redoc'] = get_relative_url('redoc')
data['schema'] = get_relative_url('schema')
return Response(data)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs root bypasses default auth settings

Medium Severity

DocsRootView hard-codes permission_classes = [permissions.AllowAny], which can override a deployment’s default DRF permission policy and make /docs/ publicly accessible even when the rest of the API (and possibly the docs endpoints) are intended to require authentication.

Fix in Cursor Fix in Web

6 changes: 6 additions & 0 deletions docs/apps/api_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ see the instructions in the [x_ai_description_guide](./ai_documentation/x_ai_des

This feature includes URLs which you will get if you are using [dynamic urls](../..//Installation.md)

The following endpoints are provided:
- `docs/` - Index page listing available documentation endpoints
- `docs/swagger/` - Swagger UI
- `docs/redoc/` - ReDoc UI
- `docs/schema/` - OpenAPI schema export

If you want to manually add the urls without dynamic urls add the following to your urls.py:
```
from ansible_base.api_documentation import urls
Expand Down
46 changes: 46 additions & 0 deletions test_app/tests/api_documentation/test_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
Tests for API documentation views.
"""


def test_docs_root_view_returns_index(unauthenticated_api_client):
"""Test that the docs root endpoint returns an index of documentation endpoints."""
url = '/api/v1/docs/'
response = unauthenticated_api_client.get(url)

assert response.status_code == 200
data = response.data

# Verify all expected keys are present
assert 'swagger' in data
assert 'redoc' in data
assert 'schema' in data

# Verify URLs point to the correct endpoints
assert '/docs/swagger/' in data['swagger']
assert '/docs/redoc/' in data['redoc']
assert '/docs/schema/' in data['schema']


def test_docs_root_view_allows_unauthenticated_access(unauthenticated_api_client):
"""Test that the docs root endpoint is accessible without authentication."""
url = '/api/v1/docs/'
response = unauthenticated_api_client.get(url)

assert response.status_code == 200


def test_swagger_ui_accessible(unauthenticated_api_client):
"""Test that Swagger UI is accessible at the new URL."""
url = '/api/v1/docs/swagger/'
response = unauthenticated_api_client.get(url)

assert response.status_code == 200


def test_redoc_accessible(unauthenticated_api_client):
"""Test that ReDoc is accessible."""
url = '/api/v1/docs/redoc/'
response = unauthenticated_api_client.get(url)

assert response.status_code == 200
Loading