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