rollbar 2.9.1 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,17 @@
1
1
  module Rollbar
2
2
  module Scrubbers
3
+ extend self
4
+
5
+ def scrub_value(value)
6
+ if Rollbar.configuration.randomize_scrub_length
7
+ random_filtered_value
8
+ else
9
+ '*' * (value.length rescue 8)
10
+ end
11
+ end
12
+
13
+ def random_filtered_value
14
+ '*' * (rand(5) + 3)
15
+ end
3
16
  end
4
17
  end
@@ -0,0 +1,98 @@
1
+ require 'rollbar/scrubbers'
2
+
3
+ module Rollbar
4
+ module Scrubbers
5
+ # This class contains the logic to scrub the receive parameters. It will
6
+ # scrub the parameters matching Rollbar.configuration.scrub_fields Array.
7
+ # Also, if that configuration option is se to :scrub_all, it will scrub all
8
+ # received parameters
9
+ class Params
10
+ SKIPPED_CLASSES = [Tempfile]
11
+ ATTACHMENT_CLASSES = %w(ActionDispatch::Http::UploadedFile Rack::Multipart::UploadedFile).freeze
12
+ SCRUB_ALL = :scrub_all
13
+
14
+ def self.call(*args)
15
+ new.call(*args)
16
+ end
17
+
18
+ def call(params, extra_fields = [])
19
+ return {} unless params
20
+
21
+ config = Rollbar.configuration.scrub_fields
22
+
23
+ scrub(params, build_scrub_options(config, extra_fields))
24
+ end
25
+
26
+ private
27
+
28
+ def build_scrub_options(config, extra_fields)
29
+ ary_config = Array(config)
30
+
31
+ {
32
+ :fields_regex => build_fields_regex(ary_config, extra_fields),
33
+ :scrub_all => ary_config.include?(SCRUB_ALL)
34
+ }
35
+ end
36
+
37
+ def build_fields_regex(config, extra_fields)
38
+ fields = config.find_all { |f| f.is_a?(String) || f.is_a?(Symbol) }
39
+ fields += Array(extra_fields)
40
+
41
+ return unless fields.any?
42
+
43
+ Regexp.new(fields.map { |val| Regexp.escape(val.to_s).to_s }.join('|'), true)
44
+ end
45
+
46
+ def scrub(params, options)
47
+ fields_regex = options[:fields_regex]
48
+ scrub_all = options[:scrub_all]
49
+
50
+ params.to_hash.inject({}) do |result, (key, value)|
51
+ if value.is_a?(Hash)
52
+ result[key] = scrub(value, options)
53
+ elsif value.is_a?(Array)
54
+ result[key] = scrub_array(value, options)
55
+ elsif skip_value?(value)
56
+ result[key] = "Skipped value of class '#{value.class.name}'"
57
+ elsif fields_regex && fields_regex =~ Rollbar::Encoding.encode(key).to_s || scrub_all
58
+ result[key] = Rollbar::Scrubbers.scrub_value(value)
59
+ else
60
+ result[key] = rollbar_filtered_param_value(value)
61
+ end
62
+
63
+ result
64
+ end
65
+ end
66
+
67
+ def scrub_array(array, options)
68
+ array.map do |value|
69
+ value.is_a?(Hash) ? scrub(value, options) : rollbar_filtered_param_value(value)
70
+ end
71
+ end
72
+
73
+ def rollbar_filtered_param_value(value)
74
+ if ATTACHMENT_CLASSES.include?(value.class.name)
75
+ begin
76
+ attachment_value(value)
77
+ rescue
78
+ 'Uploaded file'
79
+ end
80
+ else
81
+ value
82
+ end
83
+ end
84
+
85
+ def attachment_value(value)
86
+ {
87
+ :content_type => value.content_type,
88
+ :original_filename => value.original_filename,
89
+ :size => value.tempfile.size
90
+ }
91
+ end
92
+
93
+ def skip_value?(value)
94
+ SKIPPED_CLASSES.any? { |klass| value.is_a?(klass) }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -16,7 +16,14 @@ module Rollbar
16
16
  return if skip_report?(msg_or_context, e)
