yoti 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Gemfile +0 -2
- data/README.md +1 -1
- data/Rakefile +8 -0
- data/lib/yoti.rb +16 -0
- data/lib/yoti/activity_details.rb +17 -2
- data/lib/yoti/client.rb +2 -1
- data/lib/yoti/data_type/age_verification.rb +54 -0
- data/lib/yoti/data_type/attribute.rb +3 -0
- data/lib/yoti/data_type/base_profile.rb +13 -0
- data/lib/yoti/data_type/document_details.rb +96 -0
- data/lib/yoti/data_type/profile.rb +76 -0
- data/lib/yoti/dynamic_share_service/dynamic_scenario.rb +67 -0
- data/lib/yoti/dynamic_share_service/extension/extension.rb +45 -0
- data/lib/yoti/dynamic_share_service/extension/location_constraint_extension.rb +88 -0
- data/lib/yoti/dynamic_share_service/extension/thirdparty_attribute_extension.rb +58 -0
- data/lib/yoti/dynamic_share_service/extension/transactional_flow_extension.rb +47 -0
- data/lib/yoti/dynamic_share_service/policy/dynamic_policy.rb +184 -0
- data/lib/yoti/dynamic_share_service/policy/source_constraint.rb +88 -0
- data/lib/yoti/dynamic_share_service/policy/wanted_anchor.rb +53 -0
- data/lib/yoti/dynamic_share_service/policy/wanted_attribute.rb +85 -0
- data/lib/yoti/dynamic_share_service/share_url.rb +80 -0
- data/lib/yoti/http/profile_request.rb +1 -0
- data/lib/yoti/http/request.rb +13 -0
- data/lib/yoti/http/signed_request.rb +0 -3
- data/lib/yoti/protobuf/main.rb +11 -0
- data/lib/yoti/protobuf/sharepubapi/DataEntry_pb.rb +29 -0
- data/lib/yoti/protobuf/sharepubapi/ExtraData_pb.rb +19 -0
- data/lib/yoti/protobuf/sharepubapi/IssuingAttributes_pb.rb +23 -0
- data/lib/yoti/protobuf/sharepubapi/ThirdPartyAttribute_pb.rb +20 -0
- data/lib/yoti/sandbox.rb +5 -0
- data/lib/yoti/sandbox/anchor.rb +49 -0
- data/lib/yoti/sandbox/attribute.rb +52 -0
- data/lib/yoti/sandbox/profile.rb +171 -0
- data/lib/yoti/sandbox/sandbox.rb +105 -0
- data/lib/yoti/sandbox/sandbox_client.rb +45 -0
- data/lib/yoti/share/attribute_issuance_details.rb +43 -0
- data/lib/yoti/share/extra_data.rb +25 -0
- data/lib/yoti/ssl.rb +7 -0
- data/lib/yoti/util/age_processor.rb +4 -0
- data/lib/yoti/version.rb +1 -1
- data/rubocop.yml +4 -0
- data/yoti.gemspec +4 -2
- metadata +61 -4
- data/.travis.yml +0 -17
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sandbox
|
|
4
|
+
# Attribute describes an attribute on a sandbox profile
|
|
5
|
+
class Attribute
|
|
6
|
+
attr_accessor :name
|
|
7
|
+
attr_accessor :value
|
|
8
|
+
attr_accessor :derivation
|
|
9
|
+
attr_accessor :optional
|
|
10
|
+
attr_reader :anchors
|
|
11
|
+
|
|
12
|
+
def initialize(
|
|
13
|
+
name: '',
|
|
14
|
+
value: '',
|
|
15
|
+
derivation: '',
|
|
16
|
+
optional: false,
|
|
17
|
+
anchors: []
|
|
18
|
+
)
|
|
19
|
+
@name = name
|
|
20
|
+
@value = value
|
|
21
|
+
@derivation = derivation
|
|
22
|
+
@optional = optional
|
|
23
|
+
@anchors = anchors
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def as_json(*_args)
|
|
27
|
+
{
|
|
28
|
+
name: name,
|
|
29
|
+
value: value,
|
|
30
|
+
derivation: derivation,
|
|
31
|
+
optional: optional,
|
|
32
|
+
anchors: anchors
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def with_anchor(anchor)
|
|
37
|
+
@anchors.push anchor
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Helper functions for building derivation strings
|
|
43
|
+
module Derivation
|
|
44
|
+
def self.age_over(age)
|
|
45
|
+
"age_over:#{age}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.age_under(age)
|
|
49
|
+
"age_under:#{age}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yoti/data_type/attribute'
|
|
4
|
+
|
|
5
|
+
require 'base64'
|
|
6
|
+
|
|
7
|
+
require_relative 'attribute'
|
|
8
|
+
|
|
9
|
+
module Sandbox
|
|
10
|
+
# Builds a user profile on the sandbox instance
|
|
11
|
+
class Profile
|
|
12
|
+
attr_reader :remember_me_id
|
|
13
|
+
attr_reader :attributes
|
|
14
|
+
|
|
15
|
+
def as_json(*_args)
|
|
16
|
+
{
|
|
17
|
+
remember_me_id: remember_me_id,
|
|
18
|
+
profile_attributes: attributes.map(&:as_json)
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_json(*_args)
|
|
23
|
+
as_json.to_json
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@remember_me_id = ''
|
|
28
|
+
@attributes = []
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def with_remember_me_id(id)
|
|
32
|
+
@remember_me_id = id
|
|
33
|
+
self
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def with_attribute(
|
|
37
|
+
name:,
|
|
38
|
+
value:,
|
|
39
|
+
derivation: '',
|
|
40
|
+
optional: false,
|
|
41
|
+
anchors: []
|
|
42
|
+
)
|
|
43
|
+
@attributes.push Sandbox::Attribute.new(
|
|
44
|
+
name: name,
|
|
45
|
+
value: value,
|
|
46
|
+
derivation: derivation,
|
|
47
|
+
optional: optional,
|
|
48
|
+
anchors: anchors
|
|
49
|
+
)
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def with_given_names(value, optional: false, anchors: [])
|
|
54
|
+
with_attribute(
|
|
55
|
+
name: Yoti::Attribute::GIVEN_NAMES,
|
|
56
|
+
value: value,
|
|
57
|
+
optional: optional,
|
|
58
|
+
anchors: anchors
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def with_family_name(value, optional: false, anchors: [])
|
|
63
|
+
with_attribute(
|
|
64
|
+
name: Yoti::Attribute::FAMILY_NAME,
|
|
65
|
+
value: value,
|
|
66
|
+
optional: optional,
|
|
67
|
+
anchors: anchors
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def with_full_name(value, optional: false, anchors: [])
|
|
72
|
+
with_attribute(
|
|
73
|
+
name: Yoti::Attribute::FULL_NAME,
|
|
74
|
+
value: value,
|
|
75
|
+
optional: optional,
|
|
76
|
+
anchors: anchors
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def with_date_of_birth(date, optional: false, anchors: [])
|
|
81
|
+
with_attribute(
|
|
82
|
+
name: Yoti::Attribute::DATE_OF_BIRTH,
|
|
83
|
+
value: date.to_s,
|
|
84
|
+
optional: optional,
|
|
85
|
+
anchors: anchors
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def with_age_verification(dob:, derivation:, optional: false, anchors: [])
|
|
90
|
+
with_attribute(
|
|
91
|
+
name: Yoti::Attribute::DATE_OF_BIRTH,
|
|
92
|
+
value: dob.to_s,
|
|
93
|
+
derivation: derivation,
|
|
94
|
+
optional: optional,
|
|
95
|
+
anchors: anchors
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def with_gender(value, optional: false, anchors: [])
|
|
100
|
+
with_attribute(
|
|
101
|
+
name: Yoti::Attribute::GENDER,
|
|
102
|
+
value: value,
|
|
103
|
+
optional: optional,
|
|
104
|
+
anchors: anchors
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def with_phone_number(value, optional: false, anchors: [])
|
|
109
|
+
with_attribute(
|
|
110
|
+
name: Yoti::Attribute::PHONE_NUMBER,
|
|
111
|
+
value: value,
|
|
112
|
+
optional: optional,
|
|
113
|
+
anchors: anchors
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def with_nationality(value, optional: false, anchors: [])
|
|
118
|
+
with_attribute(
|
|
119
|
+
name: Yoti::Attribute::NATIONALITY,
|
|
120
|
+
value: value,
|
|
121
|
+
optional: optional,
|
|
122
|
+
anchors: anchors
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def with_postal_address(value, optional: false, anchors: [])
|
|
127
|
+
with_attribute(
|
|
128
|
+
name: Yoti::Attribute::POSTAL_ADDRESS,
|
|
129
|
+
value: value,
|
|
130
|
+
optional: optional,
|
|
131
|
+
anchors: anchors
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def with_structured_postal_address(data, optional: false, anchors: [])
|
|
136
|
+
with_attribute(
|
|
137
|
+
name: Yoti::Attribute::STRUCTURED_POSTAL_ADDRESS,
|
|
138
|
+
value: data.to_json,
|
|
139
|
+
optional: optional,
|
|
140
|
+
anchors: anchors
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def with_selfie(data, optional: false, anchors: [])
|
|
145
|
+
with_attribute(
|
|
146
|
+
name: Yoti::Attribute::SELFIE,
|
|
147
|
+
value: Base64.strict_encode64(data),
|
|
148
|
+
optional: optional,
|
|
149
|
+
anchors: anchors
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def with_email_address(value, optional: false, anchors: [])
|
|
154
|
+
with_attribute(
|
|
155
|
+
name: Yoti::Attribute::EMAIL_ADDRESS,
|
|
156
|
+
value: value,
|
|
157
|
+
optional: optional,
|
|
158
|
+
anchors: anchors
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def with_document_details(value, optional: false, anchors: [])
|
|
163
|
+
with_attribute(
|
|
164
|
+
name: Yoti::Attribute::DOCUMENT_DETAILS,
|
|
165
|
+
value: value,
|
|
166
|
+
optional: optional,
|
|
167
|
+
anchors: anchors
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yoti'
|
|
4
|
+
require 'yoti/configuration'
|
|
5
|
+
require 'yoti/http/signed_request'
|
|
6
|
+
|
|
7
|
+
require_relative 'sandbox_client'
|
|
8
|
+
require_relative 'profile'
|
|
9
|
+
require_relative 'anchor'
|
|
10
|
+
require_relative 'attribute'
|
|
11
|
+
|
|
12
|
+
require 'openssl'
|
|
13
|
+
require 'net/http'
|
|
14
|
+
require 'date'
|
|
15
|
+
require 'securerandom'
|
|
16
|
+
|
|
17
|
+
require 'dotenv'
|
|
18
|
+
Dotenv.load
|
|
19
|
+
|
|
20
|
+
# Singleton for sandbox test resources
|
|
21
|
+
module Sandbox
|
|
22
|
+
class << self
|
|
23
|
+
attr_accessor :sandbox_client
|
|
24
|
+
attr_accessor :dev_key
|
|
25
|
+
attr_accessor :application
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.setup!
|
|
29
|
+
return if application
|
|
30
|
+
|
|
31
|
+
read_dev_key!
|
|
32
|
+
create_application!
|
|
33
|
+
self.sandbox_client = Client.new(
|
|
34
|
+
app_id: application['id'],
|
|
35
|
+
private_key: application['private_key'],
|
|
36
|
+
base_url: ENV['SANDBOX_BASE_URL']
|
|
37
|
+
)
|
|
38
|
+
configure_yoti(
|
|
39
|
+
app_id: sandbox_client.app_id,
|
|
40
|
+
pem: sandbox_client.key.to_pem
|
|
41
|
+
)
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.configure_yoti(app_id:, pem:)
|
|
46
|
+
Yoti.configuration = Yoti::Configuration.new
|
|
47
|
+
Yoti.configuration.client_sdk_id = app_id
|
|
48
|
+
Yoti.configuration.key = pem
|
|
49
|
+
Yoti.configuration.key_file_path = ''
|
|
50
|
+
Yoti.configuration.api_endpoint = "#{ENV['SANDBOX_BASE_URL']}/"
|
|
51
|
+
Yoti::SSL.reload!
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.create_application_uri
|
|
55
|
+
uri = URI(
|
|
56
|
+
"#{ENV['SANDBOX_BASE_URL']}/#{ENV['SANDBOX_ENDPOINT']}?\
|
|
57
|
+
nonce=#{SecureRandom.uuid}×tamp=#{Time.now.to_i}"
|
|
58
|
+
)
|
|
59
|
+
uri.port = 11_443
|
|
60
|
+
uri
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.create_application!
|
|
64
|
+
Yoti.configure do |config|
|
|
65
|
+
config.key_file_path = ENV['SANDBOX_KEY']
|
|
66
|
+
config.client_sdk_id = 'DUMMY'
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
uri = create_application_uri
|
|
70
|
+
payload = { name: "Test Run #{DateTime.now.rfc3339}" }
|
|
71
|
+
|
|
72
|
+
response = Net::HTTP.start(
|
|
73
|
+
uri.hostname,
|
|
74
|
+
uri.port,
|
|
75
|
+
use_ssl: true,
|
|
76
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
|
77
|
+
) do |http|
|
|
78
|
+
unsigned = Net::HTTP::Post.new uri
|
|
79
|
+
unsigned.body = payload.to_json
|
|
80
|
+
signed_request = Yoti::SignedRequest.new(
|
|
81
|
+
unsigned,
|
|
82
|
+
"#{ENV['SANDBOX_ENDPOINT']}?#{uri.query}",
|
|
83
|
+
payload
|
|
84
|
+
).sign
|
|
85
|
+
Yoti::Log.logger.info("Creating application #{signed_request.body}")
|
|
86
|
+
http.request signed_request
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
raise "Create application failed #{response.code}: #{response.body}" unless response.code == '201'
|
|
90
|
+
|
|
91
|
+
self.application = JSON.parse(response.body)
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def self.read_dev_key!
|
|
96
|
+
self.dev_key = OpenSSL::PKey::RSA.new(
|
|
97
|
+
File.read(ENV['SANDBOX_KEY'], encoding: 'utf-8')
|
|
98
|
+
)
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def self.share(profile)
|
|
103
|
+
sandbox_client.setup_sharing_profile profile
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sandbox
|
|
4
|
+
# Client is responsible for setting up test data in the sandbox instance
|
|
5
|
+
class Client
|
|
6
|
+
attr_accessor :app_id
|
|
7
|
+
attr_accessor :key
|
|
8
|
+
attr_accessor :base_url
|
|
9
|
+
|
|
10
|
+
def initialize(app_id:, private_key:, base_url:)
|
|
11
|
+
@app_id = app_id
|
|
12
|
+
@base_url = base_url
|
|
13
|
+
@key = OpenSSL::PKey::RSA.new(Base64.decode64(private_key))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def setup_sharing_profile(profile)
|
|
17
|
+
endpoint = "/apps/#{app_id}/tokens?\
|
|
18
|
+
nonce=#{SecureRandom.uuid}×tamp=#{Time.now.to_i}"
|
|
19
|
+
uri = URI(
|
|
20
|
+
"#{@base_url}/#{endpoint}"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
response = Net::HTTP.start(
|
|
24
|
+
uri.hostname,
|
|
25
|
+
uri.port,
|
|
26
|
+
use_ssl: true,
|
|
27
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
|
28
|
+
) do |http|
|
|
29
|
+
unsigned = Net::HTTP::Post.new uri
|
|
30
|
+
unsigned.body = profile.to_json
|
|
31
|
+
signed_request = Yoti::SignedRequest.new(
|
|
32
|
+
unsigned,
|
|
33
|
+
endpoint,
|
|
34
|
+
profile
|
|
35
|
+
).sign
|
|
36
|
+
http.request signed_request
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
raise "Failed to share profile #{response.code}: #{response.body}" unless response.code == '201'
|
|
40
|
+
|
|
41
|
+
token = JSON.parse(response.body)['token']
|
|
42
|
+
token
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'base64'
|
|
4
|
+
|
|
5
|
+
module Yoti
|
|
6
|
+
module Share
|
|
7
|
+
class Definition
|
|
8
|
+
attr_reader :name
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Constructor
|
|
12
|
+
#
|
|
13
|
+
# @param [String] name
|
|
14
|
+
#
|
|
15
|
+
def initialize(name)
|
|
16
|
+
@name = name
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class AttributeIssuanceDetails
|
|
21
|
+
attr_reader :token
|
|
22
|
+
attr_reader :attributes
|
|
23
|
+
attr_reader :expiry_date
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# Constructor
|
|
27
|
+
#
|
|
28
|
+
# @param [Yoti::Protobuf::Sharepubapi::ThirdPartyAttribute] data_entry
|
|
29
|
+
#
|
|
30
|
+
def initialize(data_entry)
|
|
31
|
+
@token = Base64.encode64(data_entry.issuance_token)
|
|
32
|
+
begin
|
|
33
|
+
@expiry_date = DateTime.parse(data_entry.issuing_attributes.expiry_date)
|
|
34
|
+
rescue ArgumentError
|
|
35
|
+
@expiry_date = nil
|
|
36
|
+
end
|
|
37
|
+
@attributes = data_entry.issuing_attributes.definitions.map do |defn|
|
|
38
|
+
Definition.new(defn.name)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yoti
|
|
4
|
+
module Share
|
|
5
|
+
class ExtraData
|
|
6
|
+
attr_reader :attribute_issuance_details
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# Constructor
|
|
10
|
+
#
|
|
11
|
+
# @param [Yoti::Protobuf::Sharepubapi::ExtraData] proto
|
|
12
|
+
#
|
|
13
|
+
def initialize(proto)
|
|
14
|
+
@attribute_issuance_details = nil
|
|
15
|
+
|
|
16
|
+
proto.list.each do |data_entry|
|
|
17
|
+
if data_entry.type == :THIRD_PARTY_ATTRIBUTE && @attribute_issuance_details.nil?
|
|
18
|
+
attribute = Yoti::Protobuf::Sharepubapi::ThirdPartyAttribute.decode(data_entry.value)
|
|
19
|
+
@attribute_issuance_details = Yoti::Share::AttributeIssuanceDetails.new(attribute)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|