Skip to content

Commit 1bf0ad1

Browse files
Merge pull request #795 from rollbar/scrub-all-whitelist
Make scrub_all work correctly (including with whitelist)
2 parents a92b46f + 2cb63fa commit 1bf0ad1

File tree

4 files changed

+222
-64
lines changed

4 files changed

+222
-64
lines changed

lib/rollbar/scrubbers/params.rb

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ module Scrubbers
66
# This class contains the logic to scrub the received parameters. It will
77
# scrub the parameters matching Rollbar.configuration.scrub_fields Array.
88
# Also, if that configuration option is set to :scrub_all, it will scrub all
9-
# received parameters
9+
# received parameters. It will not scrub anything that is in the scrub_whitelist
10+
# configuration array even if :scrub_all is true.
1011
class Params
1112
SKIPPED_CLASSES = [::Tempfile]
1213
ATTACHMENT_CLASSES = %w(ActionDispatch::Http::UploadedFile Rack::Multipart::UploadedFile).freeze
@@ -33,37 +34,46 @@ def build_scrub_options(config, extra_fields, whitelist)
3334
ary_config = Array(config)
3435

3536
{
36-
:fields_regex => build_fields_regex(ary_config, extra_fields, whitelist),
37-
:scrub_all => ary_config.include?(SCRUB_ALL)
37+
:fields_regex => build_fields_regex(ary_config, extra_fields),
38+
:scrub_all => ary_config.include?(SCRUB_ALL),
39+
:whitelist => build_whitelist_regex(whitelist)
3840
}
3941
end
4042

41-
def build_fields_regex(config, extra_fields, whitelist)
43+
def build_fields_regex(config, extra_fields)
4244
fields = config.find_all { |f| f.is_a?(String) || f.is_a?(Symbol) }
4345
fields += Array(extra_fields)
4446

4547
return unless fields.any?
4648

