supergood 0.1.4 → 1.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: 37e6e9b772961c8468ce6b56f08b88e16f6be31e22b123ec629695da7a1d0e06
4
- data.tar.gz: 57497dfc8b8609f8e97c063079e908db68252cce6d59bb5ff80096fee39873e2
3
+ metadata.gz: 5ee319f07b495599aa2deafcf5b6c995402e55b4d7a58ddb82d55379aa6ba99a
4
+ data.tar.gz: 15ab686e3bec0c37771e8fa2892f7cdb69312595cc8726c0d690855681eeb26d
5
5
  SHA512:
6
- metadata.gz: 35716cb5a956905973fb36193e13fa57527184291ee6f557d721f01dac2d7e53c139fdf924b4a2c68a568aec9dcc909cdf0ba8e40f6ac6a99945841de1685bc0
7
- data.tar.gz: 40d19c986a555912c4e7040f4bf2d4ee57680738c3506ae6c341f771b60ed1e51fa3c8fb4f3482469f898e493e165d9b2e8bc969188f25ffb488e64ee6250bb8
6
+ metadata.gz: 11eed3208aeec8618d71cb7a832988ef0e88cfb1d55fed315b203418cb93ab4bfba251b22ef978afd0ec672b4b849d2af28ad03435d20003db1020e154faa74c
7
+ data.tar.gz: 583dacf60c012289be925e8c49471a14d401be64a00ef0cf299e758797bd318dadb0bb3d3a64cb53ef925ba0620fe253213d6406e1ffc35e094c0c5b5e69a239
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "editor.codeActionsOnSave": {
3
- "source.fixAll.eslint": true
3
+ "source.fixAll.eslint": "explicit"
4
4
  },
5
5
  "editor.tabSize": 2,
6
6
  }
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- supergood (0.1.3)
4
+ supergood (1.0.0)
5
5
  rudash (~> 4.0, >= 4.0.2)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -12,7 +12,7 @@ gem install supergood
12
12
 
13
13
  **Environment variables**
14
14
 
