Skip to content

Commit cc97858

Browse files
committed
feat(webhooks): maintain starred out Authorization header value
1 parent d865a42 commit cc97858

File tree

4 files changed

+93
-9
lines changed

4 files changed

+93
-9
lines changed

lib/pact_broker/domain/webhook_request.rb

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require 'pact_broker/build_http_options'
1010
require 'cgi'
1111
require 'delegate'
12+
require 'rack/utils'
1213

1314
module PactBroker
1415

lib/pact_broker/webhooks/service.rb

+26-7
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,7 @@ def self.find_by_uuid uuid
4343

4444
def self.update_by_uuid uuid, params
4545
webhook = webhook_repository.find_by_uuid(uuid)
46-
# Dirty hack to maintain existing password if it is not submitted
47-
# This is required because the password is not returned in the API response
48-
# for security purposes, so it needs to be re-entered with every response.
49-
# TODO implement proper 'secrets' management.
50-
if webhook.request.password && !params['request'].key?('password')
51-
params['request']['password'] = webhook.request.password
52-
end
46+
maintain_redacted_params(webhook, params)
5347
PactBroker::Api::Decorators::WebhookDecorator.new(webhook).from_hash(params)
5448
webhook_repository.update_by_uuid uuid, webhook
5549
end
@@ -171,6 +165,31 @@ def self.find_triggered_webhooks_for_pact pact
171165
def self.find_triggered_webhooks_for_verification verification
172166
webhook_repository.find_triggered_webhooks_for_verification(verification)
173167
end
168+
169+
private
170+
171+
# Dirty hack to maintain existing password or Authorization header if it is submitted with value ****
172+
# This is required because the password and Authorization header is **** out in the API response
173+
# for security purposes, so it would need to be re-entered with every response.
174+
# TODO implement proper 'secrets' management.
175+
def self.maintain_redacted_params(webhook, params)
176+
if webhook.request.password && password_key_does_not_exist_or_is_starred?(params)
177+
params['request']['password'] = webhook.request.password
178+
end
179+
180+
new_headers = params['request']['headers'] ||= {}
181+
existing_headers = webhook.request.headers
182+
starred_new_headers = new_headers.select { |key, value| value =~ /^\**$/ }
183+
starred_new_headers.each do | (key, value) |
184+
new_headers[key] = existing_headers[key]
185+
end
186+
params['request']['headers'] = new_headers
187+
params
188+
end
189+
190+
def self.password_key_does_not_exist_or_is_starred?(params)
191+
!params['request'].key?('password') || params.dig('request','password') =~ /^\**$/
192+
end
174193
end
175194
end
176195
end

lib/pact_broker/webhooks/webhook_request_template.rb

+5-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def initialize attributes = {}
2222
@url = attributes[:url]
2323
@username = attributes[:username]
2424
@password = attributes[:password]
25-
@headers = attributes[:headers] || {}
25+
@headers = Rack::Utils::HeaderHash.new(attributes[:headers] || {})
2626
@body = attributes[:body]
2727
@uuid = attributes[:uuid]
2828
end
@@ -64,6 +64,10 @@ def redacted_headers
6464
end
6565
end
6666

67+
def headers= headers
68+
@headers = Rack::Utils::HeaderHash.new(headers)
69+
end
70+
6771
private
6872

6973
def to_s

spec/lib/pact_broker/webhooks/service_spec.rb

+61-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ module Webhooks
3838
allow_any_instance_of(PactBroker::Webhooks::Repository).to receive(:find_by_uuid).and_return(existing_webhook)
3939
end
4040

41-
let(:request) { PactBroker::Webhooks::WebhookRequestTemplate.new(password: 'password')}
41+
let(:request) { PactBroker::Webhooks::WebhookRequestTemplate.new(password: existing_password, headers: headers)}
42+
let(:existing_password) { nil }
43+
let(:headers) { {} }
4244
let(:existing_webhook) { PactBroker::Domain::Webhook.new(request: request) }
4345
let(:params) do
4446
{
@@ -50,7 +52,19 @@ module Webhooks
5052

5153
subject { Service.update_by_uuid("1234", params) }
5254

55+
it "sends through the params to the repository" do
56+
updated_webhook = nil
57+
allow_any_instance_of(PactBroker::Webhooks::Repository).to receive(:update_by_uuid) do | instance, uuid, webhook |
58+
updated_webhook = webhook
59+
true
60+
end
61+
subject
62+
expect(updated_webhook.request.url).to eq 'http://url'
63+
end
64+
5365
context "when the webhook has a password and the incoming parameters do not contain a password" do
66+
let(:existing_password) { 'password' }
67+
5468
it "does not overwite the password" do
5569
updated_webhook = nil
5670
allow_any_instance_of(PactBroker::Webhooks::Repository).to receive(:update_by_uuid) do | instance, uuid, webhook |
@@ -62,6 +76,52 @@ module Webhooks
6276
end
6377
end
6478

79+
context "when the webhook has a password and the incoming parameters contain a *** password" do
80+
let(:existing_password) { 'password' }
81+
let(:params) do
82+
{
83+
'request' => {
84+
'url' => 'http://url',
85+
'password' => '*******'
86+
}
87+
}
88+
end
89+
90+
it "does not overwite the password" do
91+
updated_webhook = nil
92+
allow_any_instance_of(PactBroker::Webhooks::Repository).to receive(:update_by_uuid) do | instance, uuid, webhook |
93+
updated_webhook = webhook
94+
true
95+
end
96+
subject
97+
expect(updated_webhook.request.password).to eq 'password'
98+
end
99+
end
100+
101+
context "when the webhook has an authorization header and the incoming parameters contain a *** authorization header" do
102+
let(:headers) { { 'Authorization' => 'existing'} }
103+
let(:params) do
104+
{
105+
'request' => {
106+
'url' => "http://url",
107+
'headers' => {
108+
'authorization' => "***********"
109+
}
110+
}
111+
}
112+
end
113+
114+
it "does not overwite the authorization header" do
115+
updated_webhook = nil
116+
allow_any_instance_of(PactBroker::Webhooks::Repository).to receive(:update_by_uuid) do | instance, uuid, webhook |
117+
updated_webhook = webhook
118+
true
119+
end
120+
subject
121+
expect(updated_webhook.request.headers['Authorization']).to eq 'existing'
122+
end
123+
end
124+
65125
context "the incoming parameters contain a password" do
66126
let(:params) do
67127
{

0 commit comments

Comments
 (0)