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,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SmartIdRuby
|
|
4
|
+
module Models
|
|
5
|
+
# Represents response for active Smart-ID session query.
|
|
6
|
+
# state - Required. Current state of the session, e.g. "RUNNING", "COMPLETE".
|
|
7
|
+
# result - Required if state is "COMPLETE". Details about how session ended.
|
|
8
|
+
# signature_protocol - Required if end result is OK. Signature protocol used, e.g. "ACSP_V2" or "RAW_DIGEST_SIGNATURE".
|
|
9
|
+
# signature - Required if end result is OK. Signature data containing the actual signature and related information.
|
|
10
|
+
# cert - Required if end result is OK. Signer's certificate data.
|
|
11
|
+
# ignored_properties - Properties that were ignored from the session request.
|
|
12
|
+
# interaction_type_used - Required if end result is OK. Interaction type that was used in the session.
|
|
13
|
+
# device_ip_address - IP address of the device used in the session.
|
|
14
|
+
class SessionStatus
|
|
15
|
+
attr_reader :state, :result, :signature_protocol, :signature, :cert,
|
|
16
|
+
:ignored_properties, :interaction_type_used, :device_ip_address
|
|
17
|
+
|
|
18
|
+
def initialize(
|
|
19
|
+
state: nil,
|
|
20
|
+
result: nil,
|
|
21
|
+
signature_protocol: nil,
|
|
22
|
+
signature: nil,
|
|
23
|
+
cert: nil,
|
|
24
|
+
ignored_properties: nil,
|
|
25
|
+
interaction_type_used: nil,
|
|
26
|
+
device_ip_address: nil
|
|
27
|
+
)
|
|
28
|
+
@state = state
|
|
29
|
+
@result = result
|
|
30
|
+
@signature_protocol = signature_protocol
|
|
31
|
+
@signature = signature
|
|
32
|
+
@cert = cert
|
|
33
|
+
@ignored_properties = ignored_properties
|
|
34
|
+
@interaction_type_used = interaction_type_used
|
|
35
|
+
@device_ip_address = device_ip_address
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def running?
|
|
39
|
+
state.to_s.casecmp("RUNNING").zero?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def complete?
|
|
43
|
+
state.to_s.casecmp("COMPLETE").zero?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def to_h
|
|
47
|
+
{
|
|
48
|
+
state: state,
|
|
49
|
+
result: result.to_h,
|
|
50
|
+
signatureProtocol: signature_protocol,
|
|
51
|
+
signature: signature.to_h,
|
|
52
|
+
cert: cert.to_h,
|
|
53
|
+
ignoredProperties: ignored_properties,
|
|
54
|
+
interactionTypeUsed: interaction_type_used,
|
|
55
|
+
deviceIpAddress: device_ip_address
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.from_h(payload)
|
|
60
|
+
return new unless payload.is_a?(Hash)
|
|
61
|
+
|
|
62
|
+
new(
|
|
63
|
+
state: fetch(payload, :state),
|
|
64
|
+
result: SessionResult.from_h(fetch(payload, :result)),
|
|
65
|
+
signature_protocol: fetch(payload, :signatureProtocol),
|
|
66
|
+
signature: SessionSignature.from_h(fetch(payload, :signature)),
|
|
67
|
+
cert: SessionCertificate.from_h(fetch(payload, :cert)),
|
|
68
|
+
ignored_properties: fetch(payload, :ignoredProperties),
|
|
69
|
+
interaction_type_used: fetch(payload, :interactionTypeUsed),
|
|
70
|
+
device_ip_address: fetch(payload, :deviceIpAddress)
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.fetch(payload, key)
|
|
75
|
+
payload[key] || payload[key.to_s]
|
|
76
|
+
end
|
|
77
|
+
private_class_method :fetch
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Represents session result data returned by Smart-ID.
|
|
81
|
+
class SessionResult
|
|
82
|
+
attr_reader :end_result, :document_number, :details
|
|
83
|
+
|
|
84
|
+
def initialize(end_result: nil, document_number: nil, details: nil)
|
|
85
|
+
@end_result = end_result
|
|
86
|
+
@document_number = document_number
|
|
87
|
+
@details = details
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def to_h
|
|
91
|
+
{
|
|
92
|
+
endResult: end_result,
|
|
93
|
+
documentNumber: document_number,
|
|
94
|
+
details: details.to_h
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def self.from_h(payload)
|
|
99
|
+
return nil unless payload.is_a?(Hash)
|
|
100
|
+
|
|
101
|
+
new(
|
|
102
|
+
end_result: fetch(payload, :endResult),
|
|
103
|
+
document_number: fetch(payload, :documentNumber),
|
|
104
|
+
details: SessionResultDetails.from_h(fetch(payload, :details))
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.fetch(payload, key)
|
|
109
|
+
payload[key] || payload[key.to_s]
|
|
110
|
+
end
|
|
111
|
+
private_class_method :fetch
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Represents additional result details for a session.
|
|
115
|
+
class SessionResultDetails
|
|
116
|
+
attr_reader :interaction
|
|
117
|
+
|
|
118
|
+
def initialize(interaction: nil)
|
|
119
|
+
@interaction = interaction
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def to_h
|
|
123
|
+
{
|
|
124
|
+
interaction: interaction
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.from_h(payload)
|
|
129
|
+
return nil unless payload.is_a?(Hash)
|
|
130
|
+
|
|
131
|
+
new(interaction: fetch(payload, :interaction))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def self.fetch(payload, key)
|
|
135
|
+
payload[key] || payload[key.to_s]
|
|
136
|
+
end
|
|
137
|
+
private_class_method :fetch
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Represents signature details in session status response.
|
|
141
|
+
class SessionSignature
|
|
142
|
+
attr_reader :value, :server_random, :user_challenge, :flow_type,
|
|
143
|
+
:signature_algorithm, :signature_algorithm_parameters
|
|
144
|
+
|
|
145
|
+
def initialize(
|
|
146
|
+
value: nil,
|
|
147
|
+
server_random: nil,
|
|
148
|
+
user_challenge: nil,
|
|
149
|
+
flow_type: nil,
|
|
150
|
+
signature_algorithm: nil,
|
|
151
|
+
signature_algorithm_parameters: nil
|
|
152
|
+
)
|
|
153
|
+
@value = value
|
|
154
|
+
@server_random = server_random
|
|
155
|
+
@user_challenge = user_challenge
|
|
156
|
+
@flow_type = flow_type
|
|
157
|
+
@signature_algorithm = signature_algorithm
|
|
158
|
+
@signature_algorithm_parameters = signature_algorithm_parameters
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def to_h
|
|
162
|
+
{
|
|
163
|
+
value: value,
|
|
164
|
+
serverRandom: server_random,
|
|
165
|
+
userChallenge: user_challenge,
|
|
166
|
+
flowType: flow_type,
|
|
167
|
+
signatureAlgorithm: signature_algorithm,
|
|
168
|
+
signatureAlgorithmParameters: signature_algorithm_parameters.to_h
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def self.from_h(payload)
|
|
173
|
+
return nil unless payload.is_a?(Hash)
|
|
174
|
+
|
|
175
|
+
new(
|
|
176
|
+
value: fetch(payload, :value),
|
|
177
|
+
server_random: fetch(payload, :serverRandom),
|
|
178
|
+
user_challenge: fetch(payload, :userChallenge),
|
|
179
|
+
flow_type: fetch(payload, :flowType),
|
|
180
|
+
signature_algorithm: fetch(payload, :signatureAlgorithm),
|
|
181
|
+
signature_algorithm_parameters: SessionSignatureAlgorithmParameters.from_h(
|
|
182
|
+
fetch(payload, :signatureAlgorithmParameters)
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def self.fetch(payload, key)
|
|
188
|
+
payload[key] || payload[key.to_s]
|
|
189
|
+
end
|
|
190
|
+
private_class_method :fetch
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Represents signature algorithm parameters for session signature.
|
|
194
|
+
class SessionSignatureAlgorithmParameters
|
|
195
|
+
attr_reader :hash_algorithm, :mask_gen_algorithm, :salt_length, :trailer_field
|
|
196
|
+
|
|
197
|
+
def initialize(hash_algorithm: nil, mask_gen_algorithm: nil, salt_length: nil, trailer_field: nil)
|
|
198
|
+
@hash_algorithm = hash_algorithm
|
|
199
|
+
@mask_gen_algorithm = mask_gen_algorithm
|
|
200
|
+
@salt_length = salt_length
|
|
201
|
+
@trailer_field = trailer_field
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def to_h
|
|
205
|
+
{
|
|
206
|
+
hashAlgorithm: hash_algorithm,
|
|
207
|
+
maskGenAlgorithm: mask_gen_algorithm,
|
|
208
|
+
saltLength: salt_length,
|
|
209
|
+
trailerField: trailer_field
|
|
210
|
+
}
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def self.from_h(payload)
|
|
214
|
+
return nil unless payload.is_a?(Hash)
|
|
215
|
+
|
|
216
|
+
new(
|
|
217
|
+
hash_algorithm: fetch(payload, :hashAlgorithm),
|
|
218
|
+
mask_gen_algorithm: fetch(payload, :maskGenAlgorithm),
|
|
219
|
+
salt_length: fetch(payload, :saltLength),
|
|
220
|
+
trailer_field: fetch(payload, :trailerField)
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def self.fetch(payload, key)
|
|
225
|
+
payload[key] || payload[key.to_s]
|
|
226
|
+
end
|
|
227
|
+
private_class_method :fetch
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Represents certificate payload in session status response.
|
|
231
|
+
class SessionCertificate
|
|
232
|
+
attr_reader :value, :certificate_level
|
|
233
|
+
|
|
234
|
+
def initialize(value: nil, certificate_level: nil)
|
|
235
|
+
@value = value
|
|
236
|
+
@certificate_level = certificate_level
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def to_h
|
|
240
|
+
{
|
|
241
|
+
value: value,
|
|
242
|
+
certificateLevel: certificate_level
|
|
243
|
+
}
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def self.from_h(payload)
|
|
247
|
+
return nil unless payload.is_a?(Hash)
|
|
248
|
+
|
|
249
|
+
new(
|
|
250
|
+
value: fetch(payload, :value),
|
|
251
|
+
certificate_level: fetch(payload, :certificateLevel)
|
|
252
|
+
)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def self.fetch(payload, key)
|
|
256
|
+
payload[key] || payload[key.to_s]
|
|
257
|
+
end
|
|
258
|
+
private_class_method :fetch
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SmartIdRuby
|
|
4
|
+
module Models
|
|
5
|
+
# Represents validated signature response data.
|
|
6
|
+
class SignatureResponse
|
|
7
|
+
attr_reader :end_result, :signature_value_in_base64, :algorithm_name, :flow_type, :certificate,
|
|
8
|
+
:requested_certificate_level, :certificate_level, :document_number, :interaction_flow_used,
|
|
9
|
+
:device_ip_address, :rsa_ssa_pss_parameters
|
|
10
|
+
|
|
11
|
+
def initialize(
|
|
12
|
+
end_result:,
|
|
13
|
+
signature_value_in_base64:,
|
|
14
|
+
algorithm_name:,
|
|
15
|
+
flow_type:,
|
|
16
|
+
certificate:,
|
|
17
|
+
requested_certificate_level:,
|
|
18
|
+
certificate_level:,
|
|
19
|
+
document_number:,
|
|
20
|
+
interaction_flow_used:,
|
|
21
|
+
device_ip_address:,
|
|
22
|
+
rsa_ssa_pss_parameters:
|
|
23
|
+
)
|
|
24
|
+
@end_result = end_result
|
|
25
|
+
@signature_value_in_base64 = signature_value_in_base64
|
|
26
|
+
@algorithm_name = algorithm_name
|
|
27
|
+
@flow_type = flow_type
|
|
28
|
+
@certificate = certificate
|
|
29
|
+
@requested_certificate_level = requested_certificate_level
|
|
30
|
+
@certificate_level = certificate_level
|
|
31
|
+
@document_number = document_number
|
|
32
|
+
@interaction_flow_used = interaction_flow_used
|
|
33
|
+
@device_ip_address = device_ip_address
|
|
34
|
+
@rsa_ssa_pss_parameters = rsa_ssa_pss_parameters
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SmartIdRuby
|
|
4
|
+
# Represents interaction payload used in notification-based flows.
|
|
5
|
+
class NotificationInteraction
|
|
6
|
+
DISPLAY_TEXT_AND_PIN = "displayTextAndPIN"
|
|
7
|
+
CONFIRMATION_MESSAGE = "confirmationMessage"
|
|
8
|
+
CONFIRMATION_MESSAGE_AND_VERIFICATION_CODE_CHOICE = "confirmationMessageAndVerificationCodeChoice"
|
|
9
|
+
|
|
10
|
+
DISPLAY_TEXT_60_MAX_LENGTH = 60
|
|
11
|
+
DISPLAY_TEXT_200_MAX_LENGTH = 200
|
|
12
|
+
|
|
13
|
+
attr_reader :type, :display_text60, :display_text200
|
|
14
|
+
|
|
15
|
+
def initialize(type:, display_text60: nil, display_text200: nil)
|
|
16
|
+
@type = type&.to_s
|
|
17
|
+
@display_text60 = display_text60
|
|
18
|
+
@display_text200 = display_text200
|
|
19
|
+
|
|
20
|
+
validate!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_h
|
|
24
|
+
{
|
|
25
|
+
type: type,
|
|
26
|
+
displayText60: display_text60,
|
|
27
|
+
displayText200: display_text200
|
|
28
|
+
}.compact
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.display_text_and_pin(display_text60)
|
|
32
|
+
new(type: DISPLAY_TEXT_AND_PIN, display_text60: display_text60)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.confirmation_message(display_text200)
|
|
36
|
+
new(type: CONFIRMATION_MESSAGE, display_text200: display_text200)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.confirmation_message_and_verification_code_choice(display_text200)
|
|
40
|
+
new(type: CONFIRMATION_MESSAGE_AND_VERIFICATION_CODE_CHOICE, display_text200: display_text200)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def validate!
|
|
46
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for 'type' must be set" if blank?(type)
|
|
47
|
+
|
|
48
|
+
case type
|
|
49
|
+
when DISPLAY_TEXT_AND_PIN
|
|
50
|
+
validate_display_text!(display_text60, "displayText60", DISPLAY_TEXT_60_MAX_LENGTH)
|
|
51
|
+
when CONFIRMATION_MESSAGE, CONFIRMATION_MESSAGE_AND_VERIFICATION_CODE_CHOICE
|
|
52
|
+
validate_display_text!(display_text200, "displayText200", DISPLAY_TEXT_200_MAX_LENGTH)
|
|
53
|
+
else
|
|
54
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Unsupported interaction type: #{type}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate_display_text!(value, field_name, max_length)
|
|
59
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Value for '#{field_name}' cannot be empty" if blank?(value)
|
|
60
|
+
return if value.to_s.length <= max_length
|
|
61
|
+
|
|
62
|
+
raise SmartIdRuby::Errors::RequestSetupError,
|
|
63
|
+
"Value for '#{field_name}' cannot be longer than #{max_length} characters"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def blank?(value)
|
|
67
|
+
value.nil? || value.to_s.strip.empty?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "rqrcode"
|
|
5
|
+
|
|
6
|
+
module SmartIdRuby
|
|
7
|
+
# Utility for generating QR-code images and Data URIs.
|
|
8
|
+
class QrCodeGenerator
|
|
9
|
+
DEFAULT_QR_CODE_WIDTH_PX = 610
|
|
10
|
+
DEFAULT_QR_CODE_HEIGHT_PX = 610
|
|
11
|
+
DEFAULT_QUIET_AREA_SIZE_MODULES = 4
|
|
12
|
+
DEFAULT_FILE_FORMAT = "png"
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def generate_data_uri(data)
|
|
16
|
+
image = generate_image(data)
|
|
17
|
+
convert_to_data_uri(image, DEFAULT_FILE_FORMAT)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def generate_image(
|
|
21
|
+
data,
|
|
22
|
+
width_px = DEFAULT_QR_CODE_WIDTH_PX,
|
|
23
|
+
height_px = DEFAULT_QR_CODE_HEIGHT_PX,
|
|
24
|
+
quiet_area_size = DEFAULT_QUIET_AREA_SIZE_MODULES
|
|
25
|
+
)
|
|
26
|
+
validate_data!(data)
|
|
27
|
+
|
|
28
|
+
qrcode = RQRCode::QRCode.new(data.to_s, level: :l)
|
|
29
|
+
qrcode.as_png(
|
|
30
|
+
border_modules: quiet_area_size.to_i,
|
|
31
|
+
module_px_size: 1
|
|
32
|
+
).resample_nearest_neighbor(width_px.to_i, height_px.to_i)
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
raise if e.is_a?(SmartIdRuby::Errors::RequestSetupError)
|
|
35
|
+
|
|
36
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Unable to create QR-code: #{e.message}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def convert_to_data_uri(image, file_format = DEFAULT_FILE_FORMAT)
|
|
40
|
+
format = file_format.to_s.downcase
|
|
41
|
+
image_bytes = image_to_bytes(image, format)
|
|
42
|
+
encoded = Base64.strict_encode64(image_bytes)
|
|
43
|
+
"data:image/#{format};base64,#{encoded}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def validate_data!(data)
|
|
49
|
+
return unless data.nil? || data.to_s.empty?
|
|
50
|
+
|
|
51
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Provided data cannot be empty"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def image_to_bytes(image, file_format)
|
|
55
|
+
if image.respond_to?(:to_datastream)
|
|
56
|
+
image.to_datastream.to_s
|
|
57
|
+
elsif image.is_a?(String)
|
|
58
|
+
image
|
|
59
|
+
else
|
|
60
|
+
raise SmartIdRuby::Errors::RequestSetupError, "Unable to generate QR-code as #{file_format}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|