yoti 1.6.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE.md +17 -0
- data/CONTRIBUTING.md +0 -29
- data/README.md +4 -0
- data/Rakefile +7 -10
- data/lib/yoti.rb +50 -1
- data/lib/yoti/activity_details.rb +5 -1
- data/lib/yoti/client.rb +13 -4
- data/lib/yoti/configuration.rb +7 -2
- data/lib/yoti/data_type/age_verification.rb +1 -1
- data/lib/yoti/data_type/base_profile.rb +1 -1
- data/lib/yoti/data_type/document_details.rb +5 -13
- data/lib/yoti/data_type/image.rb +4 -12
- data/lib/yoti/data_type/image_jpeg.rb +2 -0
- data/lib/yoti/data_type/image_png.rb +2 -0
- data/lib/yoti/data_type/media.rb +19 -0
- data/lib/yoti/doc_scan/client.rb +163 -0
- data/lib/yoti/doc_scan/constants.rb +28 -0
- data/lib/yoti/doc_scan/session/create/create_session_result.rb +50 -0
- data/lib/yoti/doc_scan/session/create/document_filter.rb +31 -0
- data/lib/yoti/doc_scan/session/create/document_restrictions_filter.rb +140 -0
- data/lib/yoti/doc_scan/session/create/notification_config.rb +142 -0
- data/lib/yoti/doc_scan/session/create/orthogonal_restrictions_filter.rb +150 -0
- data/lib/yoti/doc_scan/session/create/requested_check.rb +39 -0
- data/lib/yoti/doc_scan/session/create/requested_document_authenticity_check.rb +53 -0
- data/lib/yoti/doc_scan/session/create/requested_face_match_check.rb +95 -0
- data/lib/yoti/doc_scan/session/create/requested_liveness_check.rb +108 -0
- data/lib/yoti/doc_scan/session/create/requested_task.rb +39 -0
- data/lib/yoti/doc_scan/session/create/requested_text_extraction_task.rb +94 -0
- data/lib/yoti/doc_scan/session/create/required_document.rb +31 -0
- data/lib/yoti/doc_scan/session/create/required_id_document.rb +53 -0
- data/lib/yoti/doc_scan/session/create/sdk_config.rb +221 -0
- data/lib/yoti/doc_scan/session/create/session_specification.rb +203 -0
- data/lib/yoti/doc_scan/session/retrieve/authenticity_check_response.rb +12 -0
- data/lib/yoti/doc_scan/session/retrieve/breakdown_response.rb +38 -0
- data/lib/yoti/doc_scan/session/retrieve/check_response.rb +63 -0
- data/lib/yoti/doc_scan/session/retrieve/details_response.rb +28 -0
- data/lib/yoti/doc_scan/session/retrieve/document_fields_response.rb +21 -0
- data/lib/yoti/doc_scan/session/retrieve/face_map_response.rb +21 -0
- data/lib/yoti/doc_scan/session/retrieve/face_match_check_response.rb +12 -0
- data/lib/yoti/doc_scan/session/retrieve/frame_response.rb +21 -0
- data/lib/yoti/doc_scan/session/retrieve/generated_check_response.rb +28 -0
- data/lib/yoti/doc_scan/session/retrieve/generated_media.rb +28 -0
- data/lib/yoti/doc_scan/session/retrieve/generated_text_data_check_response.rb +12 -0
- data/lib/yoti/doc_scan/session/retrieve/get_session_result.rb +113 -0
- data/lib/yoti/doc_scan/session/retrieve/id_document_resource_response.rb +52 -0
- data/lib/yoti/doc_scan/session/retrieve/liveness_check_response.rb +12 -0
- data/lib/yoti/doc_scan/session/retrieve/liveness_resource_response.rb +24 -0
- data/lib/yoti/doc_scan/session/retrieve/media_response.rb +38 -0
- data/lib/yoti/doc_scan/session/retrieve/page_response.rb +27 -0
- data/lib/yoti/doc_scan/session/retrieve/recommendation_response.rb +34 -0
- data/lib/yoti/doc_scan/session/retrieve/report_response.rb +31 -0
- data/lib/yoti/doc_scan/session/retrieve/resource_container.rb +50 -0
- data/lib/yoti/doc_scan/session/retrieve/resource_response.rb +39 -0
- data/lib/yoti/doc_scan/session/retrieve/task_response.rb +87 -0
- data/lib/yoti/doc_scan/session/retrieve/text_data_check_response.rb +12 -0
- data/lib/yoti/doc_scan/session/retrieve/text_extraction_task_response.rb +18 -0
- data/lib/yoti/doc_scan/session/retrieve/zoom_liveness_resource_response.rb +33 -0
- data/lib/yoti/doc_scan/support/supported_documents.rb +60 -0
- data/lib/yoti/dynamic_share_service/extension/thirdparty_attribute_extension.rb +73 -12
- data/lib/yoti/dynamic_share_service/policy/dynamic_policy.rb +17 -17
- data/lib/yoti/dynamic_share_service/share_url.rb +28 -33
- data/lib/yoti/errors.rb +17 -2
- data/lib/yoti/http/aml_check_request.rb +12 -6
- data/lib/yoti/http/payloads/aml_address.rb +4 -0
- data/lib/yoti/http/payloads/aml_profile.rb +7 -1
- data/lib/yoti/http/profile_request.rb +11 -6
- data/lib/yoti/http/request.rb +220 -18
- data/lib/yoti/http/signed_request.rb +13 -4
- data/lib/yoti/protobuf/main.rb +16 -6
- data/lib/yoti/share/attribute_issuance_details.rb +1 -1
- data/lib/yoti/ssl.rb +3 -2
- data/lib/yoti/util/anchor_processor.rb +1 -1
- data/lib/yoti/util/validation.rb +41 -0
- data/lib/yoti/version.rb +1 -1
- data/rubocop.yml +9 -1
- data/yoti.gemspec +2 -5
- metadata +51 -60
- data/lib/yoti/sandbox.rb +0 -5
- data/lib/yoti/sandbox/anchor.rb +0 -49
- data/lib/yoti/sandbox/attribute.rb +0 -52
- data/lib/yoti/sandbox/profile.rb +0 -171
- data/lib/yoti/sandbox/sandbox.rb +0 -105
- 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.
|
18
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
38
|
-
|
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
|
-
|
45
|
-
|
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(*
|
20
|
-
as_json.to_json
|
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]
|
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]
|
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}×tamp=#{Time.now.to_i}"
|
20
|
-
end
|
21
|
-
|
22
16
|
def self.create_share_url(scenario)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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}×tamp=#{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
|
data/lib/yoti/errors.rb
CHANGED
@@ -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
|
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
|
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 [
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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 [
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
data/lib/yoti/http/request.rb
CHANGED
@@ -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 [
|
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
|
-
#
|
28
|
-
|
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
|
-
|
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
|
60
|
+
raise RequestError.new("Unsuccessful Yoti API call: #{http_res.message}", http_res) unless response_is_success(http_res)
|
38
61
|
|
39
|
-
|
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
|
118
|
+
add_payload(http_req)
|
53
119
|
when 'PUT'
|
54
120
|
http_req = Net::HTTP::Put.new(uri)
|
55
|
-
http_req
|
121
|
+
add_payload(http_req)
|
56
122
|
when 'PATCH'
|
57
123
|
http_req = Net::HTTP::Patch.new(uri)
|
58
|
-
http_req
|
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(
|
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
|
-
|
77
|
-
timestamp = Time.now.to_i
|
78
|
-
|
79
|
-
"/#{@endpoint}/#{token}"\
|
80
|
-
"?nonce=#{nonce}"\
|
81
|
-
"×tamp=#{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
|