Evaluate file access permissions

This commit is contained in:
2026-01-04 17:49:33 +01:00
parent 49c8d804d6
commit bfb0dc18cd
2 changed files with 38 additions and 2 deletions

View File

@@ -21,7 +21,7 @@ from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from ram.views import UploadImage from ram.views import UploadImage, DownloadFile
from portal.views import Render404 from portal.views import Render404
handler404 = Render404.as_view() handler404 = Render404.as_view()
@@ -32,6 +32,7 @@ urlpatterns = [
path("tinymce/upload_image", UploadImage.as_view(), name="upload_image"), path("tinymce/upload_image", UploadImage.as_view(), name="upload_image"),
path("portal/", include("portal.urls")), path("portal/", include("portal.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("media/files/<path:filename>", DownloadFile.as_view(), name="download_file"),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Enable the "/dcc" routing only if the "driver" app is active # Enable the "/dcc" routing only if the "driver" app is active

View File

@@ -5,19 +5,25 @@ import posixpath
from pathlib import Path from pathlib import Path
from PIL import Image, UnidentifiedImageError from PIL import Image, UnidentifiedImageError
from django.views import View from django.apps import apps
from django.conf import settings from django.conf import settings
from django.http import ( from django.http import (
Http404,
HttpResponseBadRequest, HttpResponseBadRequest,
HttpResponseForbidden, HttpResponseForbidden,
FileResponse,
JsonResponse, JsonResponse,
) )
from django.views import View
from django.utils.text import slugify as slugify from django.utils.text import slugify as slugify
from django.utils.encoding import smart_str
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from rest_framework.pagination import LimitOffsetPagination from rest_framework.pagination import LimitOffsetPagination
from ram.models import PrivateDocument
class CustomLimitOffsetPagination(LimitOffsetPagination): class CustomLimitOffsetPagination(LimitOffsetPagination):
default_limit = 10 default_limit = 10
@@ -67,3 +73,32 @@ class UploadImage(View):
), ),
} }
) )
class DownloadFile(View):
def get(self, request, filename):
# Clean up the filename to prevent directory traversal attacks
filename = os.path.basename(filename)
# Find a document where the stored file name matches
# Find all models inheriting from PublishableFile
for model in apps.get_models():
if issubclass(model, PrivateDocument) and not model._meta.abstract:
try:
doc = model.objects.get(file__endswith=filename)
if doc.private and not request.user.is_staff:
raise Http404("File not found")
file_path = doc.file.path
if not os.path.exists(file_path):
raise Http404("File not found")
response = FileResponse(open(file_path, "rb"), as_attachment=True)
response["Content-Disposition"] = (
f'attachment; filename="{smart_str(os.path.basename(file_path))}"'
)
return response
except model.DoesNotExist:
continue
raise Http404("File not found")