0001-backoffice-fix-access-to-uploaded-file-in-file-widge.patch
tests/backoffice_pages/test_all.py | ||
---|---|---|
8 | 8 | |
9 | 9 |
import pytest |
10 | 10 |
import responses |
11 |
from webtest import Upload |
|
11 | 12 | |
12 | 13 |
import wcs.qommon.storage as st |
13 | 14 |
from wcs import fields |
... | ... | |
4877 | 4878 |
assert formdata.workflow_roles == {'_foobar': [user.roles[0]]} |
4878 | 4879 | |
4879 | 4880 |
assert 'button_a_button' in resp.text |
4881 | ||
4882 | ||
4883 |
def test_backoffice_workflow_form_file_access(pub): |
|
4884 |
FormDef.wipe() |
|
4885 |
Workflow.wipe() |
|
4886 | ||
4887 |
role = pub.role_class(name='xxx1') |
|
4888 |
role.store() |
|
4889 | ||
4890 |
user = create_superuser(pub) |
|
4891 |
user.roles.append(role.id) |
|
4892 |
user.store() |
|
4893 | ||
4894 |
wf = Workflow(name='test') |
|
4895 |
status = wf.add_status('New', 'st1') |
|
4896 |
next_status = wf.add_status('Next', 'st2') |
|
4897 | ||
4898 |
status.items = [] |
|
4899 |
display_form = status.add_action('form', id='_display_form') |
|
4900 |
display_form.by = ['_receiver'] |
|
4901 |
display_form.varname = 'blah' |
|
4902 |
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form) |
|
4903 |
display_form.formdef.fields = [ |
|
4904 |
fields.FileField(id='1', label='test', type='file', varname='file'), |
|
4905 |
fields.StringField(id='2', label='test2', type='string', required=True), |
|
4906 |
] |
|
4907 | ||
4908 |
jump = status.add_action('jumponsubmit', id='_jump') |
|
4909 |
jump.status = next_status.id |
|
4910 | ||
4911 |
wf.store() |
|
4912 | ||
4913 |
formdef = FormDef() |
|
4914 |
formdef.name = 'test' |
|
4915 |
formdef.workflow_id = wf.id |
|
4916 |
formdef.workflow_roles = {'_receiver': role.id} |
|
4917 |
formdef.fields = [] |
|
4918 |
formdef.store() |
|
4919 | ||
4920 |
formdef.data_class().wipe() |
|
4921 | ||
4922 |
formdata = formdef.data_class()() |
|
4923 |
formdata.just_created() |
|
4924 |
formdata.store() |
|
4925 | ||
4926 |
app = login(get_app(pub)) |
|
4927 |
resp = app.get(formdata.get_url(backoffice=True)) |
|
4928 |
resp.form['fblah_1$file'] = Upload('test3.txt', b'foobar3', 'text/plain') |
|
4929 |
resp = resp.form.submit('submit') |
|
4930 |
# it will fail on the equired string field; this allows testing |
|
4931 |
# the temporary file URL. |
|
4932 |
assert resp.click('test3.txt').body == b'foobar3' |
wcs/backoffice/management.py | ||
---|---|---|
41 | 41 |
from wcs.formdata import FormData |
42 | 42 |
from wcs.formdef import FormDef |
43 | 43 |
from wcs.forms.backoffice import FormDefUI |
44 |
from wcs.forms.common import FormStatusPage |
|
44 |
from wcs.forms.common import FormPageMixin, FormStatusPage
|
|
45 | 45 |
from wcs.roles import logged_users_role |
46 | 46 |
from wcs.variables import LazyFieldVar, LazyList |
47 | 47 |
from wcs.workflows import ActionsTracingEvolutionPart, WorkflowStatusItem, item_classes, template_on_formdata |
... | ... | |
752 | 752 |
return FormPage(component) |
753 | 753 | |
754 | 754 | |
755 |
class FormPage(Directory): |
|
755 |
class FormPage(Directory, FormPageMixin):
|
|
756 | 756 |
do_not_call_in_templates = True |
757 | 757 |
_q_exports = [ |
758 | 758 |
'', |
... | ... | |
2926 | 2926 |
except KeyError: |
2927 | 2927 |
raise errors.TraversalError() |
2928 | 2928 | |
2929 |
return FormBackOfficeStatusPage(self.formdef, filled) |
|
2929 |
return FormBackOfficeStatusPage(self.formdef, filled, parent_view=self)
|
|
2930 | 2930 | |
2931 | 2931 |
def live(self): |
2932 | 2932 |
return FormBackofficeEditPage(self.formdef.url_name).live() |
... | ... | |
2951 | 2951 |
'action', |
2952 | 2952 |
'live', |
2953 | 2953 |
'inspect', |
2954 |
'tempfile', |
|
2954 | 2955 |
('inspect-tool', 'inspect_tool'), |
2955 | 2956 |
('download-as-zip', 'download_as_zip'), |
2956 | 2957 |
('lateral-block', 'lateral_block'), |
wcs/forms/common.py | ||
---|---|---|
941 | 941 |
return f._q_index() |
942 | 942 | |
943 | 943 |
raise errors.AccessForbiddenError() |
944 | ||
945 | ||
946 |
class FormPageMixin: |
|
947 |
user = None |
|
948 | ||
949 |
def tempfile(self): |
|
950 |
get_request().ignore_session = True |
|
951 |
self.check_access() |
|
952 |
if self.user and not self.user.id == get_session().user: |
|
953 |
self.check_receiver() |
|
954 |
try: |
|
955 |
t = get_request().form['t'] |
|
956 |
tempfile = get_session().get_tempfile(t) |
|
957 |
except KeyError: |
|
958 |
raise errors.TraversalError() |
|
959 |
if tempfile is None: |
|
960 |
raise errors.TraversalError() |
|
961 |
response = get_response() |
|
962 | ||
963 |
# force potential HTML upload to be used as-is (not decorated with theme) |
|
964 |
# and with minimal permissions |
|
965 |
response.filter = {} |
|
966 |
response.set_header( |
|
967 |
'Content-Security-Policy', |
|
968 |
'default-src \'none\'; img-src %s;' % get_request().build_absolute_uri(), |
|
969 |
) |
|
970 | ||
971 |
if tempfile['content_type']: |
|
972 |
response.set_content_type(tempfile['content_type']) |
|
973 |
else: |
|
974 |
response.set_content_type('application/octet-stream') |
|
975 |
if tempfile['charset']: |
|
976 |
response.set_charset(tempfile['charset']) |
|
977 | ||
978 |
if get_request().form.get('thumbnail') == '1': |
|
979 |
try: |
|
980 |
thumbnail = misc.get_thumbnail( |
|
981 |
get_session().get_tempfile_path(t), content_type=tempfile['content_type'] |
|
982 |
) |
|
983 |
except misc.ThumbnailError: |
|
984 |
pass |
|
985 |
else: |
|
986 |
response.set_content_type('image/png') |
|
987 |
return thumbnail |
|
988 |
return get_session().get_tempfile_content(t).get_file_pointer().read() |
wcs/forms/root.py | ||
---|---|---|
37 | 37 |
from wcs.fields import MissingBlockFieldError, SetValueError |
38 | 38 |
from wcs.formdata import Evolution, FormData |
39 | 39 |
from wcs.formdef import FormDef |
40 |
from wcs.forms.common import FormStatusPage, FormTemplateMixin |
|
40 |
from wcs.forms.common import FormPageMixin, FormStatusPage, FormTemplateMixin
|
|
41 | 41 |
from wcs.qommon.admin.texts import TextsDirectory |
42 | 42 |
from wcs.qommon.form import get_selection_error_text |
43 | 43 |
from wcs.qommon.storage import Equal, NothingToUpdate |
... | ... | |
256 | 256 |
return TrackingCodeDirectory(component, self.formdef) |
257 | 257 | |
258 | 258 | |
259 |
class FormPage(Directory, FormTemplateMixin): |
|
259 |
class FormPage(Directory, FormPageMixin, FormTemplateMixin):
|
|
260 | 260 |
_q_exports = [ |
261 | 261 |
'', |
262 | 262 |
'tempfile', |
... | ... | |
312 | 312 |
def go_to_backoffice(self): |
313 | 313 |
return redirect(self.formdef.get_admin_url()) |
314 | 314 | |
315 |
def check_role(self):
|
|
315 |
def check_access(self):
|
|
316 | 316 |
if self.formdef.roles: |
317 | 317 |
if not self.user: |
318 | 318 |
raise errors.AccessUnauthorizedError() |
... | ... | |
941 | 941 |
self._pages = None |
942 | 942 | |
943 | 943 |
def _q_index(self): |
944 |
self.check_role()
|
|
944 |
self.check_access()
|
|
945 | 945 |
authentication_context_check_result = self.check_authentication_context() |
946 | 946 |
if authentication_context_check_result: |
947 | 947 |
return authentication_context_check_result |
... | ... | |
1676 | 1676 |
break |
1677 | 1677 |
return redirect(url or '.') |
1678 | 1678 | |
1679 |
def tempfile(self): |
|
1680 |
get_request().ignore_session = True |
|
1681 |
self.check_role() |
|
1682 |
if self.user and not self.user.id == get_session().user: |
|
1683 |
self.check_receiver() |
|
1684 |
try: |
|
1685 |
t = get_request().form['t'] |
|
1686 |
tempfile = get_session().get_tempfile(t) |
|
1687 |
except KeyError: |
|
1688 |
raise errors.TraversalError() |
|
1689 |
if tempfile is None: |
|
1690 |
raise errors.TraversalError() |
|
1691 |
response = get_response() |
|
1692 | ||
1693 |
# force potential HTML upload to be used as-is (not decorated with theme) |
|
1694 |
# and with minimal permissions |
|
1695 |
response.filter = {} |
|
1696 |
response.set_header( |
|
1697 |
'Content-Security-Policy', |
|
1698 |
'default-src \'none\'; img-src %s;' % get_request().build_absolute_uri(), |
|
1699 |
) |
|
1700 | ||
1701 |
if tempfile['content_type']: |
|
1702 |
response.set_content_type(tempfile['content_type']) |
|
1703 |
else: |
|
1704 |
response.set_content_type('application/octet-stream') |
|
1705 |
if tempfile['charset']: |
|
1706 |
response.set_charset(tempfile['charset']) |
|
1707 | ||
1708 |
if get_request().form.get('thumbnail') == '1': |
|
1709 |
try: |
|
1710 |
thumbnail = misc.get_thumbnail( |
|
1711 |
get_session().get_tempfile_path(t), content_type=tempfile['content_type'] |
|
1712 |
) |
|
1713 |
except misc.ThumbnailError: |
|
1714 |
pass |
|
1715 |
else: |
|
1716 |
response.set_content_type('image/png') |
|
1717 |
return thumbnail |
|
1718 |
return get_session().get_tempfile_content(t).get_file_pointer().read() |
|
1719 | ||
1720 | 1679 |
def validating(self, data, page_error_messages=None): |
1721 | 1680 |
self.on_validation_page = True |
1722 | 1681 |
get_request().view_name = 'validation' |
1723 |
- |