lhc 13.2.0 → 13.4.0.pre.pro1766.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yml +3 -15
  3. data/.github/workflows/test.yml +3 -15
  4. data/.rubocop.yml +341 -19
  5. data/README.md +45 -0
  6. data/lhc.gemspec +3 -1
  7. data/lib/lhc/concerns/lhc/fix_invalid_encoding_concern.rb +1 -0
  8. data/lib/lhc/config.rb +16 -0
  9. data/lib/lhc/endpoint.rb +3 -0
  10. data/lib/lhc/error.rb +4 -2
  11. data/lib/lhc/interceptors/auth.rb +10 -1
  12. data/lib/lhc/interceptors/caching.rb +5 -0
  13. data/lib/lhc/interceptors/logging.rb +4 -2
  14. data/lib/lhc/interceptors/monitoring.rb +7 -1
  15. data/lib/lhc/interceptors/retry.rb +2 -0
  16. data/lib/lhc/interceptors/rollbar.rb +3 -2
  17. data/lib/lhc/interceptors/throttle.rb +7 -2
  18. data/lib/lhc/interceptors/zipkin.rb +2 -0
  19. data/lib/lhc/interceptors.rb +1 -0
  20. data/lib/lhc/request.rb +30 -1
  21. data/lib/lhc/response/data.rb +1 -1
  22. data/lib/lhc/response.rb +1 -0
  23. data/lib/lhc/scrubber.rb +45 -0
  24. data/lib/lhc/scrubbers/auth_scrubber.rb +32 -0
  25. data/lib/lhc/scrubbers/body_scrubber.rb +30 -0
  26. data/lib/lhc/scrubbers/headers_scrubber.rb +40 -0
  27. data/lib/lhc/scrubbers/params_scrubber.rb +14 -0
  28. data/lib/lhc/version.rb +1 -1
  29. data/lib/lhc.rb +70 -59
  30. data/spec/config/scrubs_spec.rb +108 -0
  31. data/spec/error/to_s_spec.rb +6 -6
  32. data/spec/formats/multipart_spec.rb +1 -1
  33. data/spec/interceptors/caching/multilevel_cache_spec.rb +1 -1
  34. data/spec/interceptors/define_spec.rb +1 -0
  35. data/spec/interceptors/logging/main_spec.rb +21 -1
  36. data/spec/interceptors/rollbar/main_spec.rb +27 -15
  37. data/spec/request/scrubbed_headers_spec.rb +101 -0
  38. data/spec/request/scrubbed_options_spec.rb +185 -0
  39. data/spec/request/scrubbed_params_spec.rb +25 -0
  40. data/spec/response/data_spec.rb +2 -2
  41. data/spec/support/zipkin_mock.rb +1 -0
  42. metadata +34 -8
  43. data/.rubocop.localch.yml +0 -325
@@ -42,6 +42,7 @@ class LHC::Caching < LHC::Interceptor
42
42
  def before_request
43
43
  return unless cache?(request)
44
44
  return if response_data.blank?
45
+
45
46
  from_cache(request, response_data)
46
47
  end
47
48
 
@@ -49,6 +50,7 @@ class LHC::Caching < LHC::Interceptor
49
50
  return unless response.success?
50
51
  return unless cache?(request)
51
52
  return if response_data.present?
53
+
52
54
  multilevel_cache.write(
53
55
  key(request, options[:key]),
54
56
  to_cache(response),
@@ -62,6 +64,7 @@ class LHC::Caching < LHC::Interceptor
62
64
  def response_data
63
65
  # stop calling multi-level cache if it already returned nil for this interceptor instance
64
66
  return @response_data if defined? @response_data
67
+
65
68
  @response_data ||= multilevel_cache.fetch(key(request, options[:key]))
66
69
  end
67
70
 
@@ -81,6 +84,7 @@ class LHC::Caching < LHC::Interceptor
81
84
 
82
85
  def central_cache
83
86
  return nil if central.blank? || (central[:read].blank? && central[:write].blank?)
87
+
84
88
  {}.tap do |options|
85
89
  options[:read] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:read]) if central[:read].present?
86
90
  options[:write] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:write]) if central[:write].present?
@@ -92,6 +96,7 @@ class LHC::Caching < LHC::Interceptor
92
96
  # return false if this interceptor cannot work
93
97
  def cache?(request)
94
98
  return false unless request.options[:cache]
99
+
95
100
  (local_cache || central_cache) &&
96
101
  cached_method?(request.method, options[:methods])
97
102
  end
@@ -7,14 +7,15 @@ class LHC::Logging < LHC::Interceptor
7
7
 
8
8
  def before_request
9
9
  return unless logger
10
+
10
11
  logger.info(
11
12
  [
12
13
  'Before LHC request',
13
14
  "<#{request.object_id}>",
14
15
  request.method.upcase,
15
16
  "#{request.url} at #{Time.now.iso8601}",
16
- "Params=#{request.params}",
17
- "Headers=#{request.headers}",
17
+ "Params=#{request.scrubbed_params}",
18
+ "Headers=#{request.scrubbed_headers}",
18
19
  request.source ? "\nCalled from #{request.source}" : nil
19
20
  ].compact.join(' ')
20
21
  )
