Module: BetterAuth::SSO::SAML
- Defined in:
- lib/better_auth/sso/saml.rb
Constant Summary collapse
- DEFAULT_ATTRIBUTE_MAP =
{ email: %w[email mail emailAddress Email EmailAddress], name: %w[name displayName cn Name DisplayName], given_name: %w[givenName firstName FirstName], family_name: %w[familyName lastName LastName] }.freeze
Class Method Summary collapse
- .assertion_identifier(response, email) ⇒ Object
- .auth_request_url(settings: nil, request_options: {}, **_options) ⇒ Object
- .build_settings(provider, context, config, overrides = nil) ⇒ Object
- .first_attribute(attributes, names) ⇒ Object
- .response_parser(settings: nil, response_options: {}, attribute_map: DEFAULT_ATTRIBUTE_MAP, **_options) ⇒ Object
- .sso_options(**options) ⇒ Object
- .validate_response_xml!(raw_response, config) ⇒ Object
Class Method Details
.assertion_identifier(response, email) ⇒ Object
109 110 111 |
# File 'lib/better_auth/sso/saml.rb', line 109 def assertion_identifier(response, email) response.assertion_id || response.nameid || response.sessionindex || email end |
.auth_request_url(settings: nil, request_options: {}, **_options) ⇒ Object
28 29 30 31 32 33 34 |
# File 'lib/better_auth/sso/saml.rb', line 28 def auth_request_url(settings: nil, request_options: {}, **) lambda do |provider:, relay_state:, context:| config = BetterAuth::Plugins.normalize_hash(provider["samlConfig"] || provider[:samlConfig] || {}) saml_settings = settings.respond_to?(:call) ? settings.call(provider: provider, context: context, saml_config: config) : build_settings(provider, context, config, settings) OneLogin::RubySaml::Authrequest.new.create(saml_settings, {RelayState: relay_state}.merge()) end end |
.build_settings(provider, context, config, overrides = nil) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/better_auth/sso/saml.rb', line 62 def build_settings(provider, context, config, overrides = nil) settings = overrides || OneLogin::RubySaml::Settings.new provider_id = provider.fetch("providerId") base_url = context.context.base_url settings.assertion_consumer_service_url = config[:callback_url] || "#{base_url}/sso/saml2/sp/acs/#{provider_id}" settings.sp_entity_id = config.dig(:sp_metadata, :entity_id) || config[:audience] || "#{base_url}/sso/saml2/sp/metadata?providerId=#{URI.encode_www_form_component(provider_id)}" settings.idp_entity_id = provider["issuer"] || provider[:issuer] settings.idp_sso_service_url = config[:entry_point] settings.idp_cert = config[:cert] unless config[:cert].to_s.empty? settings.name_identifier_format = config[:identifier_format] unless config[:identifier_format].to_s.empty? settings.private_key = config[:sp_private_key] unless config[:sp_private_key].to_s.empty? settings.certificate = config[:sp_certificate] unless config[:sp_certificate].to_s.empty? settings.security[:want_assertions_signed] = config.fetch(:want_assertions_signed, true) settings.security[:want_messages_signed] = config.fetch(:want_messages_signed, false) settings.security[:want_assertions_encrypted] = config.fetch(:want_assertions_encrypted, false) settings.security[:strict_audience_validation] = true settings.security[:digest_method] = config[:digest_algorithm] || XMLSecurity::Document::SHA256 settings.security[:signature_method] = config[:signature_algorithm] || XMLSecurity::Document::RSA_SHA256 settings end |
.first_attribute(attributes, names) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'lib/better_auth/sso/saml.rb', line 100 def first_attribute(attributes, names) Array(names).each do |name| value = attributes[name] value = value.first if value.is_a?(Array) return value unless value.to_s.empty? end nil end |
.response_parser(settings: nil, response_options: {}, attribute_map: DEFAULT_ATTRIBUTE_MAP, **_options) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/better_auth/sso/saml.rb', line 36 def response_parser(settings: nil, response_options: {}, attribute_map: DEFAULT_ATTRIBUTE_MAP, **) lambda do |raw_response:, provider:, context:| config = BetterAuth::Plugins.normalize_hash(provider["samlConfig"] || provider[:samlConfig] || {}) saml_settings = settings.respond_to?(:call) ? settings.call(provider: provider, context: context, saml_config: config) : build_settings(provider, context, config, settings) validate_response_xml!(raw_response, config) response = OneLogin::RubySaml::Response.new(raw_response, {settings: saml_settings}.merge()) unless response.is_valid? raise BetterAuth::APIError.new("BAD_REQUEST", message: "Invalid SAML response") end attributes = response.attributes email = first_attribute(attributes, attribute_map.fetch(:email)) || response.nameid raise BetterAuth::APIError.new("BAD_REQUEST", message: "Invalid SAML response") if email.to_s.empty? given_name = first_attribute(attributes, attribute_map.fetch(:given_name)) family_name = first_attribute(attributes, attribute_map.fetch(:family_name)) name = first_attribute(attributes, attribute_map.fetch(:name)) || [given_name, family_name].compact.join(" ").strip { email: email.to_s.downcase, name: name.to_s.empty? ? email.to_s : name.to_s, id: assertion_identifier(response, email), email_verified: true } end end |
.sso_options(**options) ⇒ Object
19 20 21 22 23 24 25 26 |
# File 'lib/better_auth/sso/saml.rb', line 19 def (**) { saml: { auth_request_url: auth_request_url(**), parse_response: response_parser(**) } } end |
.validate_response_xml!(raw_response, config) ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/better_auth/sso/saml.rb', line 83 def validate_response_xml!(raw_response, config) BetterAuth::Plugins.sso_validate_single_saml_assertion!(raw_response) xml = Base64.decode64(raw_response.to_s) BetterAuth::Plugins.sso_validate_saml_algorithms!( xml, on_deprecated: config.fetch(:on_deprecated_algorithm, "reject"), allowed_signature_algorithms: config[:allowed_signature_algorithms], allowed_digest_algorithms: config[:allowed_digest_algorithms], allowed_key_encryption_algorithms: config[:allowed_key_encryption_algorithms], allowed_data_encryption_algorithms: config[:allowed_data_encryption_algorithms] ) rescue BetterAuth::APIError raise rescue raise BetterAuth::APIError.new("BAD_REQUEST", message: "Invalid SAML response") end |