deprecations: forbid import of new python expressions (#72093)

This commit is contained in:
Lauréline Guérin 2024-03-04 17:02:17 +01:00 committed by Lauréline Guérin
parent 78f2796266
commit 86f28b8037
8 changed files with 261 additions and 19 deletions

View File

@ -7,11 +7,11 @@ import pytest
from quixote.http_request import Upload as QuixoteUpload
from wcs import fields
from wcs.backoffice.deprecations import DeprecationsScanAfterJob
from wcs.blocks import BlockDef
from wcs.backoffice.deprecations import DeprecatedElementsDetected, DeprecationsScanAfterJob
from wcs.blocks import BlockDef, BlockdefImportError
from wcs.carddef import CardDef
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.data_sources import NamedDataSource, NamedDataSourceImportError
from wcs.formdef import FormDef, FormdefImportError
from wcs.mail_templates import MailTemplate
from wcs.qommon.form import UploadedFile
from wcs.qommon.http_request import HTTPRequest
@ -23,8 +23,8 @@ from wcs.wf.geolocate import GeolocateWorkflowStatusItem
from wcs.wf.jump import JumpWorkflowStatusItem
from wcs.wf.notification import SendNotificationWorkflowStatusItem
from wcs.wf.redirect_to_url import RedirectToUrlWorkflowStatusItem
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
from wcs.wscalls import NamedWsCall
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowImportError
from wcs.wscalls import NamedWsCall, NamedWsCallImportError
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
from .test_all import create_superuser
@ -525,3 +525,124 @@ def test_deprecations_inspect_pages_old_format(pub):
resp = app.get('/backoffice/studio/deprecations/')
assert resp.pyquery('.section--python-condition li a')
def test_deprecations_on_import(pub):
formdef = FormDef()
formdef.name = 'foobar'
formdef.fields = [
fields.PageField(id='1', label='page1', condition={'type': 'python', 'value': 'True'}),
]
formdef.store()
blockdef = BlockDef()
blockdef.name = 'foobar'
blockdef.fields = [
fields.StringField(id='2', label='python_prefill', prefill={'type': 'formula', 'value': '1 + 2'}),
]
blockdef.store()
workflow = Workflow(name='test')
st0 = workflow.add_status('Status0', 'st0')
sendsms = st0.add_action('sendsms', id='_sendsms')
sendsms.to = 'xxx'
sendsms.condition = {'type': 'python', 'value': 'True'}
sendsms.parent = st0
st0.items.append(sendsms)
workflow.store()
data_source = NamedDataSource(name='ds_python')
data_source.data_source = {'type': 'formula', 'value': repr([('1', 'un'), ('2', 'deux')])}
data_source.store()
wscall = NamedWsCall()
wscall.name = 'Hello'
wscall.request = {'url': 'http://example.net', 'qs_data': {'a': '=1+2'}}
wscall.store()
mail_template = MailTemplate() # no python expression in mail templates
mail_template.name = 'Hello2'
mail_template.subject = 'plop'
mail_template.body = 'plop [ezt] plop'
mail_template.store()
job = DeprecationsScanAfterJob()
job.check_deprecated_elements_in_object(formdef)
formdef_xml = formdef.export_to_xml()
FormDef.import_from_xml_tree(formdef_xml)
job = DeprecationsScanAfterJob()
job.check_deprecated_elements_in_object(blockdef)
blockdef_xml = blockdef.export_to_xml()
BlockDef.import_from_xml_tree(blockdef_xml)
job = DeprecationsScanAfterJob()
job.check_deprecated_elements_in_object(workflow)
workflow_xml = workflow.export_to_xml()
Workflow.import_from_xml_tree(workflow_xml)
job = DeprecationsScanAfterJob()
job.check_deprecated_elements_in_object(data_source)
data_source_xml = data_source.export_to_xml()
NamedDataSource.import_from_xml_tree(data_source_xml)
job = DeprecationsScanAfterJob()
job.check_deprecated_elements_in_object(wscall)
wscall_xml = wscall.export_to_xml()
NamedWsCall.import_from_xml_tree(wscall_xml)
job = DeprecationsScanAfterJob()
job.check_deprecated_elements_in_object(mail_template)
mail_template_xml = mail_template.export_to_xml()
MailTemplate.import_from_xml_tree(mail_template_xml)
if not pub.site_options.has_section('options'):
pub.site_options.add_section('options')
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
job = DeprecationsScanAfterJob()
with pytest.raises(DeprecatedElementsDetected) as excinfo:
job.check_deprecated_elements_in_object(formdef)
assert str(excinfo.value) == 'Python expression detected'
with pytest.raises(FormdefImportError) as excinfo:
FormDef.import_from_xml_tree(formdef_xml)
assert str(excinfo.value) == 'Python expression detected'
job = DeprecationsScanAfterJob()
with pytest.raises(DeprecatedElementsDetected) as excinfo:
job.check_deprecated_elements_in_object(blockdef)
assert str(excinfo.value) == 'Python expression detected'
with pytest.raises(BlockdefImportError) as excinfo:
BlockDef.import_from_xml_tree(blockdef_xml)
assert str(excinfo.value) == 'Python expression detected'
job = DeprecationsScanAfterJob()
with pytest.raises(DeprecatedElementsDetected) as excinfo:
job.check_deprecated_elements_in_object(workflow)
assert str(excinfo.value) == 'Python expression detected'
with pytest.raises(WorkflowImportError) as excinfo:
Workflow.import_from_xml_tree(workflow_xml)
assert str(excinfo.value) == 'Python expression detected'
job = DeprecationsScanAfterJob()
with pytest.raises(DeprecatedElementsDetected) as excinfo:
job.check_deprecated_elements_in_object(data_source)
assert str(excinfo.value) == 'Python expression detected'
with pytest.raises(NamedDataSourceImportError) as excinfo:
NamedDataSource.import_from_xml_tree(data_source_xml)
assert str(excinfo.value) == 'Python expression detected'
job = DeprecationsScanAfterJob()
with pytest.raises(DeprecatedElementsDetected) as excinfo:
job.check_deprecated_elements_in_object(wscall)
assert str(excinfo.value) == 'Python expression detected'
with pytest.raises(NamedWsCallImportError) as excinfo:
NamedWsCall.import_from_xml_tree(wscall_xml)
assert str(excinfo.value) == 'Python expression detected'
# no python expressions
job = DeprecationsScanAfterJob()
job.check_deprecated_elements_in_object(mail_template)
MailTemplate.import_from_xml_tree(mail_template_xml)

View File

@ -31,10 +31,15 @@ from wcs.qommon import _, ezt, template
from wcs.qommon.afterjobs import AfterJob
from wcs.qommon.template import Template
from wcs.wf.export_to_model import UploadValidationError
from wcs.wf.form import FormWorkflowStatusItem
from wcs.workflows import Workflow
from wcs.wscalls import NamedWsCall
class DeprecatedElementsDetected(Exception):
pass
class DeprecationsDirectory(Directory):
do_not_call_in_templates = True
_q_exports = ['', 'scan']
@ -409,3 +414,28 @@ class DeprecationsScanAfterJob(AfterJob):
fd,
indent=2,
)
def check_deprecated_elements_in_object(self, obj):
if not get_publisher().has_site_option('forbid-new-python-expressions'):
# for perfs, don't check object if nothing is forbidden
return
self.report_lines = []
objects = [obj]
if isinstance(obj, Workflow):
for status in obj.possible_status:
for item in status.items:
if isinstance(item, FormWorkflowStatusItem) and item.formdef:
objects.append(item.formdef)
if obj.variables_formdef:
objects.append(obj.variables_formdef)
if obj.backoffice_fields_formdef:
objects.append(obj.backoffice_fields_formdef)
self.check_objects(objects)
for report_line in self.report_lines:
if 'python' in report_line['category'] and get_publisher().has_site_option(
'forbid-new-python-expressions'
):
raise DeprecatedElementsDetected(_('Python expression detected'))