17
17
 
18
18
  params = msg_or_context.reject{ |k| PARAM_BLACKLIST.include?(k) }
19
- scope = { :request => { :params => params } }
19
+ scope = {
20
+ :request => { :params => params },
21
+ :framework => "Sidekiq: #{::Sidekiq::VERSION}"
22
+ }
23
+ if params.is_a?(Hash)
24
+ scope[:context] = params['class']
25
+ scope[:queue] = params['queue']
26
+ end
20
27
 
21
28
  Rollbar.scope(scope).error(e, :use_exception_level_filters => true)
22
29
  end
@@ -1,3 +1,3 @@
1
1
  module Rollbar
2
- VERSION = "2.9.1"
2
+ VERSION = '2.10.0'
3
3
  end
@@ -258,7 +258,7 @@ describe HomeController do
258
258
  it "should raise a NameError and have PUT params in the reported exception" do
259
259
  logger_mock.should_receive(:info).with('[Rollbar] Success')
260
260
 
261
- put '/report_exception', :putparam => "putval"
261
+ put '/report_exception', { :putparam => "putval" }
262
262
 
263
263
  Rollbar.last_report.should_not be_nil
264
264
  Rollbar.last_report[:request][:params]["putparam"].should == "putval"
@@ -268,7 +268,7 @@ describe HomeController do
268
268
  it 'reports the errors successfully' do
269
269
  logger_mock.should_receive(:info).with('[Rollbar] Success')
270
270
 
271
- put '/deprecated_report_exception', :putparam => "putval"
271
+ put '/deprecated_report_exception', { :putparam => "putval" }
272
272
 
273
273
  Rollbar.last_report.should_not be_nil
274
274
  Rollbar.last_report[:request][:params]["putparam"].should == "putval"
@@ -291,7 +291,7 @@ describe HomeController do
291
291
  it "should raise an uncaught exception and report a message" do
292
292
  logger_mock.should_receive(:info).with('[Rollbar] Success').once
293
293
 
294
- expect { get '/cause_exception' }.to raise_exception
294
+ expect { get '/cause_exception' }.to raise_exception(NameError)
295
295
  end
296
296
 
297
297
  context 'show_exceptions' do
@@ -338,7 +338,7 @@ describe HomeController do
338
338
  before { cookies[:session_id] = user.id }
339
339
 
340
340
  it 'sends the current user data' do
341
- put '/report_exception', 'foo' => 'bar'
341
+ put '/report_exception', { 'foo' => 'bar' }
342
342
 
343
343
  person_data = Rollbar.last_report[:person]
344
344
 
@@ -352,7 +352,7 @@ describe HomeController do
352
352
 
353
353
  context 'with routing errors', :type => :request do
354
354
  it 'raises a RoutingError exception' do
355
- expect { get '/foo/bar', :foo => :bar }.to raise_exception
355
+ expect { get '/foo/bar', { :foo => :bar } }.to raise_exception(ActionController::RoutingError)
356
356
 
357
357
  report = Rollbar.last_report
358
358
  expect(report[:request][:params]['foo']).to be_eql('bar')
@@ -365,7 +365,7 @@ describe HomeController do
365
365
 
366
366
  expect do
367
367
  expect(controller.send(:rollbar_request_data)[:user_ip]).to be_nil
368
- end.not_to raise_exception(GetIpRaising::IpSpoofAttackError)
368
+ end.not_to raise_exception
369
369
  end
370
370
  end
371
371
 
@@ -375,7 +375,7 @@ describe HomeController do
375
375
 
376
376
  context 'with a single upload' do
377
377
  it "saves attachment data" do
378
- expect { post '/file_upload', :upload => file1 }.to raise_exception
378
+ expect { post '/file_upload', { :upload => file1 } }.to raise_exception(NameError)
379
379
 
380
380
  upload_param = Rollbar.last_report[:request][:params]['upload']
381
381
 
@@ -390,7 +390,7 @@ describe HomeController do
390
390
 
391
391
  context 'with multiple uploads', :type => :request do
392
392
  it "saves attachment data for all uploads" do
