mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Add CSP frame-ancestors support (#12404)
This commit is contained in:
committed by
GitHub
parent
706ea6692d
commit
fb4486d5f1
@@ -12,7 +12,7 @@ class EmbedController < ApplicationController
|
|||||||
layout 'embed'
|
layout 'embed'
|
||||||
|
|
||||||
rescue_from Discourse::InvalidAccess do
|
rescue_from Discourse::InvalidAccess do
|
||||||
response.headers['X-Frame-Options'] = "ALLOWALL"
|
response.headers.delete('X-Frame-Options')
|
||||||
if current_user.try(:admin?)
|
if current_user.try(:admin?)
|
||||||
@setup_url = "#{Discourse.base_url}/admin/customize/embedding"
|
@setup_url = "#{Discourse.base_url}/admin/customize/embedding"
|
||||||
@show_reason = true
|
@show_reason = true
|
||||||
@@ -24,7 +24,7 @@ class EmbedController < ApplicationController
|
|||||||
def topics
|
def topics
|
||||||
discourse_expires_in 1.minute
|
discourse_expires_in 1.minute
|
||||||
|
|
||||||
response.headers['X-Frame-Options'] = "ALLOWALL"
|
response.headers.delete('X-Frame-Options')
|
||||||
unless SiteSetting.embed_topics_list?
|
unless SiteSetting.embed_topics_list?
|
||||||
render 'embed_topics_error', status: 400
|
render 'embed_topics_error', status: 400
|
||||||
return
|
return
|
||||||
@@ -157,7 +157,7 @@ class EmbedController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
response.headers['X-Frame-Options'] = "ALLOWALL"
|
response.headers.delete('X-Frame-Options')
|
||||||
rescue URI::Error
|
rescue URI::Error
|
||||||
raise Discourse::InvalidAccess.new('invalid referer host')
|
raise Discourse::InvalidAccess.new('invalid referer host')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1602,6 +1602,7 @@ en:
|
|||||||
content_security_policy: "Enable Content-Security-Policy"
|
content_security_policy: "Enable Content-Security-Policy"
|
||||||
content_security_policy_report_only: "Enable Content-Security-Policy-Report-Only"
|
content_security_policy_report_only: "Enable Content-Security-Policy-Report-Only"
|
||||||
content_security_policy_collect_reports: "Enable CSP violation report collection at /csp_reports"
|
content_security_policy_collect_reports: "Enable CSP violation report collection at /csp_reports"
|
||||||
|
content_security_policy_frame_ancestors: "Restrict who can embed this site in iframes via CSP. Control allowed hosts on <a href='%{base_path}/admin/customize/embedding'>Embedding</a>"
|
||||||
content_security_policy_script_src: "Additional allowlisted script sources. The current host and CDN are included by default. See <a href='https://meta.discourse.org/t/mitigate-xss-attacks-with-content-security-policy/104243' target='_blank'>Mitigate XSS Attacks with Content Security Policy.</a>"
|
content_security_policy_script_src: "Additional allowlisted script sources. The current host and CDN are included by default. See <a href='https://meta.discourse.org/t/mitigate-xss-attacks-with-content-security-policy/104243' target='_blank'>Mitigate XSS Attacks with Content Security Policy.</a>"
|
||||||
invalidate_inactive_admin_email_after_days: "Admin accounts that have not visited the site in this number of days will need to re-validate their email address before logging in. Set to 0 to disable."
|
invalidate_inactive_admin_email_after_days: "Admin accounts that have not visited the site in this number of days will need to re-validate their email address before logging in. Set to 0 to disable."
|
||||||
top_menu: "Determine which items appear in the homepage navigation, and in what order. Example latest|new|unread|categories|top|read|posted|bookmarks"
|
top_menu: "Determine which items appear in the homepage navigation, and in what order. Example latest|new|unread|categories|top|read|posted|bookmarks"
|
||||||
|
|||||||
@@ -1593,6 +1593,8 @@ security:
|
|||||||
default: false
|
default: false
|
||||||
content_security_policy_collect_reports:
|
content_security_policy_collect_reports:
|
||||||
default: false
|
default: false
|
||||||
|
content_security_policy_frame_ancestors:
|
||||||
|
default: false
|
||||||
content_security_policy_script_src:
|
content_security_policy_script_src:
|
||||||
type: simple_list
|
type: simple_list
|
||||||
default: ""
|
default: ""
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class ContentSecurityPolicy
|
|||||||
directives[:script_src] = script_src
|
directives[:script_src] = script_src
|
||||||
directives[:worker_src] = worker_src
|
directives[:worker_src] = worker_src
|
||||||
directives[:report_uri] = report_uri if SiteSetting.content_security_policy_collect_reports
|
directives[:report_uri] = report_uri if SiteSetting.content_security_policy_collect_reports
|
||||||
|
directives[:frame_ancestors] = frame_ancestors if restrict_embed?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -73,5 +74,17 @@ class ContentSecurityPolicy
|
|||||||
def report_uri
|
def report_uri
|
||||||
"#{base_url}/csp_reports"
|
"#{base_url}/csp_reports"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def frame_ancestors
|
||||||
|
[
|
||||||
|
"'self'",
|
||||||
|
*EmbeddableHost.pluck(:host).map { |host| "https://#{host}" }
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def restrict_embed?
|
||||||
|
SiteSetting.content_security_policy_frame_ancestors &&
|
||||||
|
!SiteSetting.embed_any_origin
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -154,6 +154,39 @@ describe ContentSecurityPolicy do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'frame-ancestors' do
|
||||||
|
context 'with content_security_policy_frame_ancestors enabled' do
|
||||||
|
before do
|
||||||
|
SiteSetting.content_security_policy_frame_ancestors = true
|
||||||
|
Fabricate(:embeddable_host, host: 'https://a.org')
|
||||||
|
Fabricate(:embeddable_host, host: 'https://b.org')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'always has self' do
|
||||||
|
frame_ancestors = parse(policy)['frame-ancestors']
|
||||||
|
expect(frame_ancestors).to include("'self'")
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes all EmbeddableHost' do
|
||||||
|
EmbeddableHost
|
||||||
|
frame_ancestors = parse(policy)['frame-ancestors']
|
||||||
|
expect(frame_ancestors).to include("https://a.org")
|
||||||
|
expect(frame_ancestors).to include("https://b.org")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with content_security_policy_frame_ancestors disabled' do
|
||||||
|
before do
|
||||||
|
SiteSetting.content_security_policy_frame_ancestors = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not set frame-ancestors' do
|
||||||
|
frame_ancestors = parse(policy)['frame-ancestors']
|
||||||
|
expect(frame_ancestors).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'can be extended by plugins' do
|
it 'can be extended by plugins' do
|
||||||
plugin = Class.new(Plugin::Instance) do
|
plugin = Class.new(Plugin::Instance) do
|
||||||
attr_accessor :enabled
|
attr_accessor :enabled
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ describe EmbedController do
|
|||||||
'REFERER' => 'https://example.com/evil-trout'
|
'REFERER' => 'https://example.com/evil-trout'
|
||||||
}
|
}
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(response.headers['X-Frame-Options']).to eq("ALLOWALL")
|
expect(response.headers['X-Frame-Options']).to be_nil
|
||||||
expect(response.body).to match("data-embed-id=\"de-1234\"")
|
expect(response.body).to match("data-embed-id=\"de-1234\"")
|
||||||
expect(response.body).to match("data-topic-id=\"#{topic.id}\"")
|
expect(response.body).to match("data-topic-id=\"#{topic.id}\"")
|
||||||
expect(response.body).to match("data-referer=\"https://example.com/evil-trout\"")
|
expect(response.body).to match("data-referer=\"https://example.com/evil-trout\"")
|
||||||
@@ -157,7 +157,7 @@ describe EmbedController do
|
|||||||
context "success" do
|
context "success" do
|
||||||
after do
|
after do
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(response.headers['X-Frame-Options']).to eq("ALLOWALL")
|
expect(response.headers['X-Frame-Options']).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it "tells the topic retriever to work when no previous embed is found" do
|
it "tells the topic retriever to work when no previous embed is found" do
|
||||||
@@ -249,5 +249,21 @@ describe EmbedController do
|
|||||||
expect(response.body).to match(I18n.t('embed.error'))
|
expect(response.body).to match(I18n.t('embed.error'))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "CSP frame-ancestors enabled" do
|
||||||
|
before do
|
||||||
|
SiteSetting.content_security_policy_frame_ancestors = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes all the hosts" do
|
||||||
|
get '/embed/comments',
|
||||||
|
params: { embed_url: embed_url },
|
||||||
|
headers: { 'REFERER' => "http://eviltrout.com/wat/1-2-3.html" }
|
||||||
|
|
||||||
|
expect(response.headers['Content-Security-Policy']).to match(/frame-ancestors.*https:\/\/discourse\.org/)
|
||||||
|
expect(response.headers['Content-Security-Policy']).to match(/frame-ancestors.*https:\/\/example\.com/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user