Skip to content

Commit

Permalink
Merge pull request #283 from twitter/named-appends
Browse files Browse the repository at this point in the history
add support for named appends
  • Loading branch information
oreoshake committed Sep 6, 2016
2 parents a1d74cf + 7abe764 commit db82b58
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 0 deletions.
55 changes: 55 additions & 0 deletions README.md
Expand Up @@ -94,6 +94,61 @@ use SecureHeaders::Middleware

All headers except for PublicKeyPins have a default value. See the [corresponding classes for their defaults](https://github.com/twitter/secureheaders/tree/master/lib/secure_headers/headers).

## Named Appends

Named Appends are blocks of code that can be reused and composed during requests. e.g. If a certain partial is rendered conditionally, and the csp needs to be adjusted for that partial, you can create a named append for that situation. The value returned by the block will be passed into `append_content_security_policy_directives`. The current request object is passed as an argument to the block for even more flexibility.

```ruby
def show
if include_widget?
@widget = widget.render
use_content_security_policy_named_append(:widget_partial)
end
end


SecureHeaders::Configuration.named_append(:widget_partial) do |request|
if request.controller_instance.current_user.in_test_bucket?
{ child_src: %w(beta.thirdpartyhost.com) }
else
{ child_src: %w(thirdpartyhost.com) }
end
end
```

You can use as many named appends as you would like per request, but be careful because order of inclusion matters. Consider the following:

```ruby
SecureHeader::Configuration.default do |config|
config.csp = { default_src: %w('self')}
end

SecureHeaders::Configuration.named_append(:A) do |request|
{ default_src: %w(myhost.com) }
end

SecureHeaders::Configuration.named_append(:B) do |request|
{ script_src: %w('unsafe-eval') }
end
```

The following code will produce different policies due to the way policies are normalized (e.g. providing a previously undefined directive that inherits from `default-src`, removing host source values when `*` is provided. Removing `'none'` when additional values are present, etc.):

```ruby
def index
use_content_security_policy_named_append(:A)
use_content_security_policy_named_append(:B)
# produces default-src 'self' myhost.com; script-src 'self' myhost.com 'unsafe-eval';
end

def show
use_content_security_policy_named_append(:B)
use_content_security_policy_named_append(:A)
# produces default-src 'self' myhost.com; script-src 'self' 'unsafe-eval';
end
```


## Named overrides

Named overrides serve two purposes:
Expand Down
9 changes: 9 additions & 0 deletions lib/secure_headers.rb
Expand Up @@ -72,6 +72,11 @@ def append_content_security_policy_directives(request, additions)
override_secure_headers_request_config(request, config)
end

def use_content_security_policy_named_append(request, name)
additions = SecureHeaders::Configuration.named_appends(name).call(request)
append_content_security_policy_directives(request, additions)
end

# Public: override X-Frame-Options settings for this request.
#
# value - deny, sameorigin, or allowall
Expand Down Expand Up @@ -267,4 +272,8 @@ def override_content_security_policy_directives(additions)
def override_x_frame_options(value)
SecureHeaders.override_x_frame_options(request, value)
end

def use_content_security_policy_named_append(name)
SecureHeaders.use_content_security_policy_named_append(request, name)
end
end
11 changes: 11 additions & 0 deletions lib/secure_headers/configuration.rb
Expand Up @@ -46,6 +46,17 @@ def get(name = DEFAULT_CONFIG)
@configurations[name]
end

def named_appends(name)
@appends ||= {}
@appends[name]
end

def named_append(name, target = nil, &block)
@appends ||= {}
raise "Provide a configuration block" unless block_given?
@appends[name] = block
end

private

# Private: add a valid configuration to the global set of named configs.
Expand Down
22 changes: 22 additions & 0 deletions spec/lib/secure_headers_spec.rb
Expand Up @@ -151,6 +151,28 @@ module SecureHeaders
expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self'; script-src mycdn.com 'unsafe-inline' anothercdn.com")
end

it "supports named appends" do
Configuration.default do |config|
config.csp = {
default_src: %w('self')
}
end

Configuration.named_append(:moar_default_sources) do |request|
{ default_src: %w(https:)}
end

Configuration.named_append(:how_about_a_script_src_too) do |request|
{ script_src: %w('unsafe-inline')}
end

SecureHeaders.use_content_security_policy_named_append(request, :moar_default_sources)
SecureHeaders.use_content_security_policy_named_append(request, :how_about_a_script_src_too)
hash = SecureHeaders.header_hash_for(request)

expect(hash[CSP::HEADER_NAME]).to eq("default-src 'self' https:; script-src 'self' https: 'unsafe-inline'")
end

it "dups global configuration just once when overriding n times and only calls idempotent_additions? once" do
Configuration.default do |config|
config.csp = {
Expand Down

0 comments on commit db82b58

Please sign in to comment.