Skip to content

Commit fd2e81f

Browse files
committed
feat: add Content Security Policy header
1 parent 081d158 commit fd2e81f

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

lib/pact_broker/app.rb

+11
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@
1515
require 'rack/pact_broker/convert_404_to_hal'
1616
require 'rack/pact_broker/reset_thread_data'
1717
require 'rack/pact_broker/add_vary_header'
18+
require 'rack/pact_broker/use_when'
1819
require 'sucker_punch'
1920

2021
module PactBroker
2122

2223
class App
2324
include PactBroker::Logging
25+
using Rack::PactBroker::UseWhen
2426

2527
attr_accessor :configuration
2628

@@ -162,6 +164,15 @@ def configure_middleware
162164
# NOTE THAT NONE OF THIS IS PROTECTED BY AUTH - is that ok?
163165
if configuration.use_rack_protection
164166
@app_builder.use Rack::Protection, except: [:path_traversal, :remote_token, :session_hijacking, :http_origin]
167+
168+
is_hal_browser = ->(env) { env['PATH_INFO'] == '/hal-browser/browser.html' }
169+
not_hal_browser = ->(env) { env['PATH_INFO'] != '/hal-browser/browser.html' }
170+
171+
@app_builder.use_when not_hal_browser,
172+
Rack::Protection::ContentSecurityPolicy, configuration.content_security_policy
173+
@app_builder.use_when is_hal_browser,
174+
Rack::Protection::ContentSecurityPolicy,
175+
configuration.content_security_policy.merge(configuration.hal_browser_content_security_policy_overrides)
165176
end
166177
@app_builder.use Rack::PactBroker::InvalidUriProtection
167178
@app_builder.use Rack::PactBroker::ResetThreadData

lib/pact_broker/configuration.rb

+15
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class Configuration
4343
attr_accessor :semver_formats
4444
attr_accessor :enable_public_badge_access, :shields_io_base_url, :badge_provider_mode
4545
attr_accessor :disable_ssl_verification
46+
attr_accessor :content_security_policy, :hal_browser_content_security_policy_overrides
4647
attr_accessor :base_equality_only_on_content_that_affects_verification_results
4748
attr_reader :api_error_reporters
4849
attr_reader :custom_logger
@@ -90,6 +91,20 @@ def self.default_configuration
9091
config.webhook_http_method_whitelist = ['POST']
9192
config.webhook_scheme_whitelist = ['https']
9293
config.webhook_host_whitelist = []
94+
# TODO get rid of unsafe-inline
95+
config.content_security_policy = {
96+
script_src: "'self' 'unsafe-inline'",
97+
style_src: "'self' 'unsafe-inline'",
98+
img_src: "'self' data:",
99+
font_src: "'self' data:",
100+
base_uri: "'self'",
101+
frame_src: "'self'",
102+
frame_ancestors: "'self'"
103+
}
104+
config.hal_browser_content_security_policy_overrides = {
105+
script_src: "'self' 'unsafe-inline' 'unsafe-eval'",
106+
frame_ancestors: "'self'"
107+
}
93108
config
94109
end
95110

lib/rack/pact_broker/use_when.rb

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
=begin
2+
3+
Conditionally use Rack Middleware.
4+
5+
Usage:
6+
7+
condition_proc = ->(env) { env['PATH_INFO'] == '/match' }
8+
use_when condition_proc, SomeMiddleware, options
9+
10+
I feel sure there must be something like this officially supported somewhere, but I can't find it.
11+
12+
=end
13+
14+
module Rack
15+
module PactBroker
16+
module UseWhen
17+
class ConditionallyUseMiddleware
18+
def initialize(app, condition_proc, middleware, *args, &block)
19+
@app_without_middleware = app
20+
@condition_proc = condition_proc
21+
@middleware = middleware
22+
@args = args
23+
@block = block
24+
end
25+
26+
def call(env)
27+
if condition_proc.call(env)
28+
app_with_middleware.call(env)
29+
else
30+
app_without_middleware.call(env)
31+
end
32+
end
33+
34+
private
35+
36+
attr_reader :app_without_middleware, :condition_proc, :middleware, :args, :block
37+
38+
def app_with_middleware
39+
@app_with_middleware ||= begin
40+
rack_builder = ::Rack::Builder.new
41+
rack_builder.use middleware, *args, &block
42+
rack_builder.run app_without_middleware
43+
rack_builder.to_app
44+
end
45+
end
46+
end
47+
48+
refine Rack::Builder do
49+
def use_when(condition_proc, middleware, *args, &block)
50+
use(ConditionallyUseMiddleware, condition_proc, middleware, *args, &block)
51+
end
52+
end
53+
end
54+
end
55+
end
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'rack/pact_broker/use_when'
2+
require 'rack/test'
3+
4+
module Rack
5+
module PactBroker
6+
describe UseWhen do
7+
8+
using Rack::PactBroker::UseWhen
9+
include Rack::Test::Methods
10+
11+
class TestMiddleware
12+
def initialize(app, additional_headers)
13+
@app = app
14+
@additional_headers = additional_headers
15+
end
16+
17+
def call(env)
18+
status, headers, body = @app.call(env)
19+
[status, headers.merge(@additional_headers), body]
20+
end
21+
end
22+
23+
let(:app) do
24+
target_app = -> (env) { [200, {}, []] }
25+
builder = Rack::Builder.new
26+
condition = ->(env) { env['PATH_INFO'] == '/match' }
27+
builder.use_when condition, TestMiddleware, { "Foo" => "Bar" }
28+
builder.run target_app
29+
builder.to_app
30+
end
31+
32+
context "when the condition matches" do
33+
subject { get '/match' }
34+
35+
it "uses the middleware" do
36+
expect(subject.headers).to include "Foo" => "Bar"
37+
end
38+
end
39+
40+
context "when the condition does not match" do
41+
subject { get '/no-match' }
42+
43+
it "does not use the middleware" do
44+
expect(subject.headers.keys).to_not include "Foo"
45+
end
46+
end
47+
end
48+
end
49+
end

0 commit comments

Comments
 (0)