yoti 1.6.4 → 1.7.0

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