Skip to content

Commit

Permalink
Allow adding/removing of existing Content-Security-Policy and `Perm…
Browse files Browse the repository at this point in the history
…issions-Policy` directives.

Previously, if you wanted to add/remove directives from either header, you had to redefine the defaults in the controller.

Now, you can adjust the policy directives without overwriting the global configuration.
  • Loading branch information
agrobbin committed Apr 22, 2023
1 parent 5beb062 commit 03ca950
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 0 deletions.
8 changes: 8 additions & 0 deletions actionpack/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
* Allow adding/removing of existing `Content-Security-Policy` and `Permissions-Policy` directives.

Previously, if you wanted to add/remove directives from either header, you had to redefine the defaults in the controller.

Now, you can adjust the policy directives without overwriting the global configuration.

*Alex Robbin*

* Include source location in routes extended view.

```bash
Expand Down
14 changes: 14 additions & 0 deletions actionpack/lib/action_dispatch/http/content_security_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@ def initialize_copy(other)
@directives.delete(directive)
end
end

define_method("add_#{name}") do |*sources|
existing_sources = @directives.fetch(directive, [])

public_send(name, *(existing_sources + sources).uniq)
end

define_method("remove_#{name}") do |*sources|
existing_sources = @directives.fetch(directive, [])

sources.each { |source| existing_sources.delete(source) }

public_send(name, *(existing_sources - apply_mappings(sources)))
end
end

# Specify whether to prevent the user agent from loading any assets over
Expand Down
12 changes: 12 additions & 0 deletions actionpack/lib/action_dispatch/http/permissions_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@ def initialize_copy(other)
@directives.delete(directive)
end
end

define_method("add_#{name}") do |*sources|
existing_sources = @directives.fetch(directive, [])

public_send(name, *(existing_sources + sources).uniq)
end

define_method("remove_#{name}") do |*sources|
existing_sources = @directives.fetch(directive, [])

public_send(name, *(existing_sources - apply_mappings(sources)))
end
end

%w[speaker vibrate vr].each do |directive|
Expand Down
12 changes: 12 additions & 0 deletions actionpack/test/dispatch/content_security_policy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,18 @@ def test_multiple_directives
assert_equal "script-src 'self' https:; style-src 'self' https:", @policy.build
end

def test_add_directives
@policy.script_src :self
@policy.add_script_src :https
assert_equal "script-src 'self' https:", @policy.build
end

def test_remove_directives
@policy.script_src :self, :https
@policy.remove_script_src :https
assert_equal "script-src 'self'", @policy.build
end

def test_dynamic_directives
request = ActionDispatch::Request.new("HTTP_HOST" => "www.example.com")
controller = Struct.new(:request).new(request)
Expand Down
12 changes: 12 additions & 0 deletions actionpack/test/dispatch/permissions_policy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ def test_multiple_directives_for_multiple_directives
assert_equal "geolocation 'self' https://example.com; usb 'none' https://example.com", @policy.build
end

def test_add_directives
@policy.geolocation :self
@policy.add_geolocation "https://example.com"
assert_equal "geolocation 'self' https://example.com", @policy.build
end

def test_remove_directives
@policy.geolocation :self, "https://example.com"
@policy.remove_geolocation "https://example.com"
assert_equal "geolocation 'self'", @policy.build
end

def test_invalid_directive_source
exception = assert_raises(ArgumentError) do
@policy.geolocation [:non_existent]
Expand Down
64 changes: 64 additions & 0 deletions railties/test/application/content_security_policy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,70 @@ def index
assert_policy "default-src https://example.com"
end

test "add directives to content security policy in a controller" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
content_security_policy do |p|
p.add_script_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
p.script_src :self
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:; script-src 'self' https://example.com"
end

test "remove directives to content security policy in a controller" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
content_security_policy do |p|
p.remove_script_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
p.script_src 'https://example.com'
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 "override content security policy to report only in a controller" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
Expand Down
62 changes: 62 additions & 0 deletions railties/test/application/permissions_policy_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,68 @@ def index
assert_policy "geolocation https://example.com"
end

test "add directive to permissions policy in a controller" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
permissions_policy do |p|
p.add_geolocation "https://example.com"
end
def index
render html: "<h1>Welcome to Rails!</h1>"
end
end
RUBY

app_file "config/initializers/permissions_policy.rb", <<-RUBY
Rails.application.config.permissions_policy do |p|
p.geolocation :self
end
RUBY

app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
root to: "pages#index"
end
RUBY

app("development")

get "/"
assert_policy "geolocation 'self' https://example.com"
end

test "remove directive from permissions policy in a controller" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
permissions_policy do |p|
p.remove_geolocation "https://example.com"
end
def index
render html: "<h1>Welcome to Rails!</h1>"
end
end
RUBY

app_file "config/initializers/permissions_policy.rb", <<-RUBY
Rails.application.config.permissions_policy do |p|
p.geolocation :self, "https://example.com"
end
RUBY

app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
root to: "pages#index"
end
RUBY

app("development")

get "/"
assert_policy "geolocation 'self'"
end

test "override permissions policy by unsetting a directive in a controller" do
controller :pages, <<-RUBY
class PagesController < ApplicationController
Expand Down

0 comments on commit 03ca950

Please sign in to comment.