yoti 1.6.3 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -224
  3. data/lib/yoti.rb +54 -1
  4. data/lib/yoti/client.rb +13 -4
  5. data/lib/yoti/configuration.rb +7 -2
  6. data/lib/yoti/data_type/age_verification.rb +1 -1
  7. data/lib/yoti/data_type/base_profile.rb +1 -1
  8. data/lib/yoti/data_type/image.rb +4 -12
  9. data/lib/yoti/data_type/image_jpeg.rb +2 -0
  10. data/lib/yoti/data_type/image_png.rb +2 -0
  11. data/lib/yoti/data_type/media.rb +19 -0
  12. data/lib/yoti/data_type/signed_time_stamp.rb +1 -1
  13. data/lib/yoti/doc_scan/client.rb +187 -0
  14. data/lib/yoti/doc_scan/constants.rb +31 -0
  15. data/lib/yoti/doc_scan/errors.rb +81 -0
  16. data/lib/yoti/doc_scan/session/create/create_session_result.rb +50 -0
  17. data/lib/yoti/doc_scan/session/create/document_filter.rb +31 -0
  18. data/lib/yoti/doc_scan/session/create/document_restrictions_filter.rb +140 -0
  19. data/lib/yoti/doc_scan/session/create/notification_config.rb +142 -0
  20. data/lib/yoti/doc_scan/session/create/orthogonal_restrictions_filter.rb +150 -0
  21. data/lib/yoti/doc_scan/session/create/requested_check.rb +39 -0
  22. data/lib/yoti/doc_scan/session/create/requested_document_authenticity_check.rb +95 -0
  23. data/lib/yoti/doc_scan/session/create/requested_face_match_check.rb +95 -0
  24. data/lib/yoti/doc_scan/session/create/requested_id_document_comparison_check.rb +53 -0
  25. data/lib/yoti/doc_scan/session/create/requested_liveness_check.rb +108 -0
  26. data/lib/yoti/doc_scan/session/create/requested_task.rb +39 -0
  27. data/lib/yoti/doc_scan/session/create/requested_text_extraction_task.rb +116 -0
  28. data/lib/yoti/doc_scan/session/create/required_document.rb +31 -0
  29. data/lib/yoti/doc_scan/session/create/required_id_document.rb +53 -0
  30. data/lib/yoti/doc_scan/session/create/sdk_config.rb +221 -0
  31. data/lib/yoti/doc_scan/session/create/session_specification.rb +221 -0
  32. data/lib/yoti/doc_scan/session/retrieve/authenticity_check_response.rb +12 -0
  33. data/lib/yoti/doc_scan/session/retrieve/breakdown_response.rb +38 -0
  34. data/lib/yoti/doc_scan/session/retrieve/check_response.rb +63 -0
  35. data/lib/yoti/doc_scan/session/retrieve/details_response.rb +28 -0
  36. data/lib/yoti/doc_scan/session/retrieve/document_fields_response.rb +21 -0
  37. data/lib/yoti/doc_scan/session/retrieve/document_id_photo_response.rb +21 -0
  38. data/lib/yoti/doc_scan/session/retrieve/face_map_response.rb +21 -0
  39. data/lib/yoti/doc_scan/session/retrieve/face_match_check_response.rb +12 -0
  40. data/lib/yoti/doc_scan/session/retrieve/frame_response.rb +21 -0
  41. data/lib/yoti/doc_scan/session/retrieve/generated_check_response.rb +28 -0
  42. data/lib/yoti/doc_scan/session/retrieve/generated_media.rb +28 -0
  43. data/lib/yoti/doc_scan/session/retrieve/generated_text_data_check_response.rb +12 -0
  44. data/lib/yoti/doc_scan/session/retrieve/get_session_result.rb +127 -0
  45. data/lib/yoti/doc_scan/session/retrieve/id_document_comparison_check_response.rb +12 -0
  46. data/lib/yoti/doc_scan/session/retrieve/id_document_resource_response.rb +57 -0
  47. data/lib/yoti/doc_scan/session/retrieve/liveness_check_response.rb +12 -0
  48. data/lib/yoti/doc_scan/session/retrieve/liveness_resource_response.rb +24 -0
  49. data/lib/yoti/doc_scan/session/retrieve/media_response.rb +38 -0
  50. data/lib/yoti/doc_scan/session/retrieve/page_response.rb +27 -0
  51. data/lib/yoti/doc_scan/session/retrieve/recommendation_response.rb +34 -0
  52. data/lib/yoti/doc_scan/session/retrieve/report_response.rb +31 -0
  53. data/lib/yoti/doc_scan/session/retrieve/resource_container.rb +50 -0
  54. data/lib/yoti/doc_scan/session/retrieve/resource_response.rb +39 -0
  55. data/lib/yoti/doc_scan/session/retrieve/task_response.rb +87 -0
  56. data/lib/yoti/doc_scan/session/retrieve/text_data_check_response.rb +12 -0
  57. data/lib/yoti/doc_scan/session/retrieve/text_extraction_task_response.rb +18 -0
  58. data/lib/yoti/doc_scan/session/retrieve/zoom_liveness_resource_response.rb +33 -0
  59. data/lib/yoti/doc_scan/support/supported_documents.rb +60 -0
  60. data/lib/yoti/dynamic_share_service/extension/thirdparty_attribute_extension.rb +62 -11
  61. data/lib/yoti/dynamic_share_service/policy/dynamic_policy.rb +84 -22
  62. data/lib/yoti/dynamic_share_service/share_url.rb +28 -33
  63. data/lib/yoti/errors.rb +15 -2
  64. data/lib/yoti/http/aml_check_request.rb +12 -6
  65. data/lib/yoti/http/payloads/aml_address.rb +4 -0
  66. data/lib/yoti/http/payloads/aml_profile.rb +7 -1
  67. data/lib/yoti/http/profile_request.rb +11 -6
  68. data/lib/yoti/http/request.rb +221 -18
  69. data/lib/yoti/http/signed_request.rb +13 -4
  70. data/lib/yoti/protobuf/main.rb +1 -1
  71. data/lib/yoti/ssl.rb +2 -2
  72. data/lib/yoti/util/anchor_processor.rb +1 -1
  73. data/lib/yoti/util/validation.rb +41 -0
  74. data/lib/yoti/version.rb +1 -1
  75. data/yoti.gemspec +19 -9
  76. metadata +60 -67
  77. data/.github/ISSUE_TEMPLATE.md +0 -17
  78. data/.gitignore +0 -39
  79. data/CONTRIBUTING.md +0 -127
  80. data/Guardfile +0 -11
  81. data/Rakefile +0 -49
  82. data/login_flow.png +0 -0
  83. data/rubocop.yml +0 -57
  84. data/yardstick.yml +0 -9
