backoffice: fix access to uploaded file in file widget (#70194)

This commit is contained in:
Frédéric Péters 2022-10-12 21:06:16 +02:00
parent 00230b311b
commit 36110d8082
4 changed files with 106 additions and 48 deletions

View File

@ -8,6 +8,7 @@ import zipfile
import pytest
import responses
from webtest import Upload
import wcs.qommon.storage as st
from wcs import fields
@ -4877,3 +4878,55 @@ def test_backoffice_dispatch_single_user(pub, user_template):
assert formdata.workflow_roles == {'_foobar': [user.roles[0]]}
assert 'button_a_button' in resp.text
def test_backoffice_workflow_form_file_access(pub):
FormDef.wipe()
Workflow.wipe()
role = pub.role_class(name='xxx1')
role.store()
user = create_superuser(pub)
user.roles.append(role.id)
user.store()
wf = Workflow(name='test')
status = wf.add_status('New', 'st1')
next_status = wf.add_status('Next', 'st2')
status.items = []
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_receiver']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.FileField(id='1', label='test', type='file', varname='file'),
fields.StringField(id='2', label='test2', type='string', required=True),
]
jump = status.add_action('jumponsubmit', id='_jump')
jump.status = next_status.id
wf.store()
formdef = FormDef()
formdef.name = 'test'
formdef.workflow_id = wf.id
formdef.workflow_roles = {'_receiver': role.id}
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
app = login(get_app(pub))
resp = app.get(formdata.get_url(backoffice=True))
resp.form['fblah_1$file'] = Upload('test3.txt', b'foobar3', 'text/plain')
resp = resp.form.submit('submit')
# it will fail on the equired string field; this allows testing
# the temporary file URL.
assert resp.click('test3.txt').body == b'foobar3'

View File

@ -41,7 +41,7 @@ from wcs.conditions import Condition
from wcs.formdata import FormData
from wcs.formdef import FormDef
from wcs.forms.backoffice import FormDefUI
from wcs.forms.common import FormStatusPage
from wcs.forms.common import FormdefDirectoryBase, FormStatusPage
from wcs.roles import logged_users_role
from wcs.variables import LazyFieldVar, LazyList
from wcs.workflows import ActionsTracingEvolutionPart, WorkflowStatusItem, item_classes, template_on_formdata
@ -752,7 +752,7 @@ class ManagementDirectory(Directory):
return FormPage(component)
class FormPage(Directory):
class FormPage(FormdefDirectoryBase):
do_not_call_in_templates = True
_q_exports = [
'',
@ -2926,7 +2926,7 @@ class FormPage(Directory):
except KeyError:
raise errors.TraversalError()
return FormBackOfficeStatusPage(self.formdef, filled)
return FormBackOfficeStatusPage(self.formdef, filled, parent_view=self)
def live(self):
return FormBackofficeEditPage(self.formdef.url_name).live()
@ -2951,6 +2951,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
'action',
'live',
'inspect',
'tempfile',
('inspect-tool', 'inspect_tool'),
('download-as-zip', 'download_as_zip'),
('lateral-block', 'lateral_block'),

View File

@ -941,3 +941,48 @@ class FormStatusPage(Directory, FormTemplateMixin):
return f._q_index()
raise errors.AccessForbiddenError()
class FormdefDirectoryBase(Directory):
user = None
def tempfile(self):
get_request().ignore_session = True
self.check_access()
if self.user and not self.user.id == get_session().user:
self.check_receiver()
try:
t = get_request().form['t']
tempfile = get_session().get_tempfile(t)
except KeyError:
raise errors.TraversalError()
if tempfile is None:
raise errors.TraversalError()
response = get_response()
# force potential HTML upload to be used as-is (not decorated with theme)
# and with minimal permissions
response.filter = {}
response.set_header(
'Content-Security-Policy',
'default-src \'none\'; img-src %s;' % get_request().build_absolute_uri(),
)
if tempfile['content_type']:
response.set_content_type(tempfile['content_type'])
else:
response.set_content_type('application/octet-stream')
if tempfile['charset']:
response.set_charset(tempfile['charset'])
if get_request().form.get('thumbnail') == '1':
try:
thumbnail = misc.get_thumbnail(
get_session().get_tempfile_path(t), content_type=tempfile['content_type']
)
except misc.ThumbnailError:
pass
else:
response.set_content_type('image/png')
return thumbnail
return get_session().get_tempfile_content(t).get_file_pointer().read()

View File

@ -37,7 +37,7 @@ from wcs.categories import Category
from wcs.fields import MissingBlockFieldError, SetValueError
from wcs.formdata import Evolution, FormData
from wcs.formdef import FormDef
from wcs.forms.common import FormStatusPage, FormTemplateMixin
from wcs.forms.common import FormdefDirectoryBase, FormStatusPage, FormTemplateMixin
from wcs.qommon.admin.texts import TextsDirectory
from wcs.qommon.form import get_selection_error_text
from wcs.qommon.storage import Equal, NothingToUpdate
@ -256,7 +256,7 @@ class TrackingCodesDirectory(Directory):
return TrackingCodeDirectory(component, self.formdef)
class FormPage(Directory, FormTemplateMixin):
class FormPage(FormdefDirectoryBase, FormTemplateMixin):
_q_exports = [
'',
'tempfile',
@ -312,7 +312,7 @@ class FormPage(Directory, FormTemplateMixin):
def go_to_backoffice(self):
return redirect(self.formdef.get_admin_url())
def check_role(self):
def check_access(self):
if self.formdef.roles:
if not self.user:
raise errors.AccessUnauthorizedError()
@ -941,7 +941,7 @@ class FormPage(Directory, FormTemplateMixin):
self._pages = None
def _q_index(self):
self.check_role()
self.check_access()
authentication_context_check_result = self.check_authentication_context()
if authentication_context_check_result:
return authentication_context_check_result
@ -1676,47 +1676,6 @@ class FormPage(Directory, FormTemplateMixin):
break
return redirect(url or '.')
def tempfile(self):
get_request().ignore_session = True
self.check_role()
if self.user and not self.user.id == get_session().user:
self.check_receiver()
try:
t = get_request().form['t']
tempfile = get_session().get_tempfile(t)
except KeyError:
raise errors.TraversalError()
if tempfile is None:
raise errors.TraversalError()
response = get_response()
# force potential HTML upload to be used as-is (not decorated with theme)
# and with minimal permissions
response.filter = {}
response.set_header(
'Content-Security-Policy',
'default-src \'none\'; img-src %s;' % get_request().build_absolute_uri(),
)
if tempfile['content_type']:
response.set_content_type(tempfile['content_type'])
else:
response.set_content_type('application/octet-stream')
if tempfile['charset']:
response.set_charset(tempfile['charset'])
if get_request().form.get('thumbnail') == '1':
try:
thumbnail = misc.get_thumbnail(
get_session().get_tempfile_path(t), content_type=tempfile['content_type']
)
except misc.ThumbnailError:
pass
else:
response.set_content_type('image/png')
return thumbnail
return get_session().get_tempfile_content(t).get_file_pointer().read()
def validating(self, data, page_error_messages=None):
self.on_validation_page = True
get_request().view_name = 'validation'