393
- expect { post '/file_upload', :upload => [file1, file2] }.to raise_exception
393
+ expect { post '/file_upload', { :upload => [file1, file2] } }.to raise_exception(NameError)
394
394
  sent_params = Rollbar.last_report[:request][:params]['upload']
395
395
 
396
396
  expect(sent_params).to be_kind_of(Array)
@@ -401,8 +401,9 @@ describe HomeController do
401
401
 
402
402
  context 'with session data', :type => :request do
403
403
  before { get '/set_session_data' }
404
+
404
405
  it 'reports the session data' do
405
- expect { get '/use_session_data' }.to raise_exception
406
+ expect { get '/use_session_data' }.to raise_exception(NoMethodError)
406
407
 
407
408
  session_data = Rollbar.last_report[:request][:session]
408
409
 
@@ -416,7 +417,7 @@ describe HomeController do
416
417
  it 'parses the correct headers' do
417
418
  expect do
418
419
  post '/cause_exception', params, { 'ACCEPT' => 'application/vnd.github.v3+json' }
419
- end.to raise_exception
420
+ end.to raise_exception(NameError)
420
421
 
421
422
  expect(Rollbar.last_report[:request][:params]['foo']).to be_eql('bar')
422
423
  end
@@ -438,7 +439,7 @@ describe HomeController do
438
439
  end
439
440
 
440
441
  it 'scrubs sensible data from URL' do
441
- expect { get '/cause_exception', { :password => 'my-secret-password' }, headers }.to raise_exception
442
+ expect { get '/cause_exception', { :password => 'my-secret-password' }, headers }.to raise_exception(NameError)
442
443
 
443
444
  request_data = Rollbar.last_report[:request]
444
445
 
@@ -0,0 +1,2 @@
1
+ test:
2
+ secret_key_base: 9ed31a007124058789d898117509a68022a0c5045ef3fd19e228139985d7f4365d1a908aeedbc9e0ad33c2cd62d765bccfb2b19b6cb06f4f7f2ac8a059adee91
@@ -37,7 +37,7 @@ describe HomeController do
37
37
  end
38
38
 
39
39
  it "should report uncaught exceptions" do
40
- expect { get '/current_user' }.to raise_exception
40
+ expect { get '/current_user' }.to raise_exception(NoMethodError)
41
41
 
42
42
  body = Rollbar.last_report[:body]
43
43
  trace = body[:trace] && body[:trace] || body[:trace_chain][0]
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ require 'delayed_job'
4
+ require 'delayed/worker'
5
+ require 'rollbar/delay/delayed_job'
6
+ require 'delayed/backend/test'
7
+
8
+ describe Rollbar::Delay::DelayedJob do
9
+ before do
10
+ Delayed::Backend::Test.prepare_worker
11
+ Delayed::Worker.backend = :test
12
+ end
13
+
14
+ describe '.call' do
15
+ let(:payload) { {} }
16
+ it 'calls Rollbar' do
17
+ expect(Rollbar).to receive(:process_from_async_handler).with(payload)
18
+
19
+ Rollbar::Delay::DelayedJob.call(payload)
20
+ end
21
+ end
22
+ end
@@ -20,7 +20,7 @@ describe Rollbar::Delay::Thread do
20
20
  it 'doesnt raise any exception' do
21
21
  expect do
22
22
  described_class.call(payload).join
23
- end.not_to raise_error(exception)
23
+ end.not_to raise_error
24
24
  end
25
25
  end
26
26
  end
@@ -22,6 +22,11 @@ describe Rollbar::Js::Middleware do
22
22
  <h1>Testing the middleware</h1>
23
23
  </body>
24
24
  </html>
25
+ END
26
+ end
27
+ let(:minified_html) do
28
+ <<-END
29
+ <html><head><link rel="stylesheet" href="url" type="text/css" media="screen" /><script type="text/javascript" src="foo"></script></head><body><h1>Testing the middleware</h1></body></html>
25
30
  END
26
31
  end
27
32
  let(:snippet) { 'THIS IS THE SNIPPET' }
@@ -65,6 +70,26 @@ END
65
70
  res_status, res_headers, response = subject.call(env)