@@ -3,12 +3,25 @@ module Yoti
3
3
  class ProtobufError < StandardError; end
4
4
 
5
5
  # Raises exceptions related to API requests
6
- class RequestError < StandardError; end
6
+ class RequestError < StandardError
7
+ attr_reader :response
8
+
9
+ def initialize(message, response = nil)
10
+ super(message)
11
+ @response = response
12
+ end
13
+
14
+ def message
15
+ return super if @response.nil? || @response.body.empty?
16
+
17
+ "#{super}: #{@response.body}"
18
+ end
19
+ end
7
20
 
8
21
  # Raises exceptions related to OpenSSL actions
9
22
  class SslError < StandardError; end
10
23
 
11
- # Raises exceptions realted to an incorrect gem configuration value
24
+ # Raises exceptions related to an incorrect gem configuration value
12
25
  class ConfigurationError < StandardError; end
13
26
 
14
27
  # Raises exceptions related to AML actions
@@ -1,13 +1,16 @@
1
1
  module Yoti
2
2
  # Manage the API's AML check requests
3
3
  class AmlCheckRequest
4
+ #
5
+ # @param [AmlProfile] aml_profile
6
+ #
4
7
  def initialize(aml_profile)
5
8
  @aml_profile = aml_profile
6
9
  @payload = aml_profile.payload
