Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Always yield a CSP policy instance
If the app has the CSP disabled globally allow a controller action
to enable the policy for that request.
  • Loading branch information
pixeltrix committed Mar 8, 2018
1 parent 619b1b6 commit 4ec8bf6
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 17 deletions.
7 changes: 7 additions & 0 deletions actionpack/CHANGELOG.md
@@ -1,3 +1,10 @@
* Always yield a CSP policy instance from `content_security_policy`

This allows a controller action to enable the policy individually
for a controller and/or specific actions.

*Andrew White*

* Add the ability to disable the global CSP in a controller, e.g:

class LegacyPagesController < ApplicationController
Expand Down
Expand Up @@ -17,7 +17,7 @@ module ClassMethods
def content_security_policy(enabled = true, **options, &block)
before_action(options) do
if block_given?
policy = request.content_security_policy.clone
policy = current_content_security_policy
yield policy
request.content_security_policy = policy
end
Expand All @@ -44,5 +44,9 @@ def content_security_policy?
def content_security_policy_nonce
request.content_security_policy_nonce
end

def current_content_security_policy
request.content_security_policy.try(:clone) || ActionDispatch::ContentSecurityPolicy.new
end
end
end
70 changes: 58 additions & 12 deletions actionpack/test/dispatch/content_security_policy_test.rb
Expand Up @@ -369,18 +369,6 @@ def test_generates_no_content_security_policy

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

Expand All @@ -396,3 +384,61 @@ def assert_policy(expected, report_only: false)
assert_equal expected, response.headers[expected_header]
end
end

class DisabledContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest
class PolicyController < ActionController::Base
content_security_policy only: :inline do |p|
p.default_src "https://example.com"
end

def index
head :ok
end

def inline
head :ok
end
end

ROUTES = ActionDispatch::Routing::RouteSet.new
ROUTES.draw do
scope module: "disabled_content_security_policy_integration_test" do
get "/", to: "policy#index"
get "/inline", to: "policy#inline"
end
end

class PolicyConfigMiddleware
def initialize(app)
@app = app
end

def call(env)
env["action_dispatch.content_security_policy"] = nil
env["action_dispatch.content_security_policy_nonce_generator"] = nil
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_no_content_security_policy_by_default
get "/"
assert_nil response.headers["Content-Security-Policy"]
end

def test_generates_content_security_policy_header_when_globally_disabled
get "/inline"
assert_equal "default-src https://example.com", response.headers["Content-Security-Policy"]
end
end
6 changes: 2 additions & 4 deletions railties/lib/rails/application_controller.rb
Expand Up @@ -7,10 +7,8 @@ class Rails::ApplicationController < ActionController::Base # :nodoc:
before_action :disable_content_security_policy_nonce!

content_security_policy do |policy|
if policy
policy.script_src :unsafe_inline
policy.style_src :unsafe_inline
end
policy.script_src :unsafe_inline
policy.style_src :unsafe_inline
end

private
Expand Down

0 comments on commit 4ec8bf6

Please sign in to comment.