yoti 1.6.0 → 1.7.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 +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +17 -0
  3. data/CONTRIBUTING.md +0 -29
  4. data/README.md +4 -0
  5. data/Rakefile +7 -10
  6. data/lib/yoti.rb +50 -1
  7. data/lib/yoti/activity_details.rb +5 -1
  8. data/lib/yoti/client.rb +13 -4
  9. data/lib/yoti/configuration.rb +7 -2
  10. data/lib/yoti/data_type/age_verification.rb +1 -1
  11. data/lib/yoti/data_type/base_profile.rb +1 -1
  12. data/lib/yoti/data_type/document_details.rb +5 -13
  13. data/lib/yoti/data_type/image.rb +4 -12
  14. data/lib/yoti/data_type/image_jpeg.rb +2 -0
  15. data/lib/yoti/data_type/image_png.rb +2 -0
  16. data/lib/yoti/data_type/media.rb +19 -0
  17. data/lib/yoti/doc_scan/client.rb +163 -0
  18. data/lib/yoti/doc_scan/constants.rb +28 -0
  19. data/lib/yoti/doc_scan/session/create/create_session_result.rb +50 -0
  20. data/lib/yoti/doc_scan/session/create/document_filter.rb +31 -0
  21. data/lib/yoti/doc_scan/session/create/document_restrictions_filter.rb +140 -0
  22. data/lib/yoti/doc_scan/session/create/notification_config.rb +142 -0
  23. data/lib/yoti/doc_scan/session/create/orthogonal_restrictions_filter.rb +150 -0
  24. data/lib/yoti/doc_scan/session/create/requested_check.rb +39 -0
  25. data/lib/yoti/doc_scan/session/create/requested_document_authenticity_check.rb +53 -0
  26. data/lib/yoti/doc_scan/session/create/requested_face_match_check.rb +95 -0
  27. data/lib/yoti/doc_scan/session/create/requested_liveness_check.rb +108 -0
  28. data/lib/yoti/doc_scan/session/create/requested_task.rb +39 -0
  29. data/lib/yoti/doc_scan/session/create/requested_text_extraction_task.rb +94 -0
  30. data/lib/yoti/doc_scan/session/create/required_document.rb +31 -0
  31. data/lib/yoti/doc_scan/session/create/required_id_document.rb +53 -0
  32. data/lib/yoti/doc_scan/session/create/sdk_config.rb +221 -0
  33. data/lib/yoti/doc_scan/session/create/session_specification.rb +203 -0
  34. data/lib/yoti/doc_scan/session/retrieve/authenticity_check_response.rb +12 -0
  35. data/lib/yoti/doc_scan/session/retrieve/breakdown_response.rb +38 -0
  36. data/lib/yoti/doc_scan/session/retrieve/check_response.rb +63 -0
  37. data/lib/yoti/doc_scan/session/retrieve/details_response.rb +28 -0
  38. data/lib/yoti/doc_scan/session/retrieve/document_fields_response.rb +21 -0
  39. data/lib/yoti/doc_scan/session/retrieve/face_map_response.rb +21 -0
  40. data/lib/yoti/doc_scan/session/retrieve/face_match_check_response.rb +12 -0
  41. data/lib/yoti/doc_scan/session/retrieve/frame_response.rb +21 -0
  42. data/lib/yoti/doc_scan/session/retrieve/generated_check_response.rb +28 -0
  43. data/lib/yoti/doc_scan/session/retrieve/generated_media.rb +28 -0
  44. data/lib/yoti/doc_scan/session/retrieve/generated_text_data_check_response.rb +12 -0
  45. data/lib/yoti/doc_scan/session/retrieve/get_session_result.rb +113 -0
  46. data/lib/yoti/doc_scan/session/retrieve/id_document_resource_response.rb +52 -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 +73 -12
  61. data/lib/yoti/dynamic_share_service/policy/dynamic_policy.rb +17 -17
  62. data/lib/yoti/dynamic_share_service/share_url.rb +28 -33
  63. data/lib/yoti/errors.rb +17 -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 +220 -18
  69. data/lib/yoti/http/signed_request.rb +13 -4
  70. data/lib/yoti/protobuf/main.rb +16 -6
  71. data/lib/yoti/share/attribute_issuance_details.rb +1 -1
  72. data/lib/yoti/ssl.rb +3 -2
  73. data/lib/yoti/util/anchor_processor.rb +1 -1
  74. data/lib/yoti/util/validation.rb +41 -0
  75. data/lib/yoti/version.rb +1 -1
  76. data/rubocop.yml +9 -1
  77. data/yoti.gemspec +2 -5
  78. metadata +51 -60
  79. data/lib/yoti/sandbox.rb +0 -5
  80. data/lib/yoti/sandbox/anchor.rb +0 -49
  81. data/lib/yoti/sandbox/attribute.rb +0 -52
  82. data/lib/yoti/sandbox/profile.rb +0 -171
  83. data/lib/yoti/sandbox/sandbox.rb +0 -105
  84. data/lib/yoti/sandbox/sandbox_client.rb +0 -45