View File

@ -179,12 +179,17 @@ class BlockDef(StorableObject):
return root
@classmethod
def import_from_xml(cls, fd, include_id=False, check_datasources=True):
def import_from_xml(cls, fd, include_id=False, check_datasources=True, check_deprecated=True):
try:
tree = ET.parse(fd)
except Exception:
raise ValueError()
blockdef = cls.import_from_xml_tree(tree, include_id=include_id, check_datasources=check_datasources)
blockdef = cls.import_from_xml_tree(
tree,
include_id=include_id,
check_datasources=check_datasources,
check_deprecated=check_deprecated,
)
if blockdef.slug:
try:
@ -197,7 +202,11 @@ class BlockDef(StorableObject):
return blockdef
@classmethod
def import_from_xml_tree(cls, tree, include_id=False, check_datasources=True, **kwargs):
def import_from_xml_tree(
cls, tree, include_id=False, check_datasources=True, check_deprecated=True, **kwargs
):
from wcs.backoffice.deprecations import DeprecatedElementsDetected, DeprecationsScanAfterJob
blockdef = cls()
if tree.find('name') is None or not tree.find('name').text:
raise BlockdefImportError(_('Missing name'))
@ -279,6 +288,14 @@ class BlockDef(StorableObject):
details[_('Unknown datasources')].update(unknown_datasources)
raise BlockdefImportUnknownReferencedError(_('Unknown referenced objects'), details=details)
if check_deprecated:
# check for deprecated elements
job = DeprecationsScanAfterJob()
try:
job.check_deprecated_elements_in_object(blockdef)
except DeprecatedElementsDetected as e:
raise BlockdefImportError(str(e))
return blockdef
def get_usage_fields(self):

