yoti 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e50de3d515cbe593c45eee88ea0d684d0a1eafa59d0df31e4f52977f802a0773
4
- data.tar.gz: 7e3aad79f573318e00328341abb2e162b8d438c232b488eb6bade1ac8b38151a
3
+ metadata.gz: 00ff1a296c371b2d5e94ccd5e0951963ff8cbcf9676717724369567e39ef1652
4
+ data.tar.gz: e80cb02c9f2cd4b9fdab9ac59de05aeee58cdc1066229a0bde35f4310052d562
5
5
  SHA512:
6
- metadata.gz: 2c229fc49f7ca7bb4b3868395dd3e01742b08b6371eab0e9c8f085ac23e2c653260b6e06e5f858a59e596e8cec4b2d541f008ea391a0bec9e8388cd29f4bca8f
7
- data.tar.gz: df7636a39d76928759337e949384c9e118a299da275f06aa47918819aa9012795bd6a494056b7bb49825dc920677c3e8db9385d339557436c4361f242cd87b0c
6
+ metadata.gz: 74652cb02c63be28c4296c47e073f756baf3836289cf71f646949a36d870716a7851dbe2c4c78f9fb3b771e08213d6035e1bb2508a0a4fbae340d1964d4754a5
7
+ data.tar.gz: '019dd78cc18bdfeb6bb2b852cd3ae8331062f4a5d7af312882aa143818af75ea7fc7e15e3dddf96d30c32b208c13a56327eeebc509415502a888442bd45e7b4b'
data/README.md CHANGED
@@ -19,7 +19,7 @@ Welcome to the Yoti Ruby SDK. This repository contains the tools you need to qui
19
19
  ## An Architectural view
20
20
 
21
21
  To integrate your application with Yoti, your back-end must expose a GET endpoint that Yoti will use to forward tokens.
