supergood 0.1.5 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.vscode/settings.json +1 -1
- data/Gemfile.lock +1 -1
- data/lib/supergood/api.rb +20 -11
- data/lib/supergood/client.rb +54 -31
- data/lib/supergood/constants.rb +5 -3
- data/lib/supergood/logger.rb +3 -1
- data/lib/supergood/utils.rb +251 -46
- data/lib/supergood/vendors/http.rb +1 -1
- data/lib/supergood/vendors/net-http.rb +2 -2
- data/lib/supergood/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5ee319f07b495599aa2deafcf5b6c995402e55b4d7a58ddb82d55379aa6ba99a
|
4
|
+
data.tar.gz: 15ab686e3bec0c37771e8fa2892f7cdb69312595cc8726c0d690855681eeb26d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11eed3208aeec8618d71cb7a832988ef0e88cfb1d55fed315b203418cb93ab4bfba251b22ef978afd0ec672b4b849d2af28ad03435d20003db1020e154faa74c
|
7
|
+
data.tar.gz: 583dacf60c012289be925e8c49471a14d401be64a00ef0cf299e758797bd318dadb0bb3d3a64cb53ef925ba0620fe253213d6406e1ffc35e094c0c5b5e69a239
|
data/.vscode/settings.json
CHANGED
data/Gemfile.lock
CHANGED
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' =>
|
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
|
34
|
+
uri = URI("#{@base_url}/events")
|
33
35
|
response = Net::HTTP.post(uri, payload.to_json, @header_options)
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
51
|
+
uri = URI("#{@base_url}/errors")
|
49
52
|
response = Net::HTTP.post(uri, payload.to_json, @header_options)
|
50
|
-
if response.code == '200'
|
51
|
-
|
52
|
-
|
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
|
data/lib/supergood/client.rb
CHANGED
@@ -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
|
-
|
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,16 +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
|
-
|
123
|
-
|
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])
|
124
142
|
cache_request(request_id, requested_at, request)
|
125
143
|
end
|
126
144
|
|
127
145
|
response = yield
|
128
|
-
if !ignored?(request[:domain]) && defined?(response)
|
129
|
-
puts "Caching Response"
|
146
|
+
if !ignore_endpoint && !ignored?(request[:domain]) && defined?(response)
|
130
147
|
cache_response(request_id, requested_at, response)
|
131
148
|
end
|
132
149
|
|
@@ -134,41 +151,47 @@ module Supergood
|
|
134
151
|
end
|
135
152
|
|
136
153
|
def cache_request(request_id, requested_at, request)
|
154
|
+
if !@config[:remote_config]
|
155
|
+
return
|
156
|
+
end
|
157
|
+
|
137
158
|
begin
|
138
159
|
request_payload = {
|
139
|
-
id
|
140
|
-
headers
|
141
|
-
method
|
142
|
-
url
|
143
|
-
path
|
144
|
-
search
|
145
|
-
body
|
146
|
-
requestedAt
|
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
|
147
168
|
}
|
148
169
|
@request_cache[request_id] = {
|
149
|
-
request
|
170
|
+
'request' => request_payload
|
150
171
|
}
|
151
172
|
rescue => e
|
152
|
-
log.error({ request
|
173
|
+
log.error({ 'request' => request }, e, ERRORS[:CACHING_REQUEST])
|
153
174
|
end
|
154
175
|
end
|
155
176
|
|
156
177
|
def cache_response(request_id, requested_at, response)
|
178
|
+
if !@config[:remote_config]
|
179
|
+
return
|
180
|
+
end
|
181
|
+
|
157
182
|
begin
|
158
183
|
responded_at = Time.now
|
159
184
|
duration = (responded_at - requested_at) * 1000
|
160
185
|
request_payload = @request_cache[request_id]
|
161
186
|
response_payload = {
|
162
|
-
headers
|
163
|
-
status
|
164
|
-
statusText
|
165
|
-
body
|
166
|
-
respondedAt
|
167
|
-
duration
|
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
|
168
193
|
}
|
169
|
-
@response_cache[request_id] =
|
170
|
-
response: response_payload
|
171
|
-
}), @keys_to_hash)
|
194
|
+
@response_cache[request_id] = request_payload.merge({ 'response' => response_payload })
|
172
195
|
@request_cache.delete(request_id)
|
173
196
|
rescue => e
|
174
197
|
log.error(
|
@@ -179,7 +202,7 @@ module Supergood
|
|
179
202
|
end
|
180
203
|
|
181
204
|
def ignored?(domain)
|
182
|
-
base_domain =
|
205
|
+
base_domain = Supergood::Utils.get_host_without_www(@base_url)
|
183
206
|
if domain == base_domain
|
184
207
|
return true
|
185
208
|
elsif @allowed_domains.any?
|
data/lib/supergood/constants.rb
CHANGED
@@ -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
|
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 =
|
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'
|
data/lib/supergood/logger.rb
CHANGED
data/lib/supergood/utils.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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#{
|
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
|
-
|
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)
|
data/lib/supergood/version.rb
CHANGED