yoti 1.5.0 → 1.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +17 -0
  3. data/CONTRIBUTING.md +0 -29
  4. data/Gemfile +0 -2
  5. data/README.md +1 -1
  6. data/Rakefile +16 -3
  7. data/lib/yoti.rb +16 -0
  8. data/lib/yoti/activity_details.rb +21 -2
  9. data/lib/yoti/client.rb +2 -1
  10. data/lib/yoti/data_type/age_verification.rb +54 -0
  11. data/lib/yoti/data_type/attribute.rb +3 -0
  12. data/lib/yoti/data_type/base_profile.rb +13 -0
  13. data/lib/yoti/data_type/document_details.rb +88 -0
  14. data/lib/yoti/data_type/profile.rb +76 -0
  15. data/lib/yoti/dynamic_share_service/dynamic_scenario.rb +67 -0
  16. data/lib/yoti/dynamic_share_service/extension/extension.rb +45 -0
  17. data/lib/yoti/dynamic_share_service/extension/location_constraint_extension.rb +88 -0
  18. data/lib/yoti/dynamic_share_service/extension/thirdparty_attribute_extension.rb +119 -0
  19. data/lib/yoti/dynamic_share_service/extension/transactional_flow_extension.rb +47 -0
  20. data/lib/yoti/dynamic_share_service/policy/dynamic_policy.rb +184 -0
  21. data/lib/yoti/dynamic_share_service/policy/source_constraint.rb +88 -0
  22. data/lib/yoti/dynamic_share_service/policy/wanted_anchor.rb +53 -0
  23. data/lib/yoti/dynamic_share_service/policy/wanted_attribute.rb +85 -0
  24. data/lib/yoti/dynamic_share_service/share_url.rb +82 -0
  25. data/lib/yoti/http/profile_request.rb +1 -0
  26. data/lib/yoti/http/request.rb +15 -0
  27. data/lib/yoti/http/signed_request.rb +0 -3
  28. data/lib/yoti/protobuf/main.rb +25 -4
  29. data/lib/yoti/protobuf/sharepubapi/DataEntry_pb.rb +29 -0
  30. data/lib/yoti/protobuf/sharepubapi/ExtraData_pb.rb +19 -0
  31. data/lib/yoti/protobuf/sharepubapi/IssuingAttributes_pb.rb +23 -0
  32. data/lib/yoti/protobuf/sharepubapi/ThirdPartyAttribute_pb.rb +20 -0
  33. data/lib/yoti/share/attribute_issuance_details.rb +43 -0
  34. data/lib/yoti/share/extra_data.rb +25 -0
  35. data/lib/yoti/ssl.rb +8 -0
  36. data/lib/yoti/util/age_processor.rb +4 -0
  37. data/lib/yoti/version.rb +1 -1
  38. data/rubocop.yml +4 -0
  39. data/yoti.gemspec +3 -3
  40. metadata +31 -14
  41. 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}&timestamp=#{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'
@@ -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
 
@@ -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.append value_based_on_content_type(item.data, item.content_type)
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
- encrypted_data = decode_profile(profile_content)
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