@@ -1,58 +1,119 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'time'
4
+
3
5
  module Yoti
4
6
  module DynamicSharingService
7
+ class ThirdPartyAttributeDefinition
8
+ #
9
+ # @param [String] name
10
+ #
11
+ def initialize(name)
12
+ @name = name
13
+ end
14
+
15
+ def as_json(*_args)
16
+ { name: @name }
17
+ end
18
+
19
+ def to_json(*_args)
20
+ as_json.to_json
21
+ end
22
+ end
23
+
5
24
  class ThirdPartyAttributeExtensionBuilder
6
25
  def initialize
7
26
  @expiry_date = nil
8
27
  @definitions = []
9
28
  end
10
29
 
30
+ #
31
+ # @param [DateTime,Time] expiry_date
32
+ #
33
+ # @return [self]
34
+ #
11
35
  def with_expiry_date(expiry_date)
12
36
  @expiry_date = expiry_date
13
37
  self
14
38
  end
15
39
 
40
+ #
41
+ # @param [String] names
42
+ #
43
+ # @return [self]
44
+ #
16
45
  def with_definitions(*names)
17
- names.each do |s|
18
- @definitions += [{ name: s }]
46
+ @definitions += names.map do |name|
47
+ ThirdPartyAttributeDefinition.new(name)
19
48
  end
20
49
  self
21
50
  end
22
51
 
52
+ #
53
+ # @return [ThirdPartyAttributeExtension]
54
+ #
23
55
  def build
24
- extension = ThirdPartyAttributeExtension.new
25
- extension.instance_variable_get(:@content)[:expiry_date] = @expiry_date
26
- extension.instance_variable_get(:@content)[:definitions] = @definitions
27
- extension
56
+ content = ThirdPartyAttributeExtensionContent.new(@expiry_date, @definitions)
57
+ ThirdPartyAttributeExtension.new(content)
28
58
  end
29
59
  end
30
60
 
31
61
  class ThirdPartyAttributeExtension
32
62
  EXTENSION_TYPE = 'THIRD_PARTY_ATTRIBUTE'
33
63
 
64
+ # @return [ThirdPartyAttributeExtensionContent]
34
65
  attr_reader :content
66
+
67
+ # @return [String]
35
68
  attr_reader :type
36
69
 
37
- def initialize
38
- @content = {}
70
+ #
71
+ # @param [ThirdPartyAttributeExtensionContent] content
72
+ #
73
+ def initialize(content = nil)
74
+ @content = content
39
75
  @type = EXTENSION_TYPE
40
76
  end
41
77
 
42
78
  def as_json(*_args)
43
- {
44
- type: @type,
45
- content: @content
46
- }
79
+ json = {}
80
+ json[:type] = @type
81
+ json[:content] = @content.as_json unless @content.nil?
82
+ json
47
83
  end
48
84
 
49
85
  def to_json(*_args)
50
86
  as_json.to_json
51
87
  end
52
88
 
89
+ #
90
+ # @return [ThirdPartyAttributeExtensionBuilder]
91
+ #
53
92
  def self.builder
54
93
  ThirdPartyAttributeExtensionBuilder.new
55
94
  end
56
95
  end
96
+
97
+ class ThirdPartyAttributeExtensionContent
98
+ #
99
+ # @param [DateTime,Time] expiry_date
100
+ # @param [Array<ThirdPartyAttributeDefinition>] definitions
101
+ #
102
+ def initialize(expiry_date, definitions)
103
+ @expiry_date = expiry_date
104
+ @definitions = definitions
105
+ end
106
+
107
+ def as_json(*_args)
108
+ json = {}
109
+ json[:expiry_date] = @expiry_date.to_time.utc.strftime('%FT%T.%3NZ') unless @expiry_date.nil?
110
+ json[:definitions] = @definitions.map(&:as_json)
111
+ json
112
+ end
113
+
114
+ def to_json(*_args)
115
+ as_json.to_json
116
+ end
117
+ end
57
118
  end
58
119
  end
@@ -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)
@@ -105,19 +105,19 @@ module Yoti
105
105
  end
106
106
 
107
107
  def with_family_name(options = {})
