yoti 1.0.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 +7 -0
- data/.gitignore +37 -0
- data/CHANGELOG.md +13 -0
- data/CONTRIBUTING.md +119 -0
- data/Gemfile +9 -0
- data/Guardfile +11 -0
- data/LICENSE.txt +202 -0
- data/README.md +222 -0
- data/Rakefile +46 -0
- data/lib/generators/yoti/install/install_generator.rb +13 -0
- data/lib/generators/yoti/install/templates/yoti.rb +4 -0
- data/lib/yoti/activity_details.rb +31 -0
- data/lib/yoti/client.rb +17 -0
- data/lib/yoti/configuration.rb +84 -0
- data/lib/yoti/errors.rb +13 -0
- data/lib/yoti/protobuf/v1/attribute_public_api/attribute.pb.rb +45 -0
- data/lib/yoti/protobuf/v1/attribute_public_api/list.pb.rb +33 -0
- data/lib/yoti/protobuf/v1/attribute_public_api/signing.pb.rb +27 -0
- data/lib/yoti/protobuf/v1/common_public_api/encrypted_data.pb.rb +22 -0
- data/lib/yoti/protobuf/v1/definitions/attribute-public-api/attrpubapi_v1/attribute.proto +52 -0
- data/lib/yoti/protobuf/v1/definitions/attribute-public-api/attrpubapi_v1/list.proto +27 -0
- data/lib/yoti/protobuf/v1/definitions/attribute-public-api/attrpubapi_v1/signing.proto +23 -0
- data/lib/yoti/protobuf/v1/definitions/common-public-api/compubapi_v1/encrypted_data.proto +15 -0
- data/lib/yoti/protobuf/v1/protobuf.rb +49 -0
- data/lib/yoti/request.rb +58 -0
- data/lib/yoti/ssl.rb +71 -0
- data/lib/yoti/version.rb +4 -0
- data/lib/yoti.rb +21 -0
- data/login_flow.png +0 -0
- data/rubocop.yml +27 -0
- data/yardstick.yml +9 -0
- data/yoti.gemspec +38 -0
- metadata +218 -0
data/Rakefile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
################################
|
6
|
+
# Tests #
|
7
|
+
################################
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new
|
10
|
+
task test: :spec
|
11
|
+
|
12
|
+
################################
|
13
|
+
# Rubocop #
|
14
|
+
################################
|
15
|
+
|
16
|
+
require 'rubocop/rake_task'
|
17
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
18
|
+
t.options = ['--config', 'rubocop.yml']
|
19
|
+
end
|
20
|
+
|
21
|
+
################################
|
22
|
+
# Documentation #
|
23
|
+
################################
|
24
|
+
|
25
|
+
require 'yard'
|
26
|
+
YARD::Rake::YardocTask.new do |t|
|
27
|
+
t.stats_options = ['--list-undoc']
|
28
|
+
end
|
29
|
+
|
30
|
+
yardstick_options = YAML.load_file('yardstick.yml')
|
31
|
+
|
32
|
+
require 'yardstick/rake/measurement'
|
33
|
+
Yardstick::Rake::Measurement.new(:measurement, yardstick_options) do |measurement|
|
34
|
+
measurement.output = 'measurement/report.txt'
|
35
|
+
end
|
36
|
+
|
37
|
+
require 'yardstick/rake/verify'
|
38
|
+
Yardstick::Rake::Verify.new(:verify_measurements, yardstick_options) do |verify|
|
39
|
+
verify.threshold = 88.5
|
40
|
+
end
|
41
|
+
|
42
|
+
################################
|
43
|
+
# Defaults #
|
44
|
+
################################
|
45
|
+
|
46
|
+
task default: [:spec, :rubocop]
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Yoti
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path('../templates', __FILE__)
|
5
|
+
|
6
|
+
desc 'This generator creates an Yoti initializer file at config/initializers'
|
7
|
+
|
8
|
+
def copy_initializer
|
9
|
+
template 'yoti.rb', 'config/initializers/yoti.rb'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Yoti
|
4
|
+
# Encapsulates the user profile data
|
5
|
+
class ActivityDetails
|
6
|
+
# @return [String] the outcome of the profile request, eg: SUCCESS
|
7
|
+
attr_reader :outcome
|
8
|
+
|
9
|
+
# @return [String] the Yoti ID
|
10
|
+
attr_reader :user_id
|
11
|
+
|
12
|
+
# @return [Hash] the decoded profile attributes
|
13
|
+
attr_reader :user_profile
|
14
|
+
|
15
|
+
# @param receipt [Hash] the receipt from the API request
|
16
|
+
# @param decrypted_profile [Object] Protobuf AttributeList decrypted object containing the profile attributes
|
17
|
+
def initialize(receipt, decrypted_profile)
|
18
|
+
@decrypted_profile = decrypted_profile
|
19
|
+
@user_profile = {}
|
20
|
+
|
21
|
+
if @decrypted_profile.respond_to_has_and_present?(:attributes)
|
22
|
+
@decrypted_profile.attributes.each do |field|
|
23
|
+
@user_profile[field.name] = Yoti::Protobuf.value_based_on_content_type(field.value, field.content_type)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
@user_id = receipt['remember_me_id']
|
28
|
+
@outcome = receipt['sharing_outcome']
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/yoti/client.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Yoti
|
2
|
+
# Handles all the publicly accesible Yoti methods for
|
3
|
+
# geting data using an encrypted connect token
|
4
|
+
module Client
|
5
|
+
# Performs all the steps required to get the decrypted profile from an API request
|
6
|
+
# @param encrypted_connect_token [String] token provided as a base 64 string
|
7
|
+
# @return [Object] an ActivityDetails instance encapsulating the user profile
|
8
|
+
def self.get_activity_details(encrypted_connect_token)
|
9
|
+
receipt = Yoti::Request.new(encrypted_connect_token).receipt
|
10
|
+
encrypted_data = Protobuf.current_user(receipt)
|
11
|
+
unwrapped_key = Yoti::SSL.decrypt_token(receipt['wrapped_receipt_key'])
|
12
|
+
decrypted_data = Yoti::SSL.decipher(unwrapped_key, encrypted_data.iv, encrypted_data.cipher_text)
|
13
|
+
decrypted_profile = Protobuf.attribute_list(decrypted_data)
|
14
|
+
ActivityDetails.new(receipt, decrypted_profile)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Yoti
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :client_sdk_id, :key_file_path, :key, :api_url,
|
4
|
+
:api_port, :api_version, :api_endpoint
|
5
|
+
|
6
|
+
# Set config variables by using a configuration block
|
7
|
+
def initialize
|
8
|
+
@client_sdk_id = ''
|
9
|
+
@key_file_path = ''
|
10
|
+
@key = ''
|
11
|
+
@api_url = 'https://api.yoti.com'
|
12
|
+
@api_port = 443
|
13
|
+
@api_version = 'v1'
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String] the API endpoint for the selected API version
|
17
|
+
def api_endpoint
|
18
|
+
@api_endpoint ||= "#{@api_url}/api/#{@api_version}"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Validates the configuration values set in instance variables
|
22
|
+
# @return [nil]
|
23
|
+
def validate
|
24
|
+
validate_required_all(%w(client_sdk_id))
|
25
|
+
validate_required_any(%w(key_file_path key))
|
26
|
+
validate_value('api_version', ['v1'])
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Loops through the list of required configurations and raises an error
|
32
|
+
# if a it can't find all the configuration values set
|
33
|
+
# @return [nil]
|
34
|
+
def validate_required_all(required_configs)
|
35
|
+
required_configs.each do |config|
|
36
|
+
unless config_set?(config)
|
37
|
+
message = "Configuration value `#{config}` is required."
|
38
|
+
raise ConfigurationError, message
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Loops through the list of required configurations and raises an error
|
44
|
+
# if a it can't find at least one configuration value set
|
45
|
+
# @return [nil]
|
46
|
+
def validate_required_any(required_configs)
|
47
|
+
valid = required_configs.select { |config| config_set?(config) }
|
48
|
+
|
49
|
+
return if valid.any?
|
50
|
+
|
51
|
+
config_list = required_configs.map { |conf| '`' + conf + '`' }.join(', ')
|
52
|
+
message = "At least one of the configuration values has to be set: #{config_list}."
|
53
|
+
raise ConfigurationError, message
|
54
|
+
end
|
55
|
+
|
56
|
+
# Raises an error if the setting receives an invalid value
|
57
|
+
# @param value [String] the value to be assigned
|
58
|
+
# @param allowed_values [Array] an array of allowed values for the variable
|
59
|
+
# @return [nil]
|
60
|
+
def validate_value(config, allowed_values)
|
61
|
+
value = instance_variable_get("@#{config}")
|
62
|
+
|
63
|
+
return unless invalid_value?(value, allowed_values)
|
64
|
+
|
65
|
+
message = "Configuration value `#{value}` is not allowed for `#{config}`."
|
66
|
+
raise ConfigurationError, message
|
67
|
+
end
|
68
|
+
|
69
|
+
# Checks if an allowed array of values includes the setting value
|
70
|
+
# @param value [String] the value to be checked
|
71
|
+
# @param allowed_values [Array] an array of allowed values for the variable
|
72
|
+
# @return [Boolean]
|
73
|
+
def invalid_value?(value, allowed_values)
|
74
|
+
allowed_values.any? && !allowed_values.include?(value)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Checks if a configuration has been set as a instance variable
|
78
|
+
# @param name [String] the name of the configuration
|
79
|
+
# @return [Boolean]
|
80
|
+
def config_set?(config)
|
81
|
+
instance_variable_get("@#{config}").to_s != ''
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/yoti/errors.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Yoti
|
2
|
+
# Raises exceptions related to Protobuf decoding
|
3
|
+
class ProtobufError < StandardError; end
|
4
|
+
|
5
|
+
# Raises exceptions related to API requests
|
6
|
+
class RequestError < StandardError; end
|
7
|
+
|
8
|
+
# Raises exceptions related to OpenSSL actions
|
9
|
+
class SslError < StandardError; end
|
10
|
+
|
11
|
+
# Raises exceptions realted to an incorrect gem configuration value
|
12
|
+
class ConfigurationError < StandardError; end
|
13
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'protobuf/message'
|
2
|
+
|
3
|
+
module Yoti
|
4
|
+
module Protobuf
|
5
|
+
module V1
|
6
|
+
module Attrpubapi
|
7
|
+
##
|
8
|
+
# Enum Classes
|
9
|
+
#
|
10
|
+
class ContentType < ::Protobuf::Enum
|
11
|
+
define :UNDEFINED, 0
|
12
|
+
define :STRING, 1
|
13
|
+
define :JPEG, 2
|
14
|
+
define :DATE, 3
|
15
|
+
define :PNG, 4
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Message Classes
|
20
|
+
#
|
21
|
+
class Attribute < ::Protobuf::Message; end
|
22
|
+
class Anchor < ::Protobuf::Message; end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Message Fields
|
26
|
+
#
|
27
|
+
class Attribute
|
28
|
+
optional :string, :name, 1
|
29
|
+
optional :bytes, :value, 2
|
30
|
+
optional Yoti::Protobuf::V1::Attrpubapi::ContentType, :content_type, 3
|
31
|
+
repeated Yoti::Protobuf::V1::Attrpubapi::Anchor, :anchors, 4
|
32
|
+
end
|
33
|
+
|
34
|
+
class Anchor
|
35
|
+
optional :bytes, :artifact_link, 1
|
36
|
+
repeated :bytes, :origin_server_certs, 2
|
37
|
+
optional :bytes, :artifact_signature, 3
|
38
|
+
optional :string, :sub_type, 4
|
39
|
+
optional :bytes, :signature, 5
|
40
|
+
optional :bytes, :signed_time_stamp, 6
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'protobuf/message'
|
2
|
+
require_relative 'attribute.pb'
|
3
|
+
|
4
|
+
module Yoti
|
5
|
+
module Protobuf
|
6
|
+
module V1
|
7
|
+
module Attrpubapi
|
8
|
+
##
|
9
|
+
# Message Classes
|
10
|
+
#
|
11
|
+
class AttributeAndId < ::Protobuf::Message; end
|
12
|
+
class AttributeAndIdList < ::Protobuf::Message; end
|
13
|
+
class AttributeList < ::Protobuf::Message; end
|
14
|
+
|
15
|
+
##
|
16
|
+
# Message Fields
|
17
|
+
#
|
18
|
+
class AttributeAndId
|
19
|
+
optional Yoti::Protobuf::V1::Attrpubapi::Attribute, :attribute, 1
|
20
|
+
optional :bytes, :attribute_id, 2
|
21
|
+
end
|
22
|
+
|
23
|
+
class AttributeAndIdList
|
24
|
+
repeated Yoti::Protobuf::V1::Attrpubapi::AttributeAndId, :attribute_and_id_list, 1
|
25
|
+
end
|
26
|
+
|
27
|
+
class AttributeList
|
28
|
+
repeated Yoti::Protobuf::V1::Attrpubapi::Attribute, :attributes, 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'protobuf/message'
|
2
|
+
require_relative 'attribute.pb'
|
3
|
+
|
4
|
+
module Yoti
|
5
|
+
module Protobuf
|
6
|
+
module V1
|
7
|
+
module Attrpubapi
|
8
|
+
##
|
9
|
+
# Message Classes
|
10
|
+
#
|
11
|
+
class AttributeSigning < ::Protobuf::Message; end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Message Fields
|
15
|
+
#
|
16
|
+
class AttributeSigning
|
17
|
+
optional :string, :name, 1
|
18
|
+
optional :bytes, :value, 2
|
19
|
+
optional Yoti::Protobuf::V1::Attrpubapi::ContentType, :content_type, 3
|
20
|
+
optional :bytes, :artifact_signature, 4
|
21
|
+
optional :string, :sub_type, 5
|
22
|
+
optional :bytes, :signed_time_stamp, 6
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'protobuf/message'
|
2
|
+
|
3
|
+
module Yoti
|
4
|
+
module Protobuf
|
5
|
+
module V1
|
6
|
+
module Compubapi
|
7
|
+
##
|
8
|
+
# Message Classes
|
9
|
+
#
|
10
|
+
class EncryptedData < ::Protobuf::Message; end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Message Fields
|
14
|
+
#
|
15
|
+
class EncryptedData
|
16
|
+
optional :bytes, :iv, 1
|
17
|
+
optional :bytes, :cipher_text, 2
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
//syntax = "proto2";
|
2
|
+
|
3
|
+
package attrpubapi_v1;
|
4
|
+
|
5
|
+
option java_package = "com.yoti.attrpubapi_v1";
|
6
|
+
option java_outer_classname = "AttrProto";
|
7
|
+
|
8
|
+
|
9
|
+
// ContentType indicates how to interpret the ‘Value’ field of an Attribute.
|
10
|
+
enum ContentType {
|
11
|
+
// UNDEFINED should not be seen, and is used as an error placeholder
|
12
|
+
// value.
|
13
|
+
UNDEFINED = 0;
|
14
|
+
|
15
|
+
// STRING means the value is UTF-8 encoded text.
|
16
|
+
STRING = 1;
|
17
|
+
|
18
|
+
// JPEG indicates a standard .jpeg image.
|
19
|
+
JPEG = 2;
|
20
|
+
|
21
|
+
// Date as string in RFC3339 format (YYYY-MM-DD).
|
22
|
+
DATE = 3;
|
23
|
+
|
24
|
+
// PNG indicates a standard .png image.
|
25
|
+
PNG = 4;
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
message Attribute {
|
30
|
+
optional string name = 1;
|
31
|
+
|
32
|
+
optional bytes value = 2;
|
33
|
+
|
34
|
+
optional ContentType content_type = 3;
|
35
|
+
|
36
|
+
repeated Anchor anchors = 4;
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
message Anchor {
|
41
|
+
optional bytes artifact_link = 1;
|
42
|
+
|
43
|
+
repeated bytes origin_server_certs = 2;
|
44
|
+
|
45
|
+
optional bytes artifact_signature = 3;
|
46
|
+
|
47
|
+
optional string sub_type = 4;
|
48
|
+
|
49
|
+
optional bytes signature = 5;
|
50
|
+
|
51
|
+
optional bytes signed_time_stamp = 6;
|
52
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
//syntax = "proto2";
|
2
|
+
|
3
|
+
package attrpubapi_v1;
|
4
|
+
|
5
|
+
import "Attribute.proto";
|
6
|
+
|
7
|
+
option java_package = "com.yoti.attrpubapi_v1";
|
8
|
+
option java_outer_classname = "AttrProto";
|
9
|
+
|
10
|
+
|
11
|
+
// AttributeAndId is a simple container for holding an attribute's value
|
12
|
+
// alongside its ID.
|
13
|
+
message AttributeAndId {
|
14
|
+
optional Attribute attribute = 1;
|
15
|
+
|
16
|
+
optional bytes attribute_id = 2;
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
message AttributeAndIdList{
|
21
|
+
repeated AttributeAndId attribute_and_id_list = 1;
|
22
|
+
}
|
23
|
+
|
24
|
+
|
25
|
+
message AttributeList {
|
26
|
+
repeated Attribute attributes = 1;
|
27
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
//syntax = "proto2";
|
2
|
+
|
3
|
+
package attrpubapi_v1;
|
4
|
+
|
5
|
+
import "Attribute.proto";
|
6
|
+
|
7
|
+
option java_package = "com.yoti.attrpubapi_v1";
|
8
|
+
option java_outer_classname = "AttrProto";
|
9
|
+
|
10
|
+
|
11
|
+
message AttributeSigning {
|
12
|
+
optional string name = 1;
|
13
|
+
|
14
|
+
optional bytes value = 2;
|
15
|
+
|
16
|
+
optional ContentType content_type = 3;
|
17
|
+
|
18
|
+
optional bytes artifact_signature = 4;
|
19
|
+
|
20
|
+
optional string sub_type = 5;
|
21
|
+
|
22
|
+
optional bytes signed_time_stamp = 6;
|
23
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// syntax = "proto2";
|
2
|
+
|
3
|
+
package compubapi_v1;
|
4
|
+
|
5
|
+
option java_package = "com.yoti.compubapi_v1";
|
6
|
+
option java_outer_classname = "EncryptedDataProto";
|
7
|
+
|
8
|
+
message EncryptedData {
|
9
|
+
// the iv will be used in conjunction with the secret key
|
10
|
+
// received via other channel in order to decrypt the cipher_text
|
11
|
+
optional bytes iv = 1;
|
12
|
+
|
13
|
+
// block of bytes to be decrypted
|
14
|
+
optional bytes cipher_text = 2;
|
15
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'protobuf'
|
2
|
+
require_relative 'attribute_public_api/list.pb'
|
3
|
+
require_relative 'common_public_api/encrypted_data.pb'
|
4
|
+
|
5
|
+
module Yoti
|
6
|
+
module Protobuf
|
7
|
+
class << self
|
8
|
+
CT_UNDEFINED = 0 # should not be seen, and is used as an error placeholder
|
9
|
+
CT_STRING = 1 # UTF-8 encoded text.
|
10
|
+
CT_JPEG = 2 # standard .jpeg image.
|
11
|
+
CT_DATE = 3 # string in RFC3339 format (YYYY-MM-DD)
|
12
|
+
CT_PNG = 4 # standard .png image
|
13
|
+
|
14
|
+
def current_user(receipt)
|
15
|
+
raise ProtobufError, 'The receipt has invalid data.' unless valid_recepit?(receipt)
|
16
|
+
profile_content = receipt['other_party_profile_content']
|
17
|
+
decoded_profile_content = Base64.decode64(profile_content)
|
18
|
+
V1::Compubapi::EncryptedData.decode(decoded_profile_content)
|
19
|
+
end
|
20
|
+
|
21
|
+
def attribute_list(data)
|
22
|
+
V1::Attrpubapi::AttributeList.decode(data)
|
23
|
+
end
|
24
|
+
|
25
|
+
def value_based_on_content_type(value, content_type = nil)
|
26
|
+
case content_type
|
27
|
+
when CT_UNDEFINED
|
28
|
+
raise ProtobufError, 'The content type is invalid.'
|
29
|
+
when CT_STRING
|
30
|
+
value.encode('utf-8')
|
31
|
+
when CT_JPEG
|
32
|
+
'data:image/jpeg;base64,'.concat(Base64.strict_encode64(value))
|
33
|
+
when CT_DATE
|
34
|
+
value.encode('utf-8')
|
35
|
+
when CT_PNG
|
36
|
+
'data:image/png;base64,'.concat(Base64.strict_encode64(value))
|
37
|
+
else
|
38
|
+
value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def valid_recepit?(receipt)
|
45
|
+
receipt.key?('other_party_profile_content') && !receipt['other_party_profile_content'].nil?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/yoti/request.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Yoti
|
2
|
+
# Manage the API's HTTPS requests
|
3
|
+
class Request
|
4
|
+
def initialize(encrypted_connect_token)
|
5
|
+
@encrypted_connect_token = encrypted_connect_token
|
6
|
+
@auth_key = Yoti::SSL.auth_key_from_pem
|
7
|
+
end
|
8
|
+
|
9
|
+
# @return [Hash] the receipt key from the request hash response
|
10
|
+
def receipt
|
11
|
+
request['receipt']
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def request
|
17
|
+
res = Net::HTTP.start(uri.hostname, Yoti.configuration.api_port, use_ssl: https_uri?) do |http|
|
18
|
+
http.request(req)
|
19
|
+
end
|
20
|
+
|
21
|
+
raise RequestError, "Unsuccessful Yoti API call: #{res.message}" unless res.code == '200'
|
22
|
+
JSON.parse(res.body)
|
23
|
+
end
|
24
|
+
|
25
|
+
def req
|
26
|
+
http_req = Net::HTTP::Get.new(uri)
|
27
|
+
http_req['X-Yoti-Auth-Key'] = @auth_key
|
28
|
+
http_req['X-Yoti-Auth-Digest'] = message_signature
|
29
|
+
http_req['Content-Type'] = 'application/json'
|
30
|
+
http_req['Accept'] = 'application/json'
|
31
|
+
http_req
|
32
|
+
end
|
33
|
+
|
34
|
+
def message_signature
|
35
|
+
@message_signature ||= Yoti::SSL.get_secure_signature("GET&#{path}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def uri
|
39
|
+
@uri ||= URI(Yoti.configuration.api_endpoint + path)
|
40
|
+
end
|
41
|
+
|
42
|
+
def path
|
43
|
+
@path ||= begin
|
44
|
+
nonce = SecureRandom.uuid
|
45
|
+
timestamp = Time.now.to_i
|
46
|
+
"/profile/#{token}?nonce=#{nonce}×tamp=#{timestamp}&appId=#{Yoti.configuration.client_sdk_id}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def token
|
51
|
+
@token ||= Yoti::SSL.decrypt_token(@encrypted_connect_token)
|
52
|
+
end
|
53
|
+
|
54
|
+
def https_uri?
|
55
|
+
uri.scheme == 'https'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/yoti/ssl.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Yoti
|
5
|
+
# Manages security behaviour that requires the use of OpenSSL actions
|
6
|
+
module SSL
|
7
|
+
class << self
|
8
|
+
# Gets the private key from either a String (YOTI_KEY)
|
9
|
+
# or a pem file (YOTI_KEY_FILE_PATH)
|
10
|
+
# @return [String] the content of the private key
|
11
|
+
def pem
|
12
|
+
@pem ||= begin
|
13
|
+
if Yoti.configuration.key.to_s.empty?
|
14
|
+
File.read(Yoti.configuration.key_file_path, encoding: 'utf-8')
|
15
|
+
else
|
16
|
+
Yoti.configuration.key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Uses the pem key to decrypt an encrypted connect token
|
22
|
+
# @param encrypted_connect_token [String]
|
23
|
+
# @return [String] decrypted connect token decoded in base 64
|
24
|
+
def decrypt_token(encrypted_connect_token)
|
25
|
+
raise SslError, 'Encrypted token cannot be nil.' unless encrypted_connect_token
|
26
|
+
|
27
|
+
begin
|
28
|
+
private_key.private_decrypt(Base64.urlsafe_decode64(encrypted_connect_token))
|
29
|
+
rescue => error
|
30
|
+
raise SslError, "Could not decrypt token. #{error}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Extracts the public key from pem key, converts it to a DER base 64 encoded value
|
35
|
+
# @return [String] base 64 encoded anthentication key
|
36
|
+
def auth_key_from_pem
|
37
|
+
public_key = private_key.public_key
|
38
|
+
Base64.strict_encode64(public_key.to_der)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sign message using a secure SHA256 hash and the private key
|
42
|
+
# @param message [String] message to be signed
|
43
|
+
# @return [String] signed message encoded in base 64
|
44
|
+
def get_secure_signature(message)
|
45
|
+
digest = OpenSSL::Digest::SHA256.new
|
46
|
+
Base64.strict_encode64(private_key.sign(digest, message))
|
47
|
+
end
|
48
|
+
|
49
|
+
# Uses the decrypted receipt key and the current user's iv to decode the text
|
50
|
+
# @param key [String] base 64 decoded key
|
51
|
+
# @param iv [String] base 64 decoded iv
|
52
|
+
# @param text [String] base 64 decoded cyphered text
|
53
|
+
# @return [String] base 64 decoded deciphered text
|
54
|
+
def decipher(key, iv, text)
|
55
|
+
ssl_decipher = OpenSSL::Cipher.new('AES-256-CBC')
|
56
|
+
ssl_decipher.decrypt
|
57
|
+
ssl_decipher.key = key
|
58
|
+
ssl_decipher.iv = iv
|
59
|
+
ssl_decipher.update(text) + ssl_decipher.final
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def private_key
|
65
|
+
@private_key ||= OpenSSL::PKey::RSA.new(pem)
|
66
|
+
rescue => error
|
67
|
+
raise SslError, "The secure key is invalid. #{error}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/yoti/version.rb
ADDED
data/lib/yoti.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'yoti/version'
|
2
|
+
require_relative 'yoti/configuration'
|
3
|
+
require_relative 'yoti/errors'
|
4
|
+
require_relative 'yoti/ssl'
|
5
|
+
require_relative 'yoti/request'
|
6
|
+
require_relative 'yoti/activity_details'
|
7
|
+
require_relative 'yoti/client'
|
8
|
+
require_relative 'yoti/protobuf/v1/protobuf'
|
9
|
+
|
10
|
+
# The main module namespace of the Yoti gem
|
11
|
+
module Yoti
|
12
|
+
class << self
|
13
|
+
attr_accessor :configuration
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configure
|
17
|
+
self.configuration ||= Configuration.new
|
18
|
+
yield(configuration)
|
19
|
+
configuration.validate
|
20
|
+
end
|
21
|
+
end
|
data/login_flow.png
ADDED
Binary file
|