@@ -22,6 +23,7 @@ class LHC::Logging < LHC::Interceptor
22
23
 
23
24
  def after_response
24
25
  return unless logger
26
+
25
27
  logger.info(
26
28
  [
27
29
  'After LHC response for request',
@@ -13,17 +13,20 @@ class LHC::Monitoring < LHC::Interceptor
13
13
 
14
14
  def before_request
15
15
  return unless statsd
16
+
16
17
  LHC::Monitoring.statsd.count("#{key}.before_request", 1)
17
18
  end
18
19
 
19
20
  def after_request
20
21
  return unless statsd
22
+
21
23
  LHC::Monitoring.statsd.count("#{key}.count", 1)
22
24
  LHC::Monitoring.statsd.count("#{key}.after_request", 1)
23
25
  end
24
26
 
25
27
  def after_response
26
28
  return unless statsd
29
+
27
30
  monitor_time!
28
31
  monitor_cache!
29
32
  monitor_response!
@@ -38,6 +41,7 @@ class LHC::Monitoring < LHC::Interceptor
38
41
  def monitor_cache!
39
42
  return if request.options[:cache].blank?
40
43
  return unless monitor_caching_configuration_check
44
+
41
45
  if response.from_cache?
42
46
  LHC::Monitoring.statsd.count("#{key}.cache.hit", 1)
43
47
  else
@@ -47,6 +51,7 @@ class LHC::Monitoring < LHC::Interceptor
47
51
 
48
52
  def monitor_caching_configuration_check
49
53
  return true if all_interceptor_classes.include?(LHC::Caching) && all_interceptor_classes.index(self.class) > all_interceptor_classes.index(LHC::Caching)
54
+
50
55
  warn("[WARNING] Your interceptors must include LHC::Caching and LHC::Monitoring and also in that order.")
51
56
  end
52
57
 
@@ -78,7 +83,8 @@ class LHC::Monitoring < LHC::Interceptor
78
83
  end
79
84
 
80
85
  def sanitize_url(url)
81
- return url if url.match(%r{https?://})
86
+ return url if url.match?(%r{https?://})
87
+
82
88
  "http://#{url}"
83
89
  end
84
90
 
@@ -10,6 +10,7 @@ class LHC::Retry < LHC::Interceptor
10
10
  def after_response
11
11
  response.request.options[:retries] ||= 0
12
12
  return unless retry?(response.request)
13
+
13
14
  response.request.options[:retries] += 1
14
15
  current_retry = response.request.options[:retries]
15
16
  begin
@@ -26,6 +27,7 @@ class LHC::Retry < LHC::Interceptor
26
27
  return false if request.response.success?
27
28
  return false if request.error_ignored?
28
29
  return false if !request.options.dig(:retry) && !LHC::Retry.all
30
+
29
31
  request.options[:retries] < max(request)
30
32
  end
31
33
 
@@ -9,6 +9,7 @@ class LHC::Rollbar < LHC::Interceptor
9
9
  def after_response
10
10
  return unless Object.const_defined?('Rollbar')
11
11
  return if response.success?
12
+
12
13
  request = response.request
13
14
  additional_params = request.options.fetch(:rollbar, {})
14
15
  data = {
@@ -22,8 +23,8 @@ class LHC::Rollbar < LHC::Interceptor
22
23
  request: {
23
24
  url: request.url,
24
25
  method: request.method,
25
- headers: request.headers,
26
- params: request.params
26
+ headers: request.scrubbed_headers,
27
+ params: request.scrubbed_params
27
28
  }
28
29
  }.merge additional_params
29
30
  begin
@@ -13,14 +13,17 @@ class LHC::Throttle < LHC::Interceptor
13
13
  def before_request
14
14
  options = request.options.dig(:throttle)
15
15
  return unless options
16
+
16
17
  break_options = options.dig(:break)
17
18
  return unless break_options
18
- break_when_quota_reached! if break_options.match('%')
19
+
20
+ break_when_quota_reached! if break_options.match?('%')
19
21
  end
20
22
 
21
23
  def after_response
22
24
  options = response.request.options.dig(:throttle)
23
25
  return unless throttle?(options)
26
+
24
27
  self.class.track ||= {}
25
28
  self.class.track[options.dig(:provider)] = {
26
29
  limit: limit(options: options[:limit], response: response),
@@ -40,6 +43,7 @@ class LHC::Throttle < LHC::Interceptor
40
43
  track = (self.class.track || {}).dig(options[:provider])
41
44
  return if track.blank? || track[:remaining].blank? || track[:limit].blank? || track[:expires].blank?
42
45
  return if Time.zone.now > track[:expires]
46
+
43
47
  # avoid floats by multiplying with 100
44
48
  remaining = track[:remaining] * 100
45
49
  limit = track[:limit]
@@ -80,7 +84,8 @@ class LHC::Throttle < LHC::Interceptor
80
84
  def convert_expires(value)
81
85
  return if value.blank?
82
86
  return value.call(response) if value.is_a?(Proc)
83
- return Time.parse(value) if value.match(/GMT/)
87
+ return Time.parse(value) if value.match?(/GMT/)
88
+
84
89
  Time.zone.at(value.to_i).to_datetime
85
90
  end
86
91
  end
@@ -12,6 +12,7 @@ class LHC::Zipkin < LHC::Interceptor
12
12
 
13
13
  def before_request
14
14
  return if !dependencies? || !tracing?
15
+
15
16
  ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
16
17
  # add headers even if the current trace_id should not be sampled
17
18
  B3_HEADERS.each { |method, header| request.headers[header] = trace_id.send(method).to_s }
@@ -23,6 +24,7 @@ class LHC::Zipkin < LHC::Interceptor
23
24
  def after_response
24
25
  # only sample the current call if we're instructed to do so
25
26
  return unless dependencies? && trace_id.sampled?
27
+
26
28
  end_trace!
27
29
  end
28
30
 
@@ -19,6 +19,7 @@ class LHC::Interceptors
19
19
  result = interceptor.send(name)
20
20
  if result.is_a? LHC::Response
21
21
  raise 'Response already set from another interceptor' if @response
22
+
22
23
  @response = interceptor.request.response = result
23
24
  end
24
25
  end
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
@@ -67,6 +90,7 @@ class LHC::Request
67
90
 
68
91
  def optionally_encoded_url(options)
69
92
  return options[:url] unless options.fetch(:url_encoding, true)
93
+
70
94
  encode_url(options[:url])
71
95
  end
72
96
 
@@ -85,12 +109,14 @@ class LHC::Request
85
109
 
86
110
  def translate_body(options)
87
111
  return options if options.fetch(:body, nil).blank?
112
+
88
113
  options[:body] = format.to_body(options[:body])
89
114
  options
90
115
  end
91
116
 
92
117
  def encode_url(url)
93
118
  return url if url.nil?
119
+
94
120
  Addressable::URI.escape(url)
95
121
  end
96
122
 
@@ -100,6 +126,7 @@ class LHC::Request
100
126
  options.delete(:url)
101
127
  options.each do |key, _v|
102
128
  next if TYPHOEUS_OPTIONS.include? key
129
+
103
130
  method = "#{key}="
104
131
  options.delete key unless easy.respond_to?(method)
105
132
  end
@@ -111,6 +138,7 @@ class LHC::Request
111
138
  def use_configured_endpoint!
112
139
  endpoint = LHC.config.endpoints[options[:url]]
113
140
  return unless endpoint
141
+
114
142
  # explicit options override endpoint options
115
143
  new_options = endpoint.options.deep_merge(options)
116
144
  # set new options
@@ -139,6 +167,7 @@ class LHC::Request
139
167
 
140
168
  def handle_error(response)
141
169
  return if ignore_error?
170
+
142
171
  throw_error(response) unless error_handler
143
172
  response.body_replacement = error_handler.call(response)
144
173
  end
@@ -18,7 +18,7 @@ class LHC::Response::Data
18
18
  end
19
19
  end
20
20
 
21
- def method_missing(method, *args, &block) # rubocop:disable Style/MethodMissingSuper
21
+ def method_missing(method, *args, &block)
22
22
  @base.send(method, *args, &block)
23
23
  end
24
24
 
data/lib/lhc/response.rb CHANGED
@@ -50,6 +50,7 @@ class LHC::Response
50
50
 
51
51
  def format
52
52
  return LHC::Formats::JSON.new if request.nil?
53
+
53
54
  request.format
54
55
  end
55
56
 
@@ -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.deep_dup
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?
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,32 @@
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_token] = SCRUB_DISPLAY
31
+ end
32
+ end
@@ -0,0 +1,30 @@
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?
18
+ return if scrubbed.is_a?(Hash)
19
+ return if scrubbed.is_a?(Array)
20
+
21
+ if scrubbed.is_a?(String)
22
+ json = scrubbed
23
+ else
24
+ json = scrubbed.to_json
25
+ end
26
+
27
+ parsed = JSON.parse(json)
28
+ self.scrubbed = parsed if parsed.is_a?(Hash) || parsed.is_a?(Array)
29
+ end
30
+ end
@@ -0,0 +1,40 @@
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?
29
+ return if scrubbed['Authorization'].blank?
30
+
31
+ scrubbed['Authorization'] = scrubbed['Authorization'].gsub(auth_options[:basic][:base_64_encoded_credentials], SCRUB_DISPLAY)
32
+ end
33
+
34
+ def scrub_bearer_authentication_headers!
35
+ return if @auth_options[:bearer].blank?
36
+ return if @scrubbed['Authorization'].blank?
37
+
38
+ @scrubbed['Authorization'] = scrubbed['Authorization'].gsub(auth_options[:bearer_token], SCRUB_DISPLAY)
39
+ end
40
+ 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 ||= '13.2.0'
4
+ VERSION ||= '13.4.0-pro1766.1'
5
5
  end