require 'rubygems'
require 'bundler'
Bundler.require
# Bundler.require doesn't seem to be pulling this in when used as gem...
require 'sinatra'
require 'redis'
require 'nokogiri'
require 'rack'
require 'rack-flash'
require 'warden'
if RUBY_VERSION < "1.9"
require 'backports'
require 'system_timer'
end
require 'addressable/uri'
require_relative 'login_ticket'
require_relative 'proxy_ticket'
require_relative 'service_ticket'
require_relative 'ticket_granting_ticket'
require_relative 'strategies'
module ClassyCAS
class Server < Sinatra::Base
set :redis, Proc.new { Redis.new } unless settings.respond_to?(:redis)
set :client_sites, [ "http://localhost:3001", 'http://localhost:3002'] unless settings.respond_to?(:client_sites)
set :root, File.dirname(__FILE__)
set :public, File.join(root, "/../public")
set :warden_strategies, [:simple] unless settings.respond_to?(:warden_strategies)
use Rack::Session::Cookie
use Rack::Flash, :accessorize => [:notice, :error]
use Warden::Manager do |manager|
manager.failure_app = self
manager.default_scope = :cas
manager.scope_defaults(:cas,
:strategies => settings.warden_strategies,
:action => "login"
)
end
configure :development do
set :dump_errors
end
get "/" do
redirect "/login"
end
get "/login" do
@service_url = Addressable::URI.parse(params[:service])
@renew = [true, "true", "1", 1].include?(params[:renew])
@gateway = [true, "true", "1", 1].include?(params[:gateway])
if @renew
@login_ticket = LoginTicket.create!(settings.redis)
render_login
elsif @gateway
if @service_url
if sso_session
st = ServiceTicket.new(params[:service], sso_session.username)
st.save!(settings.redis)
redirect_url = @service_url.clone
if @service_url.query_values.nil?
redirect_url.query_values = @service_url.query_values = {:ticket => st.ticket}
else
redirect_url.query_values = @service_url.query_values.merge(:ticket => st.ticket)
end
redirect redirect_url.to_s, 303
else
redirect @service_url.to_s, 303
end
else
@login_ticket = LoginTicket.create!(settings.redis)
render_login
end
else
if sso_session
if @service_url
st = ServiceTicket.new(params[:service], sso_session.username)
st.save!(settings.redis)
redirect_url = @service_url.clone
if @service_url.query_values.nil?
redirect_url.query_values = @service_url.query_values = {:ticket => st.ticket}
else
redirect_url.query_values = @service_url.query_values.merge(:ticket => st.ticket)
end
redirect redirect_url.to_s, 303
else
render_logged_in
end
else
@login_ticket = LoginTicket.create!(settings.redis)
render_login
end
end
end
post "/login" do
username = params[:username]
password = params[:password]
service_url = params[:service]
warn = [true, "true", "1", 1].include? params[:warn]
# Spec is undefined about what to do without these params, so redirecting to credential requestor
redirect "/login", 303 unless username && password && login_ticket
# Failures will throw back to self, which we've registered with Warden to handle login failures
warden.authenticate!(:scope => :cas, :action => 'unauthenticated')
tgt = TicketGrantingTicket.create!(username, settings.redis)
cookie = tgt.to_cookie(request.host)
response.set_cookie(*cookie)
if service_url && !warn
st = ServiceTicket.new(service_url, username)
st.save!(settings.redis)
redirect service_url + "?ticket=#{st.ticket}", 303
else
render_logged_in
end
end
get %r{(proxy|service)Validate} do
service_url = params[:service]
ticket = params[:ticket]
# proxy_gateway = params[:pgtUrl]
# renew = params[:renew]
xml = if service_url && ticket
if service_ticket
if service_ticket.valid_for_service?(service_url)
render_validation_success service_ticket.username
else
render_validation_error(:invalid_service)
end
else
render_validation_error(:invalid_ticket, "ticket #{ticket} not recognized")
end
else
render_validation_error(:invalid_request)
end
content_type :xml
xml
end
get '/logout' do
url = params[:url]
if sso_session
@sso_session.destroy!(settings.redis)
response.delete_cookie(*sso_session.to_cookie(request.host))
warden.logout(:cas)
flash.now[:notice] = "Logout Successful."
if url
msg = " The application you just logged out of has provided a link it would like you to follow."
msg += "Please click here to access #{url}"
flash.now[:notice] += msg
end
end
@login_ticket = LoginTicket.create!(settings.redis)
@logout = true
render_login
end
def render_login
erb :login
end
def render_logged_in
erb :logged_in
end
# Override to add user info back to client applications
def append_user_info(username, xml)
end
private
def warden
request.env["warden"]
end
def sso_session
@sso_session ||= TicketGrantingTicket.validate(request.cookies["tgt"], settings.redis)
end
def ticket_granting_ticket
@ticket_granting_ticket ||= sso_session
end
def login_ticket
@login_ticket ||= LoginTicket.validate!(params[:lt], settings.redis)
end
def service_ticket
@service_ticket ||= ServiceTicket.find!(params[:ticket], settings.redis)
end
def render_validation_error(code, message = nil)
xml = Nokogiri::XML::Builder.new do |xml|
xml.serviceResponse("xmlns:cas" => "http://www.yale.edu/tp/cas") {
xml.parent.namespace = xml.parent.namespace_definitions.first
xml['cas'].authenticationFailure(message, :code => code.to_s.upcase){
}
}
end
xml.to_xml
end
def render_validation_success(username)
xml = Nokogiri::XML::Builder.new do |xml|
xml.serviceResponse("xmlns:cas" => "http://www.yale.edu/tp/cas") {
xml.parent.namespace = xml.parent.namespace_definitions.first
xml['cas'].authenticationSuccess {
xml['cas'].user username
append_user_info(username, xml)
}
}
end
xml.to_xml
end
end
end