lhc 14.0.0 → 15.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7379ccaa5a569acd2eca8b6ea938996477391648efe913b45ff07e478f81e471
4
- data.tar.gz: a277d00a29c59e3da5c9fa4fa5b130a201d253ad65ac26a75e1bc348eacf479f
3
+ metadata.gz: 3a34aeee4fa910b8099acfdd73813103beb630400b92b449dbfbf4d87de4e041
4
+ data.tar.gz: 846b8cc9e24b9b0b9189b24d38c3e7b485096b4afd970cfc6fe48c8ee3a76d7e
5
5
  SHA512:
6
- metadata.gz: 76713bd28a5e9ce252bfef62f77d5b04cf3820a290089d7331f2d1445a4e04ff4ebcfa6180b50d75f0ce4b0b62454f3a4bbece2f7a41f37fa2ea8158e755a196
7
- data.tar.gz: e33b20e2c2d8f12b68e5ef76842bc94c61cd761be66d4d61b22b0c2501fd6dfc4041c7a9e32bff1d964e34c154d40ed6acf865cfeadb01c7318729597c96b25c
6
+ metadata.gz: 067d2e56385e931f00caa829411888881705ec19a702979332ad5074bf1a6515f0893e09111314b825fa7dd6d559518c997ac309f17e06eee245faffe93fcebd
7
+ data.tar.gz: 5284f04c83ca525e774775c41bd88d9f2dc8feeaf8f1fb47d3b9bde5677eeaad9bec6be6213597c4d1f34a2a22f8703b9f8e7fef0a4a09a58f24a6f72b2bf66b
data/README.md CHANGED
@@ -51,6 +51,7 @@ use it like:
51
51
  * [Configuration](#configuration)
52
52
  * [Configuring endpoints](#configuring-endpoints)
53
53
  * [Configuring placeholders](#configuring-placeholders)
54
+ * [Configuring scrubs](#configuring-scrubs)
54
55
  * [Interceptors](#interceptors)
55
56
  * [Quick start: Configure/Enable Interceptors](#quick-start-configureenable-interceptors)
56
57
  * [Interceptors on local request level](#interceptors-on-local-request-level)
@@ -491,6 +492,50 @@ You can configure global placeholders, that are used when generating urls from u
491
492
  LHC.get(:feedbacks) # http://datastore/v2/feedbacks
492
493
  ```
493
494
 
495
+ ### Configuring scrubs
496
+
497
+ You can filter out sensitive request data from your log files and rollbar by appending them to `LHS.config.scrubs`. These values will be marked `[FILTERED]` in the log and on rollbar. Also nested parameters are being filtered.
498
+ The scrubbing configuration affects all request done by LHC independent of the endpoint. You can scrub any attribute within `:params`, `:headers` or `:body`. For `:auth` you either can choose `:bearer` or `:basic` (default is both).
499
+
500
+ LHS scrubs per default:
501
+ - Bearer Token within the Request Header
502
+ - Basic Auth `username` and `password` within the Request Header
503
+ - `password` and `password_confirmation` within the Request Body
504
+
505
+ Enhance the default scrubbing by pushing the name of the parameter, which should be scrubbed, as string to the existing configuration.
506
+ You can also add multiple parameters at once by pushing multiple strings.
507
+
508
+ Example:
509
+ ```ruby
510
+ LHC.configure do |c|
511
+ c.scrubs[:params] << 'api_key'
512
+ c.scrubs[:body].push('user_token', 'secret_key')
513
+ end
514
+ ```
515
+
516
+ For disabling scrubbing, add following configuration:
517
+ ```ruby
518
+ LHC.configure do |c|
519
+ c.scrubs = {}
520
+ end
521
+ ```
522
+
523
+ If you want to turn off `:bearer` or `:basic` scrubbing, then just overwrite the `:auth` configuration.
524
+
525
+ Example:
526
+ ```ruby
527
+ LHC.configure do |c|
528
+ c.scrubs[:auth] = [:bearer]
529
+ end
530
+ ```
531
+
532
+ If your app has a different authentication strategy than Bearer Authentication or Basic Authentication then you can filter the data by scrubbing the whole header:
533
+ ```ruby
534
+ LHC.configure do |c|
535
+ c.scrubs[:headers] << 'Authorization'
536
+ end
537
+ ```
538
+
494
539
  ## Interceptors
495
540
 
496
541
  To monitor and manipulate the HTTP communication done with LHC, you can define interceptors that follow the (Inteceptor Pattern)[https://en.wikipedia.org/wiki/Interceptor_pattern].
data/lib/lhc.rb CHANGED
@@ -113,6 +113,17 @@ module LHC
113
113
  autoload :UnknownError,
114
114
  'lhc/errors/unknown_error'
115
115
 
116
+ autoload :Scrubber,
117
+ 'lhc/scrubber'
118
+ autoload :AuthScrubber,
119
+ 'lhc/scrubbers/auth_scrubber'
120
+ autoload :BodyScrubber,
121
+ 'lhc/scrubbers/body_scrubber'
122
+ autoload :HeadersScrubber,
123
+ 'lhc/scrubbers/headers_scrubber'
124
+ autoload :ParamsScrubber,
125
+ 'lhc/scrubbers/params_scrubber'
126
+
116
127
  autoload :Interceptor,
117
128
  'lhc/interceptor'
118
129
  autoload :Interceptors,
data/lib/lhc/config.rb CHANGED
@@ -5,9 +5,12 @@ require 'singleton'
5
5
  class LHC::Config
6
6
  include Singleton
7
7
 
8
+ attr_accessor :scrubs
9
+
8
10
  def initialize
9
11
  @endpoints = {}
10
12
  @placeholders = {}
13
+ @scrubs = default_scrubs
11
14
  end
12
15
 
13
16
  def endpoint(name, url, options = {})
@@ -42,9 +45,19 @@ class LHC::Config
42
45
  @interceptors = interceptors
43
46
  end
44
47
 
48
+ def default_scrubs
49
+ {
50
+ auth: [:bearer, :basic],
51
+ params: [],
52
+ headers: [],
53
+ body: ['password', 'password_confirmation']
54
+ }
55
+ end
56
+
45
57
  def reset
46
58
  @endpoints = {}
47
59
  @placeholders = {}
48
60
  @interceptors = nil
61
+ @scrubs = default_scrubs
49
62
  end
50
63
  end
data/lib/lhc/error.rb CHANGED
@@ -72,13 +72,12 @@ class LHC::Error < StandardError
72
72
 
73
73
  debug = []
74
74
  debug << [request.method, request.url].map { |str| self.class.fix_invalid_encoding(str) }.join(' ')
75
- debug << "Options: #{request.options}"
76
- debug << "Headers: #{request.headers}"
75
+ debug << "Options: #{request.scrubbed_options}"
76
+ debug << "Headers: #{request.scrubbed_headers}"
77
77
  debug << "Response Code: #{response.code} (#{response.options[:return_code]})"
78
78
  debug << "Response Options: #{response.options}"
79
79
  debug << response.body
80
80
  debug << _message
81
-
82
81
  debug.map { |str| self.class.fix_invalid_encoding(str) }.join("\n")
83
82
  end
84
83
  end
@@ -30,7 +30,7 @@ class LHC::Auth < LHC::Interceptor
30
30
  def basic_authentication!
31
31
  auth = auth_options[:basic]
32
32
  credentials = "#{auth[:username]}:#{auth[:password]}"
33
- set_authorization_header("Basic #{Base64.strict_encode64(credentials).chomp}")
33
+ set_basic_authorization_header(Base64.strict_encode64(credentials).chomp)
34
34
  end
35
35
 
36
36
  def bearer_authentication!
@@ -44,7 +44,13 @@ class LHC::Auth < LHC::Interceptor
44
44
  request.headers['Authorization'] = value
45
45
  end
46
46
 
47
+ def set_basic_authorization_header(base_64_encoded_credentials)
48
+ request.options[:auth][:basic].merge!(base_64_encoded_credentials: base_64_encoded_credentials)
49
+ set_authorization_header("Basic #{base_64_encoded_credentials}")
50
+ end
51
+
47
52
  def set_bearer_authorization_header(token)
53
+ request.options[:auth].merge!(bearer_token: token)
48
54
  set_authorization_header("Bearer #{token}")
49
55
  end
50
56
  # rubocop:enable Style/AccessorMethodName
@@ -14,8 +14,8 @@ class LHC::Logging < LHC::Interceptor
14
14
  "<#{request.object_id}>",
15
15
  request.method.upcase,
16
16
  "#{request.url} at #{Time.now.iso8601}",
17
- "Params=#{request.params}",
18
- "Headers=#{request.headers}",
17
+ "Params=#{request.scrubbed_params}",
18
+ "Headers=#{request.scrubbed_headers}",
19
19
  request.source ? "\nCalled from #{request.source}" : nil
20
20
  ].compact.join(' ')
21
21
  )
@@ -23,8 +23,8 @@ class LHC::Rollbar < LHC::Interceptor
23
23
  request: {
24
24
  url: request.url,
25
25
  method: request.method,
26
- headers: request.headers,
27
- params: request.params
26
+ headers: request.scrubbed_headers,
27
+ params: request.scrubbed_params
28
28
  }
29
29
  }.merge additional_params
30
30
  begin
data/lib/lhc/request.rb CHANGED
@@ -12,7 +12,13 @@ class LHC::Request
12
12
 
13
13
  TYPHOEUS_OPTIONS ||= [:params, :method, :body, :headers, :follow_location, :params_encoding]
14
14
 
15
- attr_accessor :response, :options, :raw, :format, :error_handler, :errors_ignored, :source
15
+ attr_accessor :response,
16
+ :options,
17
+ :raw,
18
+ :format,
19
+ :error_handler,
20
+ :errors_ignored,
21
+ :source
16
22
 
17
23
  def initialize(options, self_executing = true)
18
24
  self.errors_ignored = (options.fetch(:ignore, []) || []).to_a.compact
@@ -56,6 +62,23 @@ class LHC::Request
56
62
  raw.run
57
63
  end
58
64
 
65
+ def scrubbed_params
66
+ LHC::ParamsScrubber.new(params.deep_dup).scrubbed
67
+ end
68
+
69
+ def scrubbed_headers
70
+ LHC::HeadersScrubber.new(headers.deep_dup, options[:auth]).scrubbed
71
+ end
72
+
73
+ def scrubbed_options
74
+ scrubbed_options = options.deep_dup
75
+ scrubbed_options[:params] = LHC::ParamsScrubber.new(scrubbed_options[:params]).scrubbed
76
+ scrubbed_options[:headers] = LHC::HeadersScrubber.new(scrubbed_options[:headers], scrubbed_options[:auth]).scrubbed
77
+ scrubbed_options[:auth] = LHC::AuthScrubber.new(scrubbed_options[:auth]).scrubbed
78
+ scrubbed_options[:body] = LHC::BodyScrubber.new(scrubbed_options[:body]).scrubbed
79
+ scrubbed_options
80
+ end
81
+
59
82
  private
60
83
 
61
84
  attr_accessor :interceptors
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::Scrubber
4
+ attr_accessor :scrubbed
5
+
6
+ SCRUB_DISPLAY = '[FILTERED]'
7
+
8
+ def initialize(data)
9
+ @scrubbed = data
10
+ end
11
+
12
+ private
13
+
14
+ def scrub_auth_elements
15
+ LHC.config.scrubs.dig(:auth)
16
+ end
17
+
18
+ def scrub!
19
+ return if scrub_elements.blank?
20
+ return if scrubbed.blank?
21
+
22
+ LHC::Scrubber.scrub_hash!(scrub_elements, scrubbed) if scrubbed.is_a?(Hash)
23
+ LHC::Scrubber.scrub_array!(scrub_elements, scrubbed) if scrubbed.is_a?(Array)
24
+ end
25
+
26
+ def self.scrub_array!(scrub_elements, scrubbed)
27
+ scrubbed.each do |scrubbed_hash|
28
+ LHC::Scrubber.scrub_hash!(scrub_elements, scrubbed_hash)
29
+ end
30
+ end
31
+
32
+ def self.scrub_hash!(scrub_elements, scrubbed)
33
+ scrub_elements.each do |scrub_element|
34
+ if scrubbed.key?(scrub_element.to_s)
35
+ key = scrub_element.to_s
36
+ elsif scrubbed.key?(scrub_element.to_sym)
37
+ key = scrub_element.to_sym
38
+ end
39
+ next if key.blank? || scrubbed[key].blank?
40
+
41
+ scrubbed[key] = SCRUB_DISPLAY
42
+ end
43
+ scrubbed.values.each { |v| LHC::Scrubber.scrub_hash!(scrub_elements, v) if v.instance_of?(Hash) }
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::AuthScrubber < LHC::Scrubber
4
+ def initialize(data)
5
+ super(data)
6
+ scrub_auth_options!
7
+ end
8
+
9
+ private
10
+
11
+ def scrub_auth_options!
12
+ return if scrubbed.blank?
13
+ return if scrub_auth_elements.blank?
14
+
15
+ scrub_basic_auth_options! if scrub_auth_elements.include?(:basic)
16
+ scrub_bearer_auth_options! if scrub_auth_elements.include?(:bearer)
17
+ end
18
+
19
+ def scrub_basic_auth_options!
20
+ return if scrubbed[:basic].blank?
21
+
22
+ scrubbed[:basic][:username] = SCRUB_DISPLAY
23
+ scrubbed[:basic][:password] = SCRUB_DISPLAY
24
+ scrubbed[:basic][:base_64_encoded_credentials] = SCRUB_DISPLAY
25
+ end
26
+
27
+ def scrub_bearer_auth_options!
28
+ return if scrubbed[:bearer].blank?
29
+
30
+ scrubbed[:bearer] = SCRUB_DISPLAY if scrubbed[:bearer].is_a?(String)
31
+ scrubbed[:bearer_token] = SCRUB_DISPLAY
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::BodyScrubber < LHC::Scrubber
4
+ def initialize(data)
5
+ super(data)
6
+ parse!
7
+ scrub!
8
+ end
9
+
10
+ private
11
+
12
+ def scrub_elements
13
+ LHC.config.scrubs[:body]
14
+ end
15
+
16
+ def parse!
17
+ return if scrubbed.nil? || scrubbed.is_a?(Hash) || scrubbed.is_a?(Array)
18
+
19
+ if scrubbed.is_a?(String)
20
+ json = scrubbed
21
+ else
22
+ json = scrubbed.to_json
23
+ end
24
+
25
+ parsed = JSON.parse(json)
26
+ self.scrubbed = parsed if parsed.is_a?(Hash) || parsed.is_a?(Array)
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::HeadersScrubber < LHC::Scrubber
4
+ def initialize(data, auth_options)
5
+ super(data)
6
+ @auth_options = auth_options
7
+ scrub!
8
+ scrub_auth_headers!
9
+ end
10
+
11
+ private
12
+
13
+ attr_reader :auth_options
14
+
15
+ def scrub_elements
16
+ LHC.config.scrubs[:headers]
17
+ end
18
+
19
+ def scrub_auth_headers!
20
+ return if scrub_auth_elements.blank?
21
+ return if auth_options.blank?
22
+
23
+ scrub_basic_authentication_headers! if scrub_auth_elements.include?(:basic)
24
+ scrub_bearer_authentication_headers! if scrub_auth_elements.include?(:bearer)
25
+ end
26
+
27
+ def scrub_basic_authentication_headers!
28
+ return if auth_options[:basic].blank? || scrubbed['Authorization'].blank?
29
+
30
+ scrubbed['Authorization'].gsub!(auth_options[:basic][:base_64_encoded_credentials], SCRUB_DISPLAY)
31
+ end
32
+
33
+ def scrub_bearer_authentication_headers!
34
+ return if auth_options[:bearer].blank? || scrubbed['Authorization'].blank?
35
+
36
+ scrubbed['Authorization'].gsub!(auth_options[:bearer_token], SCRUB_DISPLAY)
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LHC::ParamsScrubber < LHC::Scrubber
4
+ def initialize(data)
5
+ super(data)
6
+ scrub!
7
+ end
8
+
9
+ private
10
+
11
+ def scrub_elements
12
+ LHC.config.scrubs[:params]
13
+ end
14
+ end
data/lib/lhc/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LHC
4
- VERSION ||= '14.0.0'
4
+ VERSION ||= '15.0.0'
5
5
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe LHC do
6
+ it 'has a default value for scrubs' do
7
+ expect(LHC.config.scrubs[:auth]).to eq [:bearer, :basic]
8
+ expect(LHC.config.scrubs[:params]).to eq []
9
+ expect(LHC.config.scrubs[:headers]).to eq []
10
+ expect(LHC.config.scrubs[:body]).to eq ['password', 'password_confirmation']
11
+ end
12
+
13
+ describe 'auth' do
14
+ context 'when only bearer auth should get scrubbed' do
15
+ before(:each) do
16
+ LHC.configure do |c|
17
+ c.scrubs[:auth] = [:bearer]
18
+ end
19
+ end
20
+
21
+ it 'has only bearer auth in scrubs' do
22
+ expect(LHC.config.scrubs[:auth]).to eq([:bearer])
23
+ expect(LHC.config.scrubs[:params]).to eq []
24
+ expect(LHC.config.scrubs[:headers]).to eq []
25
+ expect(LHC.config.scrubs[:body]).to eq ['password', 'password_confirmation']
26
+ end
27
+ end
28
+ end
29
+
30
+ context 'params' do
31
+ context 'when additional param "api_key" should be scrubbed' do
32
+ before(:each) do
33
+ LHC.configure do |c|
34
+ c.scrubs[:params] << 'api_key'
35
+ end
36
+ end
37
+
38
+ it 'has "api_key" in scrubs' do
39
+ expect(LHC.config.scrubs[:auth]).to eq [:bearer, :basic]
40
+ expect(LHC.config.scrubs[:params]).to eq ['api_key']
41
+ expect(LHC.config.scrubs[:headers]).to eq []
42
+ expect(LHC.config.scrubs[:body]).to eq ['password', 'password_confirmation']
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'headers' do
48
+ context 'when additional header "private_key" should be scrubbed' do
49
+ before(:each) do
50
+ LHC.configure do |c|
51
+ c.scrubs[:headers] << 'private_key'
52
+ end
53
+ end
54
+
55
+ it 'has "private_key" in scrubs' do
56
+ expect(LHC.config.scrubs[:auth]).to eq [:bearer, :basic]
57
+ expect(LHC.config.scrubs[:params]).to eq []
58
+ expect(LHC.config.scrubs[:headers]).to eq ['private_key']
59
+ expect(LHC.config.scrubs[:body]).to eq ['password', 'password_confirmation']
60
+ end
61
+ end
62
+ end
63
+
64
+ context 'body' do
65
+ context 'when only password should get scrubbed' do
66
+ before(:each) do
67
+ LHC.configure do |c|
68
+ c.scrubs[:body] = ['password']
69
+ end
70
+ end
71
+
72
+ it 'has password in scrubs' do
73
+ expect(LHC.config.scrubs[:auth]).to eq [:bearer, :basic]
74
+ expect(LHC.config.scrubs[:params]).to eq []
75
+ expect(LHC.config.scrubs[:headers]).to eq []
76
+ expect(LHC.config.scrubs[:body]).to eq(['password'])
77
+ end
78
+ end
79
+
80
+ context 'when "user_token" should be scrubbed' do
81
+ before(:each) do
82
+ LHC.configure do |c|
83
+ c.scrubs[:body] << 'user_token'
84
+ end
85
+ end
86
+
87
+ it 'has user_token in scrubs' do
88
+ expect(LHC.config.scrubs[:auth]).to eq [:bearer, :basic]
89
+ expect(LHC.config.scrubs[:params]).to eq []
90
+ expect(LHC.config.scrubs[:headers]).to eq []
91
+ expect(LHC.config.scrubs[:body]).to eq(['password', 'password_confirmation', 'user_token'])
92
+ end
93
+ end
94
+ end
95
+
96
+ context 'when nothing should be scrubbed' do
97
+ before(:each) do
98
+ LHC.configure do |c|
99
+ c.scrubs = {}
100
+ end
101
+ end
102
+
103
+ it 'does not have scrubs' do
104
+ expect(LHC.config.scrubs.blank?).to be true
105
+ expect(LHC.config.scrubs[:auth]).to be nil
106
+ end
107
+ end
108
+ end
@@ -48,10 +48,10 @@ describe LHC::Error do
48
48
  double('LHC::Request',
49
49
  method: 'GET',
50
50
  url: 'http://example.com/sessions',
51
- headers: { 'Bearer Token' => "aaaaaaaa-bbbb-cccc-dddd-eeee" },
52
- options: { followlocation: true,
53
- auth: { bearer: "aaaaaaaa-bbbb-cccc-dddd-eeee" },
54
- params: { limit: 20 }, url: "http://example.com/sessions" })
51
+ scrubbed_headers: { 'Bearer Token' => LHC::Scrubber::SCRUB_DISPLAY },
52
+ scrubbed_options: { followlocation: true,
53
+ auth: { bearer: LHC::Scrubber::SCRUB_DISPLAY },
54
+ params: { limit: 20 }, url: "http://example.com/sessions" })
55
55
  end
56
56
 
57
57
  let(:response) do
@@ -72,8 +72,8 @@ describe LHC::Error do
72
72
  it 'produces correct debug output' do
73
73
  expect(subject.to_s.split("\n")).to eq(<<-MSG.strip_heredoc.split("\n"))
74
74
  GET http://example.com/sessions
75
- Options: {:followlocation=>true, :auth=>{:bearer=>"aaaaaaaa-bbbb-cccc-dddd-eeee"}, :params=>{:limit=>20}, :url=>"http://example.com/sessions"}
76
- Headers: {"Bearer Token"=>"aaaaaaaa-bbbb-cccc-dddd-eeee"}
75
+ Options: {:followlocation=>true, :auth=>{:bearer=>"#{LHC::Scrubber::SCRUB_DISPLAY}"}, :params=>{:limit=>20}, :url=>"http://example.com/sessions"}
76
+ Headers: {"Bearer Token"=>"#{LHC::Scrubber::SCRUB_DISPLAY}"}
77
77
  Response Code: 500 (internal_error)
78
78
  Response Options: {:return_code=>:internal_error, :response_headers=>""}
79
79
  {"status":500,"message":"undefined"}
@@ -8,7 +8,7 @@ describe LHC::Logging do
8
8
  before(:each) do
9
9
  LHC.config.interceptors = [LHC::Logging]
10
10
  LHC::Logging.logger = logger
11
- stub_request(:get, 'http://local.ch').to_return(status: 200)
11
+ stub_request(:get, /http:\/\/local.ch.*/).to_return(status: 200)
12
12
  end
13
13
 
14
14
  it 'does log information before and after every request made with LHC' do
@@ -34,4 +34,24 @@ describe LHC::Logging do
34
34
  )
35
35
  end
36
36
  end
37
+
38
+ context 'sensitive data' do
39
+ before :each do
40
+ LHC.config.scrubs[:params] << 'api_key'
41
+ LHC.config.scrubs[:headers] << 'private_key'
42
+ LHC.get('http://local.ch', params: { api_key: '123-abc' }, headers: { private_key: 'abc-123' })
43
+ end
44
+
45
+ it 'does not log sensitive params information' do
46
+ expect(logger).to have_received(:info).once.with(
47
+ a_string_including("Params={:api_key=>\"#{LHC::Scrubber::SCRUB_DISPLAY}\"}")
48
+ )
49
+ end
50
+
51
+ it 'does not log sensitive header information' do
52
+ expect(logger).to have_received(:info).once.with(
53
+ a_string_including(":private_key=>\"#{LHC::Scrubber::SCRUB_DISPLAY}\"")
54
+ )
55
+ end
56
+ end
37
57
  end
@@ -36,22 +36,34 @@ describe LHC::Rollbar do
36
36
  )
37
37
  end
38
38
 
39
- context 'additional params' do
40
- it 'does report errors to rollbar with additional data' do
41
- stub_request(:get, 'http://local.ch')
42
- .to_return(status: 400)
43
- expect(-> { LHC.get('http://local.ch', rollbar: { additional: 'data' }) })
44
- .to raise_error LHC::BadRequest
45
- expect(::Rollbar).to have_received(:warning)
46
- .with(
47
- 'Status: 400 URL: http://local.ch',
48
- hash_including(
49
- response: anything,
50
- request: anything,
51
- additional: 'data'
52
- )
39
+ it 'does report errors to rollbar with additional data' do
40
+ stub_request(:get, 'http://local.ch')
41
+ .to_return(status: 400)
42
+ expect(-> { LHC.get('http://local.ch', rollbar: { additional: 'data' }) })
43
+ .to raise_error LHC::BadRequest
44
+ expect(::Rollbar).to have_received(:warning)
45
+ .with(
46
+ 'Status: 400 URL: http://local.ch',
47
+ hash_including(
48
+ response: anything,
49
+ request: anything,
50
+ additional: 'data'
53
51
  )
54
- end
52
+ )
53
+ end
54
+
55
+ it 'scrubs sensitive data' do
56
+ LHC.config.scrubs[:params] << 'api_key'
57
+ LHC.config.scrubs[:headers] << 'private_key'
58
+ stub_request(:get, 'http://local.ch?api_key=123-abc').to_return(status: 400)
59
+ expect(-> { LHC.get('http://local.ch', params: { api_key: '123-abc' }, headers: { private_key: 'abc-123' }) })
60
+ .to raise_error LHC::BadRequest
61
+ expect(::Rollbar).to have_received(:warning)
62
+ .with(
63
+ 'Status: 400 URL: http://local.ch',
64
+ response: hash_including(body: anything, code: anything, headers: anything, time: anything, timeout?: anything),
65
+ request: hash_including(url: anything, method: anything, headers: hash_including(private_key: LHC::Scrubber::SCRUB_DISPLAY), params: { api_key: LHC::Scrubber::SCRUB_DISPLAY })
66
+ )
55
67
  end
56
68
  end
57
69
  end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe LHC::Request do
6
+ let(:headers) { { private_key: 'xyz-123' } }
7
+ let(:response) { LHC.get(:local, headers: headers) }
8
+ let(:auth) { {} }
9
+
10
+ before :each do
11
+ LHC.config.endpoint(:local, 'http://local.ch', auth: auth)
12
+ stub_request(:get, 'http://local.ch').with(headers: headers)
13
+ end
14
+
15
+ it 'scrubs "private_key"' do
16
+ LHC.config.scrubs[:headers] << 'private_key'
17
+ expect(response.request.scrubbed_headers).to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
18
+ end
19
+
20
+ it 'does not add a new attribute when a non existing header should be scrubbed' do
21
+ LHC.config.scrubs[:headers] << 'anything'
22
+ expect(response.request.scrubbed_headers).not_to include('anything' => LHC::Scrubber::SCRUB_DISPLAY)
23
+ end
24
+
25
+ context 'when strings instead of symbols are provided' do
26
+ let(:headers) { { 'private_key' => 'xyz-123' } }
27
+
28
+ it 'scrubs "private_key"' do
29
+ LHC.config.scrubs[:headers] << 'private_key'
30
+ expect(response.request.scrubbed_headers).to include('private_key' => LHC::Scrubber::SCRUB_DISPLAY)
31
+ end
32
+ end
33
+
34
+ context 'other authentication strategy' do
35
+ let(:api_key) { '123456' }
36
+ let(:authorization_header) { { 'Authorization' => "Apikey #{api_key}" } }
37
+ let(:headers) { authorization_header }
38
+
39
+ it 'provides srubbed Authorization header' do
40
+ LHC.config.scrubs[:headers] << 'Authorization'
41
+ expect(response.request.scrubbed_headers).to include('Authorization' => LHC::Scrubber::SCRUB_DISPLAY)
42
+ expect(response.request.headers).to include(authorization_header)
43
+ end
44
+ end
45
+
46
+ describe 'auth' do
47
+ before :each do
48
+ LHC.config.interceptors = [LHC::Auth]
49
+ stub_request(:get, 'http://local.ch').with(headers: authorization_header)
50
+ end
51
+
52
+ let(:request) do
53
+ response = LHC.get(:local)
54
+ response.request
55
+ end
56
+
57
+ context 'bearer authentication' do
58
+ let(:bearer_token) { '123456' }
59
+ let(:authorization_header) { { 'Authorization' => "Bearer #{bearer_token}" } }
60
+ let(:auth) { { bearer: -> { bearer_token } } }
61
+
62
+ it 'provides srubbed request headers' do
63
+ expect(request.scrubbed_headers).to include('Authorization' => "Bearer #{LHC::Scrubber::SCRUB_DISPLAY}")
64
+ expect(request.headers).to include(authorization_header)
65
+ end
66
+
67
+ context 'when nothing should get scrubbed' do
68
+ before :each do
69
+ LHC.config.scrubs = {}
70
+ end
71
+
72
+ it 'does not filter beaerer auth' do
73
+ expect(request.scrubbed_headers).to include(authorization_header)
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'basic authentication' do
79
+ let(:username) { 'steve' }
80
+ let(:password) { 'abcdefg' }
81
+ let(:credentials_base_64_codiert) { Base64.strict_encode64("#{username}:#{password}").chomp }
82
+ let(:authorization_header) { { 'Authorization' => "Basic #{credentials_base_64_codiert}" } }
83
+ let(:auth) { { basic: { username: username, password: password } } }
84
+
85
+ it 'provides srubbed request headers' do
86
+ expect(request.scrubbed_headers).to include('Authorization' => "Basic #{LHC::Scrubber::SCRUB_DISPLAY}")
87
+ expect(request.headers).to include(authorization_header)
88
+ end
89
+
90
+ context 'when nothing should get scrubbed' do
91
+ before :each do
92
+ LHC.config.scrubs = {}
93
+ end
94
+
95
+ it 'does not filter basic auth' do
96
+ expect(request.scrubbed_headers).to include(authorization_header)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe LHC::Request do
6
+ before :each do
7
+ LHC.config.interceptors = [LHC::Auth]
8
+ LHC.config.endpoint(:local, 'http://local.ch', auth: auth)
9
+ LHC.config.scrubs[:params] << 'api_key'
10
+ LHC.config.scrubs[:headers] << 'private_key'
11
+ LHC.config.scrubs[:body] << 'user_token'
12
+ stub_request(:post, "http://local.ch?#{params.to_query}").with(headers: authorization_header.merge(headers), body: body.to_json)
13
+ end
14
+
15
+ let(:bearer_token) { '123456' }
16
+ let(:authorization_header) { { 'Authorization' => "Bearer #{bearer_token}" } }
17
+ let(:auth) { { bearer: -> { bearer_token } } }
18
+ let(:params) { { api_key: 'api-key-params' } }
19
+ let(:headers) { { private_key: 'private-key-header' } }
20
+ let(:body) { { user_token: 'user-token-body' } }
21
+
22
+ let(:request) do
23
+ response = LHC.post(:local, params: params, headers: headers, body: body)
24
+ response.request
25
+ end
26
+
27
+ it 'provides srubbed request options' do
28
+ expect(request.scrubbed_options[:params]).to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
29
+ expect(request.scrubbed_options[:headers]).to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
30
+ expect(request.scrubbed_options[:body]).to include(user_token: LHC::Scrubber::SCRUB_DISPLAY)
31
+ expect(request.scrubbed_options[:auth][:bearer_token]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
32
+ expect(request.scrubbed_options[:auth][:basic]).to be nil
33
+ end
34
+
35
+ context 'when bearer auth is not a proc' do
36
+ let(:auth) { { bearer: bearer_token } }
37
+
38
+ it 'also scrubbes the bearer' do
39
+ expect(request.scrubbed_options[:auth][:bearer]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
40
+ expect(request.scrubbed_options[:auth][:bearer_token]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
41
+ end
42
+ end
43
+
44
+ context 'when options do not have auth' do
45
+ let(:authorization_header) { {} }
46
+ let(:auth) { nil }
47
+
48
+ it 'provides srubbed request options' do
49
+ expect(request.scrubbed_options[:params]).to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
50
+ expect(request.scrubbed_options[:headers]).to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
51
+ expect(request.scrubbed_options[:body]).to include(user_token: LHC::Scrubber::SCRUB_DISPLAY)
52
+ expect(request.scrubbed_options[:auth]).to be nil
53
+ end
54
+ end
55
+
56
+ context 'when body data is nested' do
57
+ let(:body) do
58
+ {
59
+ data: {
60
+ attributes: {
61
+ employee: {
62
+ name: 'Muster',
63
+ surname: 'Hans',
64
+ password: 'test-1234',
65
+ password_confirmation: 'test-1234'
66
+ }
67
+ }
68
+ }
69
+ }
70
+ end
71
+
72
+ it 'srubbes nested attributes' do
73
+ expect(request.scrubbed_options[:params]).to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
74
+ expect(request.scrubbed_options[:headers]).to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
75
+ expect(request.scrubbed_options[:body][:data][:attributes][:employee]).to include(password: LHC::Scrubber::SCRUB_DISPLAY)
76
+ expect(request.scrubbed_options[:body][:data][:attributes][:employee]).to include(password_confirmation: LHC::Scrubber::SCRUB_DISPLAY)
77
+ expect(request.scrubbed_options[:auth][:bearer_token]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
78
+ expect(request.scrubbed_options[:auth][:basic]).to be nil
79
+ end
80
+ end
81
+
82
+ context 'basic authentication' do
83
+ let(:username) { 'steve' }
84
+ let(:password) { 'abcdefg' }
85
+ let(:credentials_base_64_codiert) { Base64.strict_encode64("#{username}:#{password}").chomp }
86
+ let(:authorization_header) { { 'Authorization' => "Basic #{credentials_base_64_codiert}" } }
87
+ let(:auth) { { basic: { username: username, password: password } } }
88
+
89
+ it 'provides srubbed request headers' do
90
+ expect(request.scrubbed_options[:auth][:basic][:username]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
91
+ expect(request.scrubbed_options[:auth][:basic][:password]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
92
+ expect(request.scrubbed_options[:auth][:basic][:base_64_encoded_credentials]).to eq(LHC::Scrubber::SCRUB_DISPLAY)
93
+ expect(request.scrubbed_options[:auth][:bearer]).to be nil
94
+ end
95
+ end
96
+
97
+ context 'when nothing should get scrubbed' do
98
+ before :each do
99
+ LHC.config.scrubs = {}
100
+ end
101
+
102
+ it 'does not filter anything' do
103
+ expect(request.scrubbed_options[:params]).not_to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
104
+ expect(request.scrubbed_options[:headers]).not_to include(private_key: LHC::Scrubber::SCRUB_DISPLAY)
105
+ expect(request.scrubbed_options[:body]).not_to include(user_token: LHC::Scrubber::SCRUB_DISPLAY)
106
+ expect(request.scrubbed_options[:auth][:bearer_token]).not_to eq(LHC::Scrubber::SCRUB_DISPLAY)
107
+ end
108
+ end
109
+
110
+ context 'custom data structures that respond to as_json (like LHS data or record)' do
111
+ before do
112
+ class CustomStructure
113
+
114
+ def initialize(data)
115
+ @data = data
116
+ end
117
+
118
+ def as_json
119
+ @data.as_json
120
+ end
121
+
122
+ def to_json
123
+ as_json.to_json
124
+ end
125
+ end
126
+
127
+ stub_request(:post, 'http://local.ch').with(body: custom_structure.to_json)
128
+ end
129
+
130
+ let(:custom_structure) do
131
+ CustomStructure.new(user_token: '12345')
132
+ end
133
+
134
+ let(:request) do
135
+ response = LHC.post(:local, body: custom_structure)
136
+ response.request
137
+ end
138
+
139
+ it 'provides srubbed request options' do
140
+ expect(request.scrubbed_options[:body]).to include('user_token' => LHC::Scrubber::SCRUB_DISPLAY)
141
+ end
142
+ end
143
+
144
+ context 'encoded data hash' do
145
+ let(:body) { { user_token: 'user-token-body' } }
146
+
147
+ let(:request) do
148
+ response = LHC.post(:local, body: body.to_json)
149
+ response.request
150
+ end
151
+
152
+ before :each do
153
+ stub_request(:post, 'http://local.ch').with(body: body.to_json)
154
+ end
155
+
156
+ it 'provides srubbed request options' do
157
+ expect(request.scrubbed_options[:body]).to include('user_token' => LHC::Scrubber::SCRUB_DISPLAY)
158
+ end
159
+ end
160
+
161
+ context 'array' do
162
+ let(:body) { [{ user_token: 'user-token-body' }] }
163
+
164
+ let(:request) do
165
+ response = LHC.post(:local, body: body)
166
+ response.request
167
+ end
168
+
169
+ before :each do
170
+ stub_request(:post, 'http://local.ch').with(body: body.to_json)
171
+ end
172
+
173
+ it 'provides srubbed request options' do
174
+ expect(request.scrubbed_options[:body]).to eq([user_token: LHC::Scrubber::SCRUB_DISPLAY])
175
+ end
176
+ end
177
+
178
+ context 'encoded array' do
179
+ let(:body) { [{ user_token: 'user-token-body' }] }
180
+
181
+ let(:request) do
182
+ response = LHC.post(:local, body: body.to_json)
183
+ response.request
184
+ end
185
+
186
+ before :each do
187
+ stub_request(:post, 'http://local.ch').with(body: body.to_json)
188
+ end
189
+
190
+ it 'provides srubbed request options' do
191
+ expect(request.scrubbed_options[:body]).to eq(['user_token' => LHC::Scrubber::SCRUB_DISPLAY])
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe LHC::Request do
6
+ let(:params) { { api_key: 'xyz-123', secret_key: '123-xyz' } }
7
+ let(:response) { LHC.get(:local, params: params) }
8
+
9
+ before :each do
10
+ LHC.config.endpoint(:local, 'http://local.ch')
11
+ stub_request(:get, "http://local.ch?#{params.to_query}")
12
+ end
13
+
14
+ it 'scrubs "api_key"' do
15
+ LHC.config.scrubs[:params] << 'api_key'
16
+ expect(response.request.scrubbed_params).to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
17
+ expect(response.request.scrubbed_params).to include(secret_key: '123-xyz')
18
+ end
19
+
20
+ it 'scrubs "api_key" and "secret_key"' do
21
+ LHC.config.scrubs[:params].push('api_key', 'secret_key')
22
+ expect(response.request.scrubbed_params).to include(api_key: LHC::Scrubber::SCRUB_DISPLAY)
23
+ expect(response.request.scrubbed_params).to include(secret_key: LHC::Scrubber::SCRUB_DISPLAY)
24
+ end
25
+
26
+ context 'when value is empty' do
27
+ let(:params) { { api_key: nil, secret_key: '' } }
28
+
29
+ it 'does not filter the value' do
30
+ LHC.config.scrubs[:params].push('api_key', 'secret_key')
31
+ expect(response.request.scrubbed_params).to include(api_key: nil)
32
+ expect(response.request.scrubbed_params).to include(secret_key: '')
33
+ end
34
+ end
35
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lhc
3
3
  version: !ruby/object:Gem::Version
4
- version: 14.0.0
4
+ version: 15.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/local-ch/lhc/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-11 00:00:00.000000000 Z
11
+ date: 2021-05-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -270,6 +270,11 @@ files:
270
270
  - lib/lhc/response/data/collection.rb
271
271
  - lib/lhc/response/data/item.rb
272
272
  - lib/lhc/rspec.rb
273
+ - lib/lhc/scrubber.rb
274
+ - lib/lhc/scrubbers/auth_scrubber.rb
275
+ - lib/lhc/scrubbers/body_scrubber.rb
276
+ - lib/lhc/scrubbers/headers_scrubber.rb
277
+ - lib/lhc/scrubbers/params_scrubber.rb
273
278
  - lib/lhc/test/cache_helper.rb
274
279
  - lib/lhc/version.rb
275
280
  - script/ci/build.sh
@@ -281,6 +286,7 @@ files:
281
286
  - spec/basic_methods/request_without_rails_spec.rb
282
287
  - spec/config/endpoints_spec.rb
283
288
  - spec/config/placeholders_spec.rb
289
+ - spec/config/scrubs_spec.rb
284
290
  - spec/core_ext/hash/deep_transform_values_spec.rb
285
291
  - spec/dummy/README.rdoc
286
292
  - spec/dummy/Rakefile
@@ -382,6 +388,9 @@ files:
382
388
  - spec/request/parallel_requests_spec.rb
383
389
  - spec/request/params_encoding_spec.rb
384
390
  - spec/request/request_without_rails_spec.rb
391
+ - spec/request/scrubbed_headers_spec.rb
392
+ - spec/request/scrubbed_options_spec.rb
393
+ - spec/request/scrubbed_params_spec.rb
385
394
  - spec/request/url_patterns_spec.rb
386
395
  - spec/request/user_agent_spec.rb
387
396
  - spec/request/user_agent_without_rails_spec.rb
@@ -436,6 +445,7 @@ test_files:
436
445
  - spec/basic_methods/request_without_rails_spec.rb
437
446
  - spec/config/endpoints_spec.rb
438
447
  - spec/config/placeholders_spec.rb
448
+ - spec/config/scrubs_spec.rb
439
449
  - spec/core_ext/hash/deep_transform_values_spec.rb
440
450
  - spec/dummy/README.rdoc
441
451
  - spec/dummy/Rakefile
@@ -537,6 +547,9 @@ test_files:
537
547
  - spec/request/parallel_requests_spec.rb
538
548
  - spec/request/params_encoding_spec.rb
539
549
  - spec/request/request_without_rails_spec.rb
550
+ - spec/request/scrubbed_headers_spec.rb
551
+ - spec/request/scrubbed_options_spec.rb
552
+ - spec/request/scrubbed_params_spec.rb
540
553
  - spec/request/url_patterns_spec.rb
541
554
  - spec/request/user_agent_spec.rb
542
555
  - spec/request/user_agent_without_rails_spec.rb