提交 456c3ffd 编写于 作者: A Andrew White

Add DSL for configuring Content-Security-Policy header

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
上级 28333d62
...@@ -22,6 +22,7 @@ module ActionController ...@@ -22,6 +22,7 @@ module ActionController
autoload_under "metal" do autoload_under "metal" do
autoload :ConditionalGet autoload :ConditionalGet
autoload :ContentSecurityPolicy
autoload :Cookies autoload :Cookies
autoload :DataStreaming autoload :DataStreaming
autoload :EtagWithTemplateDigest autoload :EtagWithTemplateDigest
......
...@@ -225,6 +225,7 @@ def self.without_modules(*modules) ...@@ -225,6 +225,7 @@ def self.without_modules(*modules)
Flash, Flash,
FormBuilder, FormBuilder,
RequestForgeryProtection, RequestForgeryProtection,
ContentSecurityPolicy,
ForceSSL, ForceSSL,
Streaming, Streaming,
DataStreaming, DataStreaming,
......
# frozen_string_literal: true
module ActionController #:nodoc:
module ContentSecurityPolicy
# TODO: Documentation
extend ActiveSupport::Concern
module ClassMethods
def content_security_policy(**options, &block)
before_action(options) do
if block_given?
policy = request.content_security_policy.clone
yield policy
request.content_security_policy = policy
end
end
end
def content_security_policy_report_only(report_only = true, **options)
before_action(options) do
request.content_security_policy_report_only = report_only
end
end
end
end
end
...@@ -42,6 +42,7 @@ class IllegalStateError < StandardError ...@@ -42,6 +42,7 @@ class IllegalStateError < StandardError
eager_autoload do eager_autoload do
autoload_under "http" do autoload_under "http" do
autoload :ContentSecurityPolicy
autoload :Request autoload :Request
autoload :Response autoload :Response
end end
......
# frozen_string_literal: true
module ActionDispatch #:nodoc:
class ContentSecurityPolicy
class Middleware
CONTENT_TYPE = "Content-Type".freeze
POLICY = "Content-Security-Policy".freeze
POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze
def initialize(app)
@app = app
end
def call(env)
request = ActionDispatch::Request.new env
_, headers, _ = response = @app.call(env)
return response unless html_response?(headers)
return response if policy_present?(headers)
if policy = request.content_security_policy
headers[header_name(request)] = policy.build(request.controller_instance)
end
response
end
private
def html_response?(headers)
if content_type = headers[CONTENT_TYPE]
content_type =~ /html/
end
end
def header_name(request)
if request.content_security_policy_report_only
POLICY_REPORT_ONLY
else
POLICY
end
end
def policy_present?(headers)
headers[POLICY] || headers[POLICY_REPORT_ONLY]
end
end
module Request
POLICY = "action_dispatch.content_security_policy".freeze
POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze
def content_security_policy
get_header(POLICY)
end
def content_security_policy=(policy)
set_header(POLICY, policy)
end
def content_security_policy_report_only
get_header(POLICY_REPORT_ONLY)
end
def content_security_policy_report_only=(value)
set_header(POLICY_REPORT_ONLY, value)
end
end
MAPPINGS = {
self: "'self'",
unsafe_eval: "'unsafe-eval'",
unsafe_inline: "'unsafe-inline'",
none: "'none'",
http: "http:",
https: "https:",
data: "data:",
mediastream: "mediastream:",
blob: "blob:",
filesystem: "filesystem:",
report_sample: "'report-sample'",
strict_dynamic: "'strict-dynamic'"
}.freeze
DIRECTIVES = {
base_uri: "base-uri",
child_src: "child-src",
connect_src: "connect-src",
default_src: "default-src",
font_src: "font-src",
form_action: "form-action",
frame_ancestors: "frame-ancestors",
frame_src: "frame-src",
img_src: "img-src",
manifest_src: "manifest-src",
media_src: "media-src",
object_src: "object-src",
script_src: "script-src",
style_src: "style-src",
worker_src: "worker-src"
}.freeze
private_constant :MAPPINGS, :DIRECTIVES
attr_reader :directives
def initialize
@directives = {}
yield self if block_given?
end
def initialize_copy(other)
@directives = copy_directives(other.directives)
end
DIRECTIVES.each do |name, directive|
define_method(name) do |*sources|
if sources.first
@directives[directive] = apply_mappings(sources)
else
@directives.delete(directive)
end
end
end
def block_all_mixed_content(enabled = true)
if enabled
@directives["block-all-mixed-content"] = true
else
@directives.delete("block-all-mixed-content")
end
end
def plugin_types(*types)
if types.first
@directives["plugin-types"] = types
else
@directives.delete("plugin-types")
end
end
def report_uri(uri)
@directives["report-uri"] = [uri]
end
def require_sri_for(*types)
if types.first
@directives["require-sri-for"] = types
else
@directives.delete("require-sri-for")
end
end
def sandbox(*values)
if values.empty?
@directives["sandbox"] = true
elsif values.first
@directives["sandbox"] = values
else
@directives.delete("sandbox")
end
end
def upgrade_insecure_requests(enabled = true)
if enabled
@directives["upgrade-insecure-requests"] = true
else
@directives.delete("upgrade-insecure-requests")
end
end
def build(context = nil)
build_directives(context).compact.join("; ") + ";"
end
private
def copy_directives(directives)
directives.transform_values { |sources| sources.map(&:dup) }
end
def apply_mappings(sources)
sources.map do |source|
case source
when Symbol
apply_mapping(source)
when String, Proc
source
else
raise ArgumentError, "Invalid content security policy source: #{source.inspect}"
end
end
end
def apply_mapping(source)
MAPPINGS.fetch(source) do
raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}"
end
end
def build_directives(context)
@directives.map do |directive, sources|
if sources.is_a?(Array)
"#{directive} #{build_directive(sources, context).join(' ')}"
elsif sources
directive
else
nil
end
end
end
def build_directive(sources, context)
sources.map { |source| resolve_source(source, context) }
end
def resolve_source(source, context)
case source
when String
source
when Symbol
source.to_s
when Proc
if context.nil?
raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}"
else
context.instance_exec(&source)
end
else
raise RuntimeError, "Unexpected content security policy source: #{source.inspect}"
end
end
end
end
...@@ -22,6 +22,7 @@ class Request ...@@ -22,6 +22,7 @@ class Request
include ActionDispatch::Http::Parameters include ActionDispatch::Http::Parameters
include ActionDispatch::Http::FilterParameters include ActionDispatch::Http::FilterParameters
include ActionDispatch::Http::URL include ActionDispatch::Http::URL
include ActionDispatch::ContentSecurityPolicy::Request
include Rack::Request::Env include Rack::Request::Env
autoload :Session, "action_dispatch/request/session" autoload :Session, "action_dispatch/request/session"
......
# frozen_string_literal: true
require "abstract_unit"
class ContentSecurityPolicyTest < ActiveSupport::TestCase
def setup
@policy = ActionDispatch::ContentSecurityPolicy.new
end
def test_build
assert_equal ";", @policy.build
@policy.script_src :self
assert_equal "script-src 'self';", @policy.build
end
def test_mappings
@policy.script_src :data
assert_equal "script-src data:;", @policy.build
@policy.script_src :mediastream
assert_equal "script-src mediastream:;", @policy.build
@policy.script_src :blob
assert_equal "script-src blob:;", @policy.build
@policy.script_src :filesystem
assert_equal "script-src filesystem:;", @policy.build
@policy.script_src :self
assert_equal "script-src 'self';", @policy.build
@policy.script_src :unsafe_inline
assert_equal "script-src 'unsafe-inline';", @policy.build
@policy.script_src :unsafe_eval
assert_equal "script-src 'unsafe-eval';", @policy.build
@policy.script_src :none
assert_equal "script-src 'none';", @policy.build
@policy.script_src :strict_dynamic
assert_equal "script-src 'strict-dynamic';", @policy.build
@policy.script_src :none, :report_sample
assert_equal "script-src 'none' 'report-sample';", @policy.build
end
def test_fetch_directives
@policy.child_src :self
assert_match %r{child-src 'self'}, @policy.build
@policy.child_src false
assert_no_match %r{child-src}, @policy.build
@policy.connect_src :self
assert_match %r{connect-src 'self'}, @policy.build
@policy.connect_src false
assert_no_match %r{connect-src}, @policy.build
@policy.default_src :self
assert_match %r{default-src 'self'}, @policy.build
@policy.default_src false
assert_no_match %r{default-src}, @policy.build
@policy.font_src :self
assert_match %r{font-src 'self'}, @policy.build
@policy.font_src false
assert_no_match %r{font-src}, @policy.build
@policy.frame_src :self
assert_match %r{frame-src 'self'}, @policy.build
@policy.frame_src false
assert_no_match %r{frame-src}, @policy.build
@policy.img_src :self
assert_match %r{img-src 'self'}, @policy.build
@policy.img_src false
assert_no_match %r{img-src}, @policy.build
@policy.manifest_src :self
assert_match %r{manifest-src 'self'}, @policy.build
@policy.manifest_src false
assert_no_match %r{manifest-src}, @policy.build
@policy.media_src :self
assert_match %r{media-src 'self'}, @policy.build
@policy.media_src false
assert_no_match %r{media-src}, @policy.build
@policy.object_src :self
assert_match %r{object-src 'self'}, @policy.build
@policy.object_src false
assert_no_match %r{object-src}, @policy.build
@policy.script_src :self
assert_match %r{script-src 'self'}, @policy.build
@policy.script_src false
assert_no_match %r{script-src}, @policy.build
@policy.style_src :self
assert_match %r{style-src 'self'}, @policy.build
@policy.style_src false
assert_no_match %r{style-src}, @policy.build
@policy.worker_src :self
assert_match %r{worker-src 'self'}, @policy.build
@policy.worker_src false
assert_no_match %r{worker-src}, @policy.build
end
def test_document_directives
@policy.base_uri "https://example.com"
assert_match %r{base-uri https://example\.com;}, @policy.build
@policy.plugin_types "application/x-shockwave-flash"
assert_match %r{plugin-types application/x-shockwave-flash;}, @policy.build
@policy.sandbox
assert_match %r{sandbox;}, @policy.build
@policy.sandbox "allow-scripts", "allow-modals"
assert_match %r{sandbox allow-scripts allow-modals;}, @policy.build
@policy.sandbox false
assert_no_match %r{sandbox}, @policy.build
end
def test_navigation_directives
@policy.form_action :self
assert_match %r{form-action 'self';}, @policy.build
@policy.frame_ancestors :self
assert_match %r{frame-ancestors 'self';}, @policy.build
end
def test_reporting_directives
@policy.report_uri "/violations"
assert_match %r{report-uri /violations;}, @policy.build
end
def test_other_directives
@policy.block_all_mixed_content
assert_match %r{block-all-mixed-content;}, @policy.build
@policy.block_all_mixed_content false
assert_no_match %r{block-all-mixed-content}, @policy.build
@policy.require_sri_for :script, :style
assert_match %r{require-sri-for script style;}, @policy.build
@policy.require_sri_for "script", "style"
assert_match %r{require-sri-for script style;}, @policy.build
@policy.require_sri_for
assert_no_match %r{require-sri-for}, @policy.build
@policy.upgrade_insecure_requests
assert_match %r{upgrade-insecure-requests;}, @policy.build
@policy.upgrade_insecure_requests false
assert_no_match %r{upgrade-insecure-requests}, @policy.build
end
def test_multiple_sources
@policy.script_src :self, :https
assert_equal "script-src 'self' https:;", @policy.build
end
def test_multiple_directives
@policy.script_src :self, :https
@policy.style_src :self, :https
assert_equal "script-src 'self' https:; style-src 'self' https:;", @policy.build
end
def test_dynamic_directives
request = Struct.new(:host).new("www.example.com")
controller = Struct.new(:request).new(request)
@policy.script_src -> { request.host }
assert_equal "script-src www.example.com;", @policy.build(controller)
end
def test_mixed_static_and_dynamic_directives
@policy.script_src :self, -> { "foo.com" }, "bar.com"
assert_equal "script-src 'self' foo.com bar.com;", @policy.build(Object.new)
end
def test_invalid_directive_source
exception = assert_raises(ArgumentError) do
@policy.script_src [:self]
end
assert_equal "Invalid content security policy source: [:self]", exception.message
end
def test_missing_context_for_dynamic_source
@policy.script_src -> { request.host }
exception = assert_raises(RuntimeError) do
@policy.build
end
assert_match %r{\AMissing context for the dynamic content security policy source:}, exception.message
end
def test_raises_runtime_error_when_unexpected_source
@policy.plugin_types [:flash]
exception = assert_raises(RuntimeError) do
@policy.build
end
assert_match %r{\AUnexpected content security policy source:}, exception.message
end
end
class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest
class PolicyController < ActionController::Base
content_security_policy only: :inline do |p|
p.default_src "https://example.com"
end
content_security_policy only: :conditional, if: :condition? do |p|
p.default_src "https://true.example.com"
end
content_security_policy only: :conditional, unless: :condition? do |p|
p.default_src "https://false.example.com"
end
content_security_policy only: :report_only do |p|
p.report_uri "/violations"
end
content_security_policy_report_only only: :report_only
def index
head :ok
end
def inline
head :ok
end
def conditional
head :ok
end
def report_only
head :ok
end
private
def condition?
params[:condition] == "true"
end
end
ROUTES = ActionDispatch::Routing::RouteSet.new
ROUTES.draw do
scope module: "content_security_policy_integration_test" do
get "/", to: "policy#index"
get "/inline", to: "policy#inline"
get "/conditional", to: "policy#conditional"
get "/report-only", to: "policy#report_only"
end
end
POLICY = ActionDispatch::ContentSecurityPolicy.new do |p|
p.default_src :self
end
class PolicyConfigMiddleware
def initialize(app)
@app = app
end
def call(env)
env["action_dispatch.content_security_policy"] = POLICY
env["action_dispatch.content_security_policy_report_only"] = false
env["action_dispatch.show_exceptions"] = false
@app.call(env)
end
end
APP = build_app(ROUTES) do |middleware|
middleware.use PolicyConfigMiddleware
middleware.use ActionDispatch::ContentSecurityPolicy::Middleware
end
def app
APP
end
def test_generates_content_security_policy_header
get "/"
assert_policy "default-src 'self';"
end
def test_generates_inline_content_security_policy
get "/inline"
assert_policy "default-src https://example.com;"
end
def test_generates_conditional_content_security_policy
get "/conditional", params: { condition: "true" }
assert_policy "default-src https://true.example.com;"
get "/conditional", params: { condition: "false" }
assert_policy "default-src https://false.example.com;"
end
def test_generates_report_only_content_security_policy
get "/report-only"
assert_policy "default-src 'self'; report-uri /violations;", report_only: true
end
private
def env_config
Rails.application.env_config
end
def content_security_policy
env_config["action_dispatch.content_security_policy"]
end
def content_security_policy=(policy)
env_config["action_dispatch.content_security_policy"] = policy
end
def assert_policy(expected, report_only: false)
assert_response :success
if report_only
expected_header = "Content-Security-Policy-Report-Only"
unexpected_header = "Content-Security-Policy"
else
expected_header = "Content-Security-Policy"
unexpected_header = "Content-Security-Policy-Report-Only"
end
assert_nil response.headers[unexpected_header]
assert_equal expected, response.headers[expected_header]
end
end
...@@ -266,7 +266,9 @@ def env_config ...@@ -266,7 +266,9 @@ def env_config
"action_dispatch.signed_cookie_digest" => config.action_dispatch.signed_cookie_digest, "action_dispatch.signed_cookie_digest" => config.action_dispatch.signed_cookie_digest,
"action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer, "action_dispatch.cookies_serializer" => config.action_dispatch.cookies_serializer,
"action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest, "action_dispatch.cookies_digest" => config.action_dispatch.cookies_digest,
"action_dispatch.cookies_rotations" => config.action_dispatch.cookies_rotations "action_dispatch.cookies_rotations" => config.action_dispatch.cookies_rotations,
"action_dispatch.content_security_policy" => config.content_security_policy,
"action_dispatch.content_security_policy_report_only" => config.content_security_policy_report_only
) )
end end
end end
......
...@@ -16,44 +16,46 @@ class Configuration < ::Rails::Engine::Configuration ...@@ -16,44 +16,46 @@ class Configuration < ::Rails::Engine::Configuration
:ssl_options, :public_file_server, :ssl_options, :public_file_server,
:session_options, :time_zone, :reload_classes_only_on_change, :session_options, :time_zone, :reload_classes_only_on_change,
:beginning_of_week, :filter_redirect, :x, :enable_dependency_loading, :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading,
:read_encrypted_secrets, :log_level :read_encrypted_secrets, :log_level, :content_security_policy_report_only
attr_reader :encoding, :api_only attr_reader :encoding, :api_only
def initialize(*) def initialize(*)
super super
self.encoding = Encoding::UTF_8 self.encoding = Encoding::UTF_8
@allow_concurrency = nil @allow_concurrency = nil
@consider_all_requests_local = false @consider_all_requests_local = false
@filter_parameters = [] @filter_parameters = []
@filter_redirect = [] @filter_redirect = []
@helpers_paths = [] @helpers_paths = []
@public_file_server = ActiveSupport::OrderedOptions.new @public_file_server = ActiveSupport::OrderedOptions.new
@public_file_server.enabled = true @public_file_server.enabled = true
@public_file_server.index_name = "index" @public_file_server.index_name = "index"
@force_ssl = false @force_ssl = false
@ssl_options = {} @ssl_options = {}
@session_store = nil @session_store = nil
@time_zone = "UTC" @time_zone = "UTC"
@beginning_of_week = :monday @beginning_of_week = :monday
@log_level = :debug @log_level = :debug
@generators = app_generators @generators = app_generators
@cache_store = [ :file_store, "#{root}/tmp/cache/" ] @cache_store = [ :file_store, "#{root}/tmp/cache/" ]
@railties_order = [:all] @railties_order = [:all]
@relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"] @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"]
@reload_classes_only_on_change = true @reload_classes_only_on_change = true
@file_watcher = ActiveSupport::FileUpdateChecker @file_watcher = ActiveSupport::FileUpdateChecker
@exceptions_app = nil @exceptions_app = nil
@autoflush_log = true @autoflush_log = true
@log_formatter = ActiveSupport::Logger::SimpleFormatter.new @log_formatter = ActiveSupport::Logger::SimpleFormatter.new
@eager_load = nil @eager_load = nil
@secret_token = nil @secret_token = nil
@secret_key_base = nil @secret_key_base = nil
@api_only = false @api_only = false
@debug_exception_response_format = nil @debug_exception_response_format = nil
@x = Custom.new @x = Custom.new
@enable_dependency_loading = false @enable_dependency_loading = false
@read_encrypted_secrets = false @read_encrypted_secrets = false
@content_security_policy = nil
@content_security_policy_report_only = false
end end
def load_defaults(target_version) def load_defaults(target_version)
...@@ -233,6 +235,10 @@ def annotations ...@@ -233,6 +235,10 @@ def annotations
SourceAnnotationExtractor::Annotation SourceAnnotationExtractor::Annotation
end end
def content_security_policy(&block)
@content_security_policy ||= ActionDispatch::ContentSecurityPolicy.new(&block)
end
class Custom #:nodoc: class Custom #:nodoc:
def initialize def initialize
@configurations = Hash.new @configurations = Hash.new
......
...@@ -63,6 +63,10 @@ def build_stack ...@@ -63,6 +63,10 @@ def build_stack
middleware.use ::ActionDispatch::Flash middleware.use ::ActionDispatch::Flash
end end
unless config.api_only
middleware.use ::ActionDispatch::ContentSecurityPolicy::Middleware
end
middleware.use ::Rack::Head middleware.use ::Rack::Head
middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ConditionalGet
middleware.use ::Rack::ETag, "no-cache" middleware.use ::Rack::ETag, "no-cache"
......
# Define an application-wide content security policy
# For further information see the following documentation
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
Rails.application.config.content_security_policy do |p|
p.default_src :self, :https
p.font_src :self, :https, :data
p.img_src :self, :https, :data
p.object_src :none
p.script_src :self, :https
p.style_src :self, :https, :unsafe_inline
# Specify URI for violation reports
# p.report_uri "/csp-violation-report-endpoint"
end
# Report CSP violations to a specified URI
# For further information see the following documentation:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
# Rails.application.config.content_security_policy_report_only = true
# frozen_string_literal: true
require "isolation/abstract_unit"
require "rack/test"
module ApplicationTests
class ContentSecurityPolicyTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
include Rack::Test::Methods
def setup
build_app
end
def teardown
teardown_app
end
test "default content security policy is empty" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
def index
render html: "<h1>Welcome to Rails!</h1>"
end
end
RUBY
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
root to: "pages#index"
end
RUBY
app("development")
get "/"
assert_equal ";", last_response.headers["Content-Security-Policy"]
end
test "global content security policy in an initializer" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
def index
render html: "<h1>Welcome to Rails!</h1>"
end
end
RUBY
app_file "config/initializers/content_security_policy.rb", <<-RUBY
Rails.application.config.content_security_policy do |p|
p.default_src :self, :https
end
RUBY
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
root to: "pages#index"
end
RUBY
app("development")
get "/"
assert_policy "default-src 'self' https:;"
end
test "global report only content security policy in an initializer" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
def index
render html: "<h1>Welcome to Rails!</h1>"
end
end
RUBY
app_file "config/initializers/content_security_policy.rb", <<-RUBY
Rails.application.config.content_security_policy do |p|
p.default_src :self, :https
end
Rails.application.config.content_security_policy_report_only = true
RUBY
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
root to: "pages#index"
end
RUBY
app("development")
get "/"
assert_policy "default-src 'self' https:;", report_only: true
end
test "override content security policy in a controller" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
content_security_policy do |p|
p.default_src "https://example.com"
end
def index
render html: "<h1>Welcome to Rails!</h1>"
end
end
RUBY
app_file "config/initializers/content_security_policy.rb", <<-RUBY
Rails.application.config.content_security_policy do |p|
p.default_src :self, :https
end
RUBY
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
root to: "pages#index"
end
RUBY
app("development")
get "/"
assert_policy "default-src https://example.com;"
end
test "override content security policy to report only in a controller" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
content_security_policy_report_only
def index
render html: "<h1>Welcome to Rails!</h1>"
end
end
RUBY
app_file "config/initializers/content_security_policy.rb", <<-RUBY
Rails.application.config.content_security_policy do |p|
p.default_src :self, :https
end
RUBY
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
root to: "pages#index"
end
RUBY
app("development")
get "/"
assert_policy "default-src 'self' https:;", report_only: true
end
test "global content security policy added to rack app" do
app_file "config/initializers/content_security_policy.rb", <<-RUBY
Rails.application.config.content_security_policy do |p|
p.default_src :self, :https
end
RUBY
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
app = ->(env) {
[200, { "Content-Type" => "text/html" }, ["<p>Hello, World!</p>"]]
}
root to: app
end
RUBY
app("development")
get "/"
assert_policy "default-src 'self' https:;"
end
private
def assert_policy(expected, report_only: false)
assert_equal 200, last_response.status
if report_only
expected_header = "Content-Security-Policy-Report-Only"
unexpected_header = "Content-Security-Policy"
else
expected_header = "Content-Security-Policy"
unexpected_header = "Content-Security-Policy-Report-Only"
end
assert_nil last_response.headers[unexpected_header]
assert_equal expected, last_response.headers[expected_header]
end
end
end
...@@ -42,6 +42,7 @@ def app ...@@ -42,6 +42,7 @@ def app
"ActionDispatch::Cookies", "ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore", "ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash", "ActionDispatch::Flash",
"ActionDispatch::ContentSecurityPolicy::Middleware",
"Rack::Head", "Rack::Head",
"Rack::ConditionalGet", "Rack::ConditionalGet",
"Rack::ETag" "Rack::ETag"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册