Browse Source

feat: implement Swagger authentication and permissions for admin and token users

master
mortezaei 10 months ago
parent
commit
e25fcef5bd
  1. 42
      apps/api/decorators.py
  2. 60
      apps/api/permissions.py
  3. 13
      apps/api/views/swagger_views.py
  4. 10
      config/urls.py
  5. 9
      templates/swagger/ui.html

42
apps/api/decorators.py

@ -0,0 +1,42 @@
from functools import wraps
from django.http import HttpResponseForbidden
from django.contrib.auth.models import AnonymousUser
from django.views.decorators.csrf import csrf_exempt
from rest_framework.authtoken.models import Token
def swagger_auth_required(view_func):
"""
Decorator that requires either admin authentication or valid swagger token
"""
@csrf_exempt
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
# Check if user is admin
if request.user and request.user.is_authenticated and request.user.is_staff:
return view_func(request, *args, **kwargs)
# Check swagger token in session
swagger_token = request.session.get('swagger_token')
if swagger_token:
try:
token_obj = Token.objects.get(key=swagger_token)
if token_obj.user.is_active:
return view_func(request, *args, **kwargs)
except Token.DoesNotExist:
pass
# Check Authorization header
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if auth_header.startswith('Token '):
token = auth_header.split(' ')[1]
try:
token_obj = Token.objects.get(key=token)
if token_obj.user.is_active:
return view_func(request, *args, **kwargs)
except Token.DoesNotExist:
pass
return HttpResponseForbidden("Access denied. Admin authentication or valid token required.")
return _wrapped_view

60
apps/api/permissions.py

@ -0,0 +1,60 @@
from rest_framework import permissions
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
class SwaggerTokenPermission(permissions.BasePermission):
"""
Custom permission for Swagger that allows access to authenticated users via token
or admin users via session authentication
"""
def has_permission(self, request, view):
# Check if user is admin (for session-based access)
if request.user and request.user.is_authenticated and request.user.is_staff:
return True
# Check for token in session (from our custom auth system)
swagger_token = request.session.get('swagger_token')
if swagger_token:
try:
token_obj = Token.objects.get(key=swagger_token)
if token_obj.user.is_active:
return True
except Token.DoesNotExist:
pass
# Check for Authorization header
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if auth_header.startswith('Token '):
token = auth_header.split(' ')[1]
try:
token_obj = Token.objects.get(key=token)
if token_obj.user.is_active:
return True
except Token.DoesNotExist:
pass
return False
class IsAdminOrSwaggerToken(permissions.BasePermission):
"""
Permission that allows access to admin users or users with valid swagger token
"""
def has_permission(self, request, view):
# Allow admin users
if request.user and request.user.is_authenticated and request.user.is_staff:
return True
# Check swagger token in session
swagger_token = request.session.get('swagger_token')
if swagger_token:
try:
token_obj = Token.objects.get(key=swagger_token)
return token_obj.user.is_active
except Token.DoesNotExist:
pass
return False

13
apps/api/views/swagger_views.py

@ -3,17 +3,26 @@ from django.views import View
from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.urls import reverse
from rest_framework.authtoken.models import Token
@method_decorator(staff_member_required, name='dispatch')
@method_decorator([staff_member_required, csrf_exempt], name='dispatch')
class CustomSwaggerView(View):
"""
Custom Swagger UI view with authentication banner
Requires admin login to access
"""
def get(self, request):
# Generate dynamic swagger spec URL based on current language
try:
swagger_spec_url = reverse('schema-json', kwargs={'format': '.json'})
except:
# Fallback to hardcoded URL if reverse fails
swagger_spec_url = '/en/swagger.json'
context = {
'swagger_spec_url': '/en/swagger.json', # Adjust based on your URL structure
'swagger_spec_url': swagger_spec_url,
'request': request,
}
return render(request, 'swagger/ui.html', context)

10
config/urls.py

@ -39,8 +39,10 @@ from filer import views
# Import custom API views
from apps.api.views import CustomAPIDocumentationView, CustomSwaggerView, SwaggerTokenAuthView, clear_swagger_auth
from apps.api.permissions import IsAdminOrSwaggerToken
from apps.api.decorators import swagger_auth_required
# Restricted schema view for admin users only
# Restricted schema view for admin users and authenticated swagger users
schema_view = get_schema_view(
openapi.Info(
title="Imam Javad API",
@ -50,7 +52,7 @@ schema_view = get_schema_view(
license=openapi.License(name="MIT License"),
),
public=False,
permission_classes=(permissions.IsAdminUser,),
permission_classes=(IsAdminOrSwaggerToken,),
)
@ -105,11 +107,11 @@ swagger_urlpatterns = [
path('swagger-auth/', SwaggerTokenAuthView.as_view(), name='swagger-token-auth'),
path('swagger-auth/clear/', clear_swagger_auth, name='clear-swagger-auth'),
re_path(r'^swagger(?P<format>\.json|\.yaml)$',
staff_member_required(schema_view.without_ui(cache_timeout=0)),
swagger_auth_required(schema_view.without_ui(cache_timeout=0)),
name='schema-json'),
path('swagger/', CustomSwaggerView.as_view(), name='schema-swagger-ui'),
re_path(r'^redoc/$',
staff_member_required(schema_view.with_ui('redoc', cache_timeout=0)),
swagger_auth_required(schema_view.with_ui('redoc', cache_timeout=0)),
name='schema-redoc'),
]

9
templates/swagger/ui.html

@ -1,9 +1,11 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Imam Javad API - Swagger UI</title>
{% csrf_token %}
<!-- Swagger UI CSS -->
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui.css" />
@ -310,6 +312,13 @@
{% if request.session.swagger_token %}
request.headers['Authorization'] = 'Token {{ request.session.swagger_token }}';
{% endif %}
// Add CSRF token for Django
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]');
if (csrfToken) {
request.headers['X-CSRFToken'] = csrfToken.value;
}
return request;
},
responseInterceptor: function(response) {

Loading…
Cancel
Save