diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb index ed0966fb5c4..575490008ff 100644 --- a/app/models/theme_field.rb +++ b/app/models/theme_field.rb @@ -317,6 +317,7 @@ class ThemeField < ActiveRecord::Base self.value_baked, self.error = translation_field? ? process_translation : process_html(self.value) self.error = nil unless self.error.present? self.compiler_version = COMPILER_VERSION + CSP::Extension.clear_theme_extensions_cache! elsif extra_js_field? self.value_baked, self.error = process_extra_js(self.value) self.error = nil unless self.error.present? diff --git a/lib/content_security_policy/extension.rb b/lib/content_security_policy/extension.rb index 5dd70223d06..f7b49b2307e 100644 --- a/lib/content_security_policy/extension.rb +++ b/lib/content_security_policy/extension.rb @@ -42,7 +42,9 @@ class ContentSecurityPolicy def find_theme_extensions(theme_ids) extensions = [] - Theme.where(id: Theme.transform_ids(theme_ids)).find_each do |theme| + resolved_ids = Theme.transform_ids(theme_ids) + + Theme.where(id: resolved_ids).find_each do |theme| theme.cached_settings.each do |setting, value| extensions << build_theme_extension(value.split("|")) if setting.to_s == THEME_SETTING end @@ -50,6 +52,21 @@ class ContentSecurityPolicy extensions << build_theme_extension(ThemeModifierHelper.new(theme_ids: theme_ids).csp_extensions) + html_fields = ThemeField.where( + theme_id: resolved_ids, + target_id: ThemeField.basic_targets.map { |target| Theme.targets[target.to_sym] }, + name: ThemeField.html_fields + ) + + auto_script_src_extension = { script_src: [] } + html_fields.each(&:ensure_baked!) + doc = html_fields.map(&:value_baked).join("\n") + Nokogiri::HTML.fragment(doc).css('script[src]').each do |node| + auto_script_src_extension[:script_src] << node['src'] + end + + extensions << auto_script_src_extension + extensions end diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb index 68a5dc2da2b..d9eb9788c24 100644 --- a/spec/lib/content_security_policy_spec.rb +++ b/spec/lib/content_security_policy_spec.rb @@ -212,6 +212,19 @@ describe ContentSecurityPolicy do expect(parse(theme_policy)['script-src']).to_not include('https://from-theme-flag.script') expect(parse(theme_policy)['worker-src']).to_not include('from-theme-flag.worker') end + + it 'is extended automatically when themes reference external scripts' do + policy # call this first to make sure further actions clear the cache + + theme.set_field(target: :common, name: "header", value: "