Created
June 1, 2012 13:54
-
-
Save bryanlarsen/2852329 to your computer and use it in GitHub Desktop.
single sign out middleware for rubycas-client-rails
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RubycasRailsMiddleware | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
# Hack: something somewhere is killing the log, probably because | |
# it's not being used in a thread-safe fashion. Resurrect | |
#::Rails.logger.level = Logger::DEBUG | |
if single_sign_out(env) | |
[200, {"Content-Type" => "text/html"}, "CAS Single-Sign-Out request intercepted."] | |
else | |
@app.call(env) | |
end | |
end | |
def single_sign_out(env) | |
logger = env['rack.logger'] || ::Rails.logger | |
# FIXME HACK: something somewhere is killing the log, probably because | |
# it's not using it in a thread-safe manner. resurrect. | |
logger.level = Logger::DEBUG | |
if env['REQUEST_METHOD'] != 'POST' || env['CONTENT_TYPE'] =~ %r{^multipart/} | |
return false | |
end | |
rack_input = env["rack.input"].read | |
env["rack.input"].rewind | |
params = Rack::Utils.parse_query(rack_input) | |
if params['logoutRequest'] && URI.unescape(params['logoutRequest']) =~ | |
%r{^<samlp:LogoutRequest.*?<samlp:SessionIndex>(.*)</samlp:SessionIndex>}m | |
# TODO: Maybe check that the request came from the registered CAS server? Although this might be | |
# pointless since it's easily spoofable... | |
si = $~[1] | |
else | |
return false | |
end | |
# unless config[:enable_single_sign_out] | |
# logger.warn "Ignoring single-sign-out request for CAS session #{si.inspect} because ssout functionality is not enabled (see the :enable_single_sign_out config option)." | |
# return false | |
# end | |
logger.debug "Intercepted single-sign-out request for CAS session #{si.inspect}." | |
required_sess_store = ActiveRecord::SessionStore | |
current_sess_store = ::Rails.application.config.session_store | |
if current_sess_store == required_sess_store | |
session_id = read_service_session_lookup(si) | |
if session_id | |
session = current_sess_store::Session.find_by_session_id(session_id) | |
if session | |
st = session.data[:cas_last_valid_ticket] || si | |
delete_service_session_lookup(st) if st | |
session.destroy | |
logger.debug("Destroyed #{session.inspect} for session #{session_id.inspect} corresponding to service ticket #{si.inspect}.") | |
else | |
logger.debug("Data for session #{session_id.inspect} was not found. It may have already been cleared by a local CAS logout request.") | |
end | |
logger.info("Single-sign-out for session #{session_id.inspect} completed successfuly.") | |
else | |
logger.warn("Couldn't destroy session with SessionIndex #{si} because no corresponding session id could be looked up.") | |
end | |
else | |
logger.error "Cannot process logout request because this Rails application's session store is "+ | |
" #{current_sess_store.name.inspect}. Single Sign-Out only works with the "+ | |
" #{required_sess_store.name.inspect} session store." | |
end | |
true | |
end | |
# Returns the local Rails session ID corresponding to the given | |
# ServiceTicket. This is done by reading the contents of the | |
# cas_sess.<session ticket> file created in a prior call to | |
# #store_service_session_lookup. | |
def read_service_session_lookup(st) | |
st = st.ticket if st.kind_of? CASClient::ServiceTicket | |
ssl_filename = filename_of_service_session_lookup(st) | |
return File.exists?(ssl_filename) && IO.read(ssl_filename) | |
end | |
# Removes a stored relationship between a ServiceTicket and a local | |
# Rails session id. This should be called when the session is being | |
# closed. | |
# | |
# See #store_service_session_lookup. | |
def delete_service_session_lookup(st) | |
st = st.ticket if st.kind_of? CASClient::ServiceTicket | |
ssl_filename = filename_of_service_session_lookup(st) | |
File.delete(ssl_filename) if File.exists?(ssl_filename) | |
end | |
# Returns the path and filename of the service session lookup file. | |
def filename_of_service_session_lookup(st) | |
st = st.ticket if st.kind_of? CASClient::ServiceTicket | |
return "#{Rails.root}/tmp/sessions/cas_sess.#{st}" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment