add /media generic view (#75378)
gitea/passerelle/pipeline/head This commit looks good Details

This commit is contained in:
Thomas NOËL 2023-03-14 16:23:17 +01:00 committed by Gitea
parent 019559f0c1
commit 88f787afe2
3 changed files with 80 additions and 9 deletions

View File

@ -1,9 +1,7 @@
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import include, path, re_path
from django.views.static import serve as static_serve
from .api.urls import urlpatterns as api_urls
from .base.urls import access_urlpatterns, import_export_urlpatterns
@ -22,6 +20,7 @@ from .views import (
HomePageView,
ManageAddView,
ManageView,
MediaView,
login,
logout,
menu_json,
@ -34,13 +33,7 @@ urlpatterns = [
path('manage/', manager_required(ManageView.as_view()), name='manage-home'),
re_path(r'^manage/menu.json$', manager_required(menu_json), name='menu-json'),
path('manage/add', manager_required(ManageAddView.as_view()), name='add-connector'),
re_path(
r'^media/(?P<path>.*)$',
login_required(static_serve),
{
'document_root': settings.MEDIA_ROOT,
},
),
re_path(r'^media/(?P<path>.*)$', manager_required(MediaView.as_view()), name='media'),
re_path(r'^admin/', admin.site.urls),
re_path(r'^manage/access/', decorated_includes(manager_required, include(access_urlpatterns))),
re_path(r'^manage/', decorated_includes(manager_required, include(import_export_urlpatterns))),

View File

@ -19,6 +19,7 @@ import hashlib
import inspect
import json
import logging
import os
import uuid
from urllib.parse import quote
@ -29,6 +30,7 @@ from django.contrib.auth import logout as auth_logout
from django.contrib.auth import views as auth_views
from django.core.cache import cache
from django.core.exceptions import PermissionDenied
from django.core.files.storage import DefaultStorage
from django.db import transaction
from django.db.models import Q
from django.http import Http404, HttpResponse, HttpResponseRedirect
@ -50,6 +52,7 @@ from django.views.generic import (
View,
)
from django.views.generic.detail import SingleObjectMixin
from django.views.static import serve
from jsonschema import ValidationError, validate, validators
from passerelle.base.models import BaseResource, ResourceLog
@ -629,3 +632,18 @@ class GenericExportConnectorView(GenericConnectorMixin, DetailView):
)
json.dump({'resources': [self.get_object().export_json()]}, response, indent=2)
return response
class MediaView(View):
def get(self, request, path, *args, **kwargs):
document_root = DefaultStorage().location
filename = DefaultStorage().path(path)
filename = os.path.realpath(filename)
if (
not os.path.isabs(filename)
or not filename.startswith(document_root)
or not os.path.exists(filename)
or not os.path.isfile(filename)
):
raise Http404()
return serve(request, path, document_root=document_root, show_indexes=False)

60
tests/test_media.py Normal file
View File

@ -0,0 +1,60 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2023 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from django.core.files.base import ContentFile
from passerelle.apps.pdf.models import Resource
from tests.test_manager import login
from tests.utils import setup_access_rights
@pytest.fixture
def pdf(db):
return setup_access_rights(Resource.objects.create(slug='test', title='test', description='test'))
@pytest.fixture
def cerfa_content():
with open('tests/data/cerfa_10072-02.pdf', 'rb') as fd:
return fd.read()
def test_media(app, admin_user, simple_user, pdf, cerfa_content):
pdf.fill_form_file.save('form.pdf', ContentFile(cerfa_content))
# refuse anonymous or simple user
resp = app.get('/media/pdf/test/form.pdf', status=302)
assert resp.location == '/login/?next=/media/pdf/test/form.pdf'
app = login(app, username='user', password='user')
resp = app.get('/media/pdf/test/form.pdf', status=403)
# allow manager access
app = login(app, username='admin', password='admin')
resp = app.get('/media/pdf/test/form.pdf')
assert resp.content.startswith(b'%PDF')
assert resp.headers['content-type'] == 'application/pdf'
# bad requests: 404 or 400
resp = app.get('/media/pdf/plop/there-is-not-file-here.pdf', status=404)
resp = app.get('/media/pdf/bad-slug/form.pdf', status=404)
resp = app.get('/media/pdf/', status=404)
resp = app.get('/media/pdf', status=404)
resp = app.get('/media/', status=404)
resp = app.get('/media/../etc/passwd', status=400)
resp = app.get('/media/../../../../../../../../etc/passwd', status=400)