supergood 0.1.5 → 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 +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