108
- with_wanted_attribute_by_name Attribute::FAMILY_NAME, options
108
+ with_wanted_attribute_by_name Attribute::FAMILY_NAME, **options
109
109
  end
110
110
 
111
111
  def with_given_names(options = {})
112
- with_wanted_attribute_by_name Attribute::GIVEN_NAMES, options
112
+ with_wanted_attribute_by_name Attribute::GIVEN_NAMES, **options
113
113
  end
114
114
 
115
115
  def with_full_name(options = {})
116
- with_wanted_attribute_by_name Attribute::FULL_NAME, options
116
+ with_wanted_attribute_by_name Attribute::FULL_NAME, **options
117
117
  end
118
118
 
119
119
  def with_date_of_birth(options = {})
120
- with_wanted_attribute_by_name Attribute::DATE_OF_BIRTH, options
120
+ with_wanted_attribute_by_name Attribute::DATE_OF_BIRTH, **options
121
121
  end
122
122
 
123
123
  #
@@ -135,45 +135,45 @@ 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
- with_age_derived_attribute(Attribute::AGE_OVER + age.to_s, options)
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
- with_age_derived_attribute(Attribute::AGE_UNDER + age.to_s, options)
148
+ with_age_derived_attribute(Attribute::AGE_UNDER + age.to_s, **options)
149
149
  end
150
150
 
151
151
  def with_gender(options = {})
152
- with_wanted_attribute_by_name Attribute::GENDER, options
152
+ with_wanted_attribute_by_name Attribute::GENDER, **options
153
153
  end
154
154
 
155
155
  def with_postal_address(options = {})
156
- with_wanted_attribute_by_name(Attribute::POSTAL_ADDRESS, options)
156
+ with_wanted_attribute_by_name(Attribute::POSTAL_ADDRESS, **options)
157
157
  end
158
158
 
159
159
  def with_structured_postal_address(options = {})
160
- with_wanted_attribute_by_name(Attribute::STRUCTURED_POSTAL_ADDRESS, options)
160
+ with_wanted_attribute_by_name(Attribute::STRUCTURED_POSTAL_ADDRESS, **options)
161
161
  end
162
162
 
163
163
  def with_nationality(options = {})
164
- with_wanted_attribute_by_name(Attribute::NATIONALITY, options)
164
+ with_wanted_attribute_by_name(Attribute::NATIONALITY, **options)
165
165
  end
166
166
 
167
167
  def with_phone_number(options = {})
168
- with_wanted_attribute_by_name(Attribute::PHONE_NUMBER, options)
168
+ with_wanted_attribute_by_name(Attribute::PHONE_NUMBER, **options)
169
169
  end
170
170
 
171
171
  def with_selfie(options = {})
172
- with_wanted_attribute_by_name(Attribute::SELFIE, options)
172
+ with_wanted_attribute_by_name(Attribute::SELFIE, **options)
173
173
  end
174
174
 
175
175
  def with_email(options = {})
176
- with_wanted_attribute_by_name(Attribute::EMAIL_ADDRESS, options)
176
+ with_wanted_attribute_by_name(Attribute::EMAIL_ADDRESS, **options)
177
177
  end
178
178
 
179
179
  def with_document_details
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'securerandom'
4
+
3
5
  module Yoti
4
6
  module DynamicSharingService
5
7
  class Share
@@ -11,50 +13,33 @@ module Yoti
11
13
  end
12
14
  end
13
15
 
14
- def self.create_share_url_endpoint
15
- "/qrcodes/apps/#{Yoti.configuration.client_sdk_id}"
16
- end
17
-
18
- def self.create_share_url_query
19
- "?nonce=#{SecureRandom.uuid}&timestamp=#{Time.now.to_i}"
20
- end
21
-
22
16
  def self.create_share_url(scenario)
23
- endpoint = "#{create_share_url_endpoint}#{create_share_url_query}"
24
- uri = URI("#{Yoti.configuration.api_endpoint}#{endpoint}")
25
-
26
- unsigned = Net::HTTP::Post.new uri
27
- unsigned.body = scenario.to_json
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
28
25
 
29
- signed_request = Yoti::SignedRequest.new(
30
- unsigned,
31
- endpoint,
32
- scenario
33
- ).sign
26
+ begin
27
+ create_share_url_parse_response yoti_request.execute
28
+ rescue Yoti::RequestError => e
29
+ raise if e.response.nil?
34
30
 
