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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +14 -0
  4. data/CHANGELOG.md +13 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +436 -0
  8. data/Rakefile +12 -0
  9. data/lib/smart-id-ruby-client.rb +3 -0
  10. data/lib/smart_id_ruby/callback_url.rb +18 -0
  11. data/lib/smart_id_ruby/callback_url_util.rb +54 -0
  12. data/lib/smart_id_ruby/client.rb +124 -0
  13. data/lib/smart_id_ruby/configuration.rb +184 -0
  14. data/lib/smart_id_ruby/device_link_builder.rb +301 -0
  15. data/lib/smart_id_ruby/device_link_interaction.rb +67 -0
  16. data/lib/smart_id_ruby/errors/certificate_level_mismatch_error.rb +8 -0
  17. data/lib/smart_id_ruby/errors/document_unusable_error.rb +12 -0
  18. data/lib/smart_id_ruby/errors/error.rb +8 -0
  19. data/lib/smart_id_ruby/errors/expected_linked_session_error.rb +15 -0
  20. data/lib/smart_id_ruby/errors/no_suitable_account_of_requested_type_found_error.rb +10 -0
  21. data/lib/smart_id_ruby/errors/person_should_view_smart_id_portal_error.rb +8 -0
  22. data/lib/smart_id_ruby/errors/protocol_failure_error.rb +13 -0
  23. data/lib/smart_id_ruby/errors/relying_party_account_configuration_error.rb +10 -0
  24. data/lib/smart_id_ruby/errors/request_setup_error.rb +10 -0
  25. data/lib/smart_id_ruby/errors/request_validation_error.rb +8 -0
  26. data/lib/smart_id_ruby/errors/required_interaction_not_supported_by_app_error.rb +13 -0
  27. data/lib/smart_id_ruby/errors/response_error.rb +8 -0
  28. data/lib/smart_id_ruby/errors/server_maintenance_error.rb +8 -0
  29. data/lib/smart_id_ruby/errors/session_end_result_error.rb +15 -0
  30. data/lib/smart_id_ruby/errors/session_not_complete_error.rb +8 -0
  31. data/lib/smart_id_ruby/errors/session_not_found_error.rb +8 -0
  32. data/lib/smart_id_ruby/errors/session_secret_mismatch_error.rb +8 -0
  33. data/lib/smart_id_ruby/errors/session_timeout_error.rb +12 -0
  34. data/lib/smart_id_ruby/errors/smart_id_server_error.rb +12 -0
  35. data/lib/smart_id_ruby/errors/unprocessable_response_error.rb +9 -0
  36. data/lib/smart_id_ruby/errors/unsupported_client_api_version_error.rb +8 -0
  37. data/lib/smart_id_ruby/errors/user_account_not_found_error.rb +8 -0
  38. data/lib/smart_id_ruby/errors/user_account_unusable_error.rb +12 -0
  39. data/lib/smart_id_ruby/errors/user_refused_cert_choice_error.rb +14 -0
  40. data/lib/smart_id_ruby/errors/user_refused_confirmation_message_error.rb +13 -0
  41. data/lib/smart_id_ruby/errors/user_refused_confirmation_message_with_verification_choice_error.rb +13 -0
  42. data/lib/smart_id_ruby/errors/user_refused_display_text_and_pin_error.rb +13 -0
  43. data/lib/smart_id_ruby/errors/user_refused_error.rb +12 -0
  44. data/lib/smart_id_ruby/errors/user_selected_wrong_verification_code_error.rb +13 -0
  45. data/lib/smart_id_ruby/errors.rb +31 -0
  46. data/lib/smart_id_ruby/flows/base_builder.rb +90 -0
  47. data/lib/smart_id_ruby/flows/certificate_by_document_number_request_builder.rb +130 -0
  48. data/lib/smart_id_ruby/flows/device_link_authentication_session_request_builder.rb +208 -0
  49. data/lib/smart_id_ruby/flows/device_link_certificate_choice_session_request_builder.rb +112 -0
  50. data/lib/smart_id_ruby/flows/device_link_signature_session_request_builder.rb +286 -0
  51. data/lib/smart_id_ruby/flows/linked_notification_signature_session_request_builder.rb +235 -0
  52. data/lib/smart_id_ruby/flows/notification_authentication_session_request_builder.rb +184 -0
  53. data/lib/smart_id_ruby/flows/notification_certificate_choice_session_request_builder.rb +96 -0
  54. data/lib/smart_id_ruby/flows/notification_signature_session_request_builder.rb +272 -0
  55. data/lib/smart_id_ruby/models/authentication_identity.rb +19 -0
  56. data/lib/smart_id_ruby/models/authentication_response.rb +38 -0
  57. data/lib/smart_id_ruby/models/certificate_choice_response.rb +19 -0
  58. data/lib/smart_id_ruby/models/device_link_session_response.rb +34 -0
  59. data/lib/smart_id_ruby/models/notification_authentication_session_response.rb +25 -0
  60. data/lib/smart_id_ruby/models/notification_certificate_choice_session_response.rb +25 -0
  61. data/lib/smart_id_ruby/models/notification_signature_session_response.rb +29 -0
  62. data/lib/smart_id_ruby/models/session_status.rb +261 -0
  63. data/lib/smart_id_ruby/models/signature_response.rb +38 -0
  64. data/lib/smart_id_ruby/notification_interaction.rb +70 -0
  65. data/lib/smart_id_ruby/qr_code_generator.rb +65 -0
  66. data/lib/smart_id_ruby/rest/connector.rb +364 -0
  67. data/lib/smart_id_ruby/rest/session_status_poller.rb +125 -0
  68. data/lib/smart_id_ruby/rp_challenge.rb +37 -0
  69. data/lib/smart_id_ruby/rp_challenge_generator.rb +28 -0
  70. data/lib/smart_id_ruby/semantics_identifier.rb +35 -0
  71. data/lib/smart_id_ruby/validation/authentication_certificate_validator.rb +90 -0
  72. data/lib/smart_id_ruby/validation/authentication_identity_mapper.rb +227 -0
  73. data/lib/smart_id_ruby/validation/base_authentication_response_validator.rb +304 -0
  74. data/lib/smart_id_ruby/validation/certificate_choice_response_validator.rb +104 -0
  75. data/lib/smart_id_ruby/validation/certificate_validator.rb +170 -0
  76. data/lib/smart_id_ruby/validation/device_link_authentication_response_validator.rb +76 -0
  77. data/lib/smart_id_ruby/validation/error_result_handler.rb +88 -0
  78. data/lib/smart_id_ruby/validation/notification_authentication_response_validator.rb +16 -0
  79. data/lib/smart_id_ruby/validation/signature_payload_builder.rb +62 -0
  80. data/lib/smart_id_ruby/validation/signature_response_validator.rb +345 -0
  81. data/lib/smart_id_ruby/validation/signature_value_validator.rb +76 -0
  82. data/lib/smart_id_ruby/validation/trusted_ca_cert_store.rb +20 -0
  83. data/lib/smart_id_ruby/verification_code_calculator.rb +31 -0
  84. data/lib/smart_id_ruby/version.rb +5 -0
  85. data/lib/smart_id_ruby.rb +76 -0
  86. metadata +173 -0
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ # Represents interaction payload used in device-link-based flows.
5
+ class DeviceLinkInteraction
6
+ DISPLAY_TEXT_AND_PIN = "displayTextAndPIN"
7
+ CONFIRMATION_MESSAGE = "confirmationMessage"
8
+
9
+ DISPLAY_TEXT_60_MAX_LENGTH = 60
10
+ DISPLAY_TEXT_200_MAX_LENGTH = 200
11
+
12
+ attr_reader :type, :display_text60, :display_text200
13
+
14
+ def initialize(type:, display_text60: nil, display_text200: nil)
15
+ @type = type&.to_s
16
+ @display_text60 = display_text60
17
+ @display_text200 = display_text200
18
+
19
+ validate!
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ type: type,
25
+ displayText60: display_text60,
26
+ displayText200: display_text200
27
+ }.compact
28
+ end
29
+
30
+ def self.display_text_and_pin(display_text60)
31
+ new(type: DISPLAY_TEXT_AND_PIN, display_text60: display_text60)
32
+ end
33
+
34
+ def self.confirmation_message(display_text200)
35
+ new(type: CONFIRMATION_MESSAGE, display_text200: display_text200)
36
+ end
37
+
38
+ private
39
+
40
+ def validate!
41
+ raise SmartIdRuby::Errors::RequestSetupError, "Value for 'type' must be set" if blank?(type)
42
+
43
+ case type
44
+ when DISPLAY_TEXT_AND_PIN
45
+ validate_display_text!(display_text60, "displayText60", DISPLAY_TEXT_60_MAX_LENGTH)
46
+ when CONFIRMATION_MESSAGE
47
+ validate_display_text!(display_text200, "displayText200", DISPLAY_TEXT_200_MAX_LENGTH)
48
+ else
49
+ raise SmartIdRuby::Errors::RequestSetupError, "Unsupported interaction type: #{type}"
50
+ end
51
+ end
52
+
53
+ def validate_display_text!(value, field_name, max_length)
54
+ if blank?(value)
55
+ raise SmartIdRuby::Errors::RequestSetupError, "Value for '#{field_name}' cannot be empty"
56
+ end
57
+ return if value.to_s.length <= max_length
58
+
59
+ raise SmartIdRuby::Errors::RequestSetupError,
60
+ "Value for '#{field_name}' cannot be longer than #{max_length} characters"
61
+ end
62
+
63
+ def blank?(value)
64
+ value.nil? || value.to_s.strip.empty?
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when returned certificate level is lower than the requested certificate level.
6
+ class CertificateLevelMismatchError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is DOCUMENT_UNUSABLE.
6
+ class DocumentUnusableError < Error
7
+ def initialize(message = "Document is unusable. User must either check his/her Smart-ID mobile application or turn to customer support for getting the exact reason.")
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Represents error condition.
6
+ class Error < SmartIdRuby::Error; end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Linked signature flow consists of two sessions - device link-based certificate choice session followed by the linked signature session.
6
+ # Exception will be thrown when linked signature session is not received after the device link-based certificate choice session,
7
+ # but some other session with the same document number is received instead.
8
+ class ExpectedLinkedSessionError < SessionEndResultError
9
+ def initialize
10
+ super("EXPECTED_LINKED_SESSION",
11
+ "The app received a different transaction while waiting for the linked session that follows the device-link based cert-choice session")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when user does not have a suitable account for the requested operation.
6
+ # F.e. user has non-qualified account with ADVANCED certificate level,
7
+ # but QUALIFIED certificate level is required for the operation.
8
+ class NoSuitableAccountOfRequestedTypeFoundError < Error; end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when Smart-ID API indicates that there is an issue with user document and user should check its state.
6
+ class PersonShouldViewSmartIdPortalError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Exception thrown when the session status end result is PROTOCOL_FAILURE, indicating logical error in the signing protocol.
6
+ # F.e. Constructed device link that user can interact with contains invalid schema.
7
+ class ProtocolFailureError < SessionEndResultError
8
+ def initialize
9
+ super("PROTOCOL_FAILURE", "A logical error occurred in the signing protocol.")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Exception will be thrown when there are problems with relying party account and access configuration
6
+ # or when relying party does not have access to the requested service.
7
+ # F.e. Request is made with relying party UUID and incorrect relying party name.
8
+ class RelyingPartyAccountConfigurationError < Error; end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Exception thrown when there is an issue setting up a Smart-ID request.
6
+ # This could be due to invalid parameters, configuration issues, or other
7
+ # problems that prevent from successfully preparing the request.
8
+ class RequestSetupError < Error; end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Represents request validation error condition.
6
+ class RequestValidationError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when the user's app version does not support any of the provided interactions.
6
+ class RequiredInteractionNotSupportedByAppError < SessionEndResultError
7
+ def initialize
8
+ super("REQUIRED_INTERACTION_NOT_SUPPORTED_BY_APP",
9
+ "User app version does not support any of the provided interactions.")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Represents response error condition.
6
+ class ResponseError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when request cannot be process because the Smart-ID API server is under maintenance.
6
+ class ServerMaintenanceError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Represents session end result error condition.
6
+ class SessionEndResultError < Error
7
+ attr_reader :end_result
8
+
9
+ def initialize(end_result, message = nil)
10
+ @end_result = end_result
11
+ super(message || "Session ended with result '#{end_result}'")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Represents session not complete error condition.
6
+ class SessionNotCompleteError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session with the given session ID could not be found.
6
+ class SessionNotFoundError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when the session secret digest from the callback does not match the calculated digest.
6
+ class SessionSecretMismatchError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is TIMEOUT.
6
+ class SessionTimeoutError < SessionEndResultError
7
+ def initialize
8
+ super("TIMEOUT", "Session timed out without getting any response from user")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is SERVER_ERROR, indicating a server-side technical error.
6
+ class SmartIdServerError < SessionEndResultError
7
+ def initialize
8
+ super("SERVER_ERROR", "Process was terminated due to server-side technical error")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when validation of any Smart-ID API responses fail.
6
+ # This includes responses for session initialization requests and session status responses.
7
+ class UnprocessableResponseError < Error; end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Represents unsupported client api version error condition.
6
+ class UnsupportedClientApiVersionError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when user account does not exist with the given identifier or document number.
6
+ class UserAccountNotFoundError < Error; end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is ACCOUNT_UNUSABLE.
6
+ class UserAccountUnusableError < SessionEndResultError
7
+ def initialize
8
+ super("ACCOUNT_UNUSABLE", "The account is currently unusable")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is USER_REFUSED_CERT_CHOICE.
6
+ # This happens when user has multiple accounts and presses Cancel on device choice screen on any device.
7
+ class UserRefusedCertChoiceError < SessionEndResultError
8
+ def initialize
9
+ super("USER_REFUSED_CERT_CHOICE",
10
+ "User has multiple accounts and pressed Cancel on device choice screen on any device.")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is USER_REFUSED_INTERACTION.
6
+ # This happens when user presses Cancel on confirmation message screen.
7
+ class UserRefusedConfirmationMessageError < SessionEndResultError
8
+ def initialize
9
+ super("USER_REFUSED_INTERACTION", "User cancelled on confirmationMessage screen")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is USER_REFUSED_INTERACTION.
6
+ # This happens when user presses Cancel on confirmation and verification code choice screen.
7
+ class UserRefusedConfirmationMessageWithVerificationChoiceError < SessionEndResultError
8
+ def initialize
9
+ super("USER_REFUSED_INTERACTION", "User cancelled on confirmationMessageAndVerificationCodeChoice screen")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is USER_REFUSED_INTERACTION.
6
+ # This happens when user presses Cancel on display text and PIN screen.
7
+ class UserRefusedDisplayTextAndPinError < SessionEndResultError
8
+ def initialize
9
+ super("USER_REFUSED_INTERACTION", "User pressed Cancel on PIN screen.")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is USER_REFUSED.
6
+ class UserRefusedError < SessionEndResultError
7
+ def initialize
8
+ super("USER_REFUSED", "User pressed cancel in app")
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Thrown when session status end result is WRONG_VC.
6
+ # This happens when user selects wrong verification code in the app.
7
+ class UserSelectedWrongVerificationCodeError < SessionEndResultError
8
+ def initialize
9
+ super("WRONG_VC", "User selected wrong verification code")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors/error"
4
+ require_relative "errors/session_not_found_error"
5
+ require_relative "errors/relying_party_account_configuration_error"
6
+ require_relative "errors/user_account_not_found_error"
7
+ require_relative "errors/server_maintenance_error"
8
+ require_relative "errors/person_should_view_smart_id_portal_error"
9
+ require_relative "errors/no_suitable_account_of_requested_type_found_error"
10
+ require_relative "errors/unsupported_client_api_version_error"
11
+ require_relative "errors/request_validation_error"
12
+ require_relative "errors/request_setup_error"
13
+ require_relative "errors/response_error"
14
+ require_relative "errors/unprocessable_response_error"
15
+ require_relative "errors/document_unusable_error"
16
+ require_relative "errors/certificate_level_mismatch_error"
17
+ require_relative "errors/session_not_complete_error"
18
+ require_relative "errors/session_end_result_error"
19
+ require_relative "errors/session_secret_mismatch_error"
20
+ require_relative "errors/user_refused_error"
21
+ require_relative "errors/session_timeout_error"
22
+ require_relative "errors/user_selected_wrong_verification_code_error"
23
+ require_relative "errors/required_interaction_not_supported_by_app_error"
24
+ require_relative "errors/user_refused_cert_choice_error"
25
+ require_relative "errors/protocol_failure_error"
26
+ require_relative "errors/expected_linked_session_error"
27
+ require_relative "errors/smart_id_server_error"
28
+ require_relative "errors/user_account_unusable_error"
29
+ require_relative "errors/user_refused_display_text_and_pin_error"
30
+ require_relative "errors/user_refused_confirmation_message_error"
31
+ require_relative "errors/user_refused_confirmation_message_with_verification_choice_error"
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+
6
+ module SmartIdRuby
7
+ module Flows
8
+ # Base builder with shared helper methods for request builders.
9
+ class BaseBuilder
10
+ attr_reader :connector, :relying_party_uuid, :relying_party_name
11
+
12
+ def initialize(connector)
13
+ @connector = connector
14
+ end
15
+
16
+ def with_relying_party_uuid(value)
17
+ @relying_party_uuid = value
18
+ self
19
+ end
20
+
21
+ def with_relying_party_name(value)
22
+ @relying_party_name = value
23
+ self
24
+ end
25
+
26
+ private
27
+
28
+ def blank?(value)
29
+ value.nil? || value.to_s.strip.empty?
30
+ end
31
+
32
+ def fetch_value(container, key)
33
+ return nil if container.nil?
34
+
35
+ key_str = key.to_s
36
+ snake_key = underscore_key(key_str)
37
+ candidates = [key, key_str, snake_key.to_sym, snake_key].uniq
38
+
39
+ if container.respond_to?(:[])
40
+ candidates.each do |candidate|
41
+ value = container[candidate]
42
+ return value unless value.nil?
43
+ end
44
+ end
45
+
46
+ candidates.each do |candidate|
47
+ method_name = candidate.is_a?(Symbol) ? candidate : candidate.to_s
48
+ return container.public_send(method_name) if container.respond_to?(method_name)
49
+ end
50
+
51
+ nil
52
+ end
53
+
54
+ def normalize_interactions(interactions)
55
+ Array(interactions).compact.map { |interaction| normalize_interaction(interaction) }
56
+ end
57
+
58
+ def normalize_interaction(interaction)
59
+ if interaction.respond_to?(:to_h)
60
+ interaction.to_h.transform_keys(&:to_sym)
61
+ elsif interaction.is_a?(Hash)
62
+ interaction.transform_keys(&:to_sym)
63
+ else
64
+ raise SmartIdRuby::Errors::RequestSetupError, "Unsupported interaction object type: #{interaction.class}"
65
+ end
66
+ end
67
+
68
+ def encode_interactions(interactions)
69
+ Base64.strict_encode64(JSON.generate(normalize_interactions(interactions)))
70
+ end
71
+
72
+ def normalize_capabilities(capabilities, strip: true, reject_empty: true)
73
+ values = Array(capabilities).flatten.compact.map(&:to_s)
74
+ values = values.map(&:strip) if strip
75
+ values = values.reject(&:empty?) if reject_empty
76
+ values.uniq
77
+ end
78
+
79
+ def request_properties_for_share_md(share_md_client_ip_address)
80
+ return nil if share_md_client_ip_address.nil?
81
+
82
+ { shareMdClientIpAddress: share_md_client_ip_address }
83
+ end
84
+
85
+ def underscore_key(value)
86
+ value.to_s.gsub(/([A-Z])/, "_\\1").downcase.sub(/\A_/, "")
87
+ end
88
+ end
89
+ end
90
+ end