47-
Regexp.new(fields.reject { |f| whitelist.include? f }.map { |val| Regexp.escape(val.to_s).to_s }.join('|'), true)
49+
Regexp.new(fields.map { |val| Regexp.escape(val.to_s).to_s }.join('|'), true)
50+
end
51+
52+
def build_whitelist_regex(whitelist)
53+
fields = whitelist.find_all { |f| f.is_a?(String) || f.is_a?(Symbol) }
54+
return unless fields.any?
55+
Regexp.new(fields.map { |val| /\A#{Regexp.escape(val.to_s)}\z/ }.join('|'))
4856
end
4957

5058
def scrub(params, options)
5159
fields_regex = options[:fields_regex]
5260
scrub_all = options[:scrub_all]
61+
whitelist_regex = options[:whitelist]
5362

5463
return scrub_array(params, options) if params.is_a?(Array)
5564

5665
params.to_hash.inject({}) do |result, (key, value)|
57-
if fields_regex === Rollbar::Encoding.encode(key).to_s
66+
encoded_key = Rollbar::Encoding.encode(key).to_s
67+
if (fields_regex === encoded_key) && !(whitelist_regex === encoded_key)
5868
result[key] = scrub_value(value)
5969
elsif value.is_a?(Hash)
6070
result[key] = scrub(value, options)
71+
elsif scrub_all && !(whitelist_regex === encoded_key)
72+
result[key] = scrub_value(value)
6173
elsif value.is_a?(Array)
6274
result[key] = scrub_array(value, options)
6375
elsif skip_value?(value)
6476
result[key] = "Skipped value of class '#{value.class.name}'"
65-
elsif scrub_all
66-
result[key] = scrub_value(value)
6777
else
6878
result[key] = rollbar_filtered_param_value(value)
6979
end

lib/rollbar/scrubbers/url.rb

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
module Rollbar
77
module Scrubbers
88
class URL
9+
SCRUB_ALL = :scrub_all
10+
911
def self.call(*args)
1012
new.call(*args)
1113
end
@@ -15,31 +17,39 @@ def call(options = {})
1517
return url unless Rollbar::LanguageSupport.can_scrub_url?
1618

1719
filter(url,
18-
build_regex(options[:scrub_fields], options[:whitelist] || []),
20+
build_regex(options[:scrub_fields]),
1921
options[:scrub_user],
2022
options[:scrub_password],
21-
options.fetch(:randomize_scrub_length, true))
23+
options.fetch(:randomize_scrub_length, true),
24+
options[:scrub_fields].include?(SCRUB_ALL),
25+
build_whitelist_regex(options[:whitelist] || []))
2226
rescue => e
2327
Rollbar.logger.error("[Rollbar] There was an error scrubbing the url: #{e}, options: #{options.inspect}")
2428
url
2529
end
2630

2731
private
2832

29-
def filter(url, regex, scrub_user, scrub_password, randomize_scrub_length)
33+
def build_whitelist_regex(whitelist)
34+
fields = whitelist.find_all { |f| f.is_a?(String) || f.is_a?(Symbol) }
35+
return unless fields.any?
36+
Regexp.new(fields.map { |val| /\A#{Regexp.escape(val.to_s)}\z/ }.join('|'))
37+
end
38+
39+
def filter(url, regex, scrub_user, scrub_password, randomize_scrub_length, scrub_all, whitelist)
3040
uri = URI.parse(url)
3141

3242
uri.user = filter_user(uri.user, scrub_user, randomize_scrub_length)
3343
uri.password = filter_password(uri.password, scrub_password, randomize_scrub_length)
34-
uri.query = filter_query(uri.query, regex, randomize_scrub_length)
44+
uri.query = filter_query(uri.query, regex, randomize_scrub_length, scrub_all, whitelist)
3545

3646
uri.to_s
3747
end
3848

3949
# Builds a regex to match with any of the received fields.
4050
# The built regex will also match array params like 'user_ids[]'.
41-
def build_regex(fields, whitelist)
42-
fields_or = fields.reject { |f| whitelist.include? f }.map { |field| "#{field}(\\[\\])?" }.join('|')
51+
def build_regex(fields)
52+
fields_or = fields.map { |field| "#{field}(\\[\\])?" }.join('|')
4353

4454
Regexp.new("^#{fields_or}$")
4555
end
@@ -52,12 +62,12 @@ def filter_password(password, scrub_password, randomize_scrub_length)
5262
scrub_password && password ? filtered_value(password, randomize_scrub_length) : password
5363
end
5464

55-
def filter_query(query, regex, randomize_scrub_length)
65+
def filter_query(query, regex, randomize_scrub_length, scrub_all, whitelist)
5666
return query unless query
5767

5868
params = decode_www_form(query)
5969

60-
encoded_query = encode_www_form(filter_query_params(params, regex, randomize_scrub_length))
70+
encoded_query = encode_www_form(filter_query_params(params, regex, randomize_scrub_length, scrub_all, whitelist))
6171

6272
# We want this to rebuild array params like foo[]=1&foo[]=2
6373
URI.escape(CGI.unescape(encoded_query))
@@ -71,14 +81,14 @@ def encode_www_form(params)
7181
URI.encode_www_form(params)
7282
end
7383

74-
def filter_query_params(params, regex, randomize_scrub_length)
84+
def filter_query_params(params, regex, randomize_scrub_length, scrub_all, whitelist)
7585
params.map do |key, value|
76-
[key, filter_key?(key, regex) ? filtered_value(value, randomize_scrub_length) : value]
86+
[key, filter_key?(key, regex, scrub_all, whitelist) ? filtered_value(value, randomize_scrub_length) : value]
7787
end
7888
end
7989

80-
def filter_key?(key, regex)
81-
!!(key =~ regex)
90+
def filter_key?(key, regex, scrub_all, whitelist)
91+
!(whitelist === key) && (scrub_all || regex === key)
8292
end
8393

8494
def filtered_value(value, randomize_scrub_length)

spec/rollbar/scrubbers/params_spec.rb

Lines changed: 133 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -278,31 +278,140 @@
278278
context 'with :scrub_all option' do
279279
let(:scrub_config) { :scrub_all }
280280

281-
let(:params) do
282-
{
283-
:foo => 'bar',
284-
:password => 'the-password',
285-
:bar => 'foo',
286-
:extra => {
287-
:foo => 'more-foo',
288-
:bar => 'more-bar'
281+
context 'with simple hash' do
282+
let(:params) do
283+
{
284+
:foo => 'bar',
285+
:password => 'the-password',
286+
:bar => 'foo',
287+
:extra => {
288+
:foo => 'more-foo',
289+
:bar => 'more-bar'
290+
}
289291
}
290-
}
292+
end
293+
let(:result) do
294+
{
295+
:foo => /\*+/,
296+
:password => /\*+/,
297+
:bar => /\*+/,
298+
:extra => {
299+
:foo => /\*+/,
300+
:bar => /\*+/
301+
}
302+
}
303+
end
304+
305+
it 'scrubs the required parameters' do
306+
expect(subject.call(options)).to be_eql_hash_with_regexes(result)
307+
end
291308
end
292-
let(:result) do
293-
{
294-
:foo => /\*+/,
295-
:password => /\*+/,
296-
:bar => /\*+/,
297-
:extra => {
309+
310+
context 'with nested arrays' do
311+
let(:params) do
312+
{
313+
:foo => 'bar',
314+
:password => 'the-password',
315+
:bar => 'foo',
316+
:extra => [
317+
'hello world',
318+
{
319+
:foo => 'more-foo',
320+
:bar => 'more-bar'
321+
}
322+
]
323+
}
324+
end
325+
let(:result) do
326+
{
298327
:foo => /\*+/,
299-
:bar => /\*+/
328+
:password => /\*+/,
329+
:bar => /\*+/,
330+
:extra => /\*+/,
300331
}
301-
}
332+
end
333+
334+
it 'scrubs the required parameters' do
335+
expect(subject.call(options)).to be_eql_hash_with_regexes(result)
336+
end
302337
end
303338

304-
it 'scrubs the required parameters' do
305-
expect(subject.call(options)).to be_eql_hash_with_regexes(result)
339+
context 'and with :whitelist option' do
340+
let (:whitelist) { [:foo, :buzz] }
341+
342+
context 'with simple hash' do
343+
let(:params) do
344+
{
345+
:foo => 'bar',
346+
:password => 'the-password',
347+
:bar => 'foo',
348+
:extra => {
349+
:foo => 'more-foo',
350+
:bar => 'more-bar'
351+
}
352+
}
353+
end
354+
let(:result) do
355+
{
356+
:foo => 'bar',
357+
:password => /\*+/,
358+
:bar => /\*+/,
359+
:extra => {
360+
:foo => 'more-foo',
361+
:bar => /\*+/
362+
}
363+
}
364+
end
365+
366+
it 'scrubs the required parameters' do
367+
expect(subject.call(options)).to be_eql_hash_with_regexes(result)
368+
end
369+
end
370+
371+
context 'with nested arrays' do
372+
let(:params) do
373+
{
374+
:foo => 'bar',
375+
:password => 'the-password',
376+
:bar => 'foo',
377+
:extra => [
378+
'hello world',
379+
{
380+
:foo => 'more-foo',
381+
:bar => 'more-bar'
382+
}
383+
],
384+
:buzz => [
385+
'fizzbuzz',
386+
{
387+
:a => 42,
388+
:foo => 'another-foo',
389+
:b => 'this should be scrubbed'
390+
}
391+
]
392+
}
393+
end
394+
let(:result) do
395+
{
396+
:foo => 'bar',
397+
:password => /\*+/,
398+
:bar => /\*+/,
399+
:extra => /\*+/,
400+
:buzz => [
401+
'fizzbuzz',
402+
{
403+
:a => /\*+/,
404+
:foo => 'another-foo',
405+
:b => /\*+/
406+
}
407+
]
408+
}
409+
end
410+
411+
it 'scrubs the required parameters' do
412+
expect(subject.call(options)).to be_eql_hash_with_regexes(result)
413+
end
414+
end
306415
end
307416
end
308417

@@ -330,7 +439,7 @@
330439
:foo => 'bar',
331440
:secret => /\*+/,
332441
:password => 'the-password',
333-
:password_confirmation => 'the-password'
442+
:password_confirmation => /\*+/
334443
}
335444
]
336445
end
@@ -354,7 +463,7 @@
354463
:foo => 'bar',
355464
:secret => /\*+/,
356465
:password => 'the-password',
357-
:password_confirmation => 'the-password'
466+
:password_confirmation => /\*+/
358467
}
359468
end
360469

@@ -388,7 +497,7 @@
388497
:extra => {
389498
:secret => /\*+/,
390499
:password => 'the-password',
391-
:password_confirmation => 'the-password'
500+
:password_confirmation => /\*+/
392501
},
393502
:other => {
394503
:param => /\*+/,
@@ -427,7 +536,7 @@
427536
:extra => [{
428537
:secret => /\*+/,
429538
:password => 'the-password',
430-
:password_confirmation => 'the-password'
539+
:password_confirmation => /\*+/
431540
}],
432541
:other => [{
433542
:param => /\*+/,
@@ -460,7 +569,7 @@
460569
:extra => [{
461570
:secret => /\*+/,
462571
:password => 'the-password',
463-
:password_confirmation => 'the-password',
572+
:password_confirmation => /\*+/,
464573
:skipped => "Skipped value of class 'Tempfile'"
465574
}]
466575
}

0 commit comments

Comments
 (0)