diff --git a/changes/204.a.canada.feature b/changes/204.a.canada.feature new file mode 100644 index 00000000000..fefffbacd51 --- /dev/null +++ b/changes/204.a.canada.feature @@ -0,0 +1 @@ +Adds script/style nonce capabilities to Jinja2 webassets. \ No newline at end of file diff --git a/changes/204.b.canada.changes b/changes/204.b.canada.changes new file mode 100644 index 00000000000..0bce65fa0b4 --- /dev/null +++ b/changes/204.b.canada.changes @@ -0,0 +1 @@ +Moved inline styles and JS attributes to classes and event listeners respectfully. \ No newline at end of file diff --git a/changes/204.c.canada.changes b/changes/204.c.canada.changes new file mode 100644 index 00000000000..41f8216c883 --- /dev/null +++ b/changes/204.c.canada.changes @@ -0,0 +1 @@ +Now sets the Cache-Control header to `no-cache, private` for logged in users. \ No newline at end of file diff --git a/changes/204.d.canada.changes b/changes/204.d.canada.changes new file mode 100644 index 00000000000..8161b281f2f --- /dev/null +++ b/changes/204.d.canada.changes @@ -0,0 +1 @@ +Loads image assets instead of `data:image` URIs for stricter Content-Security-Policy support. \ No newline at end of file diff --git a/ckan/lib/webassets_tools.py b/ckan/lib/webassets_tools.py index 3fe27218541..b841d5cbfbe 100644 --- a/ckan/lib/webassets_tools.py +++ b/ckan/lib/webassets_tools.py @@ -11,7 +11,8 @@ from webassets import Environment from webassets.loaders import YAMLLoader -from ckan.common import config, g +# (canada fork only): use CSP nonce to support strict-dynamic +from ckan.common import config, g, request logger = logging.getLogger(__name__) @@ -128,9 +129,13 @@ def include_asset(name: str) -> None: def _to_tag(url: str, type_: str): if type_ == u'style': - return u''.format(url) + # (canada fork only): use CSP nonce to support strict-dynamic + return u''.format( + url, str(request.environ.get('CSP_NONCE', ''))) elif type_ == u'script': - return u''.format(url) + # (canada fork only): use CSP nonce to support strict-dynamic + return u''.format( + url, str(request.environ.get('CSP_NONCE', ''))) return u'' diff --git a/ckan/logic/validators.py b/ckan/logic/validators.py index 1092347ff72..f7b76e47709 100644 --- a/ckan/logic/validators.py +++ b/ckan/logic/validators.py @@ -41,6 +41,11 @@ Missing = df.Missing missing = df.missing +# (canada fork only): http header value validator +# TODO: upstream contrib?? +header_bad_value_match = re.compile(r'[^\x20-\x7E\t]') +new_line_match = re.compile(r'[\r\n]') + def owner_org_validator(key: FlattenKey, data: FlattenDataDict, errors: FlattenErrorDict, context: Context) -> Any: @@ -1171,3 +1176,18 @@ def license_choices(value, context): if value in licenses: return value raise Invalid(_('Invalid license')) + + +# (canada fork only): http header value validator +# TODO: upstream contrib?? +def http_header_value_validator(value: Any): + """ + Sanitizes safe values for HTTP headers. + + Removes newline characters + """ + value = re.sub(new_line_match, ' ', value) + bad_values = re.search(header_bad_value_match, value) + if bad_values: + raise Invalid(f'Invalid characters for HTTP Header value: {bad_values}') + return value \ No newline at end of file diff --git a/ckan/public/base/javascript/modules/metadata-button.js b/ckan/public/base/javascript/modules/metadata-button.js index 609a8ec75e4..4d2728cb4c6 100644 --- a/ckan/public/base/javascript/modules/metadata-button.js +++ b/ckan/public/base/javascript/modules/metadata-button.js @@ -19,6 +19,7 @@ ckan.module('metadata-button', function(jQuery) { _onClick: function(event) { console.log("PRESSED THE BUTTON"); + // FIXME: TODO: move style attributes to classes var div = document.getElementById("metadata_diff"); if (div.style.display === "none") { div.style.display = "block"; diff --git a/ckan/public/base/javascript/modules/resource-upload-field.js b/ckan/public/base/javascript/modules/resource-upload-field.js index e389ad49d21..e81a0cb5a00 100644 --- a/ckan/public/base/javascript/modules/resource-upload-field.js +++ b/ckan/public/base/javascript/modules/resource-upload-field.js @@ -15,7 +15,7 @@ this.ckan.module('resource-upload-field', function (jQuery) { // revert to URL for Link option $('#resource-link-button').on('click', function() { urlField.attr('type', 'url'); - }) + }) $('#field-resource-upload').on('change', function() { if (_nameIsDirty) { @@ -34,6 +34,50 @@ this.ckan.module('resource-upload-field', function (jQuery) { $('input[name="name"]').val(file_name); }); + + // (canada fork only): CSP support + let uploadButton = $('#resource-upload-button'); + if( uploadButton.length > 0 ){ + $(uploadButton).off('click.ResourceEdit'); + $(uploadButton).on('click.ResourceEdit', function(_event){ + let uploadField = document.getElementById('resource-url-upload'); + if( typeof uploadField !== 'undefined' && uploadField != null ){ + uploadField.checked = true; + } + document.getElementById('field-resource-upload').click(); + }); + } + let linkButton = $('#resource-link-button'); + if( linkButton.length > 0 ){ + $(linkButton).off('click.ResourceEdit'); + $(linkButton).on('click.ResourceEdit', function(_event){ + let urlField = document.getElementById('resource-url-link'); + if( typeof urlField !== 'undefined' && urlField != null ){ + urlField.checked = true; + } + document.getElementById('field-resource-url').focus(); + }); + } + let removeURIButtons = $('.btn-remove-url'); + if( removeURIButtons.length > 0 ){ + $(removeURIButtons).each(function(_index, _removeURIButton){ + $(_removeURIButton).off('click.ResourceEdit'); + $(_removeURIButton).on('click.ResourceEdit', function(_event){ + let clearUploadField = document.getElementById('field-clear-upload'); + if( typeof clearUploadField !== 'undefined' && clearUploadField != null ){ + clearUploadField.checked = true; + } + document.getElementById('resource-url-none').checked = true; + document.getElementById($(_removeURIButton).attr('data-first-button')).focus(); + if( $(_removeURIButton).attr('data-is-upload') == 'true' || $(_removeURIButton).attr('data-is-upload') == true || $(_removeURIButton).attr('data-is-upload') == 'True' ){ + $('#field-resource-upload').replaceWith($('#field-resource-upload').val('').clone(true)); + }else{ + $('#field-resource-url').val(''); + } + }); + }); + } + } } }); diff --git a/ckan/templates/base.html b/ckan/templates/base.html index b42fb8e7925..d597c5d4b54 100644 --- a/ckan/templates/base.html +++ b/ckan/templates/base.html @@ -83,7 +83,8 @@ {{ h.render_assets('style') }} {%- block custom_styles %} {%- if g.site_custom_css -%} - {%- endif %} diff --git a/ckan/templates/dataviewer/base.html b/ckan/templates/dataviewer/base.html index 9f3d7147776..27c7888238c 100644 --- a/ckan/templates/dataviewer/base.html +++ b/ckan/templates/dataviewer/base.html @@ -4,7 +4,8 @@ {# remove any scripts #} {% block scripts %} - {% endblock %} diff --git a/ckan/templates/package/snippets/resource_upload_field.html b/ckan/templates/package/snippets/resource_upload_field.html index 121506e5fa6..1bf32ae09e4 100644 --- a/ckan/templates/package/snippets/resource_upload_field.html +++ b/ckan/templates/package/snippets/resource_upload_field.html @@ -22,13 +22,9 @@ {% set first_button = 'resource-upload-button' if is_upload_enabled else 'resource-link-button' %} -{% macro remove_button(js='') %} - +{% macro remove_button(is_upload) %} + {# (canada fork only): CSP support #} + {% endmacro %}