7
10
  @request = request
8
11
  end
9
12
 
10
- # @return [String] a JSON representation of the AML check response
13
+ # @return [Hash] a JSON representation of the AML check response
11
14
  def response
12
15
  JSON.parse(@request.body)
13
16
  end
@@ -15,11 +18,14 @@ module Yoti
15
18
  private
16
19
 
17
20
  def request
18
- yoti_request = Yoti::Request.new
19
- yoti_request.http_method = 'POST'
20
- yoti_request.endpoint = 'aml-check'
21
- yoti_request.payload = @payload
22
- yoti_request
21
+ Yoti::Request
22
+ .builder
23
+ .with_http_method('POST')
24
+ .with_base_url(Yoti.configuration.api_endpoint)
25
+ .with_endpoint('aml-check')
26
+ .with_query_param('appId', Yoti.configuration.client_sdk_id)
27
+ .with_payload(@payload)
28
+ .build
23
29
  end
24
30
  end
25
31
  end
@@ -7,6 +7,10 @@ module Yoti
7
7
  # @return [String] the postcode required for USA, optional otherwise
8
8
  attr_accessor :post_code
9
9
 
10
+ #
11
+ # @param [String] country
12
+ # @param [String] post_code
13
+ #
10
14
  def initialize(country, post_code = nil)
11
15
  raise AmlError, 'AmlAddress requires a country.' if country.to_s.empty?
12
16
 
@@ -1,6 +1,12 @@
1
1
  module Yoti
2
2
  # Manages the AML check Profile object
3
3
  class AmlProfile
4
+ #
5
+ # @param [String] given_names
6
+ # @param [String] family_name
7
+ # @param [AmlAddress] aml_address
8
+ # @param [String] ssn
9
+ #
4
10
  def initialize(given_names, family_name, aml_address, ssn = nil)
5
11
  @given_names = given_names
6
12
  @family_name = family_name
@@ -11,7 +17,7 @@ module Yoti
11
17
  raise AmlError, 'Request for USA require a valid SSN and postcode.' if usa_invalid
12
18
  end
13
19
 
14
- # @return [Object] the AML check request body
20
+ # @return [Hash] the AML check request body
15
21
  def payload
