Skip to content

Commit e7bb4a0

Browse files
committed
feat: add error reference to API error response and ensure potentially sensitive details from the exception message are not exposed
1 parent 23e0978 commit e7bb4a0

File tree

2 files changed

+116
-30
lines changed

2 files changed

+116
-30
lines changed

lib/pact_broker/api/resources/error_handler.rb

+32-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'webmachine/convert_request_to_rack_env'
22
require 'pact_broker/configuration'
3+
require 'securerandom'
34

45
module PactBroker
56
module Api
@@ -10,24 +11,46 @@ class ErrorHandler
1011
include PactBroker::Logging
1112

1213
def self.call e, request, response
13-
logger.error e
14+
error_reference = SecureRandom.urlsafe_base64.gsub(/[^a-z]/i, '')[0,10]
15+
logger.error "#{e.message} - error reference #{error_reference}"
1416
logger.error e.backtrace
15-
response_body = { error: { :message => e.message } }
16-
if PactBroker.configuration.show_backtrace_in_error_response?
17-
response_body[:error][:backtrace] = e.backtrace
18-
end
19-
response.body = response_body.to_json
20-
report(e, request) if reportable?(e)
17+
response.body = response_body_hash(e, error_reference).to_json
18+
report(e, error_reference, request) if reportable?(e)
2119
end
2220

2321
def self.reportable? e
2422
!e.is_a?(PactBroker::Error)
2523
end
2624

27-
def self.report e, request
25+
def self.display_message(e, error_reference)
26+
if PactBroker.configuration.show_backtrace_in_error_response?
27+
e.message
28+
else
29+
reportable?(e) ? obfuscated_error_message(error_reference) : e.message
30+
end
31+
end
32+
33+
def self.obfuscated_error_message error_reference
34+
"An error has occurred. The details have been logged with the reference #{error_reference}"
35+
end
36+
37+
def self.response_body_hash e, error_reference
38+
response_body = {
39+
error: {
40+
message: display_message(e, error_reference),
41+
reference: error_reference
42+
}
43+
}
44+
if PactBroker.configuration.show_backtrace_in_error_response?
45+
response_body[:error][:backtrace] = e.backtrace
46+
end
47+
response_body
48+
end
49+
50+
def self.report e, error_reference, request
2851
PactBroker.configuration.api_error_reporters.each do | error_notifier |
2952
begin
30-
error_notifier.call(e, env: Webmachine::ConvertRequestToRackEnv.call(request))
53+
error_notifier.call(e, env: Webmachine::ConvertRequestToRackEnv.call(request), error_reference: error_reference)
3154
rescue StandardError => e
3255
log_error(e, "Error executing api_error_reporter")
3356
end

spec/lib/pact_broker/api/resources/error_handler_spec.rb

+84-21
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ module Resources
88

99
before do
1010
allow(ErrorHandler).to receive(:logger).and_return(logger)
11+
allow(SecureRandom).to receive(:urlsafe_base64).and_return("bYWfn-+yWPlf")
1112
end
1213

1314
let(:logger) { double('logger').as_null_object }
1415
let(:error) { StandardError.new('test error') }
1516
let(:thing) { double('thing', call: nil, another_call: nil) }
16-
let(:options) { { env: env } }
17+
let(:options) { { env: env, error_reference: "bYWfnyWPlf" } }
1718
let(:request) { double('request' ) }
1819
let(:response) { double('response', :body= => nil) }
1920
let(:env) { double('env') }
@@ -37,33 +38,59 @@ module Resources
3738
subject
3839
end
3940

40-
context "when the error is a PactBroker::Error or subclass" do
41-
let(:error) { Class.new(PactBroker::Error).new('test error') }
42-
43-
it "does not invoke the api error reporters" do
44-
expect(thing).to_not receive(:call).with(error, options)
45-
subject
46-
end
47-
end
48-
49-
it "creates a json error response body" do
41+
it "includes an error reference" do
5042
expect(response).to receive(:body=) do | body |
51-
expect(JSON.parse(body)['error']).to include 'message' => 'test error'
43+
expect(JSON.parse(body)['error']).to include 'reference' => "bYWfnyWPlf"
5244
end
5345
subject
5446
end
5547

56-
5748
context "when show_backtrace_in_error_response? is true" do
5849
before do
5950
allow(PactBroker.configuration).to receive(:show_backtrace_in_error_response?).and_return(true)
6051
end
6152

62-
it "includes the backtrace in the error response" do
63-
expect(response).to receive(:body=) do | body |
64-
expect(body).to include("backtrace")
53+
context "when the error is a PactBroker::Error or subclass" do
54+
let(:error) { Class.new(PactBroker::Error).new('test error') }
55+
56+
it "does not invoke the api error reporters" do
57+
expect(thing).to_not receive(:call).with(error, options)
58+
subject
59+
end
60+
61+
it "uses the error message as the message" do
62+
expect(response).to receive(:body=) do | body |
63+
expect(JSON.parse(body)['error']).to include 'message' => "test error"
64+
end
65+
subject
66+
end
67+
68+
it "includes the backtrace in the error response" do
69+
expect(response).to receive(:body=) do | body |
70+
expect(body).to include("backtrace")
71+
end
72+
subject
73+
end
74+
end
75+
context "when the error is not a PactBroker::Error or subclass" do
76+
it "invokes the api error reporters" do
77+
expect(thing).to receive(:call).with(error, options)
78+
subject
79+
end
80+
81+
it "uses the error message as the message" do
82+
expect(response).to receive(:body=) do | body |
83+
expect(JSON.parse(body)['error']).to include 'message' => "test error"
84+
end
85+
subject
86+
end
87+
88+
it "includes the backtrace in the error response" do
89+
expect(response).to receive(:body=) do | body |
90+
expect(body).to include("backtrace")
91+
end
92+
subject
6593
end
66-
subject
6794
end
6895
end
6996

@@ -72,11 +99,47 @@ module Resources
7299
allow(PactBroker.configuration).to receive(:show_backtrace_in_error_response?).and_return(false)
73100
end
74101

75-
it "does not include the backtrace in the error response" do
76-
expect(response).to receive(:body=) do | body |
77-
expect(body).to_not include("backtrace")
102+
context "when the error is a PactBroker::Error or subclass" do
103+
let(:error) { Class.new(PactBroker::Error).new('test error') }
104+
105+
it "does not invoke the api error reporters" do
106+
expect(thing).to_not receive(:call).with(error, options)
107+
subject
108+
end
109+
110+
it "uses the error message as the message" do
111+
expect(response).to receive(:body=) do | body |
112+
expect(JSON.parse(body)['error']).to include 'message' => "test error"
113+
end
114+
subject
115+
end
116+
117+
it "does not include the backtrace in the error response" do
118+
expect(response).to receive(:body=) do | body |
119+
expect(body).to_not include("backtrace")
120+
end
121+
subject
122+
end
123+
end
124+
context "when the error is not a PactBroker::Error or subclass" do
125+
it "invokes the api error reporters" do
126+
expect(thing).to receive(:call).with(error, options)
127+
subject
128+
end
129+
130+
it "uses a hardcoded error message" do
131+
expect(response).to receive(:body=) do | body |
132+
expect(JSON.parse(body)['error']['message']).to match /An error/
133+
end
134+
subject
135+
end
136+
137+
it "does not include the backtrace in the error response" do
138+
expect(response).to receive(:body=) do | body |
139+
expect(body).to_not include("backtrace")
140+
end
141+
subject
78142
end
79-
subject
80143
end
81144
end
82145

0 commit comments

Comments
 (0)