35
- response = Net::HTTP.start(
36
- uri.hostname,
37
- uri.port,
38
- use_ssl: true
39
- ) do |http|
40
- http.request signed_request
41
- end
42
-
43
- create_share_url_parse_response response
44
- end
45
-
46
- def self.create_share_url_parse_response(response)
47
- if response.code.to_i < 200 || response.code.to_i >= 300
48
- case response.code
31
+ case e.response.code
49
32
  when '400'
50
33
  raise InvalidDataError
51
34
  when '404'
52
35
  raise ApplicationNotFoundError
53
36
  else
54
- raise UnknownHTTPError, response.code
37
+ raise UnknownHTTPError, e.response.code
55
38
  end
56
39
  end
40
+ end
57
41
 
42
+ def self.create_share_url_parse_response(response)
58
43
  Share.new JSON.parse response.body
59
44
  end
60
45
 
@@ -76,5 +61,15 @@ module Yoti
76
61
  super msg
77
62
  end
78
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
79
74
  end
80
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
@@ -1,9 +1,18 @@
1
+ require 'securerandom'
2
+
1
3
  module Yoti
2
4
  # Manage the API's HTTPS requests
3
5
  class Request
6
+ # @deprecated will be removed in 2.0.0 - token is now provided with the endpoint
4
7
  # @return [String] the URL token received from Yoti Connect
5
8
  attr_accessor :encrypted_connect_token
6
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
+
7
16
  # @return [String] the HTTP method used for the request
8
17
  # The allowed methods are: GET, DELETE, POST, PUT, PATCH
9
18
  attr_accessor :http_method
@@ -11,36 +20,93 @@ module Yoti
11
20
  # @return [String] the API endpoint for the request
12
21
  attr_accessor :endpoint
13
22
 
14
- # @return [Hash] the body sent with the request
23
+ # @return [#to_json,String] the body sent with the request
15
24
  attr_accessor :payload
16
25
 
17
26
  def initialize
18
27
  @headers = {}
19
28
  end
20
29
 
30
+ #
31
+ # @return [RequestBuilder]
32
+ #
33
+ def self.builder
34
+ RequestBuilder.new
35
+ end
36
+
37
+ #
21
38
  # Adds a HTTP header to the request
39
+ #
40
+ # @param [String] header
41
+ # @param [String] value
42
+ #
22
43
  def add_header(header, value)
23
44
  @headers[header] = value
24
45
  end
25
46
 
47
+ #
26
48
  # Makes a HTTP request after signing the headers
27
- # @return [Hash] the body from the HTTP request
28
- def body
49
+ #
50
+ # @return [HTTPResponse]
51
+ #
52
+ def execute
29
53
  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
54
 
32
- 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|
33
56
  signed_request = SignedRequest.new(unsigned_request, path, @payload).sign
34
57
  http.request(signed_request)
35
58
  end
36
59
 
37
- 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)
38
61
 
39
- 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
40
79
  end
41
80
 
42
81
  private
43
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
+ #
44
110
  def unsigned_request
45
111
  case @http_method
46
112
  when 'GET'
@@ -49,13 +115,13 @@ module Yoti
49
115
  http_req = Net::HTTP::Delete.new(uri)
50
116
  when 'POST'
51
117
  http_req = Net::HTTP::Post.new(uri)
52
- http_req.body = @payload.to_json unless @payload.to_s.empty?
118
+ add_payload(http_req)
53
119
  when 'PUT'
54
120
  http_req = Net::HTTP::Put.new(uri)
55
- http_req.body = @payload.to_json unless @payload.to_s.empty?
121
+ add_payload(http_req)
56
122
  when 'PATCH'
57
123
  http_req = Net::HTTP::Patch.new(uri)
58
- http_req.body = @payload.to_json unless @payload.to_s.empty?
124
+ add_payload(http_req)
59
125
  else
60
126
  raise RequestError, "Request method not allowed: #{@http_method}"
61
127
  end
@@ -67,22 +133,27 @@ module Yoti
67
133
  http_req
68
134
  end
69
135
 
136
+ #
137
+ # @return [URI] the full request URI
138
+ #
70
139
  def uri
71
- @uri ||= URI(Yoti.configuration.api_endpoint + path)
140
+ @uri ||= URI(base_url + path)
72
141
  end
73
142
 
143
+ #
144
+ # @return [String] the path with query string
145
+ #
74
146
  def path
75
147
  @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}"
148
+ "/#{@endpoint}/#{token}".chomp('/') + "?#{query_string}"
83
149
  end
84
150
  end
85
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
+ #
86
157
  def token
87
158
  return '' unless @encrypted_connect_token
88
159
 
@@ -92,5 +163,136 @@ module Yoti
92
163
  def https_uri?
93
164
  uri.scheme == 'https'
94
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
95
297
  end
96
298
  end