diff options
author | Andrey Novikov <envek@envek.name> | 2018-04-17 12:48:29 +0300 |
---|---|---|
committer | Andrey Novikov <envek@envek.name> | 2018-04-17 12:56:26 +0300 |
commit | b9b660728ff771424e423ef917ad81ffadd50210 (patch) | |
tree | 66a19cf9b6508cf00f00ab851914b970637aee21 /actionpack/lib | |
parent | 5003d5996a3d29d6614ccf714ad05f911aa30cff (diff) | |
download | rails-b9b660728ff771424e423ef917ad81ffadd50210.tar.gz rails-b9b660728ff771424e423ef917ad81ffadd50210.tar.bz2 rails-b9b660728ff771424e423ef917ad81ffadd50210.zip |
Output only one nonce in CSP header per request
Diffstat (limited to 'actionpack/lib')
-rw-r--r-- | actionpack/lib/action_dispatch/http/content_security_policy.rb | 47 |
1 files changed, 30 insertions, 17 deletions
diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb index c1f80a1ffc..0573f13f94 100644 --- a/actionpack/lib/action_dispatch/http/content_security_policy.rb +++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb @@ -21,13 +21,7 @@ module ActionDispatch #:nodoc: return response if policy_present?(headers) if policy = request.content_security_policy - if policy.directives["script-src"] - if nonce = request.content_security_policy_nonce - policy.directives["script-src"] << "'nonce-#{nonce}'" - end - end - - headers[header_name(request)] = policy.build(request.controller_instance) + headers[header_name(request)] = policy.build(request) end response @@ -101,6 +95,14 @@ module ActionDispatch #:nodoc: end end + class NonceGenerator + def call(request) + if nonce = request&.content_security_policy_nonce + "'nonce-#{nonce}'" + end + end + end + MAPPINGS = { self: "'self'", unsafe_eval: "'unsafe-eval'", @@ -131,7 +133,7 @@ module ActionDispatch #:nodoc: manifest_src: "manifest-src", media_src: "media-src", object_src: "object-src", - script_src: "script-src", + # script_src handled differently style_src: "style-src", worker_src: "worker-src" }.freeze @@ -159,6 +161,15 @@ module ActionDispatch #:nodoc: end end + def script_src(*sources) + if sources.first + @directives["script-src"] = apply_mappings(sources) + @directives["script-src"] << NonceGenerator.new + else + @directives.delete("script-src") + end + end + def block_all_mixed_content(enabled = true) if enabled @directives["block-all-mixed-content"] = true @@ -205,8 +216,8 @@ module ActionDispatch #:nodoc: end end - def build(context = nil) - build_directives(context).compact.join("; ") + def build(request = nil) + build_directives(request).compact.join("; ") end private @@ -229,10 +240,10 @@ module ActionDispatch #:nodoc: end end - def build_directives(context) + def build_directives(request) @directives.map do |directive, sources| if sources.is_a?(Array) - "#{directive} #{build_directive(sources, context).join(' ')}" + "#{directive} #{build_directive(sources, request).compact.join(' ')}" elsif sources directive else @@ -241,22 +252,24 @@ module ActionDispatch #:nodoc: end end - def build_directive(sources, context) - sources.map { |source| resolve_source(source, context) } + def build_directive(sources, request) + sources.map { |source| resolve_source(source, request) } end - def resolve_source(source, context) + def resolve_source(source, request) case source when String source when Symbol source.to_s when Proc - if context.nil? + if request&.controller_instance.nil? raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}" else - context.instance_exec(&source) + request.controller_instance.instance_exec(&source) end + when NonceGenerator + source.call(request) else raise RuntimeError, "Unexpected content security policy source: #{source.inspect}" end |