View File

@ -51,6 +51,10 @@ from .qommon.xml_storage import XmlStorableObject
data_source_functions = {}
class NamedDataSourceImportError(Exception):
pass
class DataSourceError(Exception):
pass
@ -913,9 +917,22 @@ class NamedDataSource(XmlStorableObject):
return root
@classmethod
def import_from_xml_tree(cls, tree, include_id=False, **kwargs):
data_source = super().import_from_xml_tree(tree, include_id=include_id, **kwargs)
def import_from_xml_tree(cls, tree, include_id=False, check_deprecated=True, **kwargs):
from wcs.backoffice.deprecations import DeprecatedElementsDetected, DeprecationsScanAfterJob
data_source = super().import_from_xml_tree(
tree, include_id=include_id, check_deprecated=check_deprecated, **kwargs
)
DataSourceCategory.object_category_xml_import(data_source, tree, include_id=include_id)
if check_deprecated:
# check for deprecated elements
job = DeprecationsScanAfterJob()
try:
job.check_deprecated_elements_in_object(data_source)
except DeprecatedElementsDetected as e:
raise NamedDataSourceImportError(str(e))
return data_source
@classmethod

View File

@ -1465,7 +1465,9 @@ class FormDef(StorableObject):
return root
@classmethod
def import_from_xml(cls, fd, include_id=False, fix_on_error=False, check_datasources=True):
def import_from_xml(
cls, fd, include_id=False, fix_on_error=False, check_datasources=True, check_deprecated=True
):
try:
tree = ET.parse(fd)
except Exception:
@ -1475,6 +1477,7 @@ class FormDef(StorableObject):
include_id=include_id,
fix_on_error=fix_on_error,
check_datasources=check_datasources,
check_deprecated=check_deprecated,
)
if formdef.url_name:
@ -1497,8 +1500,15 @@ class FormDef(StorableObject):
@classmethod
def import_from_xml_tree(
cls, tree, include_id=False, fix_on_error=False, snapshot=False, check_datasources=True
cls,
tree,
include_id=False,
fix_on_error=False,
snapshot=False,
check_datasources=True,
check_deprecated=True,
):
from wcs.backoffice.deprecations import DeprecatedElementsDetected, DeprecationsScanAfterJob
from wcs.carddef import CardDef
formdef = cls()
@ -1714,6 +1724,14 @@ class FormDef(StorableObject):
details[_('Unknown datasources')].update(unknown_datasources)
raise FormdefImportUnknownReferencedError(_('Unknown referenced objects'), details=details)
if check_deprecated:
# check for deprecated elements
job = DeprecationsScanAfterJob()
try:
job.check_deprecated_elements_in_object(formdef)
except DeprecatedElementsDetected as e:
raise FormdefImportError(str(e))
return formdef
def finish_tests_xml_import(self):