66
71
  new_body = response.body.join
67
72
 
73
+ expect(new_body).to_not include('>>')
74
+ expect(new_body).to include(snippet)
75
+ expect(new_body).to include(config[:options].to_json)
76
+ expect(res_status).to be_eql(status)
77
+ expect(res_headers['Content-Type']).to be_eql(content_type)
78
+ end
79
+ end
80
+
81
+ context 'having a html 200 response with minified body' do
82
+ let(:body) { [minified_html] }
83
+ let(:status) { 200 }
84
+ let(:headers) do
85
+ { 'Content-Type' => content_type }
86
+ end
87
+
88
+ it 'adds the config and the snippet to the response' do
89
+ res_status, res_headers, response = subject.call(env)
90
+ new_body = response.body.join
91
+
92
+ expect(new_body).to_not include('>>')
68
93
  expect(new_body).to include(snippet)
69
94
  expect(new_body).to include(config[:options].to_json)
70
95
  expect(res_status).to be_eql(status)
@@ -72,6 +97,30 @@ END
72
97
  end
73
98
  end
74
99
 
100
+ context 'having a html 200 response and SecureHeaders defined' do
101
+ let(:body) { [html] }
102
+ let(:status) { 200 }
103
+ let(:headers) do
104
+ { 'Content-Type' => content_type }
105
+ end
106
+
107
+ before do
108
+ Object.const_set('SecureHeaders', Module.new)
109
+ allow(SecureHeaders).to receive(:content_security_policy_script_nonce) { 'lorem-ipsum-nonce' }
110
+ end
111
+
112
+ it 'renders the snippet and config in the response with nonce in script tag when SecureHeaders installed' do
113
+ res_status, res_headers, response = subject.call(env)
114
+ new_body = response.body.join
115
+
116
+ expect(new_body).to include('<script type="text/javascript" nonce="lorem-ipsum-nonce">')
117
+ expect(new_body).to include("var _rollbarConfig = #{config[:options].to_json};")
118
+ expect(new_body).to include(snippet)
119
+
120
+ Object.send(:remove_const, 'SecureHeaders')
121
+ end
122
+ end
123
+
75
124
  context 'having a html 200 response without head', :add_js => false do
76
125
  let(:body) { ['foobar'] }
77
126
  let(:status) { 200 }
@@ -22,7 +22,19 @@ describe Rollbar::LoggerProxy do
22
22
  end
23
23
  end
24
24
 
25
- describe '#call' do
25
+ describe '#log' do
26
+ context 'if Rollbar is disabled' do
27
+ before do
28
+ expect(Rollbar.configuration).to receive(:enabled).and_return(false)
29
+ end
30
+
31
+ it 'doesnt call the logger' do
32
+ expect(logger).to_not receive(:error)
33
+
34
+ subject.log('error', 'foo')
35
+ end
36
+ end
37
+
26
38
  context 'if the logger fails' do
27
39
  it 'doesnt raise' do
28
40
  allow(logger).to receive(:info).and_raise(StandardError.new)
@@ -53,30 +53,4 @@ describe Rollbar::RequestDataExtractor do
53
53
  end
54
54
  end
55
55
  end
56
-
57
- describe '#rollbar_scrubbed_value' do
58
- context 'with random scrub length' do
59
- before do
60
- allow(Rollbar.configuration).to receive(:randomize_scrub_length).and_return(true)
61
- end
62
-
63
- let(:value) { 'herecomesaverylongvalue' }
64
-
65
- it 'randomizes the scrubbed string' do
66
- expect(subject.rollbar_scrubbed(value)).to match(/\*{3,8}/)
67
- end
68
- end
69
-
70
- context 'with no-random scrub length' do
71
- before do
72
- allow(Rollbar.configuration).to receive(:randomize_scrub_length).and_return(false)
73
- end
74
-
75
- let(:value) { 'herecomesaverylongvalue' }
76
-
77
- it 'randomizes the scrubbed string' do
78
- expect(subject.rollbar_scrubbed(value)).to match(/\*{#{value.length}}/)
79
- end
80
- end
81
- end
82
56
  end
@@ -0,0 +1,268 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+ require 'rollbar/scrubbers/params'
4
+
5
+ require 'rspec/expectations'
6
+
7
+ describe Rollbar::Scrubbers::Params do
8
+ describe '.call' do
9
+ it 'calls #call in a new instance' do
10
+ arguments = [:foo, :bar]
11
+ expect_any_instance_of(described_class).to receive(:call).with(*arguments)
12
+
13
+ described_class.call(*arguments)
14
+ end
15
+ end
16
+
17
+ describe '#call' do
18
+ before do
19
+ allow(Rollbar.configuration).to receive(:scrub_fields).and_return(scrub_config)
20
+ end
21
+
22
+ context 'with scrub fields configured' do
23
+ let(:scrub_config) do
24
+ [:secret, :password]
25
+ end
26
+
27
+ context 'with simple Hash' do
28
+ let(:params) do
29
+ {
30
+ :foo => 'bar',
31
+ :secret => 'the-secret',
32
+ :password => 'the-password',
33
+ :password_confirmation => 'the-password'
34
+ }
35
+ end
36
+ let(:result) do
37
+ {
38
+ :foo => 'bar',
39
+ :secret => /\*+/,
40
+ :password => /\*+/,
41
+ :password_confirmation => /\*+/
42
+ }
43
+ end
44
+
45
+ it 'scrubs the required parameters' do
46
+ expect(subject.call(params)).to be_eql_hash_with_regexes(result)
47
+ end
48
+ end
49
+
50
+ context 'with nested Hash' do
51
+ let(:params) do
52
+ {
53
+ :foo => 'bar',
54
+ :extra => {
55
+ :secret => 'the-secret',
56
+ :password => 'the-password',
57
+ :password_confirmation => 'the-password'
58
+ }
59
+ }
60
+ end
61
+ let(:result) do
62
+ {
63
+ :foo => 'bar',
64
+ :extra => {
65
+ :secret => /\*+/,
66
+ :password => /\*+/,
67
+ :password_confirmation => /\*+/
68
+ }
69
+ }
70
+ end
71
+
72
+ it 'scrubs the required parameters' do
73
+ expect(subject.call(params)).to be_eql_hash_with_regexes(result)
74
+ end
75
+ end
76
+
77
+ context 'with nested Array' do
78
+ let(:params) do
79
+ {
80
+ :foo => 'bar',
81
+ :extra => [{
82
+ :secret => 'the-secret',
83
+ :password => 'the-password',
84
+ :password_confirmation => 'the-password'
85
+ }]
86
+ }
87
+ end
88
+ let(:result) do
89
+ {
90
+ :foo => 'bar',
91
+ :extra => [{
92
+ :secret => /\*+/,
93
+ :password => /\*+/,
94
+ :password_confirmation => /\*+/
95
+ }]
96
+ }
97
+ end
98
+
99
+ it 'scrubs the required parameters' do
100
+ expect(subject.call(params)).to be_eql_hash_with_regexes(result)
101
+ end
102
+ end
103
+
104
+ context 'with skipped instance' do
105
+ let(:tempfile) { Tempfile.new('foo') }
106
+ let(:params) do
107
+ {
108
+ :foo => 'bar',
109
+ :extra => [{
110
+ :secret => 'the-secret',
111
+ :password => 'the-password',
112
+ :password_confirmation => 'the-password',
113
+ :skipped => tempfile
114
+ }]
115
+ }
116
+ end
117
+ let(:result) do
118
+ {
119
+ :foo => 'bar',
120
+ :extra => [{
121
+ :secret => /\*+/,
122
+ :password => /\*+/,
123
+ :password_confirmation => /\*+/,
124
+ :skipped => "Skipped value of class 'Tempfile'"
125
+ }]
126
+ }
127
+ end
128
+
129
+ after { tempfile.close }
130
+
131
+ it 'scrubs the required parameters' do
132
+ expect(subject.call(params)).to be_eql_hash_with_regexes(result)
133
+ end
134
+ end
135
+
136
+ context 'with attachment instance' do
137
+ let(:tempfile) { double(:size => 100) }
138
+ let(:attachment) do
139
+ double(:class => double(:name => 'ActionDispatch::Http::UploadedFile'),
140
+ :tempfile => tempfile,
141
+ :content_type => 'content-type',
142
+ 'original_filename' => 'filename')
143
+ end
144
+ let(:params) do
145
+ {
146
+ :foo => 'bar',
147
+ :extra => [{
148
+ :secret => 'the-secret',
149
+ :password => 'the-password',
150
+ :password_confirmation => 'the-password',
151
+ :attachment => attachment
152
+ }]
153
+ }
154
+ end
155
+ let(:result) do
156
+ {
157
+ :foo => 'bar',
158
+ :extra => [{
159
+ :secret => /\*+/,
160
+ :password => /\*+/,
161
+ :password_confirmation => /\*+/,
162
+ :attachment => {
163
+ :content_type => 'content-type',
164
+ :original_filename => 'filename',
165
+ :size => 100
166
+ }
167
+ }]
168
+ }
169
+ end
170
+
171
+ it 'scrubs the required parameters' do
172
+ expect(subject.call(params)).to be_eql_hash_with_regexes(result)
173
+ end
174
+
175
+ context 'if getting the attachment values fails' do
176
+ let(:tempfile) { Object.new }
177
+ let(:attachment) do
178
+ double(:class => double(:name => 'ActionDispatch::Http::UploadedFile'),
179
+ :tempfile => tempfile,
180
+ :content_type => 'content-type',
181
+ 'original_filename' => 'filename')
182
+ end
183
+ let(:params) do
184
+ {
185
+ :foo => 'bar',
186
+ :extra => [{
187
+ :secret => 'the-secret',
188
+ :password => 'the-password',
189
+ :password_confirmation => 'the-password',
190
+ :attachment => attachment
191
+ }]
192
+ }
193
+ end
194
+ let(:result) do
195
+ {
196
+ :foo => 'bar',
197
+ :extra => [{
198
+ :secret => /\*+/,
199
+ :password => /\*+/,
200
+ :password_confirmation => /\*+/,
201
+ :attachment => 'Uploaded file'
202
+ }]
203
+ }
204
+ end
205
+
206
+ it 'scrubs the required parameters' do
207
+ expect(subject.call(params)).to be_eql_hash_with_regexes(result)
208
+ end
209
+ end
210
+ end
211
+
212
+ context 'without params' do
213
+ let(:params) do
214
+ nil
215
+ end
216
+ let(:result) do
217
+ {}
218
+ end
219
+
220
+ it 'scrubs the required parameters' do
221
+ expect(subject.call(params)).to be_eql_hash_with_regexes(result)
222
+ end
223
+ end
224
+ end
225
+
226
+ context 'with :scrub_all option' do
227
+ let(:scrub_config) { :scrub_all }
228
+ let(:params) do
229
+ {
230
+ :foo => 'bar',
231
+ :password => 'the-password',
232
+ :bar => 'foo',
233
+ :extra => {
234
+ :foo => 'more-foo',
235
+ :bar => 'more-bar'
236
+ }
237
+ }
238
+ end
239
+ let(:result) do
240
+ {
241
+ :foo => /\*+/,
242
+ :password => /\*+/,
243
+ :bar => /\*+/,
244
+ :extra => {
245
+ :foo => /\*+/,
246
+ :bar => /\*+/
247
+ }
248
+ }
249
+ end
250
+
251
+ it 'scrubs the required parameters' do
252
+ expect(subject.call(params)).to be_eql_hash_with_regexes(result)
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ describe Rollbar::Scrubbers::Params::ATTACHMENT_CLASSES do
259
+ it 'has the correct values' do
260
+ expect(described_class).to be_eql(%w(ActionDispatch::Http::UploadedFile Rack::Multipart::UploadedFile).freeze)
261
+ end
262
+ end
263
+
264
+ describe Rollbar::Scrubbers::Params::SKIPPED_CLASSES do
265
+ it 'has the correct values' do
266
+ expect(described_class).to be_eql([Tempfile])
267
+ end
268
+ end