trusona 0.16.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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.env.example +4 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +24 -0
- data/Gemfile +8 -0
- data/Guardfile +54 -0
- data/LICENSE +201 -0
- data/README.md +234 -0
- data/Rakefile +8 -0
- data/bin/console +21 -0
- data/bin/setup +8 -0
- data/certs/trusona.pem +26 -0
- data/checksum/trusona-0.16.0.gem.sha512 +1 -0
- data/integrations/device_user_binding_integration_spec.rb +6 -0
- data/integrations/identity_documents_spec.rb +28 -0
- data/integrations/spec_helper.rb +23 -0
- data/integrations/tru_code_spec.rb +80 -0
- data/integrations/trusonafication_spec.rb +61 -0
- data/integrations/user_accounts_spec.rb +39 -0
- data/integrations/user_identifiers_spec.rb +32 -0
- data/lib/trusona.rb +157 -0
- data/lib/trusona/api/client.rb +60 -0
- data/lib/trusona/api/hashed_message.rb +71 -0
- data/lib/trusona/api/signed_request.rb +96 -0
- data/lib/trusona/api/verified_response.rb +85 -0
- data/lib/trusona/device.rb +31 -0
- data/lib/trusona/device_user_binding.rb +56 -0
- data/lib/trusona/errors.rb +51 -0
- data/lib/trusona/helpers/key_normalizer.rb +15 -0
- data/lib/trusona/helpers/time_normalizer.rb +17 -0
- data/lib/trusona/identity_document.rb +64 -0
- data/lib/trusona/mappers/base_mapper.rb +69 -0
- data/lib/trusona/mappers/device_mapper.rb +13 -0
- data/lib/trusona/mappers/device_user_binding_mapper.rb +13 -0
- data/lib/trusona/mappers/identity_document_mapper.rb +17 -0
- data/lib/trusona/mappers/nil_mapper.rb +11 -0
- data/lib/trusona/mappers/tru_code_mapper.rb +13 -0
- data/lib/trusona/mappers/trusonafication_mapper.rb +19 -0
- data/lib/trusona/mappers/user_account_mapper.rb +13 -0
- data/lib/trusona/mappers/user_identifier_mapper.rb +13 -0
- data/lib/trusona/resources/base_resource.rb +29 -0
- data/lib/trusona/resources/device.rb +22 -0
- data/lib/trusona/resources/device_user_binding.rb +30 -0
- data/lib/trusona/resources/device_user_binding_activation.rb +27 -0
- data/lib/trusona/resources/identity_document.rb +36 -0
- data/lib/trusona/resources/tru_code.rb +42 -0
- data/lib/trusona/resources/trusonafication.rb +137 -0
- data/lib/trusona/resources/user_account.rb +84 -0
- data/lib/trusona/resources/user_identifier.rb +49 -0
- data/lib/trusona/resources/validators.rb +22 -0
- data/lib/trusona/services/account_lookups_service.rb +17 -0
- data/lib/trusona/services/base_service.rb +117 -0
- data/lib/trusona/services/device_user_bindings_service.rb +22 -0
- data/lib/trusona/services/devices_service.rb +15 -0
- data/lib/trusona/services/identity_documents_service.rb +31 -0
- data/lib/trusona/services/tru_codes_service.rb +26 -0
- data/lib/trusona/services/trusonafication_service.rb +16 -0
- data/lib/trusona/services/user_accounts_service.rb +17 -0
- data/lib/trusona/services/user_identifiers_service.rb +16 -0
- data/lib/trusona/tru_code.rb +38 -0
- data/lib/trusona/tru_code_config.rb +45 -0
- data/lib/trusona/trusonafication.rb +133 -0
- data/lib/trusona/user_account.rb +17 -0
- data/lib/trusona/user_identifier.rb +15 -0
- data/lib/trusona/version.rb +5 -0
- data/lib/trusona/workers/device_finder.rb +18 -0
- data/lib/trusona/workers/device_user_binding_activator.rb +25 -0
- data/lib/trusona/workers/device_user_binding_creator.rb +26 -0
- data/lib/trusona/workers/identity_document_finder.rb +25 -0
- data/lib/trusona/workers/tru_code_creator.rb +17 -0
- data/lib/trusona/workers/tru_code_finder.rb +19 -0
- data/lib/trusona/workers/trusonafication_creator.rb +46 -0
- data/lib/trusona/workers/trusonafication_finder.rb +27 -0
- data/lib/trusona/workers/user_account_finder.rb +39 -0
- data/lib/trusona/workers/user_identifier_creator.rb +17 -0
- data/lib/trusona/workers/user_identifier_finder.rb +29 -0
- data/release-gem +17 -0
- data/trusona.gemspec +43 -0
- metadata +333 -0
- metadata.gz.sig +1 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trusona
|
4
|
+
module Resources
|
5
|
+
#
|
6
|
+
## A TruCode used for magic logins
|
7
|
+
class TruCode < BaseResource
|
8
|
+
include Trusona::Resources::Validators
|
9
|
+
include Trusona::Helpers::KeyNormalizer
|
10
|
+
|
11
|
+
attr_reader :relying_party_id, :payload, :id
|
12
|
+
|
13
|
+
def initialize(params = {})
|
14
|
+
normalized_params = normalize_keys(params)
|
15
|
+
@id = normalized_params[:id]
|
16
|
+
@payload = normalized_params[:payload]
|
17
|
+
@relying_party_id = normalized_params[:relying_party_id] ||
|
18
|
+
Trusona::TruCodeConfig.new.relying_party_id
|
19
|
+
raise ArgumentError unless validate
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_json
|
23
|
+
JSON(to_h)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_h
|
27
|
+
{
|
28
|
+
id: @id,
|
29
|
+
relying_party_id: @relying_party_id,
|
30
|
+
payload: @payload
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def validate
|
37
|
+
return false unless present?(@relying_party_id)
|
38
|
+
true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trusona
|
4
|
+
module Resources
|
5
|
+
##
|
6
|
+
# Represents a Trusonafication resource
|
7
|
+
class Trusonafication < BaseResource
|
8
|
+
include Trusona::Helpers::KeyNormalizer
|
9
|
+
include Trusona::Helpers::TimeNormalizer
|
10
|
+
|
11
|
+
attr_accessor :device_identifier, :user_identifier, :trucode_id,
|
12
|
+
:resource, :action, :level, :id,
|
13
|
+
:accepted_level, :trusona_id, :expires_at,
|
14
|
+
:user_presence, :callback_url, :prompt
|
15
|
+
|
16
|
+
# rubocop:disable Metrics/AbcSize
|
17
|
+
# rubocop:disable Metrics/MethodLength
|
18
|
+
def initialize(params = {})
|
19
|
+
@params = normalize_keys(params)
|
20
|
+
return if @params.nil?
|
21
|
+
self.accepted_level = determine_accepted_level(@params)
|
22
|
+
self.action = @params[:action]
|
23
|
+
self.device_identifier = @params[:device_identifier]
|
24
|
+
self.user_identifier = @params[:user_identifier]
|
25
|
+
self.trucode_id = @params[:trucode_id]
|
26
|
+
self.expires_at = normalize_time(@params[:expires_at])
|
27
|
+
self.id = @params[:id]
|
28
|
+
self.level = @params[:level]
|
29
|
+
self.resource = @params[:resource]
|
30
|
+
self.trusona_id = @params[:trusona_id]
|
31
|
+
self.prompt = defaulting_to(true, @params[:prompt])
|
32
|
+
self.user_presence = defaulting_to(true, @params[:user_presence])
|
33
|
+
self.callback_url = @params[:callback_url]
|
34
|
+
|
35
|
+
@status = @params[:status]
|
36
|
+
end
|
37
|
+
# rubocop:enable Metrics/MethodLength
|
38
|
+
# rubocop:enable Metrics/AbcSize
|
39
|
+
|
40
|
+
def to_h
|
41
|
+
@params
|
42
|
+
end
|
43
|
+
|
44
|
+
# rubocop:disable Metrics/MethodLength
|
45
|
+
def to_json
|
46
|
+
JSON(device_identifier: device_identifier,
|
47
|
+
user_identifier: user_identifier,
|
48
|
+
trucode_id: trucode_id,
|
49
|
+
trusona_id: trusona_id,
|
50
|
+
resource: resource,
|
51
|
+
action: action,
|
52
|
+
desired_level: level,
|
53
|
+
id: id,
|
54
|
+
status: @status,
|
55
|
+
prompt: prompt,
|
56
|
+
user_presence: user_presence,
|
57
|
+
callback_url: callback_url,
|
58
|
+
expires_at: expires_at&.iso8601)
|
59
|
+
end
|
60
|
+
# rubocop:enable Metrics/MethodLength
|
61
|
+
|
62
|
+
def accepted?
|
63
|
+
return true if status == :accepted
|
64
|
+
return true if status == :accepted_at_higher_level
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
68
|
+
def rejected?
|
69
|
+
!accepted?
|
70
|
+
end
|
71
|
+
|
72
|
+
def valid?
|
73
|
+
attributes_present && attributes_filled
|
74
|
+
end
|
75
|
+
|
76
|
+
# rubocop:disable MethodLength
|
77
|
+
# rubocop:disable CyclomaticComplexity
|
78
|
+
def status
|
79
|
+
case @status
|
80
|
+
when 'INVALID_TRUSONA_ID'
|
81
|
+
:invalid_trusona_id
|
82
|
+
when 'IN_PROGRESS'
|
83
|
+
:in_progress
|
84
|
+
when 'REJECTED'
|
85
|
+
:rejected
|
86
|
+
when 'ACCEPTED'
|
87
|
+
:accepted
|
88
|
+
when 'INVALID_EMAIL'
|
89
|
+
:invalid_email
|
90
|
+
when 'ACCEPTED_AT_LOWER_LEVEL'
|
91
|
+
:accepted_at_lower_level
|
92
|
+
when 'ACCEPTED_AT_HIGHER_LEVEL'
|
93
|
+
:accepted_at_higher_level
|
94
|
+
when 'EXPIRED'
|
95
|
+
:expired
|
96
|
+
else
|
97
|
+
:invalid
|
98
|
+
end
|
99
|
+
end
|
100
|
+
# rubocop:enable CyclomaticComplexity
|
101
|
+
# rubocop:enable MethodLength
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def defaulting_to(value, param)
|
106
|
+
return value if param.nil?
|
107
|
+
return value if param.respond_to?(:empty?) && param.empty?
|
108
|
+
param
|
109
|
+
end
|
110
|
+
|
111
|
+
def determine_accepted_level(params)
|
112
|
+
params[:result][:accepted_level] unless params[:result].nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
def attributes_present
|
116
|
+
return false if identifier.nil?
|
117
|
+
return false if resource.nil?
|
118
|
+
return false if action.nil?
|
119
|
+
return false if level.nil?
|
120
|
+
|
121
|
+
true
|
122
|
+
end
|
123
|
+
|
124
|
+
def attributes_filled
|
125
|
+
return false if identifier.empty?
|
126
|
+
return false if resource.empty?
|
127
|
+
return false if action.empty?
|
128
|
+
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def identifier
|
133
|
+
device_identifier || user_identifier || trucode_id || trusona_id
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trusona
|
4
|
+
module Resources
|
5
|
+
#
|
6
|
+
## a potentially scanned and or paired TruCode
|
7
|
+
class UserAccount < BaseResource
|
8
|
+
module Status
|
9
|
+
ACTIVE = 'active'
|
10
|
+
INACTIVE = 'inactive'
|
11
|
+
UNKNOWN = 'unknown'
|
12
|
+
end
|
13
|
+
|
14
|
+
module Level
|
15
|
+
ENTRY = 'entry'
|
16
|
+
ESSENTIAL = 'essential'
|
17
|
+
EXECUTIVE = 'executive'
|
18
|
+
end
|
19
|
+
|
20
|
+
include Trusona::Resources::Validators
|
21
|
+
include Trusona::Helpers::KeyNormalizer
|
22
|
+
|
23
|
+
attr_reader :id, :trusona_id, :email, :status, :first_name, :last_name,
|
24
|
+
:nickname, :handle, :emails, :max_level
|
25
|
+
|
26
|
+
# rubocop:disable Metrics/MethodLength
|
27
|
+
# rubocop:disable Metrics/AbcSize
|
28
|
+
def initialize(params = {})
|
29
|
+
params_with_symbol_keys = normalize_keys(params)
|
30
|
+
@trusona_id = params_with_symbol_keys[:trusona_id]
|
31
|
+
@id = @trusona_id
|
32
|
+
@email = params_with_symbol_keys[:email]
|
33
|
+
@status = determine_status(params_with_symbol_keys[:status])
|
34
|
+
@first_name = params_with_symbol_keys[:first_name]
|
35
|
+
@last_name = params_with_symbol_keys[:last_name]
|
36
|
+
@nickname = params_with_symbol_keys[:nickname]
|
37
|
+
@handle = params_with_symbol_keys[:handle]
|
38
|
+
@emails = parse_emails(params_with_symbol_keys[:emails])
|
39
|
+
@max_level = parse_max_level(params_with_symbol_keys[:metadata])
|
40
|
+
|
41
|
+
@params = params_with_symbol_keys
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/MethodLength
|
44
|
+
# rubocop:enable Metrics/AbcSize
|
45
|
+
|
46
|
+
def to_json
|
47
|
+
JSON(to_h)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def determine_status(status)
|
53
|
+
return Status::INACTIVE if status == 'inactive'
|
54
|
+
return Status::ACTIVE if status == 'active'
|
55
|
+
Status::UNKNOWN
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_emails(emails)
|
59
|
+
return [] if emails.nil? || emails.empty?
|
60
|
+
emails.map { |e| UserAccountEmail.new(e) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_max_level(metadata)
|
64
|
+
return Level::ENTRY unless metadata
|
65
|
+
level = metadata['max_level'] || metadata[:max_level]
|
66
|
+
|
67
|
+
return Level::ESSENTIAL if level == 'essential'
|
68
|
+
return Level::EXECUTIVE if level == 'executive'
|
69
|
+
Level::ENTRY
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
## An email associated with a user account
|
74
|
+
class UserAccountEmail
|
75
|
+
attr_reader :email, :id, :verified
|
76
|
+
def initialize(email = {})
|
77
|
+
@email = email[:email] || email['email']
|
78
|
+
@id = email[:id] || email['id']
|
79
|
+
@verified = email[:verified] || email['verified'] || false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trusona
|
4
|
+
module Resources
|
5
|
+
#
|
6
|
+
## a relying party specific user identifier
|
7
|
+
class UserIdentifier < BaseResource
|
8
|
+
include Trusona::Resources::Validators
|
9
|
+
attr_reader :identifier, :trusona_id
|
10
|
+
|
11
|
+
def initialize(params = {})
|
12
|
+
@params = params
|
13
|
+
@identifier = params[:identifier]
|
14
|
+
@trusona_id = params[:trusona_id]
|
15
|
+
@id = @identifier
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_h
|
19
|
+
@params
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_json
|
23
|
+
JSON(to_h)
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid?
|
27
|
+
validate
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate
|
31
|
+
attributes_present && attributes_filled
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def attributes_present
|
37
|
+
return false unless @params.key?(:identifier)
|
38
|
+
return false unless @params.key?(:trusona_id)
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
def attributes_filled
|
43
|
+
return false if @params.fetch(:identifier).empty?
|
44
|
+
return false if @params.fetch(:trusona_id).empty?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trusona
|
4
|
+
module Resources
|
5
|
+
#
|
6
|
+
## Basic validators for working with Resources
|
7
|
+
module Validators
|
8
|
+
def present?(item)
|
9
|
+
return false if item.nil? || item.to_s.empty?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
validate
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trusona
|
4
|
+
module Services
|
5
|
+
#
|
6
|
+
## User Accounts Service
|
7
|
+
class AccountLookupsService < BaseService
|
8
|
+
def initialize(client: nil, mapper: nil)
|
9
|
+
@client = client || Trusona::Api::HTTPClient.new(
|
10
|
+
Trusona.config.api_host
|
11
|
+
)
|
12
|
+
@mapper = mapper || Trusona::Mappers::UserAccountMapper.new
|
13
|
+
@resource_path = '/internal/v1/account_lookups'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Trusona
|
4
|
+
module Services
|
5
|
+
#
|
6
|
+
## The starting point for building other services that interact with the
|
7
|
+
## Trusona API
|
8
|
+
class BaseService
|
9
|
+
attr_accessor :resource_path
|
10
|
+
|
11
|
+
def initialize(client: Trusona::Api::HTTPClient.new,
|
12
|
+
mapper: Trusona::Mappers::BaseMapper.new)
|
13
|
+
@client = client
|
14
|
+
@mapper = mapper
|
15
|
+
@resource_path = '/base-service'
|
16
|
+
end
|
17
|
+
|
18
|
+
def index
|
19
|
+
handle(@client.get(collection_path))
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(resource)
|
23
|
+
raise Trusona::InvalidResourceError unless resource.id
|
24
|
+
handle(@client.get(member_path(resource)), resource)
|
25
|
+
end
|
26
|
+
|
27
|
+
def create(resource)
|
28
|
+
puts resource.to_json
|
29
|
+
raise Trusona::InvalidResourceError unless resource.valid?
|
30
|
+
handle(@client.post(collection_path, resource.to_json), resource)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update(resource)
|
34
|
+
raise Trusona::InvalidResourceError unless resource.id
|
35
|
+
handle(@client.patch(member_path(resource), resource.to_json), resource)
|
36
|
+
end
|
37
|
+
|
38
|
+
def collection_path
|
39
|
+
@resource_path
|
40
|
+
end
|
41
|
+
|
42
|
+
def member_path(resource)
|
43
|
+
[collection_path, resource.id].join('/')
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def verify_response(response)
|
49
|
+
raise Trusona::SigningError unless response.verified?
|
50
|
+
end
|
51
|
+
|
52
|
+
# rubocop:disable MethodLength
|
53
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
54
|
+
def handle(response, resource = {})
|
55
|
+
@response = response
|
56
|
+
raise if resource.nil?
|
57
|
+
case response.code
|
58
|
+
when 200..299
|
59
|
+
success(response, resource)
|
60
|
+
when 400
|
61
|
+
bad_request
|
62
|
+
when 404
|
63
|
+
not_found
|
64
|
+
when 403
|
65
|
+
unauthorized
|
66
|
+
when 424
|
67
|
+
failed_dependency
|
68
|
+
when 500..599
|
69
|
+
server_error
|
70
|
+
else
|
71
|
+
raise Trusona::RequestError, readable_error
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# rubocop:enable MethodLength
|
75
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
76
|
+
|
77
|
+
def success(response, resource)
|
78
|
+
verify_response(response)
|
79
|
+
@mapper.map(response, resource)
|
80
|
+
end
|
81
|
+
|
82
|
+
def bad_request
|
83
|
+
raise Trusona::BadRequestError, readable_error
|
84
|
+
end
|
85
|
+
|
86
|
+
def failed_dependency
|
87
|
+
raise Trusona::FailedDependencyError, readable_error
|
88
|
+
end
|
89
|
+
|
90
|
+
def not_found
|
91
|
+
raise Trusona::ResourceNotFoundError, readable_error
|
92
|
+
end
|
93
|
+
|
94
|
+
def unauthorized
|
95
|
+
raise Trusona::UnauthorizedRequestError, readable_error
|
96
|
+
end
|
97
|
+
|
98
|
+
def server_error
|
99
|
+
raise Trusona::ApiError
|
100
|
+
end
|
101
|
+
|
102
|
+
def readable_error
|
103
|
+
default = '[UNKNOWN] Error - An unknown error has occurred.'
|
104
|
+
return default unless @response
|
105
|
+
body = @response.to_h
|
106
|
+
msg = []
|
107
|
+
msg << "[#{body['error']}] #{body['message']} - #{body['description']}"
|
108
|
+
return msg.join("\n") unless body['field_errors']
|
109
|
+
body['field_errors'].each do |field|
|
110
|
+
msg << "\t #{field.join(' => ')}"
|
111
|
+
end
|
112
|
+
|
113
|
+
msg.join("\n")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|