yoti 1.5.0 → 1.6.4
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 +4 -4
- data/.github/ISSUE_TEMPLATE.md +17 -0
- data/CONTRIBUTING.md +0 -29
- data/Gemfile +0 -2
- data/README.md +1 -1
- data/Rakefile +16 -3
- data/lib/yoti.rb +16 -0
- data/lib/yoti/activity_details.rb +21 -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 +88 -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 +119 -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 +82 -0
- data/lib/yoti/http/profile_request.rb +1 -0
- data/lib/yoti/http/request.rb +15 -0
- data/lib/yoti/http/signed_request.rb +0 -3
- data/lib/yoti/protobuf/main.rb +25 -4
- 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/share/attribute_issuance_details.rb +43 -0
- data/lib/yoti/share/extra_data.rb +25 -0
- data/lib/yoti/ssl.rb +8 -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 +3 -3
- metadata +31 -14
- data/.travis.yml +0 -17
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yoti
|
4
|
+
module DynamicSharingService
|
5
|
+
# A list of anchors to require for a dynamic share
|
6
|
+
class SourceConstraint
|
7
|
+
DRIVING_LICENCE = 'DRIVING_LICENCE'
|
8
|
+
PASSPORT = 'PASSPORT'
|
9
|
+
NATIONAL_ID = 'NATIONAL_ID'
|
10
|
+
PASS_CARD = 'PASS_CARD'
|
11
|
+
|
12
|
+
SOURCE_CONSTRAINT = 'SOURCE'
|
13
|
+
|
14
|
+
attr_reader :anchors
|
15
|
+
|
16
|
+
def soft_preference
|
17
|
+
return @soft_preference if @soft_preference
|
18
|
+
|
19
|
+
false
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_json(*_args)
|
23
|
+
as_json.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
def as_json(*_args)
|
27
|
+
obj = {
|
28
|
+
type: SOURCE_CONSTRAINT,
|
29
|
+
preferred_sources: {
|
30
|
+
anchors: @anchors.map(&:as_json)
|
31
|
+
}
|
32
|
+
}
|
33
|
+
obj[:preferred_sources][:soft_preference] = @soft_preference unless @soft_preference.nil?
|
34
|
+
obj
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@anchors = []
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.builder
|
42
|
+
SourceConstraintBuilder.new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Builder for SourceConstraint
|
47
|
+
class SourceConstraintBuilder
|
48
|
+
def initialize
|
49
|
+
@constraint = SourceConstraint.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_anchor_by_value(value, sub_type)
|
53
|
+
anchor = WantedAnchor.builder.with_value(value).with_sub_type(sub_type).build
|
54
|
+
with_anchor(anchor)
|
55
|
+
end
|
56
|
+
|
57
|
+
def with_anchor(anchor)
|
58
|
+
@constraint.anchors.push(anchor)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_passport(sub_type = nil)
|
63
|
+
with_anchor_by_value(SourceConstraint::PASSPORT, sub_type)
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_driving_licence(sub_type = nil)
|
67
|
+
with_anchor_by_value(SourceConstraint::DRIVING_LICENCE, sub_type)
|
68
|
+
end
|
69
|
+
|
70
|
+
def with_national_id(sub_type = nil)
|
71
|
+
with_anchor_by_value(SourceConstraint::NATIONAL_ID, sub_type)
|
72
|
+
end
|
73
|
+
|
74
|
+
def with_passcard(sub_type = nil)
|
75
|
+
with_anchor_by_value(SourceConstraint::PASS_CARD, sub_type)
|
76
|
+
end
|
77
|
+
|
78
|
+
def with_soft_preference(preference = true)
|
79
|
+
@constraint.instance_variable_set(:@soft_preference, preference)
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
def build
|
84
|
+
Marshal.load Marshal.dump @constraint
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yoti
|
4
|
+
module DynamicSharingService
|
5
|
+
# A wanted anchor for a source based constraint
|
6
|
+
class WantedAnchor
|
7
|
+
attr_reader :value
|
8
|
+
attr_reader :sub_type
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@value = ''
|
12
|
+
@sub_type = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_json(*_args)
|
16
|
+
as_json.to_json
|
17
|
+
end
|
18
|
+
|
19
|
+
def as_json
|
20
|
+
obj = {
|
21
|
+
name: @value
|
22
|
+
}
|
23
|
+
obj[:sub_type] = @sub_type if @sub_type
|
24
|
+
obj
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.builder
|
28
|
+
WantedAnchorBuilder.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Builder for WantedAnchor
|
33
|
+
class WantedAnchorBuilder
|
34
|
+
def initialize
|
35
|
+
@anchor = WantedAnchor.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def with_value(value)
|
39
|
+
@anchor.instance_variable_set(:@value, value)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def with_sub_type(sub_type = nil)
|
44
|
+
@anchor.instance_variable_set(:@sub_type, sub_type)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def build
|
49
|
+
Marshal.load Marshal.dump @anchor
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yoti
|
4
|
+
module DynamicSharingService
|
5
|
+
# Describes a wanted attribute in a dynamic sharing policy
|
6
|
+
class WantedAttribute
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :derivation
|
9
|
+
attr_reader :constraints
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@constraints = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def accept_self_asserted
|
16
|
+
return true if @accept_self_asserted
|
17
|
+
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_json(*_args)
|
22
|
+
as_json.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
def as_json(*_args)
|
26
|
+
obj = {
|
27
|
+
name: @name
|
28
|
+
}
|
29
|
+
obj[:derivation] = @derivation if derivation
|
30
|
+
obj[:accept_self_asserted] = @accept_self_asserted if accept_self_asserted
|
31
|
+
obj[:constraints] = @constraints.map(&:as_json) unless constraints.empty?
|
32
|
+
obj
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.builder
|
36
|
+
WantedAttributeBuilder.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Builder for WantedAttribute
|
41
|
+
class WantedAttributeBuilder
|
42
|
+
def initialize
|
43
|
+
@attribute = WantedAttribute.new
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# @param [String] name
|
48
|
+
#
|
49
|
+
def with_name(name)
|
50
|
+
@attribute.instance_variable_set(:@name, name)
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# @param [String] derivation
|
56
|
+
#
|
57
|
+
def with_derivation(derivation)
|
58
|
+
@attribute.instance_variable_set(:@derivation, derivation)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# @param constraint Constraint to apply to the requested attribute
|
64
|
+
#
|
65
|
+
def with_constraint(constraint)
|
66
|
+
@attribute.constraints.push(constraint)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# @param [Bool] accept
|
72
|
+
#
|
73
|
+
def with_accept_self_asserted(accept = true)
|
74
|
+
@attribute.instance_variable_set(:@accept_self_asserted, accept)
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def build
|
79
|
+
raise 'Attribute name missing' if @attribute.name.nil? || @attribute.name == ''
|
80
|
+
|
81
|
+
Marshal.load Marshal.dump @attribute
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Yoti
|
6
|
+
module DynamicSharingService
|
7
|
+
class Share
|
8
|
+
attr_reader :share_url, :ref_id
|
9
|
+
|
10
|
+
def initialize(data)
|
11
|
+
@share_url = data['qrcode']
|
12
|
+
@ref_id = data['ref_id']
|
13
|
+
end
|
14
|
+
end
|
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}×tamp=#{Time.now.to_i}"
|
22
|
+
end
|
23
|
+
|
24
|
+
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
|
44
|
+
|
45
|
+
create_share_url_parse_response response
|
46
|
+
end
|
47
|
+
|
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
|
51
|
+
when '400'
|
52
|
+
raise InvalidDataError
|
53
|
+
when '404'
|
54
|
+
raise ApplicationNotFoundError
|
55
|
+
else
|
56
|
+
raise UnknownHTTPError, response.code
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Share.new JSON.parse response.body
|
61
|
+
end
|
62
|
+
|
63
|
+
class InvalidDataError < StandardError
|
64
|
+
def initialize(msg = 'JSON is incorrect, contains invalid data')
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class ApplicationNotFoundError < StandardError
|
70
|
+
def initialize(msg = 'Application was not found')
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class UnknownHTTPError < StandardError
|
76
|
+
def initialize(code = nil, msg = 'Unknown HTTP Error')
|
77
|
+
msg = "#{msg}: #{code}" unless code.nil?
|
78
|
+
super msg
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -15,6 +15,7 @@ module Yoti
|
|
15
15
|
|
16
16
|
def request
|
17
17
|
yoti_request = Yoti::Request.new
|
18
|
+
yoti_request.add_header('X-Yoti-Auth-Key', Yoti::SSL.auth_key_from_pem)
|
18
19
|
yoti_request.encrypted_connect_token = @encrypted_connect_token
|
19
20
|
yoti_request.http_method = 'GET'
|
20
21
|
yoti_request.endpoint = 'profile'
|
data/lib/yoti/http/request.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
1
3
|
module Yoti
|
2
4
|
# Manage the API's HTTPS requests
|
3
5
|
class Request
|
@@ -14,6 +16,15 @@ module Yoti
|
|
14
16
|
# @return [Hash] the body sent with the request
|
15
17
|
attr_accessor :payload
|
16
18
|
|
19
|
+
def initialize
|
20
|
+
@headers = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# Adds a HTTP header to the request
|
24
|
+
def add_header(header, value)
|
25
|
+
@headers[header] = value
|
26
|
+
end
|
27
|
+
|
17
28
|
# Makes a HTTP request after signing the headers
|
18
29
|
# @return [Hash] the body from the HTTP request
|
19
30
|
def body
|
@@ -51,6 +62,10 @@ module Yoti
|
|
51
62
|
raise RequestError, "Request method not allowed: #{@http_method}"
|
52
63
|
end
|
53
64
|
|
65
|
+
@headers.each do |header, value|
|
66
|
+
http_req[header] = value
|
67
|
+
end
|
68
|
+
|
54
69
|
http_req
|
55
70
|
end
|
56
71
|
|
@@ -11,12 +11,9 @@ module Yoti
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def sign
|
14
|
-
@http_req['X-Yoti-Auth-Key'] = @auth_key
|
15
14
|
@http_req['X-Yoti-Auth-Digest'] = message_signature
|
16
15
|
@http_req['X-Yoti-SDK'] = Yoti.configuration.sdk_identifier
|
17
16
|
@http_req['X-Yoti-SDK-Version'] = "#{Yoti.configuration.sdk_identifier}-#{Yoti::VERSION}"
|
18
|
-
@http_req['Content-Type'] = 'application/json'
|
19
|
-
@http_req['Accept'] = 'application/json'
|
20
17
|
@http_req
|
21
18
|
end
|
22
19
|
|
data/lib/yoti/protobuf/main.rb
CHANGED
@@ -6,6 +6,9 @@ require 'json'
|
|
6
6
|
require_relative 'attrpubapi/List_pb.rb'
|
7
7
|
require_relative 'compubapi/EncryptedData_pb.rb'
|
8
8
|
require_relative 'compubapi/SignedTimestamp_pb.rb'
|
9
|
+
require_relative 'sharepubapi/ExtraData_pb.rb'
|
10
|
+
require_relative 'sharepubapi/IssuingAttributes_pb.rb'
|
11
|
+
require_relative 'sharepubapi/ThirdPartyAttribute_pb.rb'
|
9
12
|
|
10
13
|
module Yoti
|
11
14
|
module Protobuf
|
@@ -40,12 +43,20 @@ module Yoti
|
|
40
43
|
decipher_profile(receipt['profile_content'], receipt['wrapped_receipt_key'])
|
41
44
|
end
|
42
45
|
|
46
|
+
def extra_data(receipt)
|
47
|
+
return nil if receipt['extra_data_content'].nil? || receipt['extra_data_content'] == ''
|
48
|
+
|
49
|
+
decipher_extra_data(receipt['extra_data_content'], receipt['wrapped_receipt_key'])
|
50
|
+
end
|
51
|
+
|
43
52
|
def attribute_list(data)
|
44
53
|
Yoti::Protobuf::Attrpubapi::AttributeList.decode(data)
|
45
54
|
end
|
46
55
|
|
47
56
|
def value_based_on_attribute_name(value, attr_name)
|
48
57
|
case attr_name
|
58
|
+
when Yoti::Attribute::DOCUMENT_DETAILS
|
59
|
+
Yoti::DocumentDetails.new(value)
|
49
60
|
when Yoti::Attribute::DOCUMENT_IMAGES
|
50
61
|
raise(TypeError, 'Document Images could not be decoded') unless value.is_a?(Yoti::MultiValue)
|
51
62
|
|
@@ -83,7 +94,7 @@ module Yoti
|
|
83
94
|
proto_multi_value = Yoti::Protobuf::Attrpubapi::MultiValue.decode(value)
|
84
95
|
items = []
|
85
96
|
proto_multi_value.values.each do |item|
|
86
|
-
items
|
97
|
+
items << value_based_on_content_type(item.data, item.content_type)
|
87
98
|
end
|
88
99
|
MultiValue.new(items)
|
89
100
|
end
|
@@ -100,11 +111,21 @@ module Yoti
|
|
100
111
|
end
|
101
112
|
|
102
113
|
def decipher_profile(profile_content, wrapped_key)
|
103
|
-
|
104
|
-
unwrapped_key = Yoti::SSL.decrypt_token(wrapped_key)
|
105
|
-
decrypted_data = Yoti::SSL.decipher(unwrapped_key, encrypted_data.iv, encrypted_data.cipher_text)
|
114
|
+
decrypted_data = decipher_data(profile_content, wrapped_key)
|
106
115
|
Protobuf.attribute_list(decrypted_data)
|
107
116
|
end
|
117
|
+
|
118
|
+
def decipher_extra_data(extra_data_content, wrapped_key)
|
119
|
+
decrypted_data = decipher_data(extra_data_content, wrapped_key)
|
120
|
+
proto = Yoti::Protobuf::Sharepubapi::ExtraData.decode(decrypted_data)
|
121
|
+
Share::ExtraData.new(proto)
|
122
|
+
end
|
123
|
+
|
124
|
+
def decipher_data(encrypted_content, wrapped_key)
|
125
|
+
encrypted_data = decode_profile(encrypted_content)
|
126
|
+
unwrapped_key = Yoti::SSL.decrypt_token(wrapped_key)
|
127
|
+
Yoti::SSL.decipher(unwrapped_key, encrypted_data.iv, encrypted_data.cipher_text)
|
128
|
+
end
|
108
129
|
end
|
109
130
|
end
|
110
131
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: DataEntry.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
7
|
+
add_message "sharepubapi_v1.DataEntry" do
|
8
|
+
optional :type, :enum, 1, "sharepubapi_v1.DataEntry.Type"
|
9
|
+
optional :value, :bytes, 2
|
10
|
+
end
|
11
|
+
add_enum "sharepubapi_v1.DataEntry.Type" do
|
12
|
+
value :UNDEFINED, 0
|
13
|
+
value :INVOICE, 1
|
14
|
+
value :PAYMENT_TRANSACTION, 2
|
15
|
+
value :LOCATION, 3
|
16
|
+
value :TRANSACTION, 4
|
17
|
+
value :AGE_VERIFICATION_SECRET, 5
|
18
|
+
value :THIRD_PARTY_ATTRIBUTE, 6
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Yoti
|
23
|
+
module Protobuf
|
24
|
+
module Sharepubapi
|
25
|
+
DataEntry = Google::Protobuf::DescriptorPool.generated_pool.lookup("sharepubapi_v1.DataEntry").msgclass
|
26
|
+
DataEntry::Type = Google::Protobuf::DescriptorPool.generated_pool.lookup("sharepubapi_v1.DataEntry.Type").enummodule
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|