mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 23:07:28 +08:00
FEATURE: Calculate CSP based on active themes (#6976)
This commit is contained in:
@ -4,8 +4,8 @@ require_dependency 'content_security_policy/extension'
|
|||||||
|
|
||||||
class ContentSecurityPolicy
|
class ContentSecurityPolicy
|
||||||
class << self
|
class << self
|
||||||
def policy
|
def policy(theme_ids = [])
|
||||||
new.build
|
new.build(theme_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
def base_url
|
def base_url
|
||||||
@ -14,10 +14,10 @@ class ContentSecurityPolicy
|
|||||||
attr_writer :base_url
|
attr_writer :base_url
|
||||||
end
|
end
|
||||||
|
|
||||||
def build
|
def build(theme_ids)
|
||||||
builder = Builder.new
|
builder = Builder.new
|
||||||
|
|
||||||
Extension.theme_extensions.each { |extension| builder << extension }
|
Extension.theme_extensions(theme_ids).each { |extension| builder << extension }
|
||||||
Extension.plugin_extensions.each { |extension| builder << extension }
|
Extension.plugin_extensions.each { |extension| builder << extension }
|
||||||
builder << Extension.site_setting_extension
|
builder << Extension.site_setting_extension
|
||||||
|
|
||||||
|
@ -17,12 +17,13 @@ class ContentSecurityPolicy
|
|||||||
|
|
||||||
THEME_SETTING = 'extend_content_security_policy'
|
THEME_SETTING = 'extend_content_security_policy'
|
||||||
|
|
||||||
def theme_extensions
|
def theme_extensions(theme_ids)
|
||||||
cache['theme_extensions'] ||= find_theme_extensions
|
key = "theme_extensions_#{Theme.transform_ids(theme_ids).join(',')}"
|
||||||
|
cache[key] ||= find_theme_extensions(theme_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_theme_extensions_cache!
|
def clear_theme_extensions_cache!
|
||||||
cache['theme_extensions'] = nil
|
cache.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -31,10 +32,10 @@ class ContentSecurityPolicy
|
|||||||
@cache ||= DistributedCache.new('csp_extensions')
|
@cache ||= DistributedCache.new('csp_extensions')
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_theme_extensions
|
def find_theme_extensions(theme_ids)
|
||||||
extensions = []
|
extensions = []
|
||||||
|
|
||||||
Theme.find_each do |theme|
|
Theme.where(id: Theme.transform_ids(theme_ids)).find_each do |theme|
|
||||||
theme.cached_settings.each do |setting, value|
|
theme.cached_settings.each do |setting, value|
|
||||||
extensions << build_theme_extension(value) if setting.to_s == THEME_SETTING
|
extensions << build_theme_extension(value) if setting.to_s == THEME_SETTING
|
||||||
end
|
end
|
||||||
|
@ -12,11 +12,11 @@ class ContentSecurityPolicy
|
|||||||
_, headers, _ = response = @app.call(env)
|
_, headers, _ = response = @app.call(env)
|
||||||
|
|
||||||
return response unless html_response?(headers)
|
return response unless html_response?(headers)
|
||||||
|
|
||||||
ContentSecurityPolicy.base_url = request.host_with_port if Rails.env.development?
|
ContentSecurityPolicy.base_url = request.host_with_port if Rails.env.development?
|
||||||
|
|
||||||
headers['Content-Security-Policy'] = policy if SiteSetting.content_security_policy
|
theme_ids = env[:resolved_theme_ids]
|
||||||
headers['Content-Security-Policy-Report-Only'] = policy if SiteSetting.content_security_policy_report_only
|
headers['Content-Security-Policy'] = policy(theme_ids) if SiteSetting.content_security_policy
|
||||||
|
headers['Content-Security-Policy-Report-Only'] = policy(theme_ids) if SiteSetting.content_security_policy_report_only
|
||||||
|
|
||||||
response
|
response
|
||||||
end
|
end
|
||||||
|
@ -120,31 +120,42 @@ describe ContentSecurityPolicy do
|
|||||||
Discourse.plugins.pop
|
Discourse.plugins.pop
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'can be extended by themes' do
|
context "with a theme" do
|
||||||
policy # call this first to make sure further actions clear the cache
|
let!(:theme) {
|
||||||
|
Fabricate(:theme).tap do |t|
|
||||||
theme = Fabricate(:theme)
|
|
||||||
settings = <<~YML
|
settings = <<~YML
|
||||||
extend_content_security_policy:
|
extend_content_security_policy:
|
||||||
type: list
|
type: list
|
||||||
default: 'script-src: from-theme.com'
|
default: 'script-src: from-theme.com'
|
||||||
YML
|
YML
|
||||||
theme.set_field(target: :settings, name: :yaml, value: settings)
|
t.set_field(target: :settings, name: :yaml, value: settings)
|
||||||
theme.save!
|
t.save!
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
expect(parse(policy)['script-src']).to include('from-theme.com')
|
def theme_policy
|
||||||
|
policy([theme.id])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'can be extended by themes' do
|
||||||
|
policy # call this first to make sure further actions clear the cache
|
||||||
|
|
||||||
|
expect(parse(policy)['script-src']).not_to include('from-theme.com')
|
||||||
|
|
||||||
|
expect(parse(theme_policy)['script-src']).to include('from-theme.com')
|
||||||
|
|
||||||
theme.update_setting(:extend_content_security_policy, "script-src: https://from-theme.net|worker-src: from-theme.com")
|
theme.update_setting(:extend_content_security_policy, "script-src: https://from-theme.net|worker-src: from-theme.com")
|
||||||
theme.save!
|
theme.save!
|
||||||
|
|
||||||
expect(parse(policy)['script-src']).to_not include('from-theme.com')
|
expect(parse(theme_policy)['script-src']).to_not include('from-theme.com')
|
||||||
expect(parse(policy)['script-src']).to include('https://from-theme.net')
|
expect(parse(theme_policy)['script-src']).to include('https://from-theme.net')
|
||||||
expect(parse(policy)['worker-src']).to include('from-theme.com')
|
expect(parse(theme_policy)['worker-src']).to include('from-theme.com')
|
||||||
|
|
||||||
theme.destroy!
|
theme.destroy!
|
||||||
|
|
||||||
expect(parse(policy)['script-src']).to_not include('https://from-theme.net')
|
expect(parse(theme_policy)['script-src']).to_not include('https://from-theme.net')
|
||||||
expect(parse(policy)['worker-src']).to_not include('from-theme.com')
|
expect(parse(theme_policy)['worker-src']).to_not include('from-theme.com')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'can be extended by site setting' do
|
it 'can be extended by site setting' do
|
||||||
@ -160,7 +171,7 @@ describe ContentSecurityPolicy do
|
|||||||
end.to_h
|
end.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
def policy
|
def policy(theme_ids = [])
|
||||||
ContentSecurityPolicy.policy
|
ContentSecurityPolicy.policy(theme_ids)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user