Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
single sign out middleware for rubycas-client-rails
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
You can’t perform that action at this time.