16
22
  {
17
23
  given_names: @given_names,
@@ -1,6 +1,9 @@
1
1
  module Yoti
2
2
  # Manage the API's profile requests
3
3
  class ProfileRequest
4
+ #
5
+ # @param [String] encrypted_connect_token
6
+ #
4
7
  def initialize(encrypted_connect_token)
5
8
  @encrypted_connect_token = encrypted_connect_token
6
9
  @request = request
@@ -14,12 +17,14 @@ module Yoti
14
17
  private
15
18
 
16
19
  def request
17
- yoti_request = Yoti::Request.new
18
- yoti_request.add_header('X-Yoti-Auth-Key', Yoti::SSL.auth_key_from_pem)
19
- yoti_request.encrypted_connect_token = @encrypted_connect_token
20
- yoti_request.http_method = 'GET'
21
- yoti_request.endpoint = 'profile'
22
- yoti_request
20
+ Yoti::Request
21
+ .builder
22
+ .with_http_method('GET')
23
+ .with_base_url(Yoti.configuration.api_endpoint)
24
+ .with_endpoint("profile/#{Yoti::SSL.decrypt_token(@encrypted_connect_token)}")
25
+ .with_query_param('appId', Yoti.configuration.client_sdk_id)
26
+ .with_header('X-Yoti-Auth-Key', Yoti::SSL.auth_key_from_pem)
27
+ .build
23
28
  end
24
29
  end
25
30
  end
@@ -1,9 +1,19 @@
1
+ require 'securerandom'
2
+ require 'cgi'
3
+
1
4
  module Yoti
2
5
  # Manage the API's HTTPS requests
3
6
  class Request
7
+ # @deprecated will be removed in 2.0.0 - token is now provided with the endpoint
4
8
  # @return [String] the URL token received from Yoti Connect
5
9
  attr_accessor :encrypted_connect_token
6
10
 
11
+ # @return [String] the base URL
12
+ attr_writer :base_url
13
+
14
+ # @return [Hash] query params to add to the request
15
+ attr_accessor :query_params
16
+
7
17
  # @return [String] the HTTP method used for the request
8
18
  # The allowed methods are: GET, DELETE, POST, PUT, PATCH
9
19
  attr_accessor :http_method
@@ -11,36 +21,93 @@ module Yoti
11
21
  # @return [String] the API endpoint for the request
12
22
  attr_accessor :endpoint
13
23
 
14
- # @return [Hash] the body sent with the request
24
+ # @return [#to_json,String] the body sent with the request
15
25
  attr_accessor :payload
16
26
 
17
27
  def initialize
18
28
  @headers = {}
19
29
  end
20
30
 
31
+ #
32
+ # @return [RequestBuilder]
33
+ #
34
+ def self.builder
35
+ RequestBuilder.new
36
+ end
37
+
38
+ #
21
39
  # Adds a HTTP header to the request
40
+ #
41
+ # @param [String] header
42
+ # @param [String] value
43
+ #
22
44
  def add_header(header, value)
23
45
  @headers[header] = value
24
46
  end
25
47
 
48
+ #
26
49
  # Makes a HTTP request after signing the headers
27
- # @return [Hash] the body from the HTTP request
28
- def body
50
+ #
51
+ # @return [HTTPResponse]
52
+ #
53
+ def execute
29
54
  raise RequestError, 'The request requires a HTTP method.' unless @http_method
30
- raise RequestError, 'The payload needs to be a hash.' unless @payload.to_s.empty? || @payload.is_a?(Hash)
31
55
 
32
- res = Net::HTTP.start(uri.hostname, Yoti.configuration.api_port, use_ssl: https_uri?) do |http|
56
+ http_res = Net::HTTP.start(uri.hostname, Yoti.configuration.api_port, use_ssl: https_uri?) do |http|
33
57
  signed_request = SignedRequest.new(unsigned_request, path, @payload).sign
34
58
  http.request(signed_request)
35
59
  end
36
60
 
37
- raise RequestError, "Unsuccessful Yoti API call: #{res.message}" unless res.code == '200'
61
+ raise RequestError.new("Unsuccessful Yoti API call: #{http_res.message}", http_res) unless response_is_success(http_res)
38
62
 
39
- res.body
63
+ http_res
64
+ end
65
+
66
+ #
67
+ # Makes a HTTP request and returns the body after signing the headers
68
+ #
69
+ # @return [String]
70
+ #
71
+ def body
72
+ execute.body
73
+ end
74
+
75
+ #
76
+ # @return [String] the base URL
77
+ #
78
+ def base_url
79
+ @base_url ||= Yoti.configuration.api_endpoint
40
80
  end
41
81
 
42
82
  private
43
83
 
84
+ #
85
+ # @param [Net::HTTPResponse] http_res
86
+ #
87
+ # @return [Boolean]
88
+ #
89
+ def response_is_success(http_res)
90
+ http_res.code.to_i >= 200 && http_res.code.to_i < 300
91
+ end
92
+
93
+ #
94
+ # Adds payload to provided HTTP request
95
+ #
96
+ # @param [Net::HTTPRequest] http_req
97
+ #
98
+ def add_payload(http_req)
99
+ return if @payload.to_s.empty?
100
+
101
+ if @payload.is_a?(String)
102
+ http_req.body = @payload
103
+ elsif @payload.respond_to?(:to_json)
104
+ http_req.body = @payload.to_json
105
+ end
106
+ end
107
+
108
+ #
109
+ # @return [Net::HTTPRequest] the unsigned HTTP request
110
+ #
44
111
  def unsigned_request
45
112
  case @http_method
46
113
  when 'GET'
@@ -49,13 +116,13 @@ module Yoti
49
116
  http_req = Net::HTTP::Delete.new(uri)
50
117
  when 'POST'
51
118
  http_req = Net::HTTP::Post.new(uri)
52
- http_req.body = @payload.to_json unless @payload.to_s.empty?
119
+ add_payload(http_req)
53
120
  when 'PUT'
54
121
  http_req = Net::HTTP::Put.new(uri)
55
- http_req.body = @payload.to_json unless @payload.to_s.empty?
122
+ add_payload(http_req)
56
123
  when 'PATCH'
57
124
  http_req = Net::HTTP::Patch.new(uri)
58
- http_req.body = @payload.to_json unless @payload.to_s.empty?
125
+ add_payload(http_req)
59
126
  else
60
127
  raise RequestError, "Request method not allowed: #{@http_method}"
61
128
  end
@@ -67,22 +134,27 @@ module Yoti
67
134
  http_req
68
135
  end
69
136
 
137
+ #
138
+ # @return [URI] the full request URI
139
+ #
70
140
  def uri
71
- @uri ||= URI(Yoti.configuration.api_endpoint + path)
141
+ @uri ||= URI(base_url + path)
72
142
  end
73
143
 
144
+ #
145
+ # @return [String] the path with query string
146
+ #
74
147
  def path
75
148
  @path ||= begin
76
- nonce = SecureRandom.uuid
77
- timestamp = Time.now.to_i
78
-
79
- "/#{@endpoint}/#{token}"\
80
- "?nonce=#{nonce}"\
81
- "&timestamp=#{timestamp}"\
82
- "&appId=#{Yoti.configuration.client_sdk_id}"
149
+ "/#{@endpoint}/#{token}".chomp('/') + "?#{query_string}"
83
150
  end
84
151
  end
85
152
 
153
+ #
154
+ # @deprecated will be removed in 2.0.0 - token is now provided with the endpoint
155
+ #
156
+ # @return [String] the decrypted connect token
157
+ #
86
158
  def token
87
159
  return '' unless @encrypted_connect_token
88
160
 
@@ -92,5 +164,136 @@ module Yoti
92
164
  def https_uri?
93
165
  uri.scheme == 'https'
94
166
  end
167
+
168
+ #
169
+ # Builds query string including nonce and timestamp
170
+ #
171
+ # @return [String]
172
+ #
173
+ def query_string
174
+ params = {
175
+ nonce: SecureRandom.uuid,
176
+ timestamp: Time.now.to_i
177
+ }
178
+
179
+ if @query_params.nil?
180
+ # @deprecated this default will be removed in 2.0.0
181
+ # Append appId when no custom query params are provided.
182
+ params.merge!(appId: Yoti.configuration.client_sdk_id)
183
+ else
184
+ Validation.assert_is_a(Hash, @query_params, 'query_params')
185
+ params.merge!(@query_params)
186
+ end
187
+
188
+ params.map do |k, v|
189
+ CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s)
190
+ end.join('&')
191
+ end
192
+ end
193
+
194
+ #
195
+ # Builder for {Request}
196
+ #
197
+ class RequestBuilder
198
+ def initialize
199
+ @headers = {}
200
+ @query_params = {}
201
+ end
202
+
203
+ #
204
+ # Sets the base URL
205
+ #
206
+ # @param [String] base_url
207
+ #
208
+ # @return [self]
209
+ #
210
+ def with_base_url(base_url)
211
+ Validation.assert_is_a(String, base_url, 'base_url')
212
+ @base_url = base_url
213
+ self
214
+ end
215
+
216
+ #
217
+ # Adds a HTTP header to the request
218
+ #
219
+ # @param [String] header
220
+ # @param [String] value
221
+ #
222
+ # @return [self]
223
+ #
224
+ def with_header(header, value)
225
+ Validation.assert_is_a(String, header, 'header')
226
+ Validation.assert_is_a(String, value, 'value')
227
+ @headers[header] = value
228
+ self
229
+ end
230
+
231
+ #
232
+ # Adds a query parameter to the request
233
+ #
234
+ # @param [String] key
235
+ # @param [String] value
236
+ #
237
+ # @return [self]
238
+ #
239
+ def with_query_param(key, value)
240
+ Validation.assert_is_a(String, key, 'key')
241
+ Validation.assert_is_a(String, value, 'value')
242
+ @query_params[key] = value
243
+ self
244
+ end
245
+
246
+ #
247
+ # Sets the HTTP method
248
+ #
249
+ # @param [String] http_method
250
+ #
251
+ # @return [self]
252
+ #
253
+ def with_http_method(http_method)
254
+ Validation.assert_is_a(String, http_method, 'http_method')
255
+ @http_method = http_method
256
+ self
257
+ end
258
+
259
+ #
260
+ # Sets the API endpoint for the request
261
+ #
262
+ # @param [String] endpoint
263
+ #
264
+ # @return [self]
265
+ #
266
+ def with_endpoint(endpoint)
267
+ Validation.assert_is_a(String, endpoint, 'endpoint')
268
+ @endpoint = endpoint
269
+ self
270
+ end
271
+
272
+ #
273
+ # Sets the body sent with the request
274
+ #
275
+ # @param [#to_json,String] payload
276
+ #
277
+ # @return [self]
278
+ #
279
+ def with_payload(payload)
280
+ Validation.assert_respond_to(:to_json, payload, 'payload') unless payload.is_a?(String)
281
+ @payload = payload
282
+ self
283
+ end
284
+
285
+ #
286
+ # @return [Request]
287
+ #
288
+ def build
289
+ request = Request.new
290
+ request.base_url = @base_url
291
+ request.endpoint = @endpoint
292
+ request.query_params = @query_params
293
+ request.http_method = @http_method
294
+ request.payload = @payload
295
+ @headers.map { |k, v| request.add_header(k, v) }
296
+ request
297
+ end
95
298
  end