View File

@ -31,7 +31,7 @@ class XmlStorableObject(StorableObject):
first_byte = fd.read(1)
fd.seek(0)
if first_byte == b'<':
return cls.import_from_xml(fd, include_id=True)
return cls.import_from_xml(fd, include_id=True, check_deprecated=False)
else:
obj = StorableObject.storage_load(fd)
obj._upgrade_must_store = True
@ -84,12 +84,12 @@ class XmlStorableObject(StorableObject):
sub.text = role.name
@classmethod
def import_from_xml(cls, fd, include_id=False):
def import_from_xml(cls, fd, include_id=False, check_deprecated=True):
try:
tree = ET.parse(fd)
except Exception:
raise ValueError()
return cls.import_from_xml_tree(tree, include_id=include_id)
return cls.import_from_xml_tree(tree, include_id=include_id, check_deprecated=check_deprecated)
@classmethod
def import_from_xml_tree(cls, tree, include_id=False, **kwargs):

View File

@ -1233,12 +1233,17 @@ class Workflow(StorableObject):
return root
@classmethod
def import_from_xml(cls, fd, include_id=False, check_datasources=True):
def import_from_xml(cls, fd, include_id=False, check_datasources=True, check_deprecated=True):
try:
tree = ET.parse(fd)
except Exception:
raise ValueError()
workflow = cls.import_from_xml_tree(tree, include_id=include_id, check_datasources=check_datasources)
workflow = cls.import_from_xml_tree(
tree,
include_id=include_id,
check_datasources=check_datasources,
check_deprecated=check_deprecated,
)
if workflow.slug and cls.get_by_slug(workflow.slug):
# slug already in use, reset so a new one will be generated on store()
@ -1247,7 +1252,11 @@ class Workflow(StorableObject):
return workflow
@classmethod
def import_from_xml_tree(cls, tree, include_id=False, snapshot=False, check_datasources=True):
def import_from_xml_tree(
cls, tree, include_id=False, snapshot=False, check_datasources=True, check_deprecated=True
):
from wcs.backoffice.deprecations import DeprecatedElementsDetected, DeprecationsScanAfterJob
workflow = cls()
if tree.find('name') is None or not tree.find('name').text:
raise WorkflowImportError(_('Missing name'))
@ -1359,6 +1368,14 @@ class Workflow(StorableObject):
_('Unknown referenced objects'), details=unknown_referenced_objects_details
)
if check_deprecated:
# check for deprecated elements
job = DeprecationsScanAfterJob()
try:
job.check_deprecated_elements_in_object(workflow)
except DeprecatedElementsDetected as e:
raise WorkflowImportError(str(e))
return workflow
def get_list_of_roles(

View File

@ -44,6 +44,10 @@ from .qommon.template import Template
from .qommon.xml_storage import XmlStorableObject
class NamedWsCallImportError(Exception):
pass
class PayloadError(Exception):
pass
@ -280,6 +284,24 @@ class NamedWsCall(XmlStorableObject):
if self.request.get('post_data'):
yield from self.request.get('post_data').values()
@classmethod
def import_from_xml_tree(cls, tree, include_id=False, check_deprecated=True, **kwargs):
from wcs.backoffice.deprecations import DeprecatedElementsDetected, DeprecationsScanAfterJob
wscall = super().import_from_xml_tree(
tree, include_id=include_id, check_deprecated=check_deprecated, **kwargs
)
if check_deprecated:
# check for deprecated elements
job = DeprecationsScanAfterJob()
try:
job.check_deprecated_elements_in_object(wscall)
except DeprecatedElementsDetected as e:
raise NamedWsCallImportError(str(e))
return wscall
def export_request_to_xml(self, element, attribute_name, **kwargs):
request = getattr(self, attribute_name)
for attr in ('url', 'request_signature_key', 'method', 'timeout', 'cache_duration'):