Skip to content

Instantly share code, notes, and snippets.

@larsar
Created November 21, 2013 12:33
Show Gist options
  • Select an option

  • Save larsar/7580819 to your computer and use it in GitHub Desktop.

Select an option

Save larsar/7580819 to your computer and use it in GitHub Desktop.
FEIDE authentication controllers for Rails, using "omniauth". Code written mostly by Christian Martin. https://github.com/cmartin81
class AuthenticationsController < ApplicationController
def index
if current_user
@authentications = current_user.authentications
else
redirect_to root_path
end
end
def create
omniauth = request.env["omniauth.auth"]
provider = omniauth['provider']
if provider == "saml"
uid = omniauth[:extra][:raw_info][:eduPersonPrincipalName]
ext_session_id = omniauth[:uid]
provider = "feide"
else
uid = omniauth['uid']
end
authentication = Authentication.find_by_provider_and_uid(provider, uid)
if authentication
unless authentication.user.banned?
flash[:notice] = t('authentications.controller.provider_signed_in', {:provider => Authentication.provider_title(provider)})
authentication.user.update_attribute(:ext_session_id, ext_session_id) if ext_session_id
sign_in_and_redirect(:user, authentication.user)
else
flash[:error] = "This account has been suspended."
redirect_to root_path
end
elsif current_user
current_user.authentications.create!(:provider => provider, :uid => uid)
flash[:notice] = t('authentications.controller.linked_provider', {:provider => Authentication.provider_title(provider)})
redirect_to authentications_url
else
flash[:error] = t('authentications.controller.provider_sign_in_failed', {:provider => Authentication.provider_title(provider)})
redirect_to new_user_session_path
end
end
def destroy
@authentication = current_user.authentications.find(params[:id])
provider = @authentication.provider
@authentication.destroy
flash[:notice] = t('authentications.controller.unlinked_provider')
redirect_to authentications_url
end
def failure
omniauth = request.env["omniauth.auth"]
if omniauth
provider = omniauth['provider']
if provider == "saml"
provider = "feide"
end
flash[:error] = t('authentications.controller.provider_failure')+": "+params[:message]
else
flash[:error] = t('authentications.controller.general_failure', :message => params[:message])
end
redirect_to new_user_session_path
end
def handle_unverified_request
# Workaround for session reset problem with SAML
end
end
require 'onelogin/ruby-saml/response'
require 'onelogin/ruby-saml/logoutrequest'
require "cgi"
class FeideController < ApplicationController
STATUS_SUCCESS= 'Success'
STATUS_REQUESTER= 'Requester'
STATUS_RESPONDER= 'Responder'
def request_feide_logout
ext_session = params[:ext_session_id]
request = vaild_request({:nameID => ext_session, :sessionIndex => ext_session ,:saml_settings => saml_settings})
user = User.find_by_ext_session_id ext_session
user.update_attribute(:ext_session_id, nil) unless user.nil?
redirect_to "#{saml_settings.assertion_consumer_logout_service_url}?SAMLRequest=#{prepare_saml_parameter(request)}"
end
def consume
if params[:SAMLRequest] #incoming request from FEIDE.
#MUST check the parameter against the signature
result=handle_request_callback
redirect_to result
return
elsif params[:SAMLResponse] #incoming response from FEIDE (After we initiated logout)
#MUST check the parameter against the signature
result= handle_response_callback
flash[:notice] = t('user.signed_out')
redirect_to root_path
end
end
private
def handle_request_callback
decode_SAMLRequest(params[:SAMLRequest])
user = User.find_by_ext_session_id @saml_session
if user.nil?
#Handle if unvalid response
logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response({:id => @saml_request_id, :saml_settings => saml_settings}, STATUS_RESPONDER), saml_settings)
else
sign_out(user)
user.update_attribute(:ext_session_id, nil)
logoutresponse = Onelogin::Saml::Logoutresponse.new(valid_response({:id => @saml_request_id, :saml_settings => saml_settings}), saml_settings)
end
return "#{saml_settings.assertion_consumer_logout_service_url}?SAMLResponse=#{prepare_saml_parameter(logoutresponse.response)}"
end
def handle_response_callback
decode_SAMLResponse(params[:SAMLResponse])
true
end
def saml_settings
settings = Onelogin::Saml::Settings.new
settings.assertion_consumer_logout_service_url = Settings.feide.slo_target
settings.issuer = Settings.feide.issuer
settings.idp_sso_target_url = Settings.feide.sso_target
settings.idp_cert = Base64.decode64(Settings.feide.idp_cert)
settings.idp_slo_target_url = Settings.feide.slo_target
settings
end
def decode_SAMLRequest(saml_request)
@saml_request = decode_string saml_request
@saml_request_id = @saml_request[/ID=['"](.+?)['"]/, 1]
@saml_issueInstand = @saml_request[/IssueInstant=['"](.+?)['"]/, 1]
@saml_session = @saml_request[/<saml:NameID.*>(.+?)<\/saml:NameID>/, 1]
end
def decode_SAMLResponse(saml_response)
@saml_request = decode_string saml_response
@saml_session = @saml_request[/ID=['"](.+?)['"]/, 1]
@saml_status = @saml_request[/StatusCode Value="=['"](.+?)['"]/, 1]
end
def decode_string(saml_input)
zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
saml_xml = zstream.inflate(Base64.decode64(saml_input))
zstream.finish
zstream.close
return saml_xml
end
def prepare_saml_parameter(response)
deflated = Zlib::Deflate.deflate(response, 9)[2..-5]
encoded_result = Base64.encode64 deflated
return CGI.escape encoded_result
end
def valid_response(opts = {}, status = STATUS_SUCCESS)
"<samlp:LogoutResponse xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"#{random_id}\" Version=\"2.0\" IssueInstant=\"#{Time.now.strftime('%Y-%m-%dT%H:%M:%SZ')}\" Destination=\"#{saml_settings.assertion_consumer_logout_service_url}\" InResponseTo=\"#{opts[:id]}\"><saml:Issuer>#{saml_settings.issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:#{status}\"/></samlp:Status></samlp:LogoutResponse>"
end
#nameID
#sessionIndex
def vaild_request(opts = {})
"<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"#{random_id}\" Version=\"2.0\" Destination=\"#{saml_settings.assertion_consumer_logout_service_url}\" IssueInstant=\"#{Time.now.strftime('%Y-%m-%dT%H:%M:%SZ')}\"> <saml:Issuer > #{saml_settings.issuer} </saml:Issuer> <saml:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:transient\" SPNameQualifier=\"#{saml_settings.issuer}\">#{opts[:nameID]} </saml:NameID> <samlp:SessionIndex>#{opts[:sessionIndex]} </samlp:SessionIndex></samlp:LogoutRequest>"
end
def random_id
"_#{UUID.new.generate}"
end
end
# initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :saml,
:assertion_consumer_service_url => Settings.feide.consumer_url,
:issuer => Settings.feide.issuer,
:idp_sso_target_url => Settings.feide.sso_target,
:idp_cert => Base64.decode64(Settings.feide.idp_cert || "unset"),
:name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
provider :twitter, Settings.twitter.key, Settings.twitter.secret
provider :facebook, Settings.facebook.key, Settings.facebook.secret, :scope => 'email'
provider :google_oauth2, Settings.google.key, Settings.google.secret, {}
end
match '/feide/slo', :controller => 'feide', :action => 'consume'
match '/feide/request_feide_logout', :controller => 'feide', :action => 'request_feide_logout'
match '/auth/:provider/callback' => 'authentications#create'
match '/auth/failure', :controller => 'authentications', :action => 'failure'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment