wip/75378-add-media-generic-view #139
|
@ -1,9 +1,7 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
from django.urls import include, path, re_path
|
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 .api.urls import urlpatterns as api_urls
|
||||||
from .base.urls import access_urlpatterns, import_export_urlpatterns
|
from .base.urls import access_urlpatterns, import_export_urlpatterns
|
||||||
|
@ -22,6 +20,7 @@ from .views import (
|
||||||
HomePageView,
|
HomePageView,
|
||||||
ManageAddView,
|
ManageAddView,
|
||||||
ManageView,
|
ManageView,
|
||||||
|
MediaView,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
menu_json,
|
menu_json,
|
||||||
|
@ -34,13 +33,7 @@ urlpatterns = [
|
||||||
path('manage/', manager_required(ManageView.as_view()), name='manage-home'),
|
path('manage/', manager_required(ManageView.as_view()), name='manage-home'),
|
||||||
re_path(r'^manage/menu.json$', manager_required(menu_json), name='menu-json'),
|
re_path(r'^manage/menu.json$', manager_required(menu_json), name='menu-json'),
|
||||||
path('manage/add', manager_required(ManageAddView.as_view()), name='add-connector'),
|
path('manage/add', manager_required(ManageAddView.as_view()), name='add-connector'),
|
||||||
re_path(
|
re_path(r'^media/(?P<path>.*)$', manager_required(MediaView.as_view()), name='media'),
|
||||||
r'^media/(?P<path>.*)$',
|
|
||||||
login_required(static_serve),
|
|
||||||
{
|
|
||||||
'document_root': settings.MEDIA_ROOT,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
re_path(r'^admin/', admin.site.urls),
|
re_path(r'^admin/', admin.site.urls),
|
||||||
re_path(r'^manage/access/', decorated_includes(manager_required, include(access_urlpatterns))),
|
re_path(r'^manage/access/', decorated_includes(manager_required, include(access_urlpatterns))),
|
||||||
re_path(r'^manage/', decorated_includes(manager_required, include(import_export_urlpatterns))),
|
re_path(r'^manage/', decorated_includes(manager_required, include(import_export_urlpatterns))),
|
||||||
|
|
|
@ -19,6 +19,7 @@ import hashlib
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from urllib.parse import quote
|
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.contrib.auth import views as auth_views
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.core.files.storage import DefaultStorage
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
|
@ -50,6 +52,7 @@ from django.views.generic import (
|
||||||
View,
|
View,
|
||||||
)
|
)
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
from django.views.static import serve
|
||||||
from jsonschema import ValidationError, validate, validators
|
from jsonschema import ValidationError, validate, validators
|
||||||
|
|
||||||
from passerelle.base.models import BaseResource, ResourceLog
|
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)
|
json.dump({'resources': [self.get_object().export_json()]}, response, indent=2)
|
||||||
return response
|
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)
|
||||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue