Skip to content

Instantly share code, notes, and snippets.

@sheeley
Last active September 13, 2021 21:30
Show Gist options
  • Save sheeley/7044243 to your computer and use it in GitHub Desktop.
Save sheeley/7044243 to your computer and use it in GitHub Desktop.
SAML Controller for Rails (used when POSTing data to IDP and decrypting on return)
class SessionsController < ApplicationController
skip_before_filter :verify_authenticity_token, only: :saml
def new
request = Onelogin::Saml::Authrequest.new
settings = saml_settings
request_doc = request.create_authentication_xml_doc(settings)
request_str = request_doc.to_s #.force_encoding("UTF-8")
logger.debug request_str
request_str = Base64.strict_encode64(request_str)
# create a form in your view that POSTs to your IDP
# <input name="SAMLRequest" value="@SAMLRequest"/>
@SAMLRequest = request_str
end
def saml_settings
Onelogin::Saml::Settings.new(
assertion_consumer_service_url: ENV['SAML_ASSERTION_CONSUMER_SERVICE_URL'],
issuer: ENV['SAML_ISSUER'],
idp_sso_target_url: ENV['SAML_IDP_SSO_TARGET_URL'],
idp_cert_fingerprint: ENV['SAML_IDP_CERT_FINGERPRINT'],
name_identifier_format: ENV['SAML_NAME_IDENTIFIER_FORMAT']
)
end
def create
auth = request.env["omniauth.auth"]
user = User.where(:provider => auth['provider'],
:uid => auth['uid'].to_s).first || User.create_with_omniauth(auth)
# Reset the session after successful login, per
# 2.8 Session Fixation – Countermeasures:
# http://guides.rubyonrails.org/security.html#session-fixation-countermeasures
# binding.pry
reset_session
session[:user_id] = user.id
user.add_role :admin if User.count == 1 # make the first user an admin
if user.email.blank?
redirect_to edit_user_path(user), :alert => "Please enter your email address."
else
redirect_to root_url, :notice => 'Signed in!'
end
end
def saml
# Onelogin handles the basic SAML response
response = Onelogin::Saml::Response.new(request.params['SAMLResponse'])
response.settings = saml_settings
# decrypt the document
enc_key = REXML::XPath.first(response.document, "//ds:KeyInfo/xenc:EncryptedKey/xenc:CipherData/xenc:CipherValue").text
enc_value = REXML::XPath.first(response.document, "//xenc:EncryptedData/xenc:CipherData/xenc:CipherValue").text
private_key = OpenSSL::PKey::RSA.new(ENV['SAML_PRIVATE_KEY'])
data_key = private_key.private_decrypt(Base64.decode64(enc_key), OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
actual_output = decrypt_cipher_data(data_key, enc_value)
# for some reason, the decrypted string has extra chars at the end, so remove them if they're there
output_end = '</saml:Assertion>'
trim_to = actual_output.index output_end
actual_output = actual_output[0..trim_to + output_end.length-1] if not trim_to.nil?
# create a new xml doc, grab the key/values from it
decrypted_doc = REXML::Document.new actual_output
values = {}
REXML::XPath.each(decrypted_doc, "//saml:AttributeStatement/saml:Attribute") do |elem|
value_name = elem.attribute 'Name'
if not value_name.nil?
value_name = value_name.value
values[value_name] = elem.children[0].text if not elem.children.nil? and not elem.children[0].nil?
end
end
request.env["omniauth.auth"] = {
'uid' => values['name_id'],
'provider' => 'saml',
'info' => {
'name' => "#{values['FirstName']} #{values['LastName']}",
'email' => values['Email']
}
}
create
end
def destroy
reset_session
redirect_to root_url, :notice => 'Signed out!'
end
def failure
redirect_to root_url, :alert => "Authentication error: #{params[:message].humanize}"
end
private
def decrypt_cipher_data(key_cipher, cipher_data)
cipher_data_str = Base64.decode64(cipher_data)
mcrypt_iv = cipher_data_str[0..15]
cipher_data_str = cipher_data_str[16..-1]
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.decrypt
cipher.padding = 0
cipher.key = key_cipher
cipher.iv = mcrypt_iv
result = cipher.update(cipher_data_str)
result << cipher.final
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment