rollbar 2.9.1 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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