From b40ea6ccf5fdd3116405e0d5233387bf34e20b37 Mon Sep 17 00:00:00 2001 From: Rudolf Cardinal Date: Mon, 3 May 2021 23:36:27 +0100 Subject: [PATCH 1/4] Support nonce attributes for script/style tags, via Field, for HTTP Content-Security-Policy environments --- deform/field.py | 61 ++++++++++++++++++- deform/templates/autocomplete_input.pt | 2 +- deform/templates/checked_input.pt | 2 +- deform/templates/dateinput.pt | 2 +- deform/templates/datetimeinput.pt | 2 +- deform/templates/file_upload.pt | 2 +- deform/templates/form.pt | 2 +- deform/templates/moneyinput.pt | 2 +- deform/templates/richtext.pt | 4 +- deform/templates/select2.pt | 4 +- deform/templates/selectize.pt | 2 +- deform/templates/sequence.pt | 4 +- deform/templates/textinput.pt | 2 +- deform/templates/timeinput.pt | 2 +- deform/tests/test_field.py | 26 +++++++++ deform/tests/test_widget.py | 81 ++++++++++++++++++++++++++ 16 files changed, 183 insertions(+), 17 deletions(-) diff --git a/deform/field.py b/deform/field.py index 3758bd8d..e4f4b6b2 100644 --- a/deform/field.py +++ b/deform/field.py @@ -2,6 +2,7 @@ # Standard Library import itertools import re +from typing import Optional import unicodedata import weakref @@ -126,6 +127,29 @@ class Field(object): ``autofocus`` set to a true-ish value (``on``, ``True``, or ``autofocus``) will receive focus on page load. Default: ``None``. + script_nonce + Nonce (number-used-once) for use with `` diff --git a/deform/tests/test_widget.py b/deform/tests/test_widget.py index 0c1ecf08..e0b25741 100644 --- a/deform/tests/test_widget.py +++ b/deform/tests/test_widget.py @@ -2131,7 +2131,7 @@ class TestSequenceSchema(SequenceSchema): parsed_html = BeautifulSoup(html, features="html.parser") script_tags = parsed_html.body.find('script') style_tags = parsed_html.body.find('style') - # Chameleon/Zope/ZPT strips out the tags if the content is blank. + # BeautifulSoup strips out the tags if the content is blank. # So there will be no "nonce" attr at all. # import pdb; pdb.set_trace() self.assertFalse(nonce_attr in str(script_tags)) From 8b2462a5dfbab09ee7a36ef3abb3ae3f21750b16 Mon Sep 17 00:00:00 2001 From: Rudolf Cardinal Date: Tue, 4 May 2021 10:05:00 +0100 Subject: [PATCH 3/4] Removed the need for unsafe-eval by shifting click-handler code from sequence_item.pt prototype to deform.js. Also trivial code style consistency tweaks. --- deform/static/scripts/deform.js | 37 ++++++++++++++++++++----------- deform/templates/sequence.pt | 28 +++++++++++------------ deform/templates/sequence_item.pt | 8 ------- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/deform/static/scripts/deform.js b/deform/static/scripts/deform.js index 17ba4a20..eef18a65 100644 --- a/deform/static/scripts/deform.js +++ b/deform/static/scripts/deform.js @@ -23,11 +23,12 @@ var deform = { }, load: function() { - $(function() { - if (!deform_loaded) { - deform.processCallbacks(); - deform_loaded = true; - }}); + $(function() { + if (!deform_loaded) { + deform.processCallbacks(); + deform_loaded = true; + } + }); }, processCallbacks: function () { @@ -35,8 +36,7 @@ var deform = { var oid = item[0]; var callback = item[1]; callback(oid); - } - ); + }); deform.clearCallbacks(); }, @@ -73,7 +73,7 @@ var deform = { var labelselector = 'label[for=' + oldid + ']'; var $fornodes = $htmlnode.find(labelselector); $fornodes.attr('for', newid); - }); + }); // replace names a containing ```deformField`` like we do for ids @@ -82,7 +82,7 @@ var deform = { var oldname = $node.attr('name'); var newname = oldname.replace(fieldmatch, "deformField$1-" + genid); $node.attr('name', newname); - }); + }); $htmlnode.insertBefore(before); @@ -92,8 +92,8 @@ var deform = { var newid = idmap[oid]; if (newid) { callback(newid); - } - }); + } + }); deform.clearCallbacks(); var old_len = parseInt(before.attr('now_len')||'0', 10); @@ -112,7 +112,7 @@ var deform = { var orderable = parseInt($before_node.attr('orderable')||'0', 10); if (now_len < max_len) { - deform.addSequenceItem($proto_node, $before_node); + deform.addSequenceItem($proto_node, $before_node); deform.processSequenceButtons($oid_node, min_len, max_len, now_len + 1, orderable); } @@ -149,10 +149,21 @@ var deform = { $lis.find('.deform-close-button').not($lis.find('.deform-seq-container .deform-close-button')).toggle(show_closebutton); oid_node.find('.deform-seq-add').not(oid_node.find('.deform-seq-container .deform-seq-add')).toggle(show_addbutton); $lis.find('.deform-order-button').not($lis.find('.deform-seq-container .deform-order-button')).toggle(orderable && has_multiple); + // Since "onclick" isn't OK with restricted content security policy, + // we add the click handler here, removing the need for evaluation of + // item prototype code that containts Javascript. + // - for pre-existing items, the click handler is added at page + // creation via processSequenceButtons(), thanks to a + // deform.addCallback call in sequence.pt; + // - for newly created items (added by the user), we get here via + // appendSequenceItem(). + $lis.find('.deform-close-button').click(function () { + deform.removeSequenceItem(this); + }); }, randomString: function (length) { - var chr='0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'; + var chr = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'; chr = chr.split(''); if (! length) { diff --git a/deform/templates/sequence.pt b/deform/templates/sequence.pt index 7cd10b00..b83c6ab0 100644 --- a/deform/templates/sequence.pt +++ b/deform/templates/sequence.pt @@ -61,10 +61,10 @@ diff --git a/deform/templates/sequence_item.pt b/deform/templates/sequence_item.pt index 19bee74b..c98ee4a1 100644 --- a/deform/templates/sequence_item.pt +++ b/deform/templates/sequence_item.pt @@ -30,14 +30,6 @@ tal:condition="not field.widget.hidden" title="Remove" i18n:attributes="title">× - From 14d3bb121791cbba6ff1711db41e67191c0a55c3 Mon Sep 17 00:00:00 2001 From: Rudolf Cardinal Date: Tue, 4 May 2021 12:29:43 +0100 Subject: [PATCH 4/4] Changes for tox linter --- deform/field.py | 8 ++++---- deform/tests/test_field.py | 5 +++-- deform/tests/test_widget.py | 40 +++++++++++++++++++++++-------------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/deform/field.py b/deform/field.py index ec60970c..c3882b91 100644 --- a/deform/field.py +++ b/deform/field.py @@ -302,7 +302,7 @@ def __init__( self.set_appstruct(appstruct) def found_first(self): - """ Set have_first_input of ancestors """ + """Set have_first_input of ancestors""" self.have_first_input = True if self.parent is not None: self.parent.found_first() @@ -314,7 +314,7 @@ def parent(self) -> Optional["Field"]: return self._parent() def get_root(self): - """ Return the root field in the field hierarchy (the form field) """ + """Return the root field in the field hierarchy (the form field)""" node = self while True: parent = node.parent @@ -386,7 +386,7 @@ def translate(self, msgid): return msgid def __iter__(self): - """ Iterate over the children fields of this field. """ + """Iterate over the children fields of this field.""" return iter(self.children) def __getitem__(self, name): @@ -661,7 +661,7 @@ def serialize(self, cstruct=_marker, **kw): return self.widget.serialize(**values) def deserialize(self, pstruct): - """ Deserialize the pstruct into a cstruct and return the cstruct.""" + """Deserialize the pstruct into a cstruct and return the cstruct.""" return self.widget.deserialize(self, pstruct) def render(self, appstruct=_marker, **kw): diff --git a/deform/tests/test_field.py b/deform/tests/test_field.py index 6dec9ce5..17d584a0 100644 --- a/deform/tests/test_field.py +++ b/deform/tests/test_field.py @@ -775,8 +775,9 @@ def test_nonces(self) -> None: nonce3 = "dummy_nonce_3" schema = DummySchema() - root1 = self._makeOne(schema, renderer="abc", - script_nonce=nonce1, style_nonce=nonce2) + root1 = self._makeOne( + schema, renderer="abc", script_nonce=nonce1, style_nonce=nonce2 + ) child1 = Field(schema, name="child1", parent=root1) root1.children = [child1] self.assertEqual(root1.script_nonce_recursive, nonce1) diff --git a/deform/tests/test_widget.py b/deform/tests/test_widget.py index e0b25741..173b2fbe 100644 --- a/deform/tests/test_widget.py +++ b/deform/tests/test_widget.py @@ -2061,23 +2061,29 @@ def test_nonce_present_render(self): ``SequenceWidget`` uses both ``