Projet

Général

Profil

0001-backoffice-fix-access-to-uploaded-file-in-file-widge.patch

Frédéric Péters, 13 octobre 2022 08:52

Télécharger (9,45 ko)

Voir les différences:

Subject: [PATCH] backoffice: fix access to uploaded file in file widget
 (#70194)

 tests/backoffice_pages/test_all.py | 53 ++++++++++++++++++++++++++++++
 wcs/backoffice/management.py       |  7 ++--
 wcs/forms/common.py                | 45 +++++++++++++++++++++++++
 wcs/forms/root.py                  | 49 +++------------------------
 4 files changed, 106 insertions(+), 48 deletions(-)
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 FormdefDirectoryBase, 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(FormdefDirectoryBase):
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 FormdefDirectoryBase(Directory):
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 FormdefDirectoryBase, 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(FormdefDirectoryBase, 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
-