smart-id-ruby-client 0.1.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/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/CHANGELOG.md +13 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +436 -0
- data/Rakefile +12 -0
- data/lib/smart-id-ruby-client.rb +3 -0
- data/lib/smart_id_ruby/callback_url.rb +18 -0
- data/lib/smart_id_ruby/callback_url_util.rb +54 -0
- data/lib/smart_id_ruby/client.rb +124 -0
- data/lib/smart_id_ruby/configuration.rb +184 -0
- data/lib/smart_id_ruby/device_link_builder.rb +301 -0
- data/lib/smart_id_ruby/device_link_interaction.rb +67 -0
- data/lib/smart_id_ruby/errors/certificate_level_mismatch_error.rb +8 -0
- data/lib/smart_id_ruby/errors/document_unusable_error.rb +12 -0
- data/lib/smart_id_ruby/errors/error.rb +8 -0
- data/lib/smart_id_ruby/errors/expected_linked_session_error.rb +15 -0
- data/lib/smart_id_ruby/errors/no_suitable_account_of_requested_type_found_error.rb +10 -0
- data/lib/smart_id_ruby/errors/person_should_view_smart_id_portal_error.rb +8 -0
- data/lib/smart_id_ruby/errors/protocol_failure_error.rb +13 -0
- data/lib/smart_id_ruby/errors/relying_party_account_configuration_error.rb +10 -0
- data/lib/smart_id_ruby/errors/request_setup_error.rb +10 -0
- data/lib/smart_id_ruby/errors/request_validation_error.rb +8 -0
- data/lib/smart_id_ruby/errors/required_interaction_not_supported_by_app_error.rb +13 -0
- data/lib/smart_id_ruby/errors/response_error.rb +8 -0
- data/lib/smart_id_ruby/errors/server_maintenance_error.rb +8 -0
- data/lib/smart_id_ruby/errors/session_end_result_error.rb +15 -0
- data/lib/smart_id_ruby/errors/session_not_complete_error.rb +8 -0
- data/lib/smart_id_ruby/errors/session_not_found_error.rb +8 -0
- data/lib/smart_id_ruby/errors/session_secret_mismatch_error.rb +8 -0
- data/lib/smart_id_ruby/errors/session_timeout_error.rb +12 -0
- data/lib/smart_id_ruby/errors/smart_id_server_error.rb +12 -0
- data/lib/smart_id_ruby/errors/unprocessable_response_error.rb +9 -0
- data/lib/smart_id_ruby/errors/unsupported_client_api_version_error.rb +8 -0
- data/lib/smart_id_ruby/errors/user_account_not_found_error.rb +8 -0
- data/lib/smart_id_ruby/errors/user_account_unusable_error.rb +12 -0
- data/lib/smart_id_ruby/errors/user_refused_cert_choice_error.rb +14 -0
- data/lib/smart_id_ruby/errors/user_refused_confirmation_message_error.rb +13 -0
- data/lib/smart_id_ruby/errors/user_refused_confirmation_message_with_verification_choice_error.rb +13 -0
- data/lib/smart_id_ruby/errors/user_refused_display_text_and_pin_error.rb +13 -0
- data/lib/smart_id_ruby/errors/user_refused_error.rb +12 -0
- data/lib/smart_id_ruby/errors/user_selected_wrong_verification_code_error.rb +13 -0
- data/lib/smart_id_ruby/errors.rb +31 -0
- data/lib/smart_id_ruby/flows/base_builder.rb +90 -0
- data/lib/smart_id_ruby/flows/certificate_by_document_number_request_builder.rb +130 -0
- data/lib/smart_id_ruby/flows/device_link_authentication_session_request_builder.rb +208 -0
- data/lib/smart_id_ruby/flows/device_link_certificate_choice_session_request_builder.rb +112 -0
- data/lib/smart_id_ruby/flows/device_link_signature_session_request_builder.rb +286 -0
- data/lib/smart_id_ruby/flows/linked_notification_signature_session_request_builder.rb +235 -0
- data/lib/smart_id_ruby/flows/notification_authentication_session_request_builder.rb +184 -0
- data/lib/smart_id_ruby/flows/notification_certificate_choice_session_request_builder.rb +96 -0
- data/lib/smart_id_ruby/flows/notification_signature_session_request_builder.rb +272 -0
- data/lib/smart_id_ruby/models/authentication_identity.rb +19 -0
- data/lib/smart_id_ruby/models/authentication_response.rb +38 -0
- data/lib/smart_id_ruby/models/certificate_choice_response.rb +19 -0
- data/lib/smart_id_ruby/models/device_link_session_response.rb +34 -0
- data/lib/smart_id_ruby/models/notification_authentication_session_response.rb +25 -0
- data/lib/smart_id_ruby/models/notification_certificate_choice_session_response.rb +25 -0
- data/lib/smart_id_ruby/models/notification_signature_session_response.rb +29 -0
- data/lib/smart_id_ruby/models/session_status.rb +261 -0
- data/lib/smart_id_ruby/models/signature_response.rb +38 -0
- data/lib/smart_id_ruby/notification_interaction.rb +70 -0
- data/lib/smart_id_ruby/qr_code_generator.rb +65 -0
- data/lib/smart_id_ruby/rest/connector.rb +364 -0
- data/lib/smart_id_ruby/rest/session_status_poller.rb +125 -0
- data/lib/smart_id_ruby/rp_challenge.rb +37 -0
- data/lib/smart_id_ruby/rp_challenge_generator.rb +28 -0
- data/lib/smart_id_ruby/semantics_identifier.rb +35 -0
- data/lib/smart_id_ruby/validation/authentication_certificate_validator.rb +90 -0
- data/lib/smart_id_ruby/validation/authentication_identity_mapper.rb +227 -0
- data/lib/smart_id_ruby/validation/base_authentication_response_validator.rb +304 -0
- data/lib/smart_id_ruby/validation/certificate_choice_response_validator.rb +104 -0
- data/lib/smart_id_ruby/validation/certificate_validator.rb +170 -0
- data/lib/smart_id_ruby/validation/device_link_authentication_response_validator.rb +76 -0
- data/lib/smart_id_ruby/validation/error_result_handler.rb +88 -0
- data/lib/smart_id_ruby/validation/notification_authentication_response_validator.rb +16 -0
- data/lib/smart_id_ruby/validation/signature_payload_builder.rb +62 -0
- data/lib/smart_id_ruby/validation/signature_response_validator.rb +345 -0
- data/lib/smart_id_ruby/validation/signature_value_validator.rb +76 -0
- data/lib/smart_id_ruby/validation/trusted_ca_cert_store.rb +20 -0
- data/lib/smart_id_ruby/verification_code_calculator.rb +31 -0
- data/lib/smart_id_ruby/version.rb +5 -0
- data/lib/smart_id_ruby.rb +76 -0
- metadata +173 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "openssl"
|
|
5
|
+
|
|
6
|
+
module SmartIdRuby
|
|
7
|
+
module Flows
|
|
8
|
+
# Builds certificate by document number requests.
|
|
9
|
+
class CertificateByDocumentNumberRequestBuilder < BaseBuilder
|
|
10
|
+
BASE64_PATTERN = /\A[A-Za-z0-9+\/]+={0,2}\z/
|
|
11
|
+
SUPPORTED_STATES = %w[OK DOCUMENT_UNUSABLE].freeze
|
|
12
|
+
CERTIFICATE_LEVEL_ORDER = {
|
|
13
|
+
"ADVANCED" => 1,
|
|
14
|
+
"QUALIFIED" => 2,
|
|
15
|
+
"QSCD" => 2
|
|
16
|
+
}.freeze
|
|
17
|
+
DEFAULT_CERTIFICATE_LEVEL = "QUALIFIED"
|
|
18
|
+
|
|
19
|
+
def initialize(connector)
|
|
20
|
+
super(connector)
|
|
21
|
+
@document_number = nil
|
|
22
|
+
@certificate_level = DEFAULT_CERTIFICATE_LEVEL
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def with_document_number(document_number)
|
|
26
|
+
@document_number = document_number
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def with_certificate_level(certificate_level)
|
|
31
|
+
@certificate_level = certificate_level
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def get_certificate_by_document_number
|
|
36
|
+
validate_request_parameters
|
|
37
|
+
request = create_request
|
|
38
|
+
response = connector.get_certificate_by_document_number(@document_number, request)
|
|
39
|
+
validate_response_parameters(response)
|
|
40
|
+
|
|
41
|
+
cert = fetch_value(response, :cert)
|
|
42
|
+
cert_level = fetch_value(cert, :certificateLevel).to_s
|
|
43
|
+
cert_value = fetch_value(cert, :value).to_s
|
|
44
|
+
{
|
|
45
|
+
certificate_level: cert_level,
|
|
46
|
+
certificate: OpenSSL::X509::Certificate.new(Base64.strict_decode64(cert_value))
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def create_request
|
|
53
|
+
{
|
|
54
|
+
relyingPartyUUID: relying_party_uuid,
|
|
55
|
+
relyingPartyName: relying_party_name,
|
|
56
|
+
certificateLevel: @certificate_level&.to_s
|
|
57
|
+
}.compact
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def validate_request_parameters
|
|
61
|
+
if blank?(@document_number)
|
|
62
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'documentNumber' cannot be empty"
|
|
63
|
+
end
|
|
64
|
+
if blank?(relying_party_uuid)
|
|
65
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'relyingPartyUUID' cannot be empty"
|
|
66
|
+
end
|
|
67
|
+
if blank?(relying_party_name)
|
|
68
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'relyingPartyName' cannot be empty"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def validate_response_parameters(response)
|
|
73
|
+
if response.nil?
|
|
74
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError, "Queried certificate response is not provided"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
validate_state(response)
|
|
78
|
+
cert = fetch_value(response, :cert)
|
|
79
|
+
if cert.nil?
|
|
80
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError, "Queried certificate response field 'cert' is missing"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
validate_certificate_level(cert)
|
|
84
|
+
validate_certificate_value(cert)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def validate_state(response)
|
|
88
|
+
state = fetch_value(response, :state)
|
|
89
|
+
if blank?(state)
|
|
90
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError, "Queried certificate response field 'state' is missing"
|
|
91
|
+
end
|
|
92
|
+
unless SUPPORTED_STATES.include?(state)
|
|
93
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError, "Queried certificate response field 'state' has unsupported value"
|
|
94
|
+
end
|
|
95
|
+
if state == "DOCUMENT_UNUSABLE"
|
|
96
|
+
raise SmartIdRuby::Errors::DocumentUnusableError
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def validate_certificate_level(cert)
|
|
101
|
+
response_level = fetch_value(cert, :certificateLevel)
|
|
102
|
+
if blank?(response_level)
|
|
103
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError, "Queried certificate response field 'cert.certificateLevel' is missing"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
unless CERTIFICATE_LEVEL_ORDER.key?(response_level)
|
|
107
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
108
|
+
"Queried certificate response field 'cert.certificateLevel' has unsupported value"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
requested_level = @certificate_level.nil? ? DEFAULT_CERTIFICATE_LEVEL : @certificate_level.to_s
|
|
112
|
+
if CERTIFICATE_LEVEL_ORDER[response_level] < CERTIFICATE_LEVEL_ORDER.fetch(requested_level, CERTIFICATE_LEVEL_ORDER[DEFAULT_CERTIFICATE_LEVEL])
|
|
113
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError, "Queried certificate has lower level than requested"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def validate_certificate_value(cert)
|
|
118
|
+
cert_value = fetch_value(cert, :value)
|
|
119
|
+
if blank?(cert_value)
|
|
120
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError, "Queried certificate response field 'cert.value' is missing"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
return if BASE64_PATTERN.match?(cert_value)
|
|
124
|
+
|
|
125
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
126
|
+
"Queried certificate response field 'cert.value' does not have Base64-encoded value"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module SmartIdRuby
|
|
6
|
+
module Flows
|
|
7
|
+
# Builds device link authentication session requests.
|
|
8
|
+
class DeviceLinkAuthenticationSessionRequestBuilder < BaseBuilder
|
|
9
|
+
INITIAL_CALLBACK_URL_PATTERN = %r{\Ahttps://[^|]+\z}
|
|
10
|
+
RP_CHALLENGE_MIN_LENGTH = 44
|
|
11
|
+
RP_CHALLENGE_MAX_LENGTH = 88
|
|
12
|
+
|
|
13
|
+
attr_reader :authentication_session_request
|
|
14
|
+
|
|
15
|
+
def initialize(connector)
|
|
16
|
+
super(connector)
|
|
17
|
+
@certificate_level = "QUALIFIED"
|
|
18
|
+
@signature_algorithm = "rsassa-pss"
|
|
19
|
+
@hash_algorithm = "SHA3-512"
|
|
20
|
+
@interactions = nil
|
|
21
|
+
@share_md_client_ip_address = nil
|
|
22
|
+
@capabilities = nil
|
|
23
|
+
@semantics_identifier = nil
|
|
24
|
+
@document_number = nil
|
|
25
|
+
@initial_callback_url = nil
|
|
26
|
+
@rp_challenge = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def with_certificate_level(certificate_level)
|
|
30
|
+
@certificate_level = certificate_level
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def with_rp_challenge(rp_challenge)
|
|
35
|
+
@rp_challenge = rp_challenge
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def with_signature_algorithm(signature_algorithm)
|
|
40
|
+
@signature_algorithm = signature_algorithm
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def with_hash_algorithm(hash_algorithm)
|
|
45
|
+
@hash_algorithm = hash_algorithm
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def with_interactions(interactions)
|
|
50
|
+
@interactions = interactions
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def with_share_md_client_ip_address(share_md_client_ip_address)
|
|
55
|
+
@share_md_client_ip_address = share_md_client_ip_address
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def with_capabilities(*capabilities)
|
|
60
|
+
@capabilities = normalize_capabilities(capabilities, strip: false, reject_empty: false)
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def with_semantics_identifier(semantics_identifier)
|
|
65
|
+
@semantics_identifier = semantics_identifier
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def with_document_number(document_number)
|
|
70
|
+
@document_number = document_number
|
|
71
|
+
self
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def with_initial_callback_url(initial_callback_url)
|
|
75
|
+
@initial_callback_url = initial_callback_url
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def init_authentication_session
|
|
80
|
+
validate_request_parameters
|
|
81
|
+
request = create_authentication_request
|
|
82
|
+
response = init_session(request)
|
|
83
|
+
validate_response_parameters(response)
|
|
84
|
+
@authentication_session_request = request
|
|
85
|
+
|
|
86
|
+
SmartIdRuby::Models::DeviceLinkSessionResponse.from_h(response)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def init_session(request)
|
|
92
|
+
if @semantics_identifier && @document_number
|
|
93
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Only one of 'semanticsIdentifier' or 'documentNumber' may be set"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if @semantics_identifier
|
|
97
|
+
connector.init_device_link_authentication(request, @semantics_identifier)
|
|
98
|
+
elsif @document_number
|
|
99
|
+
connector.init_device_link_authentication_with_document(request, @document_number)
|
|
100
|
+
else
|
|
101
|
+
connector.init_anonymous_device_link_authentication(request)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def validate_request_parameters
|
|
106
|
+
if blank?(relying_party_uuid)
|
|
107
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'relyingPartyUUID' cannot be empty"
|
|
108
|
+
end
|
|
109
|
+
if blank?(relying_party_name)
|
|
110
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'relyingPartyName' cannot be empty"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
validate_signature_parameters
|
|
114
|
+
validate_interactions
|
|
115
|
+
validate_initial_callback_url
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def validate_signature_parameters
|
|
119
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'rpChallenge' cannot be empty" if blank?(@rp_challenge)
|
|
120
|
+
|
|
121
|
+
begin
|
|
122
|
+
Base64.strict_decode64(@rp_challenge)
|
|
123
|
+
rescue ArgumentError
|
|
124
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'rpChallenge' must be Base64-encoded string"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
unless @rp_challenge.length.between?(RP_CHALLENGE_MIN_LENGTH, RP_CHALLENGE_MAX_LENGTH)
|
|
128
|
+
raise SmartIdRuby::Errors::RequestSetupError,
|
|
129
|
+
"Value for 'rpChallenge' must have length between #{RP_CHALLENGE_MIN_LENGTH} and #{RP_CHALLENGE_MAX_LENGTH} characters"
|
|
130
|
+
end
|
|
131
|
+
if @signature_algorithm.nil?
|
|
132
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'signatureAlgorithm' must be set"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if @hash_algorithm.nil?
|
|
136
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'hashAlgorithm' must be set"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def validate_interactions
|
|
141
|
+
normalized_interactions = normalize_interactions(@interactions)
|
|
142
|
+
if normalized_interactions.empty?
|
|
143
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'interactions' cannot be empty"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
interaction_types = normalized_interactions.map { |interaction| interaction[:type] }
|
|
147
|
+
if interaction_types.any?(&:nil?)
|
|
148
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Each interaction must include a 'type' value"
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if interaction_types.uniq.length != interaction_types.length
|
|
152
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'interactions' cannot contain duplicate types"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def validate_initial_callback_url
|
|
157
|
+
return if blank?(@initial_callback_url)
|
|
158
|
+
return if INITIAL_CALLBACK_URL_PATTERN.match?(@initial_callback_url)
|
|
159
|
+
|
|
160
|
+
raise SmartIdRuby::Errors::RequestSetupError,
|
|
161
|
+
"Value for 'initialCallbackUrl' must match pattern ^https://[^|]+$ and must not contain unencoded vertical bars"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def create_authentication_request
|
|
165
|
+
{
|
|
166
|
+
relyingPartyUUID: relying_party_uuid,
|
|
167
|
+
relyingPartyName: relying_party_name,
|
|
168
|
+
certificateLevel: @certificate_level&.to_s,
|
|
169
|
+
signatureProtocol: "ACSP_V2",
|
|
170
|
+
signatureProtocolParameters: {
|
|
171
|
+
rpChallenge: @rp_challenge,
|
|
172
|
+
signatureAlgorithm: @signature_algorithm.to_s,
|
|
173
|
+
signatureAlgorithmParameters: {
|
|
174
|
+
hashAlgorithm: @hash_algorithm.to_s
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
interactions: encode_interactions(@interactions),
|
|
178
|
+
requestProperties: request_properties,
|
|
179
|
+
capabilities: @capabilities,
|
|
180
|
+
initialCallbackUrl: @initial_callback_url
|
|
181
|
+
}.compact
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def request_properties
|
|
185
|
+
request_properties_for_share_md(@share_md_client_ip_address)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def validate_response_parameters(response)
|
|
189
|
+
if blank?(fetch_value(response, :sessionID))
|
|
190
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
191
|
+
"Device link authentication session initialisation response field 'sessionID' is missing or empty"
|
|
192
|
+
end
|
|
193
|
+
if blank?(fetch_value(response, :sessionToken))
|
|
194
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
195
|
+
"Device link authentication session initialisation response field 'sessionToken' is missing or empty"
|
|
196
|
+
end
|
|
197
|
+
if blank?(fetch_value(response, :sessionSecret))
|
|
198
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
199
|
+
"Device link authentication session initialisation response field 'sessionSecret' is missing or empty"
|
|
200
|
+
end
|
|
201
|
+
if blank?(fetch_value(response, :deviceLinkBase))
|
|
202
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
203
|
+
"Device link authentication session initialisation response field 'deviceLinkBase' is missing or empty"
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SmartIdRuby
|
|
4
|
+
module Flows
|
|
5
|
+
# Builds device link certificate choice session requests.
|
|
6
|
+
class DeviceLinkCertificateChoiceSessionRequestBuilder < BaseBuilder
|
|
7
|
+
INITIAL_CALLBACK_URL_PATTERN = %r{\Ahttps://[^|]+\z}
|
|
8
|
+
NONCE_MAX_LENGTH = 30
|
|
9
|
+
|
|
10
|
+
def initialize(connector)
|
|
11
|
+
super(connector)
|
|
12
|
+
@certificate_level = nil
|
|
13
|
+
@nonce = nil
|
|
14
|
+
@capabilities = nil
|
|
15
|
+
@share_md_client_ip_address = nil
|
|
16
|
+
@initial_callback_url = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def with_certificate_level(certificate_level)
|
|
20
|
+
@certificate_level = certificate_level
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def with_nonce(nonce)
|
|
25
|
+
@nonce = nonce
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def with_capabilities(*capabilities)
|
|
30
|
+
@capabilities = normalize_capabilities(capabilities)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def with_share_md_client_ip_address(share_md_client_ip_address)
|
|
35
|
+
@share_md_client_ip_address = share_md_client_ip_address
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def with_initial_callback_url(initial_callback_url)
|
|
40
|
+
@initial_callback_url = initial_callback_url
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def init_certificate_choice
|
|
45
|
+
validate_request_parameters
|
|
46
|
+
request = create_certificate_request
|
|
47
|
+
response = connector.init_device_link_certificate_choice(request)
|
|
48
|
+
validate_response_parameters(response)
|
|
49
|
+
SmartIdRuby::Models::DeviceLinkSessionResponse.from_h(response)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def validate_request_parameters
|
|
55
|
+
if blank?(relying_party_uuid)
|
|
56
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'relyingPartyUUID' cannot be empty"
|
|
57
|
+
end
|
|
58
|
+
if blank?(relying_party_name)
|
|
59
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'relyingPartyName' cannot be empty"
|
|
60
|
+
end
|
|
61
|
+
if !@nonce.nil? && (@nonce.empty? || @nonce.length > NONCE_MAX_LENGTH)
|
|
62
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'nonce' must have length between 1 and 30 characters"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
validate_initial_callback_url
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def create_certificate_request
|
|
69
|
+
{
|
|
70
|
+
relyingPartyUUID: relying_party_uuid,
|
|
71
|
+
relyingPartyName: relying_party_name,
|
|
72
|
+
certificateLevel: @certificate_level&.to_s,
|
|
73
|
+
nonce: @nonce,
|
|
74
|
+
capabilities: @capabilities,
|
|
75
|
+
requestProperties: request_properties,
|
|
76
|
+
initialCallbackUrl: @initial_callback_url
|
|
77
|
+
}.compact
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def request_properties
|
|
81
|
+
request_properties_for_share_md(@share_md_client_ip_address)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def validate_initial_callback_url
|
|
85
|
+
return if blank?(@initial_callback_url)
|
|
86
|
+
return if INITIAL_CALLBACK_URL_PATTERN.match?(@initial_callback_url)
|
|
87
|
+
|
|
88
|
+
raise SmartIdRuby::Errors::RequestSetupError,
|
|
89
|
+
"Value for 'initialCallbackUrl' must match pattern ^https://[^|]+$ and must not contain unencoded vertical bars"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_response_parameters(response)
|
|
93
|
+
if blank?(fetch_value(response, :sessionID))
|
|
94
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
95
|
+
"Device link certificate choice session initialisation response field 'sessionID' is missing or empty"
|
|
96
|
+
end
|
|
97
|
+
if blank?(fetch_value(response, :sessionToken))
|
|
98
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
99
|
+
"Device link certificate choice session initialisation response field 'sessionToken' is missing or empty"
|
|
100
|
+
end
|
|
101
|
+
if blank?(fetch_value(response, :sessionSecret))
|
|
102
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
103
|
+
"Device link certificate choice session initialisation response field 'sessionSecret' is missing or empty"
|
|
104
|
+
end
|
|
105
|
+
if blank?(fetch_value(response, :deviceLinkBase))
|
|
106
|
+
raise SmartIdRuby::Errors::UnprocessableResponseError,
|
|
107
|
+
"Device link certificate choice session initialisation response field 'deviceLinkBase' is missing or empty"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|