yoti 1.5.0 → 1.6.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|