connect-sdk-ruby 2.30.2 → 2.31.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/connect-sdk-ruby.gemspec +3 -2
- data/lib/ingenico/connect/sdk/client.rb +14 -0
- data/lib/ingenico/connect/sdk/communicator.rb +15 -0
- data/lib/ingenico/connect/sdk/defaultimpl/default_connection.rb +27 -3
- data/lib/ingenico/connect/sdk/logging/log_message_builder.rb +15 -7
- data/lib/ingenico/connect/sdk/logging/logging_util.rb +49 -36
- data/lib/ingenico/connect/sdk/logging/obfuscation/body_obfuscator.rb +98 -0
- data/lib/ingenico/connect/sdk/logging/obfuscation/header_obfuscator.rb +52 -0
- data/lib/ingenico/connect/sdk/logging/obfuscation/obfuscation_capable.rb +18 -0
- data/lib/ingenico/connect/sdk/logging/obfuscation/obfuscation_rule.rb +45 -0
- data/lib/ingenico/connect/sdk/logging/request_log_message_builder.rb +6 -4
- data/lib/ingenico/connect/sdk/logging/response_log_message_builder.rb +4 -2
- data/lib/ingenico/connect/sdk/logging.rb +4 -0
- data/lib/ingenico/connect/sdk/meta_data_provider.rb +1 -1
- data/lib/ingenico/connect/sdk/modules.rb +4 -0
- data/spec/fixtures/resources/logging/bodyWithCardCustomObfuscated.json +21 -0
- data/spec/lib/logging/header_obfuscator_spec.rb +7 -7
- data/spec/lib/logging/legacy_logging_spec.rb +178 -0
- data/spec/lib/logging/obfuscation/body_obfuscator_spec.rb +93 -0
- data/spec/lib/logging/obfuscation/header_obfuscator_spec.rb +166 -0
- data/spec/lib/logging/value_obfuscator_spec.rb +3 -3
- metadata +30 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f9cc67b441796020e9aa26d52bdd4d27e4151ad7ee0011929f623e10eb83ab5
|
4
|
+
data.tar.gz: f67e1575cafad14f2ffebae9544d76bd0e2c5ced65f9ba0ba0903ad56d231abd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8347f98939d41bab6d3b17023f81ec5813d0cb272ab938dae81541de458a5ec5210e0bd83a085b432e2d30692c81cab220f43eca8042b433e37da08591cfa302
|
7
|
+
data.tar.gz: af0b2d7c03d66496bc3335c1bc7785f59a2b3054fe8459f53d93372884af9ed29d12dfdc5dcd0697692d7c6705575020cba08443bd51ab7368b1a815bf717db4
|
data/README.md
CHANGED
@@ -94,7 +94,8 @@ In order to run the unit and integration tests, some additional dependencies are
|
|
94
94
|
* [rake](https://ruby.github.io/rake/) 12.3.3 or higher
|
95
95
|
* [rspec](https://github.com/rspec/rspec) 3.5 or higher
|
96
96
|
* [webmock](https://github.com/bblimke/webmock) 2.1 or higher
|
97
|
-
* [sinatra](https://github.com/sinatra/sinatra) 1
|
97
|
+
* [sinatra](https://github.com/sinatra/sinatra) 2.1 or higher
|
98
|
+
* [webrick](https://github.com/ruby/webrick) 1.7 or higher
|
98
99
|
|
99
100
|
They can be installed using the following command:
|
100
101
|
|
data/connect-sdk-ruby.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = 'connect-sdk-ruby'
|
3
|
-
spec.version = '2.
|
3
|
+
spec.version = '2.31.0'
|
4
4
|
spec.authors = ['Ingenico ePayments']
|
5
5
|
spec.email = ['github@epay.ingenico.com']
|
6
6
|
spec.summary = %q{SDK to communicate with the Ingenico ePayments platform using the Ingenico Connect Server API}
|
@@ -23,7 +23,8 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency 'yard', '~> 0.9'
|
24
24
|
spec.add_development_dependency 'rspec', '~> 3.5'
|
25
25
|
spec.add_development_dependency 'webmock', '~> 2.1'
|
26
|
-
spec.add_development_dependency 'sinatra', '~> 1
|
26
|
+
spec.add_development_dependency 'sinatra', '~> 2.1'
|
27
|
+
spec.add_development_dependency 'webrick', '~> 1.7'
|
27
28
|
spec.add_development_dependency 'rake', '~> 12.3', '>= 12.3.3'
|
28
29
|
# spec.metadata['yard.run'] = 'yri' # compiles yard doc on install
|
29
30
|
end
|
@@ -5,6 +5,7 @@
|
|
5
5
|
require 'ingenico/connect/sdk/api_resource'
|
6
6
|
require 'ingenico/connect/sdk/data_object'
|
7
7
|
require 'ingenico/connect/sdk/logging/logging_capable'
|
8
|
+
require 'ingenico/connect/sdk/logging/obfuscation/obfuscation_capable'
|
8
9
|
require 'ingenico/connect/sdk/merchant/merchant_client'
|
9
10
|
require 'base64'
|
10
11
|
|
@@ -21,6 +22,7 @@ module Ingenico
|
|
21
22
|
# Thread safe.
|
22
23
|
class Client < ApiResource
|
23
24
|
include Logging::LoggingCapable
|
25
|
+
include Logging::Obfuscation::ObfuscationCapable
|
24
26
|
|
25
27
|
# @return [String]
|
26
28
|
def self.API_VERSION
|
@@ -63,6 +65,18 @@ module Ingenico
|
|
63
65
|
@communicator.close_expired_connections
|
64
66
|
end
|
65
67
|
|
68
|
+
# Sets the current body obfuscator to use.
|
69
|
+
# @param body_obfuscator [Ingenico::Connect::SDK::Logging::Obfuscation::BodyObfuscator]
|
70
|
+
def set_body_obfuscator(body_obfuscator)
|
71
|
+
@communicator.set_body_obfuscator(body_obfuscator)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Sets the current header obfuscator to use.
|
75
|
+
# @param header_obfuscator [Ingenico::Connect::SDK::Logging::Obfuscation::HeaderObfuscator]
|
76
|
+
def set_header_obfuscator(header_obfuscator)
|
77
|
+
@communicator.set_header_obfuscator(header_obfuscator)
|
78
|
+
end
|
79
|
+
|
66
80
|
# Turns on logging using the given communicator logger.
|
67
81
|
# @param communicator_logger [Ingenico::Connect::SDK::Logging::CommunicatorLogger]
|
68
82
|
def enable_logging(communicator_logger)
|
@@ -11,6 +11,7 @@ module Ingenico::Connect::SDK
|
|
11
11
|
#
|
12
12
|
class Communicator
|
13
13
|
include Logging::LoggingCapable
|
14
|
+
include Logging::Obfuscation::ObfuscationCapable
|
14
15
|
|
15
16
|
# Creates a new Communicator based on a session and marshaller.
|
16
17
|
#
|
@@ -363,6 +364,20 @@ module Ingenico::Connect::SDK
|
|
363
364
|
connection.close_expired_connections if connection.is_a? PooledConnection
|
364
365
|
end
|
365
366
|
|
367
|
+
# Sets the current body obfuscator to use.
|
368
|
+
# @param body_obfuscator [Ingenico::Connect::SDK::Logging::Obfuscation::BodyObfuscator]
|
369
|
+
def set_body_obfuscator(body_obfuscator)
|
370
|
+
connection = @session.connection
|
371
|
+
connection.set_body_obfuscator(body_obfuscator) if connection.is_a? Logging::Obfuscation::ObfuscationCapable
|
372
|
+
end
|
373
|
+
|
374
|
+
# Sets the current header obfuscator to use.
|
375
|
+
# @param header_obfuscator [Ingenico::Connect::SDK::Logging::Obfuscation::HeaderObfuscator]
|
376
|
+
def set_header_obfuscator(header_obfuscator)
|
377
|
+
connection = @session.connection
|
378
|
+
connection.set_header_obfuscator(header_obfuscator) if connection.is_a? Logging::Obfuscation::ObfuscationCapable
|
379
|
+
end
|
380
|
+
|
366
381
|
# Enables logging outgoing requests and incoming responses by registering the _communicator_logger_.
|
367
382
|
# Note that only one logger can be registered at a time and calling _enable_logging_
|
368
383
|
# a second time will override the old logger instance with the new one.
|
@@ -16,6 +16,7 @@ end
|
|
16
16
|
module Ingenico::Connect::SDK
|
17
17
|
module DefaultImpl
|
18
18
|
class DefaultConnection < PooledConnection
|
19
|
+
include Ingenico::Connect::SDK::Logging::Obfuscation::ObfuscationCapable
|
19
20
|
using RefineHTTPClient
|
20
21
|
|
21
22
|
CONTENT_TYPE = 'Content-Type'.freeze
|
@@ -50,6 +51,9 @@ module Ingenico::Connect::SDK
|
|
50
51
|
@http_client.connect_timeout = @connect_timeout
|
51
52
|
@http_client.send_timeout = @socket_timeout
|
52
53
|
@http_client.receive_timeout = @socket_timeout
|
54
|
+
|
55
|
+
@body_obfuscator = Ingenico::Connect::SDK::Logging::Obfuscation::BodyObfuscator.default_obfuscator
|
56
|
+
@header_obfuscator = Ingenico::Connect::SDK::Logging::Obfuscation::HeaderObfuscator.default_obfuscator
|
53
57
|
end
|
54
58
|
|
55
59
|
private
|
@@ -202,13 +206,29 @@ module Ingenico::Connect::SDK
|
|
202
206
|
|
203
207
|
# logging code
|
204
208
|
|
209
|
+
# Sets the current body obfuscator to use.
|
210
|
+
# @param body_obfuscator [Ingenico::Connect::SDK::Logging::Obfuscation::BodyObfuscator]
|
211
|
+
def set_body_obfuscator(body_obfuscator)
|
212
|
+
raise ArgumentError, 'body_obfuscator is required' unless body_obfuscator
|
213
|
+
|
214
|
+
@body_obfuscator = body_obfuscator
|
215
|
+
end
|
216
|
+
|
217
|
+
# Sets the current header obfuscator to use.
|
218
|
+
# @param header_obfuscator [Ingenico::Connect::SDK::Logging::Obfuscation::HeaderObfuscator]
|
219
|
+
def set_header_obfuscator(header_obfuscator)
|
220
|
+
raise ArgumentError, 'header_obfuscator is required' unless header_obfuscator
|
221
|
+
|
222
|
+
@header_obfuscator = header_obfuscator
|
223
|
+
end
|
224
|
+
|
205
225
|
# Enables logging outgoing requests and incoming responses by registering the _communicator_logger_.
|
206
226
|
# Note that only one logger can be registered at a time and calling _enable_logging_
|
207
227
|
# a second time will override the old logger instance with the new one.
|
208
228
|
#
|
209
229
|
# @param communicator_logger [Ingenico::Connect::SDK::Logging::CommunicatorLogger] the communicator logger the requests and responses are logged to
|
210
230
|
def enable_logging(communicator_logger)
|
211
|
-
raise ArgumentError, '
|
231
|
+
raise ArgumentError, 'communicator_logger is required' unless communicator_logger
|
212
232
|
|
213
233
|
@communicator_logger = communicator_logger
|
214
234
|
end
|
@@ -238,7 +258,9 @@ module Ingenico::Connect::SDK
|
|
238
258
|
headers = args[:headers]
|
239
259
|
body = args[:body]
|
240
260
|
content_type = args[:content_type]
|
241
|
-
log_msg_builder = Ingenico::Connect::SDK::Logging::RequestLogMessageBuilder.new(request_id, method, uri
|
261
|
+
log_msg_builder = Ingenico::Connect::SDK::Logging::RequestLogMessageBuilder.new(request_id, method, uri,
|
262
|
+
@body_obfuscator,
|
263
|
+
@header_obfuscator)
|
242
264
|
headers.each { |k, v| log_msg_builder.add_headers(k, v) } if headers
|
243
265
|
|
244
266
|
if binary?(headers)
|
@@ -264,7 +286,9 @@ module Ingenico::Connect::SDK
|
|
264
286
|
body = args[:body] unless args[:body].nil?
|
265
287
|
content_type = args[:content_type]
|
266
288
|
|
267
|
-
log_msg_builder = Ingenico::Connect::SDK::Logging::ResponseLogMessageBuilder.new(request_id, status_code, duration
|
289
|
+
log_msg_builder = Ingenico::Connect::SDK::Logging::ResponseLogMessageBuilder.new(request_id, status_code, duration,
|
290
|
+
@body_obfuscator,
|
291
|
+
@header_obfuscator)
|
268
292
|
unless headers.nil?
|
269
293
|
headers = convert_from_sdk_headers(headers)
|
270
294
|
headers.each do |key, value|
|
@@ -13,19 +13,27 @@ module Ingenico::Connect::SDK
|
|
13
13
|
attr_reader :headers
|
14
14
|
attr_reader :body
|
15
15
|
attr_reader :content_type
|
16
|
+
attr_reader :body_obfuscator
|
17
|
+
attr_reader :header_obfuscator
|
16
18
|
|
17
19
|
# Create a new LogMessageBuilder
|
18
|
-
def initialize(request_id
|
20
|
+
def initialize(request_id,
|
21
|
+
body_obfuscator = Obfuscation::BodyObfuscator.default_obfuscator,
|
22
|
+
header_obfuscator = Obfuscation::HeaderObfuscator.default_obfuscator)
|
19
23
|
raise ArgumentError if request_id.nil? or request_id.empty?
|
24
|
+
raise ArgumentError if body_obfuscator.nil?
|
25
|
+
raise ArgumentError if header_obfuscator.nil?
|
20
26
|
@request_id = request_id
|
21
27
|
@headers = ''
|
28
|
+
@body_obfuscator = body_obfuscator
|
29
|
+
@header_obfuscator = header_obfuscator
|
22
30
|
end
|
23
31
|
|
24
32
|
# Adds a single header to the #headers string
|
25
33
|
def add_headers(name, value)
|
26
34
|
@headers += ', ' if @headers.length > 0
|
27
35
|
@headers += name + '="'
|
28
|
-
@headers +=
|
36
|
+
@headers += @header_obfuscator.obfuscate_header(name, value) unless value.nil?
|
29
37
|
@headers += '"'
|
30
38
|
end
|
31
39
|
|
@@ -37,7 +45,7 @@ module Ingenico::Connect::SDK
|
|
37
45
|
if is_binary(content_type)
|
38
46
|
@body = "<binary content>"
|
39
47
|
else
|
40
|
-
@body =
|
48
|
+
@body = @body_obfuscator.obfuscate_body(body)
|
41
49
|
end
|
42
50
|
@content_type = content_type
|
43
51
|
end
|
@@ -49,9 +57,9 @@ module Ingenico::Connect::SDK
|
|
49
57
|
|
50
58
|
def to_s
|
51
59
|
if self.class == LogMessageBuilder
|
52
|
-
|
60
|
+
super.to_s
|
53
61
|
else
|
54
|
-
|
62
|
+
get_message
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
@@ -63,10 +71,10 @@ module Ingenico::Connect::SDK
|
|
63
71
|
# Returns whether or not the content type is binary
|
64
72
|
def is_binary(content_type)
|
65
73
|
if content_type.nil?
|
66
|
-
|
74
|
+
false
|
67
75
|
else
|
68
76
|
content_type = content_type.downcase
|
69
|
-
|
77
|
+
!(content_type.start_with?("text/") || content_type.include?("json") || content_type.include?("xml"))
|
70
78
|
end
|
71
79
|
end
|
72
80
|
end
|
@@ -1,7 +1,12 @@
|
|
1
|
+
require 'ingenico/connect/sdk/logging/obfuscation/body_obfuscator'
|
2
|
+
require 'ingenico/connect/sdk/logging/obfuscation/header_obfuscator'
|
3
|
+
|
1
4
|
module Ingenico::Connect::SDK
|
2
5
|
module Logging
|
3
6
|
|
4
7
|
# Class responsible for obfuscating sensitive data in a message body.
|
8
|
+
#
|
9
|
+
# @deprecated This class shouldn't have been exposed
|
5
10
|
class ValueObfuscator
|
6
11
|
|
7
12
|
class << self
|
@@ -71,8 +76,10 @@ module Ingenico::Connect::SDK
|
|
71
76
|
def repeat_mask(count)
|
72
77
|
@mask_character * count
|
73
78
|
end
|
74
|
-
end
|
79
|
+
end
|
75
80
|
|
81
|
+
#
|
82
|
+
# @deprecated This class shouldn't have been exposed
|
76
83
|
class Obfuscator
|
77
84
|
def initialize(obfuscators, case_insensitive)
|
78
85
|
raise ArgumentError unless obfuscators.is_a? Hash
|
@@ -102,7 +109,7 @@ module Ingenico::Connect::SDK
|
|
102
109
|
def _insensitive(key)
|
103
110
|
key.respond_to?(:upcase) ? key.upcase : key
|
104
111
|
end
|
105
|
-
end
|
112
|
+
end
|
106
113
|
|
107
114
|
def copy(obfuscators, case_insensitive)
|
108
115
|
cp = case_insensitive ? HashClod.new(obfuscators) : obfuscators
|
@@ -117,6 +124,8 @@ module Ingenico::Connect::SDK
|
|
117
124
|
end
|
118
125
|
|
119
126
|
# A convenient wrapper to build obfuscators
|
127
|
+
#
|
128
|
+
# @deprecated This class shouldn't have been exposed
|
120
129
|
class Builder
|
121
130
|
attr_accessor :obfuscators
|
122
131
|
|
@@ -151,16 +160,26 @@ module Ingenico::Connect::SDK
|
|
151
160
|
end # Obfuscator
|
152
161
|
|
153
162
|
# Class that obfuscates headers of a message
|
163
|
+
#
|
164
|
+
# @deprecated This class shouldn't have been exposed
|
154
165
|
class HeaderObfuscator < Obfuscator
|
155
166
|
def initialize(obfuscators)
|
156
167
|
# case insensitive
|
157
168
|
super(obfuscators, true)
|
169
|
+
obfuscation_rules = obfuscators.map { |key, value| [key, ->(v) { value.obfuscate_value(v) }]}
|
170
|
+
@obfuscator = Obfuscation::HeaderObfuscator.new(obfuscation_rules)
|
171
|
+
end
|
172
|
+
|
173
|
+
def obfuscate_value(key, value)
|
174
|
+
@obfuscator.obfuscate_header(key, value)
|
158
175
|
end
|
159
176
|
|
160
177
|
def self.builder
|
161
178
|
Builder.new
|
162
179
|
end
|
163
180
|
|
181
|
+
#
|
182
|
+
# @deprecated This class shouldn't have been exposed
|
164
183
|
class Builder < Obfuscator::Builder
|
165
184
|
def initialize
|
166
185
|
@obfuscators = {}
|
@@ -180,55 +199,39 @@ module Ingenico::Connect::SDK
|
|
180
199
|
HeaderObfuscator.new(obfuscators)
|
181
200
|
end
|
182
201
|
end
|
183
|
-
end
|
202
|
+
end
|
184
203
|
|
185
204
|
# Class that obfuscates properties in the JSON body of a message
|
205
|
+
#
|
206
|
+
# @deprecated This class shouldn't have been exposed
|
186
207
|
class PropertyObfuscator < Obfuscator
|
187
208
|
def initialize(obfuscators)
|
188
209
|
# case sensitive
|
189
210
|
super(obfuscators, false)
|
190
|
-
@
|
211
|
+
@obfuscation_rules = obfuscators.map { |key, value| [key, ->(v) { value.obfuscate_value(v) }]}
|
212
|
+
@obfuscator = Obfuscation::BodyObfuscator.new(@obfuscation_rules)
|
191
213
|
end
|
192
214
|
|
193
215
|
private
|
194
216
|
|
195
|
-
def build_property_pattern(pn)
|
196
|
-
return /$^/ if pn.empty? # no possible match
|
197
|
-
# Regex to create:
|
198
|
-
# (["'])(X|Y|Z)\1\s*:\s*(?:(["'])(.*?)(?<!\\)\3|([^"'\s\[\{]\S*))
|
199
|
-
# Groups:
|
200
|
-
# 1: opening " or ' for the property name
|
201
|
-
# 2: property name
|
202
|
-
# 3: opening " or ' for the value
|
203
|
-
# 4: quoted value
|
204
|
-
# 5: non-quoted-value
|
205
|
-
# The negative lookbehind is to allow escaped quotes to be part of
|
206
|
-
# the value. What this does not allow currently is having values end
|
207
|
-
# with a \ (which would be escaped to \\).
|
208
|
-
regex = pn.inject("([\"'])(") { |r, p| "#{r}#{Regexp.quote(p)}|"}.chop <<
|
209
|
-
")\\1\\s*:\\s*(?:([\"'])(.*?)(?<!\\\\)\\3|([^\"'\\s\\[\\{]((?!,)\\S)*))"
|
210
|
-
/#{regex}/m # dotall mode
|
211
|
-
end
|
212
|
-
|
213
217
|
public
|
214
218
|
|
219
|
+
def obfuscate_value(key, value)
|
220
|
+
obfuscation_rule = @obfuscation_rules[property_name]
|
221
|
+
return obfuscation_rule.call(value) if obfuscation_rule
|
222
|
+
value
|
223
|
+
end
|
224
|
+
|
215
225
|
def obfuscate(body)
|
216
|
-
|
217
|
-
return '' if body.empty?
|
218
|
-
|
219
|
-
body.gsub(@property_pattern) do
|
220
|
-
m = Regexp.last_match
|
221
|
-
property_name = m[2]
|
222
|
-
value = m[4] || m[5]
|
223
|
-
# copy value 'cause it's part of m[0]
|
224
|
-
m[0].sub(value, obfuscate_value(property_name, value.dup))
|
225
|
-
end
|
226
|
+
@obfuscator.obfuscate_body(body)
|
226
227
|
end
|
227
228
|
|
228
229
|
def self.builder
|
229
230
|
Builder.new
|
230
231
|
end
|
231
232
|
|
233
|
+
#
|
234
|
+
# @deprecated This class shouldn't have been exposed
|
232
235
|
class Builder < Obfuscator::Builder
|
233
236
|
def initialize
|
234
237
|
@obfuscators = {}
|
@@ -258,9 +261,11 @@ module Ingenico::Connect::SDK
|
|
258
261
|
PropertyObfuscator.new(obfuscators)
|
259
262
|
end
|
260
263
|
end
|
261
|
-
end
|
264
|
+
end
|
262
265
|
|
263
266
|
module LoggingUtil
|
267
|
+
#
|
268
|
+
# @deprecated This constant shouldn't have been exposed
|
264
269
|
@@PROPERTY_OBFUSCATOR = PropertyObfuscator.builder
|
265
270
|
.with_keep_end_count("cardNumber", 4)
|
266
271
|
.with_keep_end_count("expiryDate", 2)
|
@@ -279,6 +284,8 @@ module Ingenico::Connect::SDK
|
|
279
284
|
.with_fixed_length("encryptedCustomerInput", 8)
|
280
285
|
.build
|
281
286
|
|
287
|
+
#
|
288
|
+
# @deprecated This constant shouldn't have been exposed
|
282
289
|
@@HEADER_OBFUSCATOR = HeaderObfuscator.builder
|
283
290
|
.with_fixed_length("Authorization", 8)
|
284
291
|
.with_fixed_length("WWW-Authenticate", 8)
|
@@ -288,13 +295,19 @@ module Ingenico::Connect::SDK
|
|
288
295
|
.with_fixed_length("X-GCS-CallerPassword", 8)
|
289
296
|
.build
|
290
297
|
|
298
|
+
#
|
299
|
+
# @deprecated This doesn't support custom obfuscation. Use BodyObfuscator instead
|
300
|
+
# @see Obfuscation::BodyObfuscator
|
291
301
|
def self.obfuscate_body(body)
|
292
|
-
|
302
|
+
return Obfuscation::BodyObfuscator.default_obfuscator.obfuscate_body(body)
|
293
303
|
end
|
294
304
|
|
305
|
+
#
|
306
|
+
# @deprecated This doesn't support custom obfuscation. Use HeaderObfuscator instead
|
307
|
+
# @see Obfuscation::HeaderObfuscator
|
295
308
|
def self.obfuscate_header(name, value)
|
296
|
-
|
309
|
+
return Obfuscation::HeaderObfuscator.default_obfuscator.obfuscate_header(name, value)
|
297
310
|
end
|
298
|
-
end
|
311
|
+
end
|
299
312
|
end
|
300
313
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'ingenico/connect/sdk/logging/obfuscation/obfuscation_rule'
|
2
|
+
|
3
|
+
module Ingenico::Connect::SDK
|
4
|
+
module Logging
|
5
|
+
module Obfuscation
|
6
|
+
# A class that can be used to obfuscate properties in JSON bodies.
|
7
|
+
class BodyObfuscator
|
8
|
+
|
9
|
+
# Creates a new body obfuscator.
|
10
|
+
# This will contain some pre-defined obfuscation rules, as well as any provided custom rules
|
11
|
+
#
|
12
|
+
# @param additional_rules [Hash] An optional hash where the keys are property names and the values are
|
13
|
+
# functions that obfuscate a single value
|
14
|
+
def initialize(additional_rules=nil)
|
15
|
+
@obfuscation_rules = {
|
16
|
+
"cardNumber" => Obfuscation.obfuscate_all_but_last(4),
|
17
|
+
"expiryDate" => Obfuscation.obfuscate_all_but_last(2),
|
18
|
+
"cvv" => Obfuscation.obfuscate_all,
|
19
|
+
"iban" => Obfuscation.obfuscate_all_but_last(4),
|
20
|
+
"accountNumber" => Obfuscation.obfuscate_all_but_last(4),
|
21
|
+
"reformattedAccountNumber" => Obfuscation.obfuscate_all_but_last(4),
|
22
|
+
"bin" => Obfuscation.obfuscate_all_but_first(6),
|
23
|
+
"value" => Obfuscation.obfuscate_all,
|
24
|
+
"keyId" => Obfuscation.obfuscate_with_fixed_length(8),
|
25
|
+
"secretKey" => Obfuscation.obfuscate_with_fixed_length(8),
|
26
|
+
"publicKey" => Obfuscation.obfuscate_with_fixed_length(8),
|
27
|
+
"userAuthenticationToken" => Obfuscation.obfuscate_with_fixed_length(8),
|
28
|
+
"encryptedPayload" => Obfuscation.obfuscate_with_fixed_length(8),
|
29
|
+
"decryptedPayload" => Obfuscation.obfuscate_with_fixed_length(8),
|
30
|
+
"encryptedCustomerInput" => Obfuscation.obfuscate_with_fixed_length(8),
|
31
|
+
}
|
32
|
+
if additional_rules
|
33
|
+
additional_rules.each do |name, rule|
|
34
|
+
@obfuscation_rules[name] = rule
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
@property_pattern = build_property_pattern(@obfuscation_rules.keys)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def build_property_pattern(pn)
|
44
|
+
return /$^/ if pn.empty? # no possible match
|
45
|
+
# Regex to create:
|
46
|
+
# (["'])(X|Y|Z)\1\s*:\s*(?:(["'])(.*?)(?<!\\)\3|([^"'\s\[\{]\S*))
|
47
|
+
# Groups:
|
48
|
+
# 1: opening " or ' for the property name
|
49
|
+
# 2: property name
|
50
|
+
# 3: opening " or ' for the value
|
51
|
+
# 4: quoted value
|
52
|
+
# 5: non-quoted-value
|
53
|
+
# The negative lookbehind is to allow escaped quotes to be part of
|
54
|
+
# the value. What this does not allow currently is having values end
|
55
|
+
# with a \ (which would be escaped to \\).
|
56
|
+
regex = pn.inject("([\"'])(") { |r, p| "#{r}#{Regexp.quote(p)}|"}.chop <<
|
57
|
+
")\\1\\s*:\\s*(?:([\"'])(.*?)(?<!\\\\)\\3|([^\"'\\s\\[\\{]((?!,)\\S)*))"
|
58
|
+
/#{regex}/m # dotall mode
|
59
|
+
end
|
60
|
+
|
61
|
+
def obfuscate_value(property_name, value)
|
62
|
+
obfuscation_rule = @obfuscation_rules[property_name]
|
63
|
+
return obfuscation_rule.call(value) if obfuscation_rule
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
67
|
+
public
|
68
|
+
|
69
|
+
# Obfuscates the given body as necessary.
|
70
|
+
#
|
71
|
+
# @return (String)
|
72
|
+
def obfuscate_body(body)
|
73
|
+
return nil if body.nil?
|
74
|
+
return '' if body.empty?
|
75
|
+
|
76
|
+
body.gsub(@property_pattern) do
|
77
|
+
m = Regexp.last_match
|
78
|
+
property_name = m[2]
|
79
|
+
value = m[4] || m[5]
|
80
|
+
# copy value 'cause it's part of m[0]
|
81
|
+
m[0].sub(value, obfuscate_value(property_name, value.dup))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
@@default_obfuscator = BodyObfuscator.new
|
88
|
+
|
89
|
+
public
|
90
|
+
|
91
|
+
# @return [BodyObfuscator]
|
92
|
+
def self.default_obfuscator
|
93
|
+
@@default_obfuscator
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'ingenico/connect/sdk/logging/obfuscation/obfuscation_rule'
|
2
|
+
|
3
|
+
module Ingenico::Connect::SDK
|
4
|
+
module Logging
|
5
|
+
module Obfuscation
|
6
|
+
# A class that can be used to obfuscate headers.
|
7
|
+
class HeaderObfuscator
|
8
|
+
|
9
|
+
# Creates a new header obfuscator.
|
10
|
+
# This will contain some pre-defined obfuscation rules, as well as any provided custom rules
|
11
|
+
#
|
12
|
+
# @param additional_rules [Hash] An optional hash where the keys are header names and the values are
|
13
|
+
# functions that obfuscate a single value
|
14
|
+
def initialize(additional_rules=nil)
|
15
|
+
@obfuscation_rules = {
|
16
|
+
"authorization" => Obfuscation.obfuscate_with_fixed_length(8),
|
17
|
+
"www-authenticate" => Obfuscation.obfuscate_with_fixed_length(8),
|
18
|
+
"proxy-authenticate" => Obfuscation.obfuscate_with_fixed_length(8),
|
19
|
+
"proxy-authorization" => Obfuscation.obfuscate_with_fixed_length(8),
|
20
|
+
"x-gcs-authentication-token" => Obfuscation.obfuscate_with_fixed_length(8),
|
21
|
+
"x-gcs-callerpassword" => Obfuscation.obfuscate_with_fixed_length(8),
|
22
|
+
}
|
23
|
+
if additional_rules
|
24
|
+
additional_rules.each do |name, rule|
|
25
|
+
@obfuscation_rules[name.downcase] = rule
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Obfuscates the value for the given header as necessary.
|
31
|
+
#
|
32
|
+
# @return (String)
|
33
|
+
def obfuscate_header(header_name, value)
|
34
|
+
obfuscation_rule = @obfuscation_rules[header_name.downcase]
|
35
|
+
return obfuscation_rule.call(value) if obfuscation_rule
|
36
|
+
value
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
@@default_obfuscator = HeaderObfuscator.new
|
42
|
+
|
43
|
+
public
|
44
|
+
|
45
|
+
# @return [HeaderObfuscator]
|
46
|
+
def self.default_obfuscator
|
47
|
+
@@default_obfuscator
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Ingenico::Connect::SDK
|
2
|
+
module Logging
|
3
|
+
module Obfuscation
|
4
|
+
# Abstract mixin module that allows specifying body and header obfuscators for an object.
|
5
|
+
module ObfuscationCapable
|
6
|
+
# Sets the current body obfuscator to use.
|
7
|
+
def set_body_obfuscator(body_obfuscator)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Sets the current header obfuscator to use.
|
12
|
+
def set_header_obfuscator(header_obfuscator)
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Ingenico::Connect::SDK
|
2
|
+
module Logging
|
3
|
+
module Obfuscation
|
4
|
+
|
5
|
+
# Returns an obfuscation rule (callable) that will replace all characters with *
|
6
|
+
def self.obfuscate_all
|
7
|
+
->(value) do
|
8
|
+
return value if value.nil? or value.empty?
|
9
|
+
'*' * (value || '').length
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns an obfuscation rule (function) that will replace values with a fixed length string containing only *
|
14
|
+
def self.obfuscate_with_fixed_length(fixed_length)
|
15
|
+
->(value) { '*' * fixed_length }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns an obfuscation rule (function) that will keep a fixed number of characters at the start,
|
19
|
+
# then replaces all other characters with *
|
20
|
+
def self.obfuscate_all_but_first(count)
|
21
|
+
->(value) do
|
22
|
+
return value if value.nil? or value.empty?
|
23
|
+
return value if value.length < count
|
24
|
+
# range describes the range of characters to replace with asterisks
|
25
|
+
range = count...value.length
|
26
|
+
value[range] = '*' * range.size
|
27
|
+
value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an obfuscation rule that will keep a fixed number of characters at the end,
|
32
|
+
# then replaces all other characters with *
|
33
|
+
def self.obfuscate_all_but_last(count)
|
34
|
+
->(value) do
|
35
|
+
return value if value.nil? or value.empty?
|
36
|
+
return value if value.length < count
|
37
|
+
# range describes the range of characters to replace with asterisks
|
38
|
+
range = 0...(value.length - count)
|
39
|
+
value[range] = '*' * range.size
|
40
|
+
value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -4,8 +4,10 @@ module Ingenico::Connect::SDK
|
|
4
4
|
# Class that converts data about a request into a properly formatted log message.
|
5
5
|
# Formats request id, http method, uri, headers and body into a helpful message.
|
6
6
|
class RequestLogMessageBuilder < Ingenico::Connect::SDK::Logging::LogMessageBuilder
|
7
|
-
def initialize(request_id, method, uri
|
8
|
-
|
7
|
+
def initialize(request_id, method, uri,
|
8
|
+
body_obfuscator = Obfuscation::BodyObfuscator.default_obfuscator,
|
9
|
+
header_obfuscator = Obfuscation::HeaderObfuscator.default_obfuscator)
|
10
|
+
super(request_id, body_obfuscator, header_obfuscator)
|
9
11
|
@method = method
|
10
12
|
@uri = uri
|
11
13
|
end
|
@@ -31,9 +33,9 @@ module Ingenico::Connect::SDK
|
|
31
33
|
def format_uri
|
32
34
|
'' unless @uri && @uri.path
|
33
35
|
if @uri.query.nil?
|
34
|
-
|
36
|
+
@uri.path
|
35
37
|
else
|
36
|
-
|
38
|
+
"#{@uri.path}?#{@uri.query}" unless @uri.query.nil?
|
37
39
|
end
|
38
40
|
# @uri.path + '?' + empty_if_null(@uri.query)
|
39
41
|
end
|