96
299
  end
@@ -3,6 +3,11 @@ require 'base64'
3
3
  module Yoti
4
4
  # Converts a basic Net::HTTP request into a Yoti Signed Request
5
5
  class SignedRequest
6
+ #
7
+ # @param [Net::HTTPRequest] unsigned_request
8
+ # @param [String] path
9
+ # @param [#to_json,String] payload
10
+ #
6
11
  def initialize(unsigned_request, path, payload = {})
7
12
  @http_req = unsigned_request
8
13
  @path = path
@@ -10,6 +15,9 @@ module Yoti
10
15
  @auth_key = Yoti::SSL.auth_key_from_pem
11
16
  end
12
17
 
18
+ #
19
+ # @return [Net::HTTPRequest]
20
+ #
13
21
  def sign
14
22
  @http_req['X-Yoti-Auth-Digest'] = message_signature
15
23
  @http_req['X-Yoti-SDK'] = Yoti.configuration.sdk_identifier
@@ -20,18 +28,19 @@ module Yoti
20
28
  private
21
29
 
22
30
  def message_signature
23
- @message_signature ||= Yoti::SSL.get_secure_signature("#{http_method}&#{@path}#{payload_string}")
31
+ @message_signature ||= Yoti::SSL.get_secure_signature("#{http_method}&#{@path}#{base64_payload}")
24
32
  end
25
33
 
26
34
  def http_method
27
35
  @http_req.method
28
36
  end
29
37
 
30
- # Create the base64 encoded request body
31
- def payload_string
38
+ def base64_payload
32
39
  return '' unless @payload
33
40
 
34
- '&' + Base64.strict_encode64(@payload.to_json)
41
+ payload_string = @payload.is_a?(String) ? @payload : @payload.to_json
42
+
43
+ '&' + Base64.strict_encode64(payload_string)
35
44
  end
36
45
  end
37
46
  end