15
- Set the environment variables `SUPERGOOD_CLIENT_ID` and `SUPERGOOD_CLIENT_SECRET` using the API keys generated in the [getting started instructions](../../getting-started.md).
15
+ Set the environment variables `SUPERGOOD_CLIENT_ID` and `SUPERGOOD_CLIENT_SECRET` using the API keys generated in the [getting started instructions](https://github.com/supergoodsystems/docs/blob/main/getting-started.md).
16
16
 
17
17
  Initialize the Supergood client at the root of your application, or anywhere you're making API calls with the following code:
18
18
 
@@ -26,7 +26,7 @@ Supergood.init()
26
26
 
27
27
  You can also pass the API keys in manually without setting environment variables.\
28
28
  \
29
- Replace `<CLIENT_ID>` and `<CLIENT_SECRET>` with the API keys you generated in the [getting started instructions](../../getting-started.md).
29
+ Replace `<CLIENT_ID>` and `<CLIENT_SECRET>` with the API keys you generated in the [getting started instructions](https://github.com/supergoodsystems/docs/blob/main/getting-started.md).
30
30
 
31
31
  ```ruby
32
32
  require 'supergood'
data/lib/supergood/api.rb CHANGED
@@ -8,7 +8,9 @@ module Supergood
8
8
  @base_url = base_url
9
9
  @header_options = {
10
10
  'Content-Type' => 'application/json',
11
- 'Authorization' => 'Basic ' + Base64.encode64(client_id + ':' + client_secret).gsub(/\n/, '')
11
+ 'Authorization' => "Basic #{Base64.encode64("#{client_id}:#{client_secret}").gsub(/\n/, '')}",
12
+ 'supergood-api' => 'supergood-rb',
13
+ 'supergood-api-version' => VERSION
12
14
  }
13
15
  @local_only = client_id == LOCAL_CLIENT_ID && client_secret == LOCAL_CLIENT_SECRET
14
16
  end
@@ -29,11 +31,12 @@ module Supergood
29
31
  if @local_only
30
32
  @log.debug(payload)
31
33
  else
32
- uri = URI(@base_url + '/api/events')
34
+ uri = URI("#{@base_url}/events")
33
35
  response = Net::HTTP.post(uri, payload.to_json, @header_options)
34
- if response.code == '200'
35
- return JSON.parse(response.body, symbolize_names: true)
36
- elsif response.code == '401'
36
+
37
+ return JSON.parse(response.body) if response.code == '200'
38
+
39
+ if response.code == '401'
37
40
  raise SupergoodException.new ERRORS[:UNAUTHORIZED]
38
41
  elsif response.code != '200' && response.code != '201'
39
42
  raise SupergoodException.new ERRORS[:POSTING_EVENTS]
@@ -45,14 +48,20 @@ module Supergood
45
48
  if @local_only
46
49
  @log.debug(payload)
47
50
  else
48
- uri = URI(@base_url + '/api/errors')
51
+ uri = URI("#{@base_url}/errors")
49
52
  response = Net::HTTP.post(uri, payload.to_json, @header_options)
50
- if response.code == '200'
51
- return JSON.parse(response.body, symbolize_names: true)
52
- else
53
- @log.warn(ERRORS[:POSTING_ERRORS])
54
- end
53
+ return JSON.parse(response.body, symbolize_names: true) if response.code == '200'
54
+
55
+ @log.warn(ERRORS[:POSTING_ERRORS])
55
56
  end
56
57
  end
58
+
59
+ def get_remote_config
60
+ uri = URI(@base_url + '/config')
61
+ response = Net::HTTP.get_response(uri, @header_options)
62
+ return JSON.parse(response.body) if response.code == '200'
63
+
64
+ raise SupergoodException.new ERRORS[:CONFIG_FETCH_ERROR]
65
+ end
57
66
  end
58
67
  end
@@ -11,7 +11,7 @@ Dotenv.load
11
11
 
12
12
  module Supergood
13
13
 
14
- DEFAULT_SUPERGOOD_BASE_URL = 'https://dashboard.supergood.ai'
14
+ DEFAULT_SUPERGOOD_BASE_URL = 'https://api.supergood.ai'
15
15
  class << self
16
16
  def init(config={})
17
17
  supergood_client_id = config[:client_id] || ENV['SUPERGOOD_CLIENT_ID']
@@ -31,7 +31,6 @@ module Supergood
31
31
 
32
32
  @allowed_domains = @config[:allowedDomains]
33
33
  @ignored_domains = @config[:ignoredDomains]
34
- @keys_to_hash = @config[:keysToHash]
35
34
  @logger = Supergood::Logger.new(@api, @config, @api.header_options)
36
35
 
37
36
  @api.set_logger(@logger)
@@ -40,13 +39,15 @@ module Supergood
40
39
  @response_cache = {}
41
40
 
42
41
  @interval_thread = set_interval(@config[:flushInterval]) { flush_cache }
42
+ @remote_config_thread = set_interval(@config[:remoteConfigFetchInterval]) { fetch_and_process_remote_config }
43
43
 
44
44
  @http_clients = [
45
45
  Supergood::Vendor::NetHTTP,
46
46
  Supergood::Vendor::HTTPrb
47
47
  ]
48
48
 
49
- patch_all()
49
+ fetch_and_process_remote_config
50
+ patch_all
50
51
  self
51
52
  end
52
53
 
@@ -58,6 +59,15 @@ module Supergood
58
59
  @api
59
60
  end
60
61
 
62
+ def fetch_and_process_remote_config
63
+ begin
64
+ remote_config = @api.get_remote_config
65
+ @config = @config.merge({ :remote_config => Supergood::Utils.process_remote_config(remote_config) })
66
+ rescue => e
67
+ log.error({}, e, ERRORS[:CONFIG_FETCH_ERROR])
68
+ end
69
+ end
70
+
61
71
  def flush_cache(force = false)
62
72
  # If there's notthing in the response cache, and we're not forcing a flush, then return
63
73
 
@@ -67,11 +77,8 @@ module Supergood
67
77
  return
68
78
  end
69
79
 
70
- data = @response_cache.values
71
-
72
- if force
73
- data += @request_cache.values
74
- end
80
+ data = Supergood::Utils.prepare_data(@response_cache.values, @config[:remote_config], @config[:forceRedactAll])
81
+ data += Supergood::Utils.prepare_data(@request_cache.values, @config[:remote_config], @config[:forceRedactAll]) if force
75
82
 
76
83
  begin
77
84
  api.post_events(data)
@@ -86,6 +93,7 @@ module Supergood
86
93
 
87
94
  def cleanup()
88
95
  @interval_thread.kill
96
+ @remote_config_thread.kill
89
97
  unpatch_all()
90
98
  end
91
99
 
@@ -117,14 +125,25 @@ module Supergood
117
125
  end
118
126
 
119
127
  def intercept(request)
128
+ remote_config = @config[:remote_config]
129
+
130
+ if remote_config.nil?
131
+ response = yield
132
+ return response[:original_response]
133
+ end
134
+
120
135
  request_id = SecureRandom.uuid
121
136
  requested_at = Time.now
122
- if !ignored?(request[:domain])
137
+
138
+ endpoint_config = Supergood::Utils.get_endpoint_config(request.transform_keys(&:to_s), remote_config)
139
+ ignore_endpoint = endpoint_config ? endpoint_config['ignored'] : false
140
+
141
+ if !ignore_endpoint && !ignored?(request[:domain])
123
142
  cache_request(request_id, requested_at, request)
124
143
  end
125
144
 
126
145
  response = yield
127
- if !ignored?(request[:domain]) && defined?(response)
146
+ if !ignore_endpoint && !ignored?(request[:domain]) && defined?(response)
128
147
  cache_response(request_id, requested_at, response)
129
148
  end
130
149
 
@@ -132,41 +151,47 @@ module Supergood
132
151
  end
133
152
 
134
153
  def cache_request(request_id, requested_at, request)
154
+ if !@config[:remote_config]
155
+ return
156
+ end
157
+
135
158
  begin
136
159
  request_payload = {
137
- id: request_id,
138
- headers: request[:headers],
139
- method: request[:method],
140
- url: request[:url],
141
- path: request[:path],
142
- search: request[:search] || '',
143
- body: Supergood::Utils.safe_parse_json(request[:body]),
144
- requestedAt: requested_at
160
+ 'id' => request_id,
161
+ 'headers' => Supergood::Utils.safe_parse_json(request[:headers]),
162
+ 'method' => request[:method],
163
+ 'url' => request[:url],
164
+ 'path' => request[:path],
165
+ 'search' => request[:search] || '',
166
+ 'body' => Supergood::Utils.safe_parse_json(request[:body]),
167
+ 'requestedAt' => requested_at
145
168
  }
146
169
  @request_cache[request_id] = {
147
- request: request_payload
170
+ 'request' => request_payload
148
171
  }
149
172
  rescue => e
150
- log.error({ request: request }, e, ERRORS[:CACHING_REQUEST])
173
+ log.error({ 'request' => request }, e, ERRORS[:CACHING_REQUEST])
151
174
  end
152
175
  end
153
176
 
154
177
  def cache_response(request_id, requested_at, response)
178
+ if !@config[:remote_config]
179
+ return
180
+ end
181
+
155
182
  begin
156
183
  responded_at = Time.now
157
184
  duration = (responded_at - requested_at) * 1000
158
185
  request_payload = @request_cache[request_id]
159
186
  response_payload = {
160
- headers: response[:headers],
161
- status: response[:status],
162
- statusText: response[:statusText],
163
- body: Supergood::Utils.safe_parse_json(response[:body]),
164
- respondedAt: responded_at,
165
- duration: duration.round,
187
+ 'headers' => Supergood::Utils.safe_parse_json(response[:headers]),
188
+ 'status' => response[:status],
189
+ 'statusText' => response[:statusText],
190
+ 'body' => Supergood::Utils.safe_parse_json(response[:body]),
191
+ 'respondedAt' => responded_at,
192
+ 'duration' => duration.round
166
193
  }
167
- @response_cache[request_id] = Supergood::Utils.hash_values_from_keys(request_payload.merge({
168
- response: response_payload
169
- }), @keys_to_hash)
194
+ @response_cache[request_id] = request_payload.merge({ 'response' => response_payload })
170
195
  @request_cache.delete(request_id)
171
196
  rescue => e
172
197
  log.error(
@@ -177,7 +202,7 @@ module Supergood
177
202
  end
178
203
 
179
204
  def ignored?(domain)
180
- base_domain = URI.parse(@base_url).hostname
205
+ base_domain = Supergood::Utils.get_host_without_www(@base_url)
181
206
  if domain == base_domain
182
207
  return true
183
208
  elsif @allowed_domains.any?
@@ -5,7 +5,8 @@ ERRORS = {
5
5
  POSTING_EVENTS: 'Error Posting Events',
6
6
  POSTING_ERRORS: 'Error Posting Errors',
7
7
  WRITING_TO_DISK: 'Error writing to disk',
8
- TEST_ERROR: 'Test Error for Testing Purpos es',
8
+ TEST_ERROR: 'Test Error for Testing Purposes',
9
+ CONFIG_FETCH_ERROR: 'Error Fetching Remote Config',
9
10
  UNAUTHORIZED: 'Unauthorized: Invalid Client ID or Secret. Exiting.',
10
11
  NO_CLIENT_ID:
11
12
  'No Client ID Provided, set SUPERGOOD_CLIENT_ID or pass it as an argument',
@@ -16,13 +17,14 @@ ERRORS = {
16
17
  LOCAL_CLIENT_ID = 'local-client-id';
17
18
  LOCAL_CLIENT_SECRET = 'local-client-secret';
18
19
 
19
- DEFAULT_SUPERGOOD_BYTE_LIMIT = 500000
20
+ DEFAULT_SUPERGOOD_BYTE_LIMIT = 500_000
20
21
 
21
22
  DEFAULT_CONFIG = {
22
- keysToHash: [],
23
23
  flushInterval: 1000,
24
+ remoteConfigFetchInterval: 10_000,
24
25
  ignoredDomains: [],
25
26
  allowedDomains: [],
27
+ forceRedactAll: true
26
28
  }
27
29
 
28
30
  # GZIP_START_BYTES = b'\x1f\x8b'
@@ -13,7 +13,9 @@ module Supergood
13
13
  end
14
14
 
15
15
  def error(data, error, msg)
16
- super(error)
16
+ if(ENV['SUPERGOOD_LOG_LEVEL'] == 'debug')
17
+ super(error)
18
+ end
17
19
  @api.post_errors(
18
20
  {
19
21
  error: error.backtrace.join('\n'),
@@ -1,62 +1,28 @@
1
1
  require 'rudash'
2
2
  require 'digest'
3
+ require 'uri'
4
+ require 'json'
3
5
 
4
6
  module Supergood
5
7
  module Utils
6
- def self.hash_value(input)
7
- hash = Digest::SHA1.new
8
- if input == nil
9
- return ''
10
- elsif input.class == Array
11
- return [Base64.strict_encode64(hash.update(input.to_json).to_s)]
12
- elsif input.class == Hash
13
- return {'hashed': Base64.strict_encode64(hash.update(input.to_json).to_s)}
14
- elsif input.class == String
15
- return Base64.strict_encode64(hash.update(input).to_s)
16
- end
17
- end
18
-
19
- # Hash values from specified keys, or hash if the bodies exceed a byte limit
20
- def self.hash_values_from_keys(obj, keys_to_hash, byte_limit=DEFAULT_SUPERGOOD_BYTE_LIMIT)
21
- _obj = obj
22
-
23
- if !keys_to_hash.include?('response.body')
24
- payload = R_.get(_obj, 'response.body')
25
- payload_size = payload.to_s.length()
26
- if(payload_size >= byte_limit)
27
- R_.set(_obj, 'response.body', Supergood::Utils.hash_value(payload))
28
- end
29
- end
30
-
31
- if !keys_to_hash.include?('request.body')
32
- payload = R_.get(_obj, 'request.body')
33
- payload_size = payload.to_s.length()
34
- if(payload_size >= byte_limit)
35
- R_.set(_obj, 'request.body', Supergood::Utils.hash_value(payload))
36
- end
37
- end
38
-
39
- keys_to_hash.each { |key|
40
- value = R_.get(_obj, key)
41
- if !!value
42
- R_.set(_obj, key, Supergood::Utils.hash_value(value))
43
- end
44
- }
45
8
 
46
- return _obj
9
+ def self.get_host_without_www(url)
10
+ uri = URI.parse(url)
11
+ uri = URI.parse("http://#{url}") if uri.scheme.nil?
12
+ host = uri.host.downcase
13
+ host.start_with?('www.') ? host[4..] : host
47
14
  end
48
15
 
49
16
  def self.safe_parse_json(input)
50
- if !input || input == ''
51
- return ''
52
- end
17
+ return '' if !input || input == ''
53
18
 
54
19
  begin
55
- return JSON.parse(input)
20
+ JSON.parse(input)
56
21
  rescue => e
57
22
  input
58
23
  end
59
24
  end
25
+
60
26
  def self.get_header(request_or_response)
61
27
  header = {}
62
28
  request_or_response.each_header do |k,v|
@@ -66,11 +32,250 @@ module Supergood
66
32
  end
67
33
 
68
34
  def self.request_url(http, request)
69
- URI::DEFAULT_PARSER.unescape("http#{"s" if http.use_ssl?}://#{http.address}#{request.path}")
35
+ URI::DEFAULT_PARSER.unescape("http#{'s' if http.use_ssl?}://#{http.address}#{request.path}")
70
36
  end
71
37
 
72
38
  def self.make_config(config)
73
- return DEFAULT_CONFIG.merge(config)
39
+ DEFAULT_CONFIG.merge(config)
40
+ end
41
+
42
+ def self.process_remote_config(remote_config_payload)
43
+ remote_config_payload ||= []
44
+ remote_config_payload.reduce({}) do |remote_config, domain_config|
45
+ domain = domain_config['domain']
46
+ endpoints = domain_config['endpoints']
47
+ endpoint_config = endpoints.reduce({}) do |config, endpoint|
48
+ matching_regex = endpoint['matchingRegex']
49
+ regex = matching_regex['regex']
50
+ location = matching_regex['location']
51
+
52
+ endpoint_configuration = endpoint['endpointConfiguration']
53
+ action = endpoint_configuration['action']
54
+ sensitive_keys = endpoint_configuration['sensitiveKeys'] || []
55
+ sensitive_keys = sensitive_keys.map { |key| key['keyPath'] }
56
+
57
+ config[regex] = {
58
+ 'location' => location,
59
+ 'regex' => regex,
60
+ 'ignored' => action == 'Ignore',
61
+ 'sensitive_keys' => sensitive_keys
62
+ }
63
+
64
+ config
65
+ end
66
+
67
+ remote_config[domain] = endpoint_config
68
+ remote_config
69
+ end
70
+ end
71
+
72
+ def self.get_str_representation_from_path(request, location)
73
+ url = URI(request['url'])
74
+
75
+ case location
76
+ when 'domain'
77
+ get_host_without_www(url)
78
+ when 'url'
79
+ url.to_s
80
+ when 'path'
81
+ url.path
82
+ when 'requestHeaders'
83
+ request['headers'].to_s
84
+ when 'requestBody'
85
+ request['body'].to_s
86
+ else
87
+ request[location.to_sym].to_s if request.key?(location.to_sym)
88
+ end
89
+ end
90
+
91
+ def self.get_endpoint_config(request, remote_config)
92
+ domain = remote_config.keys.find { |d| get_host_without_www(request['url']).include?(d) }
93
+ return nil unless domain
94
+
95
+ endpoint_configs = remote_config[domain]
96
+ endpoint_configs.each_value do |endpoint_config|
97
+ regex = endpoint_config['regex']
98
+ location = endpoint_config['location']
99
+ regex_obj = Regexp.new(regex)
100
+ str_representation = get_str_representation_from_path(request, location)
101
+ next unless str_representation
102
+ return endpoint_config if regex_obj.match?(str_representation)
103
+ end
104
+ nil
105
+ end
106
+
107
+ def self.expand(parts, obj, key_path)
108
+ path = key_path
109
+ return [path] if parts.empty?
110
+
111
+ part = parts.first
112
+ is_property = !part.start_with?('[')
113
+ separator = !path.empty? && is_property ? '.' : ''
114
+
115
+ # Check for array notations
116
+ if part.match?(/\[\*?\]/)
117
+ return [] unless obj.is_a?(Array)
118
+
119
+ # Expand for each element in the array
120
+ obj.flat_map.with_index do |_, index|
121
+ expand(parts[1..], obj[index], "#{path}#{separator}[#{index}]")
122
+ end
123
+ elsif part.start_with?('[') && part.end_with?(']')
124
+ # Specific index in the array
125
+ index = part[1...-1].to_i
126
+ if index.is_a?(Numeric) && index < obj.length
127
+ expand(parts[1..], obj[index], "#{path}#{separator}#{part}")
128
+ else
129
+ []
130
+ end
131
+ else
132
+ if obj && obj.is_a?(Hash) && obj.key?(part)
133
+ expand(parts[1..], obj[part], "#{path}#{separator}#{part}")
134
+ else
135
+ []
136
+ end
137
+ end
138
+ end
139
+
140
+ def self.expand_key(key, obj)
141
+ parts = key.scan(/[^.\[\]]+|\[\d*\]|\[\*\]/) || []
142
+ expand(parts, obj, '')
143
+ end
144
+
145
+ def self.expand_sensitive_key_set_for_arrays(obj, sensitive_keys)
146
+ sensitive_keys.flat_map { |key| expand_key(key, obj) }
147
+ end
148
+
149
+ def self.marshal_key_path(keypath)
150
+ keypath.gsub(/^requestHeaders/, 'request.headers')
151
+ .gsub(/^requestBody/, 'request.body')
152
+ .gsub(/^responseHeaders/, 'response.headers')
153
+ .gsub(/^responseBody/, 'response.body')
154
+ end
155
+
156
+ def self.unmarshal_key_path(keypath)
157
+ keypath.gsub(/^request\.headers/, 'requestHeaders')
158
+ .gsub(/^request\.body/, 'requestBody')
159
+ .gsub(/^response\.headers/, 'responseHeaders')
160
+ .gsub(/^response\.body/, 'responseBody')
161
+ end
162
+
163
+ def self.set_value_to_nil(hash, key_path)
164
+ keys = key_path.split('.')
165
+ current_key = keys.first
166
+ index = current_key.match(/\[(\d+)\]/)
167
+
168
+ index = index[1].to_i if index
169
+
170
+ # Convert current_key to symbol if necessary
171
+ current_key = current_key.gsub(/\[\d+\]/, '') if index
172
+
173
+ return hash unless hash.keys.include?(current_key)
174
+
175
+ if keys.length == 1
176
+ index ? hash[current_key][index] = nil : hash[current_key] = nil
177
+ elsif hash[current_key].is_a?(Hash)
178
+ set_value_to_nil(hash[current_key], keys[1..].join('.'))
179
+ elsif hash[current_key].is_a?(Array)
180
+ set_value_to_nil(hash[current_key][index], keys[1..].join('.'))
181
+ end
182
+
183
+ hash
184
+ end
185
+
186
+ def self.find_leaf_key_paths(structure, current_path = [])
187
+ key_paths = []
188
+
189
+ if structure.is_a?(Hash)
190
+ # Iterate through each key-value pair in the hash
191
+ structure.each do |key, value|
192
+ # Recursively find key paths in the value
193
+ key_paths += find_leaf_key_paths(value, current_path + [key.to_s])
194
+ end
195
+ elsif structure.is_a?(Array)
196
+ # Iterate through each element in the array
197
+ structure.each_with_index do |element, index|
198
+ # Modify how indices are appended to the path
199
+ # Check if the last element in the current_path is a hash key or an array index
200
+ if current_path.last && current_path.last.include?('[')
201
+ new_path = current_path[0...-1] + ["#{current_path.last}[#{index}]"]
202
+ else
203
+ new_path = current_path + ["[#{index}]"]
204
+ end
205
+
206
+ # Recursively find key paths in the element
207
+ key_paths += find_leaf_key_paths(element, new_path)
208
+ end
209
+ else
210
+ # Leaf node: construct the key path and add it to the list
211
+ key_path = current_path.join('.').gsub('.[', '[')
212
+ key_paths << key_path unless key_path.empty?
213
+ end
214
+
215
+ key_paths
216
+ end
217
+
218
+ def self.redact_values_from_keys(event, remote_config, force_redact_all)
219
+ sensitive_key_metadata = []
220
+ endpoint_config = get_endpoint_config(event['request'], remote_config)
221
+
222
+ unless (endpoint_config && endpoint_config['sensitive_keys'].any?) || force_redact_all
223
+ return { 'event' => event, 'sensitive_key_metadata' => sensitive_key_metadata }
224
+ end
225
+
226
+ if force_redact_all
227
+ # Need response.body in path
228
+ sensitive_keys = find_leaf_key_paths(event['response']['body'], ['response', 'body'])
229
+ sensitive_keys += find_leaf_key_paths(event['request']['body'], ['request', 'body'])
230
+ sensitive_keys += find_leaf_key_paths(event['request']['headers'], ['request', 'headers'])
231
+ sensitive_keys += find_leaf_key_paths(event['response']['headers'], ['response', 'headers'])
232
+ else
233
+ sensitive_keys = endpoint_config['sensitive_keys']
234
+ end
235
+
236
+ sensitive_keys = expand_sensitive_key_set_for_arrays(
237
+ event, sensitive_keys.map { |key| marshal_key_path(key) }
238
+ )
239
+ sensitive_keys.each do |key_path|
240
+ value = R_.get(event, key_path)
241
+ event = set_value_to_nil(event, key_path)
242
+ # Add sensitive key for array expansion
243
+ sensitive_key_metadata << { 'keyPath' => unmarshal_key_path(key_path) }.merge(redact_value(value))
244
+ end
245
+
246
+ { 'event' => event, 'sensitive_key_metadata' => sensitive_key_metadata }
247
+ end
248
+
249
+ def self.redact_value(input)
250
+ data_length = 0
251
+ data_type = 'null'
252
+ case input
253
+ when Array
254
+ data_length = input.size
255
+ data_type = 'array'
256
+ when Hash
257
+ data_length = input.to_json.bytesize
258
+ data_type = 'object'
259
+ when String
260
+ data_length = input.size
261
+ data_type = 'string'
262
+ when Numeric
263
+ data_length = input.to_s.size
264
+ data_type = input.integer? ? 'integer' : 'float'
265
+ when TrueClass, FalseClass # This is a better way to check for booleans
266
+ data_length = 1
267
+ data_type = 'boolean'
268
+ end
269
+ { 'length' => data_length, 'type' => data_type }
270
+ end
271
+
272
+ def self.prepare_data(events, remote_config, force_redact_all)
273
+ events.map do |event|
274
+ redacted_event_with_metadata = redact_values_from_keys(event, remote_config, force_redact_all)
275
+ redacted_event_with_metadata['event'].merge(
276
+ 'metadata': { 'sensitiveKeys': redacted_event_with_metadata['sensitive_key_metadata'] }
277
+ )
278
+ end
74
279
  end
75
280
  end
76
281
  end
@@ -14,7 +14,7 @@ module Supergood
14
14
  url: original_request_payload.uri.to_s,
15
15
  path: original_request_payload.uri.path,
16
16
  search: original_request_payload.uri.query,
17
- domain: original_request_payload.uri.host
17
+ domain: Supergood::Utils.get_host_without_www(original_request_payload.uri.host)
18
18
  }
19
19
  Supergood.intercept(request) do
20
20
  original_response = original_perform(original_request_payload, original_options)
@@ -10,7 +10,7 @@ module Supergood
10
10
  block = lambda do |x|
11
11
  alias original_request_method request
12
12
  def request(original_request_payload, body = nil, &block)
13
- http = self;
13
+ http = self
14
14
  url = Supergood::Utils.request_url(http, original_request_payload)
15
15
  uri = URI.parse(url)
16
16
  request = {
@@ -20,7 +20,7 @@ module Supergood
20
20
  url: url,
21
21
  path: original_request_payload.path,
22
22
  search: uri.query,
23
- domain: uri.host,
23
+ domain: Supergood::Utils.get_host_without_www(uri.host)
24
24
  }
25
25
  Supergood.intercept(request) do
26
26
  original_response = original_request_method(original_request_payload, body, &block)
@@ -1,3 +1,3 @@
1
1
  module Supergood
2
- VERSION = '0.1.4'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: supergood
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Klarfeld