mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-05 20:51:26 +00:00
feat: Securisation du téléchargement de fichier
This commit is contained in:
@ -3,6 +3,7 @@ from django.urls import path, re_path
|
||||
from .views import (
|
||||
DomainListCreateView, DomainDetailView,
|
||||
CategoryListCreateView, CategoryDetailView,
|
||||
ServeFileView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
@ -11,4 +12,6 @@ urlpatterns = [
|
||||
|
||||
re_path(r'^categories$', CategoryListCreateView.as_view(), name="category_list_create"),
|
||||
re_path(r'^categories/(?P<id>[0-9]+)$', CategoryDetailView.as_view(), name="category_detail"),
|
||||
|
||||
path('serve-file/', ServeFileView.as_view(), name="serve_file"),
|
||||
]
|
||||
@ -1,3 +1,8 @@
|
||||
import os
|
||||
import mimetypes
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import FileResponse
|
||||
from django.http.response import JsonResponse
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie, csrf_protect
|
||||
from django.utils.decorators import method_decorator
|
||||
@ -117,3 +122,55 @@ class CategoryDetailView(APIView):
|
||||
return JsonResponse({'message': 'Deleted'}, safe=False)
|
||||
except Category.DoesNotExist:
|
||||
return JsonResponse({'error': 'No object found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class ServeFileView(APIView):
|
||||
"""Sert les fichiers media de manière sécurisée avec authentification JWT."""
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
file_path = request.query_params.get('path', '')
|
||||
|
||||
if not file_path:
|
||||
return JsonResponse(
|
||||
{'error': 'Le paramètre "path" est requis'},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Nettoyer le préfixe /data/ si présent
|
||||
if file_path.startswith('/data/'):
|
||||
file_path = file_path[len('/data/'):]
|
||||
elif file_path.startswith('data/'):
|
||||
file_path = file_path[len('data/'):]
|
||||
|
||||
# Construire le chemin absolu et le résoudre pour éliminer les traversals
|
||||
absolute_path = os.path.realpath(
|
||||
os.path.join(settings.MEDIA_ROOT, file_path)
|
||||
)
|
||||
|
||||
# Protection contre le path traversal
|
||||
media_root = os.path.realpath(settings.MEDIA_ROOT)
|
||||
if not absolute_path.startswith(media_root + os.sep) and absolute_path != media_root:
|
||||
return JsonResponse(
|
||||
{'error': 'Accès non autorisé'},
|
||||
status=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
|
||||
if not os.path.isfile(absolute_path):
|
||||
return JsonResponse(
|
||||
{'error': 'Fichier introuvable'},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
content_type, _ = mimetypes.guess_type(absolute_path)
|
||||
if content_type is None:
|
||||
content_type = 'application/octet-stream'
|
||||
|
||||
response = FileResponse(
|
||||
open(absolute_path, 'rb'),
|
||||
content_type=content_type,
|
||||
)
|
||||
response['Content-Disposition'] = (
|
||||
f'inline; filename="{os.path.basename(absolute_path)}"'
|
||||
)
|
||||
return response
|
||||
|
||||
Reference in New Issue
Block a user