cartodb-4.42/lib/carto/saml_service.rb
2024-04-06 05:25:13 +00:00

135 lines
3.9 KiB
Ruby

module Carto
class SamlService
include ::LoggerHelper
def initialize(organization)
raise "organization can't be nil" unless organization
@organization = organization
end
def enabled?
carto_saml_configuration.present?
end
def authentication_request
OneLogin::RubySaml::Authrequest.new.create(saml_settings)
end
def get_user_email(saml_response_param)
response = get_saml_response(saml_response_param)
response.is_valid? && email_from_saml_response(response)
rescue OneLogin::RubySaml::ValidationError
debug_response("Invalid SAML response", response)
end
def logout_url_configured?
saml_settings.idp_slo_target_url.present?
end
# SLO (Single Log Out) request initiated from CARTO
# Returns the SAML logout request that to be redirected to
def sp_logout_request(user)
settings = saml_settings
if logout_url_configured?
settings.name_identifier_value = user.email
OneLogin::RubySaml::Logoutrequest.new.create(settings)
else
raise "SLO IdP Endpoint not found in settings for #{@organization}"
end
end
# Method to handle IdP initiated logouts.
# Yields a block that should handle the actual logout
def idp_logout_request(saml_request_param, relay_state_param)
settings = saml_settings
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(saml_request_param)
if !logout_request.is_valid?
raise "IdP initiated LogoutRequest was not valid!"
end
yield
OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request.id, nil, RelayState: relay_state_param)
end
# After sending an SP initiated LogoutRequest to the IdP, we need to accept
# the LogoutResponse, verify it, then actually delete our session.
# Yields a block that should handle the actual logout
def process_logout_response(saml_response_param)
settings = saml_settings
logout_response = OneLogin::RubySaml::Logoutresponse.new(saml_response_param, settings)
unless logout_response.validate && logout_response.success?
raise "SAML Logout response error. Validate: #{logout_response.validate}; Success: #{logout_response.success?};"
end
end
def saml_metadata
OneLogin::RubySaml::Metadata.new.generate(saml_settings, true)
end
private
def email_from_saml_response(saml_response)
email = saml_response.attributes[email_attribute]
email.present? ? email : debug_response("SAML response lacks email", saml_response)
end
def debug_response(message, response)
log_info(
message: message,
response: {
settings: response.settings.to_json,
options: response.options,
errors: response.errors,
body: response.response,
attributes: response.attributes.try(:to_h)
}
)
nil
end
def get_saml_response(saml_response_param)
OneLogin::RubySaml::Response.new(
saml_response_param,
settings: saml_settings,
allowed_clock_drift: carto_saml_configuration[:allowed_clock_drift] || 3600
)
end
def email_attribute
carto_saml_configuration[:email_attribute] || 'name_id'
end
# Our SAML library expects object properties
# Adapted from https://github.com/hryk/warden-saml-example/blob/master/application.rb
def saml_settings(settings_hash = carto_saml_configuration)
settings = OneLogin::RubySaml::Settings.new
# Make validations throw an error
settings.soft = false
settings_hash.each do |k, v|
if k.to_s == 'security'
settings.security.merge!(v.symbolize_keys)
else
method = "#{k}="
settings.__send__(method, v) if settings.respond_to?(method)
end
end
settings
end
def carto_saml_configuration
@organization.try(:auth_saml_configuration).try(:deep_symbolize_keys)
end
end
end