22
- The endpoint is configured in the [Yoti Dashboard](https://www.yoti.com/dashboard) where you create/update your application. To see an example of how this is configured, see the [Running the Examples](#running-the-examples) section.
22
+ The endpoint is configured in the [Yoti Hub](https://hub.yoti.com) where you create/update your application. To see an example of how this is configured, see the [Running the Examples](#running-the-examples) section.
23
23
 
24
24
  The image below shows how your application back-end and Yoti integrate into the context of a Login flow.
25
25
  Yoti SDK carries out for you steps 6, 7, 8 and the profile decryption in step 9.
@@ -98,7 +98,7 @@ end
98
98
 
99
99
  Make sure the following environment variables can be accessed by your app:
100
100
 
101
- `YOTI_CLIENT_SDK_ID` - found on the Key settings page on your application dashboard
101
+ `YOTI_CLIENT_SDK_ID` - found on the Key settings page on your Yoti Hub
102
102
 
103
103
  `YOTI_KEY_FILE_PATH` - the full path to your security key downloaded from the *Keys* settings page (e.g. /Users/developer/access-security.pem)
104
104
 
@@ -161,7 +161,7 @@ else
161
161
  end
162
162
  ```
163
163
 
164
- The `profile` object provides a set of attributes corresponding to user attributes. Whether the attributes are present or not depends on the settings you have applied to your app on Yoti Dashboard.
164
+ The `profile` object provides a set of attributes corresponding to user attributes. Whether the attributes are present or not depends on the settings you have applied to your app on Yoti Hub.
165
165
 
166
166
  ### Handling Users
167
167
 
@@ -194,17 +194,19 @@ Where `your_user_search_function` is a piece of logic in your app that is suppos
194
194
  You can retrieve the sources and verifiers for each attribute as follows:
195
195
 
196
196
  ```ruby
197
- given_names_sources = profile.given_names.sources // list of anchors
198
- given_names_verifiers = profile.given_names.verifiers // list of anchors
197
+ given_names_sources = profile.given_names.sources # list of anchors
198
+ given_names_verifiers = profile.given_names.verifiers # list of anchors
199
+ given_names_anchors = profile.given_names.anchors # list of anchors
199
200
  ```
200
201
  You can also retrieve further properties from these respective anchors in the following way:
201
202
 
202
203
  ```ruby
203
- // Retrieving properties of the first anchor
204
- value = given_names_sources[0].value // string
205
- sub_type = given_names_sources[0].sub_type // string
206
- time_stamp = given_names_sources[0].signed_time_stamp.time_stamp // DateTime object
207
- origin_server_certs = given_names_sources[0].origin_server_certs // list of X509 certificates
204
+ # Retrieving properties of the first anchor
205
+ type = given_names_sources[0].type # string
206
+ value = given_names_sources[0].value # string
207
+ sub_type = given_names_sources[0].sub_type # string
208
+ time_stamp = given_names_sources[0].signed_time_stamp.time_stamp # DateTime object
209
+ origin_server_certs = given_names_sources[0].origin_server_certs # list of X509 certificates
208
210
  ```
209
211
 
210
212
  In case you want to prove the sources and verifiers of the helper`ActivityDetails.age_verified` on `Age Over 18` set as age derivation, please retrieve it's original attribute from the profile as follow:
@@ -213,6 +215,7 @@ In case you want to prove the sources and verifiers of the helper`ActivityDetail
213
215
  age_attribute = profile.get_attribute('age_over:18')
214
216
  sources = age_attribute.sources
215
217
  verifiers = age_attribute.verifiers
218
+ anchors = age_attribute.anchors
216
219
  ```
217
220
 
218
221
  ## AML Integration
@@ -225,11 +228,11 @@ Yoti will provide a boolean result on the following checks:
225
228
  * Fraud list - Verify against US Social Security Administration Fraud (SSN Fraud) list
226
229
  * Watch list - Verify against watch lists from the Office of Foreign Assets Control
227
230
 
228
- To use this functionality you must ensure your application is assigned to your organisation in the Yoti Dashboard - please see [here](https://www.yoti.com/developers/documentation/#1-creating-an-organisation) for further information.
231
+ To use this functionality you must ensure your application is assigned to your organisation in the Yoti Hub - please see [here](https://developers.yoti.com/yoti-app-integration/yoti-app-integration#step-1-creating-an-organisation) for further information.
229
232
 
230
233
  For the AML check you will need to provide the following:
231
234
 
232
- * Data provided by Yoti (please ensure you have selected the Given name(s) and Family name attributes from the Data tab in the Yoti Dashboard)
235
+ * Data provided by Yoti (please ensure you have selected the Given name(s) and Family name attributes for your scenario on the Yoti Hub)
233
236
  * Given name(s)
234
237
  * Family name
235
238
  * Data that must be collected from the user:
@@ -266,7 +269,7 @@ The examples can be found in the [examples folder](examples).
266
269
 
267
270
  ### Ruby on Rails
268
271
 
269
- 1. Create your application in the [Yoti Dashboard](https://www.yoti.com/dashboard/applications)
272
+ 1. Create your application in the [Yoti Hub](https://hub.yoti.com)
270
273
  1. Set the application domain of your app to `localhost:3000`
271
274
  1. Set the scenario callback URL to `/profile`
272
275
  1. Rename the [.env.example](examples/rails/.env.example) file to `.env`
@@ -282,7 +285,7 @@ Visiting `https://localhost:3001/` should show a Yoti Connect button
282
285
 
283
286
  ### Sinatra
284
287
 
285
- 1. Create your application in the [Yoti Dashboard](https://www.yoti.com/dashboard/applications)
288
+ 1. Create your application in the [Yoti Hub](https://hub.yoti.com)
286
289
  1. Set the application domain of your app to `localhost:4567`
287
290
  1. Set the scenario callback URL to `/profile`
288
291
  1. Rename the [.env.example](examples/sinatra/.env.example) file to `.env`
@@ -307,6 +310,8 @@ Visiting `https://localhost:4567/` should show a Yoti Connect button
307
310
  * Activity Details
308
311
  * [X] Remember Me ID `remember_me_id`
309
312
  * [X] Parent Remember Me ID `parent_remember_me_id`
313
+ * [X] Receipt ID `receipt_id`
314
+ * [X] Timestamp `timestamp`
310
315
  * [X] Base64 Selfie URI `base64_selfie_uri`
311
316
  * [X] Age verified `age_verified`
312
317
  * [X] Profile `profile`
@@ -318,8 +323,14 @@ Visiting `https://localhost:4567/` should show a Yoti Connect button
318
323
  * [X] Email Address `email_address`
319
324
  * [X] Age / Date of Birth `date_of_birth`
320
325
  * [X] Address `postal_address`
326
+ * [X] Structured Postal Address `structured_postal_address`
321
327
  * [X] Gender `gender`
322
328
  * [X] Nationality `nationality`
329
+ * [X] Application Profile `application_profile`
330
+ * [X] Name `name`
331
+ * [X] URL `url`
332
+ * [X] Logo `logo`
333
+ * [X] Receipt Background Color `receipt_bgcolor`
323
334
 
324
335
  ## Support
325
336
 
data/lib/yoti.rb CHANGED
@@ -12,6 +12,8 @@ require_relative 'yoti/http/profile_request'
12
12
  require_relative 'yoti/http/request'
13
13
 
14
14
  require_relative 'yoti/data_type/anchor'
15
+ require_relative 'yoti/data_type/base_profile'
16
+ require_relative 'yoti/data_type/application_profile'
15
17
  require_relative 'yoti/data_type/profile'
16
18
  require_relative 'yoti/data_type/attribute'
17
19
  require_relative 'yoti/data_type/signed_time_stamp'
@@ -1,69 +1,167 @@
1
1
  require 'net/http'
2
2
 
3
3
  module Yoti
4
- # Encapsulates the user profile data
4
+ #
5
+ # Details of an activity between a user and the application.
6
+ #
5
7
  class ActivityDetails
6
- # @return [String] the outcome of the profile request, eg: SUCCESS
8
+ #
9
+ # The outcome of the profile request, eg: SUCCESS
10
+ #
11
+ # @return [String]
12
+ #
7
13
  attr_reader :outcome
8
14
 
15
+ #
9
16
  # @deprecated replaced by :remember_me_id
10
- # @return [String] the Yoti ID
17
+ #
18
+ # @return [String]
19
+ #
11
20
  attr_reader :user_id
12
21
 
13
- # @return [String] the Remember Me ID
22
+ #
23
+ # Return the Remember Me ID, which is a unique, stable identifier for
24
+ # a user in the context of an application.
25
+ #
26
+ # You can use it to identify returning users. This value will be different
27
+ # for the same user in different applications.
28
+ #
29
+ # @return [String]
30
+ #
14
31
  attr_reader :remember_me_id
15
32
 
16
- # @return [String] the Parent Remember Me ID
33
+ #
34
+ # Return the Parent Remember Me ID, which is a unique, stable identifier for a
35
+ # user in the context of an organisation.
36
+ #
37
+ # You can use it to identify returning users. This value is consistent for a
38
+ # given user across different applications belonging to a single organisation.
39
+ #
40
+ # @return [String]
41
+ #
17
42
  attr_reader :parent_remember_me_id
18
43
 
19
- # @return [Hash] the decoded profile attributes
44
+ #
45
+ # The decoded profile attributes
46
+ #
47
+ # @deprecated replaced by :profile
48
+ #
49
+ # @return [Hash]
50
+ #
20
51
  attr_reader :user_profile
21
52
 
22
- # @return [String] the selfie in base64 format
53
+ #
54
+ # Base64 encoded selfie image
55
+ #
56
+ # @return [String]
57
+ #
23
58
  attr_reader :base64_selfie_uri
24
59
 
25
- # @return [Boolean] the age under/over attribute
60
+ #
61
+ # The age under/over attribute
62
+ #
63
+ # @return [Boolean]
64
+ #
26
65
  attr_reader :age_verified
27
66
 
67
+ #
68
+ # Receipt ID identifying a completed activity
69
+ #
70
+ # @return [String]
71
+ #
72
+ attr_reader :receipt_id
73
+
74
+ #
75
+ # Time and date of the sharing activity
76
+ #
77
+ # @return [Time]
78
+ #
79
+ attr_reader :timestamp
80
+
81
+ #
28
82
  # @param receipt [Hash] the receipt from the API request
29
83
  # @param decrypted_profile [Object] Protobuf AttributeList decrypted object containing the profile attributes
30
- def initialize(receipt, decrypted_profile = nil)
84
+ #
85
+ def initialize(receipt, decrypted_profile = nil, decrypted_application_profile = nil)
31
86
  @remember_me_id = receipt['remember_me_id']
32
87
  @user_id = @remember_me_id
88
+ @receipt_id = receipt['receipt_id']
33
89
  @parent_remember_me_id = receipt['parent_remember_me_id']
34
90
  @outcome = receipt['sharing_outcome']
35
- @user_profile = {}
36
- @extended_profile = {}
37
- process_decrypted_profile(decrypted_profile)
91
+ @timestamp = receipt['timestamp'] ? Time.parse(receipt['timestamp']) : nil
92
+ @extended_user_profile = process_decrypted_profile(decrypted_profile)
93
+ @extended_application_profile = process_decrypted_profile(decrypted_application_profile)
94
+ @user_profile = @extended_user_profile.map do |name, attribute|
95
+ [name, attribute.value]
96
+ end.to_h
38
97
  end
39
98
 
40
- # @return [Hash] a JSON of the address
99
+ #
100
+ # The user's structured postal address as JSON
101
+ #
102
+ # @deprecated replaced by Profile.structured_postal_address
103
+ #
104
+ # @return [Hash]
105
+ #
41
106
  def structured_postal_address
42
- @user_profile['structured_postal_address']
107
+ user_profile['structured_postal_address']
43
108
  end
44
109
 
45
- # @return [Profile] of Yoti user
110
+ #
111
+ # The user profile with shared attributes and anchor information, returned
112
+ # by Yoti if the request was successful
113
+ #
114
+ # @return [Profile]
115
+ #
46
116
  def profile
47
- Yoti::Profile.new(@extended_profile)
117
+ Yoti::Profile.new(@extended_user_profile)
118
+ end
119
+
120
+ #
121
+ # Profile of an application, with convenience methods to access well-known attributes
122
+ #
123
+ # @return [ApplicationProfile]
124
+ #
125
+ def application_profile
126
+ Yoti::ApplicationProfile.new(@extended_application_profile)
48
127
  end
49
128
 
50
129
  protected
51
130
 
131
+ #
132
+ # Process the decrypted profile into key-value hash
133
+ #
134
+ # @param [Yoti::Protobuf::Attrpubapi::AttributeList] decrypted_profile
135
+ #
136
+ # @return [Hash]
137
+ #
52
138
  def process_decrypted_profile(decrypted_profile)
53
- return nil unless decrypted_profile.is_a?(Object)
54
- return nil unless decrypted_profile.respond_to?(:attributes)
139
+ return {} unless decrypted_profile.is_a?(Object)
140
+ return {} unless decrypted_profile.respond_to?(:attributes)
55
141
 
142
+ profile_data = {}
56
143
  decrypted_profile.attributes.each do |attribute|
57
144
  begin
58
- process_attribute(attribute)
145
+ profile_data[attribute.name] = process_attribute(attribute)
59
146
  process_age_verified(attribute)
60
147
  rescue StandardError => e
61
148
  Yoti::Log.logger.warn("#{e.message} (Attribute: #{attribute.name})")
62
149
  end
63
150
  end
151
+ profile_data
64
152
  end
65
153
 
154
+ #
155
+ # Converts protobuf attribute into Attribute
156
+ #
157
+ # @param [Yoti::Protobuf::Attrpubapi::Attribute] attribute
158
+ #
159
+ # @return [Attribute, nil]
160
+ #
66
161
  def process_attribute(attribute)
162
+ # Application Logo can be empty, return nil when this occurs.
163
+ return nil if attribute.name == Yoti::Attribute::APPLICATION_LOGO && attribute.value == ''
164
+
67
165
  attr_value = Yoti::Protobuf.value_based_on_content_type(attribute.value, attribute.content_type)
68
166
  attr_value = Yoti::Protobuf.value_based_on_attribute_name(attr_value, attribute.name)
69
167
 
@@ -74,10 +172,14 @@ module Yoti
74
172
  end
75
173
 
76
174
  anchors_list = Yoti::AnchorProcessor.new(attribute.anchors).process
77
- @extended_profile[attribute.name] = Yoti::Attribute.new(attribute.name, attr_value, anchors_list['sources'], anchors_list['verifiers'])
78
- @user_profile[attribute.name] = attr_value
175
+ Yoti::Attribute.new(attribute.name, attr_value, anchors_list['sources'], anchors_list['verifiers'], anchors_list)
79
176
  end
80
177
 
178
+ #
179
+ # Processes age verification
180
+ #
181
+ # @param [Yoti::Protobuf::Attrpubapi::Attribute] attribute
182
+ #
81
183
  def process_age_verified(attribute)
82
184
  @age_verified = attribute.value == 'true' if Yoti::AgeProcessor.is_age_verification(attribute.name)
83
185
  end
data/lib/yoti/client.rb CHANGED
@@ -1,20 +1,24 @@
1
1
  module Yoti
2
+ #
2
3
  # Handles all the publicly accesible Yoti methods for
3
4
  # geting data using an encrypted connect token
5
+ #
4
6
  module Client
7
+ #
5
8
  # Performs all the steps required to get the decrypted profile from an API request
9
+ #
6
10
  # @param encrypted_connect_token [String] token provided as a base 64 string
11
+ #
7
12
  # @return [Object] an ActivityDetails instance encapsulating the user profile
13
+ #
8
14
  def self.get_activity_details(encrypted_connect_token)
9
15
  receipt = Yoti::ProfileRequest.new(encrypted_connect_token).receipt
10
- encrypted_data = Protobuf.current_user(receipt)
16
+ user_profile = Protobuf.user_profile(receipt)
17
+ application_profile = Protobuf.application_profile(receipt)
11
18
 
12
- return ActivityDetails.new(receipt) if encrypted_data.nil?
19
+ return ActivityDetails.new(receipt) if user_profile.nil?
13
20
 
14
- unwrapped_key = Yoti::SSL.decrypt_token(receipt['wrapped_receipt_key'])
15
- decrypted_data = Yoti::SSL.decipher(unwrapped_key, encrypted_data.iv, encrypted_data.cipher_text)
16
- decrypted_profile = Protobuf.attribute_list(decrypted_data)
17
- ActivityDetails.new(receipt, decrypted_profile)
21
+ ActivityDetails.new(receipt, user_profile, application_profile)
18
22
  end
19
23
 
20
24
  def self.aml_check(aml_profile)
@@ -3,6 +3,8 @@ module Yoti
3
3
  attr_accessor :client_sdk_id, :key_file_path, :key, :sdk_identifier,
4
4
  :api_url, :api_port, :api_version
5
5
 
6
+ attr_writer :api_endpoint
7
+
6
8
  # Set config variables by using a configuration block
7
9
  def initialize
8
10
  @client_sdk_id = ''
@@ -1,13 +1,67 @@
1
1
  module Yoti
2
2
  # Encapsulates attribute anchor
3
3
  class Anchor
4
- attr_reader :value, :sub_type, :signed_time_stamp, :origin_server_certs
4
+ #
5
+ # Gets the value of the given anchor.
6
+ #
7
+ # Among possible options for SOURCE are "USER_PROVIDED", "PASSPORT",
8
+ # "DRIVING_LICENCE", "NATIONAL_ID" and "PASSCARD".
9
+ #
10
+ # Among possible options for VERIFIER are "YOTI_ADMIN", "YOTI_IDENTITY",
11
+ # "YOTI_OTP", "PASSPORT_NFC_SIGNATURE", "ISSUING_AUTHORITY" and
12
+ # "ISSUING_AUTHORITY_PKI".
13
+ #
14
+ # @return [String]
15
+ #
16
+ attr_reader :value
5
17
 
6
- def initialize(value, sub_type, signed_time_stamp, origin_server_certs)
18
+ #
19
+ # SubType is an indicator of any specific processing method, or subcategory,
20
+ # pertaining to an artifact.
21
+ #
22
+ # Examples:
23
+ # - For a passport, this would be either "NFC" or "OCR".
24
+ # - For a national ID, this could be "AADHAAR".
25
+ #
26
+ # @return [String]
27
+ #
28
+ attr_reader :sub_type
29
+
30
+ #
31
+ # Timestamp applied at the time of Anchor creation.
32
+ #
33
+ # @return [Yoti::SignedTimeStamp]
34
+ #
35
+ attr_reader :signed_time_stamp
36
+
37
+ #
38
+ # Certificate chain generated when this Anchor was created (attribute value was
39
+ # sourced or verified). Securely encodes the Anchor type and value.
40
+ #
41
+ # @return [Array<OpenSSL::X509::Certificate>]
42
+ #
43
+ attr_reader :origin_server_certs
44
+
45
+ #
46
+ # Gets the type of the given anchor.
47
+ #
48
+ # @return [String]
49
+ #
50
+ attr_reader :type
51
+
52
+ #
53
+ # @param [String] value
54
+ # @param [String] sub_type
55
+ # @param [Yoti::SignedTimeStamp] signed_time_stamp
56
+ # @param [Array<OpenSSL::X509::Certificate>] origin_server_certs
57
+ # @param [String] type
58
+ #
59
+ def initialize(value, sub_type, signed_time_stamp, origin_server_certs, type = nil)
7
60
  @value = value
8
61
  @sub_type = sub_type
9
62
  @signed_time_stamp = signed_time_stamp
10
63
  @origin_server_certs = origin_server_certs
64
+ @type = type
11
65
  end
12
66
  end
13
67
  end
@@ -0,0 +1,43 @@
1
+ module Yoti
2
+ #
3
+ # Profile of an application with convenience methods to access well-known attributes.
4
+ #
5
+ class ApplicationProfile < BaseProfile
6
+ #
7
+ # The name of the application.
8
+ #
9
+ # @return [Attribute, nil]
10
+ #
11
+ def name
12
+ get_attribute(Yoti::Attribute::APPLICATION_NAME)
13
+ end
14
+
15
+ #
16
+ # The URL where the application is available at.
17
+ #
18
+ # @return [Attribute, nil]
19
+ #
20
+ def url
21
+ get_attribute(Yoti::Attribute::APPLICATION_URL)
22
+ end
23
+
24
+ #
25
+ # The logo of the application that will be displayed to users that perform a share with it.
26
+ #
27
+ # @return [Attribute, nil]
28
+ #
29
+ def logo
30
+ get_attribute(Yoti::Attribute::APPLICATION_LOGO)
31
+ end
32
+
33
+ #
34
+ # The background color that will be displayed on each receipt the user gets, as a result
35
+ # of a share with the application.
36
+ #
37
+ # @return [Attribute, nil]
38
+ #
39
+ def receipt_bgcolor
40
+ get_attribute(Yoti::Attribute::APPLICATION_RECEIPT_BGCOLOR)
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,13 @@
1
1
  module Yoti
2
- # Encapsulates profile attribute
2
+ #
3
+ # A class to represent a Yoti attribute.
4
+ #
5
+ # A Yoti attribute consists of the attribute name, an associated
6
+ # attribute value, and a list of Anchors from Yoti.
7
+ #
8
+ # It may hold one or more anchors, which specify how an attribute has been provided
9
+ # and how it has been verified within the Yoti platform.
10
+ #
3
11
  class Attribute
4
12
  FAMILY_NAME = 'family_name'
5
13
  GIVEN_NAMES = 'given_names'
@@ -13,14 +21,79 @@ module Yoti
13
21
  POSTAL_ADDRESS = 'postal_address'
14
22
  STRUCTURED_POSTAL_ADDRESS = 'structured_postal_address'
15
23
  DOCUMENT_IMAGES = 'document_images'
24
+ APPLICATION_NAME = 'application_name'
25
+ APPLICATION_LOGO = 'application_logo'
26
+ APPLICATION_URL = 'application_url'
27
+ APPLICATION_RECEIPT_BGCOLOR = 'application_receipt_bgcolor'
16
28
 
17
- attr_reader :name, :value, :sources, :verifiers
29
+ #
30
+ # Gets the name of the attribute.
31
+ #
32
+ # @return [String]
33
+ #
34
+ attr_reader :name
18
35
 
19
- def initialize(name, value, sources, verifiers)
36
+ #
37
+ # Retrieves the value of an attribute. If this is null, the default value for
38
+ # the type is returned.
39
+ #
40
+ # @return [String]
41
+ #
42
+ attr_reader :value
43
+
44
+ #
45
+ # Sources are a subset of the anchors associated with an attribute, where the
46
+ # anchor type is SOURCE.
47
+ #
48
+ # @return [Array<Yoti::Anchor>]
49
+ #
50
+ attr_reader :sources
51
+
52
+ #
53
+ # Verifiers are a subset of the anchors associated with an attribute, where the
54
+ # anchor type is VERIFIER.
55
+ #
56
+ # @return [Array<Yoti::Anchor>]
57
+ #
58
+ attr_reader :verifiers
59
+
60
+ #
61
+ # Get the anchors for an attribute. If an attribute has only one SOURCE
62
+ # Anchor with the value set to "USER_PROVIDED" and zero VERIFIER Anchors,
63
+ # then the attribute is a self-certified one.
64
+ #
65
+ # @return [Array<Yoti::Anchor>]
66
+ #
67
+ attr_reader :anchors
68
+
69
+ #
70
+ # @param [String] name
71
+ # @param [String] value
72
+ # @param [Array<Yoti::Anchor>] sources
73
+ # @param [Array<Yoti::Anchor>] verifiers
74
+ # @param [Hash<String => Array>] anchors_list
75
+ #
76
+ def initialize(name, value, sources, verifiers, anchors_list = {})
20
77
  @name = name
21
78
  @value = value
22
79
  @sources = sources
23
80
  @verifiers = verifiers
81
+ @anchors = process_anchors_list(anchors_list)
82
+ end
83
+
84
+ private
85
+
86
+ #
87
+ # Flattens anchor lists into single array.
88
+ #
89
+ # @param [Hash<String => Array>] anchors_list
90
+ #
91
+ # @return [Array<Yoti::Anchor>] <description>
92
+ #
93
+ def process_anchors_list(anchors_list)
94
+ anchors = []
95
+ anchors_list.each { |_type, list| anchors += list }
96
+ anchors
24
97
  end
25
98
  end
26
99
  end
@@ -0,0 +1,33 @@
1
+ module Yoti
2
+ #
3
+ # Encapsulates Yoti user profile
4
+ #
5
+ class BaseProfile
6
+ #
7
+ # Return all attributes for the profile.
8
+ #
9
+ # @return [Hash{String => Yoti::Attribute}]
10
+ #
11
+ attr_reader :attributes
12
+
13
+ #
14
+ # @param [Hash{String => Yoti::Attribute}] profile_data
15
+ #
16
+ def initialize(profile_data)
17
+ @attributes = profile_data.is_a?(Object) ? profile_data : {}
18
+ end
19
+
20
+ #
21
+ # Get attribute value by name.
22
+ #
23
+ # @param [String] attr_name
24
+ #
25
+ # @return [Attribute, nil]
26
+ #
27
+ def get_attribute(attr_name)
28
+ return nil unless @attributes.key? attr_name
29
+
30
+ @attributes[attr_name]
31
+ end
32
+ end
33
+ end
@@ -1,51 +1,105 @@
1
1
  module Yoti
2
+ #
2
3
  # Encapsulates Yoti user profile
3
- class Profile
4
- def initialize(profile_data)
5
- profile_data = {} unless profile_data.is_a? Object
6
- @profile_data = profile_data
7
- end
8
-
4
+ #
5
+ class Profile < BaseProfile
6
+ #
7
+ # Selfie is a photograph of the user.
8
+ #
9
+ # @return [Attribute, nil]
10
+ #
9
11
  def selfie
10
12
  get_attribute(Yoti::Attribute::SELFIE)
11
13
  end
12
14
 
15
+ #
16
+ # Corresponds to primary name in passport, and surname in English.
17
+ #
18
+ # @return [Attribute, nil]
19
+ #
13
20
  def family_name
14
21
  get_attribute(Yoti::Attribute::FAMILY_NAME)
15
22
  end
16
23
 
24
+ #
25
+ # Corresponds to secondary names in passport, and first/middle names in English.
26
+ #
27
+ # @return [Attribute, nil]
28
+ #
17
29
  def given_names
18
30
  get_attribute(Yoti::Attribute::GIVEN_NAMES)
19
31
  end
20
32
 
33
+ #
34
+ # The user's full name.
35
+ #
36
+ # @return [Attribute, nil]
37
+ #
21
38
  def full_name
22
39
  get_attribute(Yoti::Attribute::FULL_NAME)
23
40
  end
24
41
 
42
+ #
43
+ # The user's phone number, as verified at registration time. This will be a number with + for
44
+ # international prefix and no spaces, e.g. "+447777123456".
45
+ #
46
+ # @return [Attribute, nil]
47
+ #
25
48
  def phone_number
26
49
  get_attribute(Yoti::Attribute::PHONE_NUMBER)
27
50
  end
28
51
 
52
+ #
53
+ # The user's verified email address.
54
+ #
55
+ # @return [Attribute, nil]
56
+ #
29
57
  def email_address
30
58
  get_attribute(Yoti::Attribute::EMAIL_ADDRESS)
31
59
  end
32
60
 
61
+ #
62
+ # Date of birth.
63
+ #
64
+ # @return [Attribute, nil]
65
+ #
33
66
  def date_of_birth
34
67
  get_attribute(Yoti::Attribute::DATE_OF_BIRTH)
35
68
  end
36
69
 
70
+ #
71
+ # Corresponds to the gender in the passport; will be one of the strings
72
+ # "MALE", "FEMALE", "TRANSGENDER" or "OTHER".
73
+ #
74
+ # @return [Attribute, nil]
75
+ #
37
76
  def gender
38
77
  get_attribute(Yoti::Attribute::GENDER)
39
78
  end
40
79
 
80
+ #
81
+ # Corresponds to the nationality in the passport.
82
+ #
83
+ # @return [Attribute, nil]
84
+ #
41
85
  def nationality
42
86
  get_attribute(Yoti::Attribute::NATIONALITY)
43
87
  end
44
88
 
89
+ #
90
+ # Document images.
91
+ #
92
+ # @return [Attribute, nil]
93
+ #
45
94
  def document_images
46
95
  get_attribute(Yoti::Attribute::DOCUMENT_IMAGES)
47
96
  end
48
97
 
98
+ #
99
+ # The user's postal address as a String.
100
+ #
101
+ # @return [Attribute, nil]
102
+ #
49
103
  def postal_address
50
104
  postal_address = get_attribute(Yoti::Attribute::POSTAL_ADDRESS)
51
105
  return postal_address unless postal_address.nil?
@@ -53,19 +107,22 @@ module Yoti
53
107
  attribute_from_formatted_address
54
108
  end
55
109
 
110
+ #
111
+ # The user's structured postal address as a JSON.
112
+ #
113
+ # @return [Attribute, nil]
114
+ #
56
115
  def structured_postal_address
57
116
  get_attribute(Yoti::Attribute::STRUCTURED_POSTAL_ADDRESS)
58
117
  end
59
118
 
60
- # @return attribute value by name
61
- def get_attribute(attr_name)
62
- return nil unless @profile_data.key? attr_name
63
-
64
- @profile_data[attr_name]
65
- end
66
-
67
119
  protected
68
120
 
121
+ #
122
+ # Creates attribute from formatted address.
123
+ #
124
+ # @return [Attribute, nil]
125
+ #
69
126
  def attribute_from_formatted_address
70
127
  return nil if structured_postal_address.nil?
71
128
  return nil unless structured_postal_address.value.key?('formatted_address')
@@ -14,6 +14,7 @@ module Yoti
14
14
  @http_req['X-Yoti-Auth-Key'] = @auth_key
15
15
  @http_req['X-Yoti-Auth-Digest'] = message_signature
16
16
  @http_req['X-Yoti-SDK'] = Yoti.configuration.sdk_identifier
17
+ @http_req['X-Yoti-SDK-Version'] = "#{Yoti.configuration.sdk_identifier}-#{Yoti::VERSION}"
17
18
  @http_req['Content-Type'] = 'application/json'
18
19
  @http_req['Accept'] = 'application/json'
19
20
  @http_req
@@ -19,12 +19,25 @@ module Yoti
19
19
  CT_MULTI_VALUE = :MULTI_VALUE # multi value
20
20
  CT_INT = :INT # integer
21
21
 
22
+ #
23
+ # @deprecated replaced by user_profile
24
+ #
22
25
  def current_user(receipt)
23
26
  return nil unless valid_receipt?(receipt)
24
27
 
25
- profile_content = receipt['other_party_profile_content']
26
- decoded_profile_content = Base64.decode64(profile_content)
27
- Yoti::Protobuf::Compubapi::EncryptedData.decode(decoded_profile_content)
28
+ decode_profile(receipt['other_party_profile_content'])
29
+ end
30
+
31
+ def user_profile(receipt)
32
+ return nil unless valid_receipt?(receipt)
33
+
34
+ decipher_profile(receipt['other_party_profile_content'], receipt['wrapped_receipt_key'])
35
+ end
36
+
37
+ def application_profile(receipt)
38
+ return nil unless valid_receipt?(receipt)
39
+
40
+ decipher_profile(receipt['profile_content'], receipt['wrapped_receipt_key'])
28
41
  end
29
42
 
30
43
  def attribute_list(data)
@@ -80,6 +93,18 @@ module Yoti
80
93
  !receipt['other_party_profile_content'].nil? &&
81
94
  receipt['other_party_profile_content'] != ''
82
95
  end
96
+
97
+ def decode_profile(profile_content)
98
+ decoded_profile_content = Base64.decode64(profile_content)
99
+ Yoti::Protobuf::Compubapi::EncryptedData.decode(decoded_profile_content)
100
+ end
101
+
102
+ 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)
106
+ Protobuf.attribute_list(decrypted_data)
107
+ end
83
108
  end
84
109
  end
85
110
  end
@@ -2,58 +2,95 @@ require 'openssl'
2
2
  require 'date'
3
3
 
4
4
  module Yoti
5
+ #
5
6
  # Parse attribute anchors
7
+ #
6
8
  class AnchorProcessor
9
+ #
10
+ # @param [Array<Yoti::Protobuf::Attrpubapi::Anchor>]
11
+ #
7
12
  def initialize(anchors_list)
8
13
  @anchors_list = anchors_list
9
14
  @get_next = false
10
15
  end
11
16
 
17
+ #
18
+ # Extract matching Attribute Anchors from list.
19
+ #
20
+ # @return [Array<Yoti::Anchor>]
21
+ #
12
22
  def process
13
- result_data = { 'sources' => [], 'verifiers' => [] }
14
- anchor_types = self.anchor_types
23
+ result_data = ANCHOR_LIST_KEYS.map { |key, _value| [key, []] }.to_h
15
24
 
16
25
  @anchors_list.each do |anchor|
17
26
  x509_certs_list = convert_certs_list_to_X509(anchor.origin_server_certs)
18
- yoti_signed_time_stamp = process_signed_time_stamp(anchor.signed_time_stamp)
19
-
20
- anchor.origin_server_certs.each do |cert|
21
- anchor_types.each do |type, oid|
22
- yoti_anchor = get_anchor_by_oid(cert, oid, anchor.sub_type, yoti_signed_time_stamp, x509_certs_list)
23
- result_data[type].push(yoti_anchor) unless yoti_anchor.nil?
24
- end
25
- end
27
+ signed_time_stamp = process_signed_time_stamp(anchor.signed_time_stamp)
28
+ yoti_anchor = get_anchor_from_certs(x509_certs_list, anchor.sub_type, signed_time_stamp)
29
+ anchor_list_key = get_anchor_list_key_by_type(yoti_anchor.type)
30
+ result_data[anchor_list_key].push(yoti_anchor)
26
31
  end
27
32
 
28
33
  result_data
29
34
  end
30
35
 
36
+ #
37
+ # Convert certificate list to a list of X509 certificates.
38
+ #
39
+ # @param [Google::Protobuf::RepeatedField] certs_list
40
+ #
41
+ # @return [Array<OpenSSL::X509::Certificate>]
42
+ #
31
43
  def convert_certs_list_to_X509(certs_list)
32
44
  x509_certs_list = []
33
45
  certs_list.each do |cert|
34
- x509_cert = OpenSSL::X509::Certificate.new cert
35
- x509_certs_list.push x509_cert
46
+ x509_certs_list.push OpenSSL::X509::Certificate.new(cert)
36
47
  end
37
-
38
48
  x509_certs_list
39
49
  end
40
50
 
51
+ #
52
+ # Return signed timestamp.
53
+ #
54
+ # @param [String] signed_time_stamp_binary
55
+ #
56
+ # @return [Yoti::SignedTimeStamp]
57
+ #
41
58
  def process_signed_time_stamp(signed_time_stamp_binary)
42
59
  signed_time_stamp = Yoti::Protobuf::Compubapi::SignedTimestamp.decode(signed_time_stamp_binary)
43
- time_in_sec = signed_time_stamp.timestamp / 1000000
44
- date_time = Time.parse(Time.at(time_in_sec).to_s)
45
- Yoti::SignedTimeStamp.new(signed_time_stamp.version, date_time)
60
+ time_in_sec = signed_time_stamp.timestamp.quo(1000000)
61
+ Yoti::SignedTimeStamp.new(signed_time_stamp.version, Time.at(time_in_sec))
46
62
  end
47
63
 
64
+ #
65
+ # Return Anchor for provided oid.
66
+ #
67
+ # @deprecated no longer in use
68
+ #
69
+ # @param [OpenSSL::X509::Certificate] cert
70
+ # @param [String] oid
71
+ # @param [String] sub_type
72
+ # @param [Yoti::SignedTimeStamp] signed_time_stamp
73
+ # @param [Array<OpenSSL::X509::Certificate>] x509_certs_list
74
+ #
75
+ # @return [Yoti::Anchor, nil]
76
+ #
48
77
  def get_anchor_by_oid(cert, oid, sub_type, signed_time_stamp, x509_certs_list)
49
78
  asn1_obj = OpenSSL::ASN1.decode(cert)
50
79
  anchor_value = get_anchor_value_by_oid(asn1_obj, oid)
51
80
 
52
- return nil if anchor_value.nil?
53
-
54
- Yoti::Anchor.new(anchor_value, sub_type, signed_time_stamp, x509_certs_list)
81
+ Yoti::Anchor.new(anchor_value, sub_type, signed_time_stamp, x509_certs_list) unless anchor_value.nil?
55
82
  end
56
83
 
84
+ #
85
+ # Return Anchor value for provided oid.
86
+ #
87
+ # @deprecated no longer in use
88
+ #
89
+ # @param [OpenSSL::ASN1::Sequence, OpenSSL::ASN1::ASN1Data, Array] obj
90
+ # @param [String] oid
91
+ #
92
+ # @return [String, nil]
93
+ #
57
94
  def get_anchor_value_by_oid(obj, oid)
58
95
  case obj
59
96
  when OpenSSL::ASN1::Sequence, Array
@@ -66,19 +103,37 @@ module Yoti
66
103
  nil
67
104
  end
68
105
 
106
+ #
107
+ # Return Anchor value for ASN1 data.
108
+ #
109
+ # @deprecated no longer in use
110
+ #
111
+ # @param [OpenSSL::ASN1::ASN1Data] value
112
+ # @param [String] oid
113
+ #
114
+ # @return [String, nil]
115
+ #
69
116
  def get_anchor_value_by_asn1_data(value, oid)
70
117
  if value.respond_to?(:to_s) && value == oid
71
118
  @get_next = true
72
119
  elsif value.respond_to?(:to_s) && @get_next
73
- raw_value = OpenSSL::ASN1.decode(value)
74
- anchor_value = raw_value.value[0].value
75
120
  @get_next = false
76
- return anchor_value
121
+ return OpenSSL::ASN1.decode(value).value[0].value
77
122
  end
78
123
 
79
124
  get_anchor_value_by_oid(value, oid)
80
125
  end
81
126
 
127
+ #
128
+ # Return Anchor value for ASN1 sequence.
129
+ #
130
+ # @deprecated no longer in use
131
+ #
132
+ # @param [OpenSSL::ASN1::Sequence, Array] obj
133
+ # @param [String] oid
134
+ #
135
+ # @return [String, nil]
136
+ #
82
137
  def get_anchor_value_by_asn1_sequence(obj, oid)
83
138
  obj.each do |child_obj|
84
139
  result = get_anchor_value_by_oid(child_obj, oid)
@@ -87,15 +142,126 @@ module Yoti
87
142
  nil
88
143
  end
89
144
 
145
+ #
146
+ # Mapping of anchor types to oid.
147
+ #
148
+ # @deprecated no longer in use
149
+ #
150
+ # @return [Hash]
151
+ #
90
152
  def anchor_types
91
- { 'sources' => '1.3.6.1.4.1.47127.1.1.1',
92
- 'verifiers' => '1.3.6.1.4.1.47127.1.1.2' }
153
+ ANCHOR_LIST_KEYS
93
154
  end
94
155
 
95
156
  protected
96
157
 
158
+ #
97
159
  # Define whether the search function get_anchor_value_by_oid
98
160
  # should return the next value in the array
161
+ #
162
+ # @deprecated no longer in use
163
+ #
164
+ # @return [Boolean]
165
+ #
99
166
  attr_reader :get_next
167
+
168
+ private
169
+
170
+ #
171
+ # Mapping of anchor types.
172
+ #
173
+ ANCHOR_TYPES = {
174
+ 'SOURCE' => '1.3.6.1.4.1.47127.1.1.1',
175
+ 'VERIFIER' => '1.3.6.1.4.1.47127.1.1.2',
176
+ 'UNKNOWN' => ''
177
+ }.freeze
178
+
179
+ #
180
+ # Mapping of anchor list keys.
181
+ #
182
+ ANCHOR_LIST_KEYS = {
183
+ 'sources' => ANCHOR_TYPES['SOURCE'],
184
+ 'verifiers' => ANCHOR_TYPES['VERIFIER'],
185
+ 'unknown' => ANCHOR_TYPES['UNKNOWN']
186
+ }.freeze
187
+
188
+ #
189
+ # Get anchor type by oid.
190
+ #
191
+ # @param [String] oid
192
+ #
193
+ def get_anchor_type_by_oid(oid)
194
+ ANCHOR_TYPES.find { |_key, value| value == oid }.first
195
+ end
196
+
197
+ #
198
+ # Get anchor list key by type.
199
+ #
200
+ # @param [String] type
201
+ #
202
+ def get_anchor_list_key_by_type(type)
203
+ ANCHOR_LIST_KEYS.find { |_key, value| value == ANCHOR_TYPES[type] }.first
204
+ end
205
+
206
+ #
207
+ # Get anchor from provided certificate list.
208
+ #
209
+ # @param [Array<OpenSSL::X509::Certificate>] x509_certs_list
210
+ # @param [String] sub_type
211
+ # @param [Yoti::SignedTimeStamp] signed_time_stamp
212
+ #
213
+ # @return [Yoti::Anchor]
214
+ #
215
+ def get_anchor_from_certs(x509_certs_list, sub_type, signed_time_stamp)
216
+ x509_certs_list.each do |cert|
217
+ ANCHOR_TYPES.each do |_type, oid|
218
+ anchor_extension = get_extension_by_oid(cert, oid)
219
+ next if anchor_extension.nil?
220
+
221
+ return Yoti::Anchor.new(
222
+ get_anchor_value(anchor_extension),
223
+ sub_type,
224
+ signed_time_stamp,
225
+ x509_certs_list,
226
+ get_anchor_type_by_oid(oid)
227
+ )
228
+ end
229
+ end
230
+ Yoti::Anchor.new('', sub_type, signed_time_stamp, x509_certs_list, 'UNKNOWN')
231
+ end
232
+
233
+ #
234
+ # Get extension for provided oid.
235
+ #
236
+ # @param [OpenSSL::X509::Certificate] cert
237
+ # @param [String] oid
238
+ #
239
+ # @return [OpenSSL::X509::Extension]
240
+ #
241
+ def get_extension_by_oid(cert, oid)
242
+ cert.extensions.each do |extension|
243
+ return extension if extension.oid == oid
244
+ end
245
+ nil
246
+ end
247
+
248
+ #
249
+ # Return Anchor value.
250
+ #
251
+ # @param [OpenSSL::X509::Extension] anchor_extension
252
+ #
253
+ # @return [String, nil]
254
+ #
255
+ def get_anchor_value(anchor_extension)
256
+ decoded_extension = OpenSSL::ASN1.decode(anchor_extension)
257
+ extension_value = decoded_extension.value[1] if decoded_extension.value.is_a?(Array)
258
+ extension_value_item = extension_value.value if extension_value.is_a?(OpenSSL::ASN1::OctetString)
259
+ if extension_value_item.is_a?(String)
260
+ decoded = OpenSSL::ASN1.decode(extension_value_item)
261
+ return decoded.value[0].value if decoded.value[0].is_a?(OpenSSL::ASN1::ASN1Data)
262
+ end
263
+
264
+ nil
265
+ end
100
266
  end
101
267
  end
data/lib/yoti/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Yoti
2
2
  # @return [String] the gem's current version
3
- VERSION = '1.4.0'.freeze
3
+ VERSION = '1.5.0'.freeze
4
4
  end
data/rubocop.yml CHANGED
@@ -20,6 +20,9 @@ Metrics/BlockLength:
20
20
  Metrics/CyclomaticComplexity:
21
21
  Max: 9
22
22
 
23
+ Metrics/ClassLength:
24
+ Max: 115
25
+
23
26
  Metrics/LineLength:
24
27
  Enabled: false
25
28
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yoti
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Zaremba
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-15 00:00:00.000000000 Z
11
+ date: 2019-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: google-protobuf
@@ -189,7 +189,9 @@ files:
189
189
  - lib/yoti/client.rb
190
190
  - lib/yoti/configuration.rb
191
191
  - lib/yoti/data_type/anchor.rb
192
+ - lib/yoti/data_type/application_profile.rb
192
193
  - lib/yoti/data_type/attribute.rb
194
+ - lib/yoti/data_type/base_profile.rb
193
195
  - lib/yoti/data_type/image.rb
194
196
  - lib/yoti/data_type/image_jpeg.rb
195
197
  - lib/yoti/data_type/image_png.rb
@@ -238,7 +240,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
238
240
  - !ruby/object:Gem::Version
239
241
  version: '0'
240
242
  requirements: []
241
- rubygems_version: 3.0.3
243
+ rubyforge_project:
244
+ rubygems_version: 2.7.9
242
245
  signing_key:
243
246
  specification_version: 4
244
247
  summary: Yoti Ruby SDK for back-end integration.