Skip to content
This repository has been archived by the owner on Feb 6, 2024. It is now read-only.

Commit

Permalink
Merge pull request #1 from rbCAS/service-whitelist
Browse files Browse the repository at this point in the history
Service whitelist
  • Loading branch information
pencil committed Feb 2, 2013
2 parents 4f11b7c + 212d077 commit 93bdc1f
Show file tree
Hide file tree
Showing 19 changed files with 362 additions and 58 deletions.
18 changes: 10 additions & 8 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,27 @@ PATH
casino_core (1.0.12)
activerecord (~> 3.2.9)
addressable (~> 2.3)
terminal-table (~> 1.4)
useragent (~> 0.4)

GEM
remote: http://rubygems.org/
specs:
activemodel (3.2.9)
activesupport (= 3.2.9)
activemodel (3.2.11)
activesupport (= 3.2.11)
builder (~> 3.0.0)
activerecord (3.2.9)
activemodel (= 3.2.9)
activesupport (= 3.2.9)
activerecord (3.2.11)
activemodel (= 3.2.11)
activesupport (= 3.2.11)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activesupport (3.2.9)
activesupport (3.2.11)
i18n (~> 0.6)
multi_json (~> 1.0)
addressable (2.3.2)
arel (3.0.2)
builder (3.0.4)
crack (0.3.1)
crack (0.3.2)
database_cleaner (0.9.1)
diff-lcs (1.1.3)
factory_girl (4.1.0)
Expand All @@ -45,8 +46,9 @@ GEM
simplecov-html (~> 0.7.1)
simplecov-html (0.7.1)
sqlite3 (1.3.6)
terminal-table (1.4.5)
tzinfo (0.3.35)
useragent (0.4.15)
useragent (0.4.16)
webmock (1.9.0)
addressable (>= 2.2.7)
crack (>= 0.1.7)
Expand Down
14 changes: 14 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Upgrade CASinoCore

Here is a list of backward-incompatible changes that were introduced.

## 1.1.0

API changes:

* `login_credential_acceptor`: The parameters of `#process` changed from `params, cookies, user_agent` to just `params, user_agent`

New callbacks:

* `login_credential_requestor` calls `#service_not_allowed` on the listener, when a service is not in the service whitelist.
* `api/service_ticket_provider` calls `#service_not_allowed_via_api` on the listener, when a service is not in the service whitelist.
1 change: 1 addition & 0 deletions casino_core.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Gem::Specification.new do |s|

s.add_runtime_dependency 'activerecord', '~> 3.2.9'
s.add_runtime_dependency 'addressable', '~> 2.3'
s.add_runtime_dependency 'terminal-table', '~> 1.4'
s.add_runtime_dependency 'useragent', '~> 0.4'
end

15 changes: 15 additions & 0 deletions db/migrate/20130105152327_create_service_rules.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class CreateServiceRules < ActiveRecord::Migration
def change
create_table :service_rules do |t|
t.boolean :enabled, null: false, default: true
t.integer :order, null: false, default: 10
t.string :name, null: false
t.string :url, null: false
t.boolean :regex, null: false, default: false

t.timestamps
end

add_index :service_rules, :url, unique: true
end
end
14 changes: 13 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.

ActiveRecord::Schema.define(:version => 20121231114141) do
ActiveRecord::Schema.define(:version => 20130105152327) do

create_table "login_tickets", :force => true do |t|
t.string "ticket", :null => false
Expand Down Expand Up @@ -47,6 +47,18 @@
add_index "proxy_tickets", ["proxy_granting_ticket_id"], :name => "index_proxy_tickets_on_proxy_granting_ticket_id"
add_index "proxy_tickets", ["ticket"], :name => "index_proxy_tickets_on_ticket", :unique => true

create_table "service_rules", :force => true do |t|
t.boolean "enabled", :default => true, :null => false
t.integer "order", :default => 10, :null => false
t.string "name", :null => false
t.string "url", :null => false
t.boolean "regex", :default => false, :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end

add_index "service_rules", ["url"], :name => "index_service_rules_on_url", :unique => true

create_table "service_tickets", :force => true do |t|
t.string "ticket", :null => false
t.string "service", :null => false
Expand Down
15 changes: 15 additions & 0 deletions lib/casino_core/helper/login_tickets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ def acquire_login_ticket
logger.debug "Created login ticket '#{ticket.ticket}'"
ticket
end

def login_ticket_valid?(lt)
ticket = CASinoCore::Model::LoginTicket.find_by_ticket lt
if ticket.nil?
logger.info "Login ticket '#{lt}' not found"
false
elsif ticket.created_at < CASinoCore::Settings.login_ticket[:lifetime].seconds.ago
logger.info "Login ticket '#{ticket.ticket}' expired"
false
else
logger.debug "Login ticket '#{ticket.ticket}' successfully validated"
ticket.delete
true
end
end
end
end
end
17 changes: 13 additions & 4 deletions lib/casino_core/helper/service_tickets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ module ServiceTickets
include CASinoCore::Helper::Tickets
include CASinoCore::Helper::ProxyTickets

class ServiceNotAllowedError < StandardError; end

def acquire_service_ticket(ticket_granting_ticket, service, credentials_supplied = nil)
service_url = clean_service_url(service)
unless CASinoCore::Model::ServiceRule.allowed?(service_url)
message = "#{service_url} is not in the list of allowed URLs"
logger.error message
raise ServiceNotAllowedError, message
end
ticket_granting_ticket.service_tickets.create!({
ticket: random_ticket_string('ST'),
service: clean_service_url(service),
service: service_url,
issued_from_credentials: !!credentials_supplied
})
end
Expand All @@ -24,9 +32,10 @@ def clean_service_url(dirty_service)
if service_uri.query_values.blank?
service_uri.query_values = nil
end
if "#{service_uri.path}".length > 1
service_uri.path = service_uri.path.gsub(/\/\z/, '')
end

service_uri.path = (service_uri.path || '').gsub(/\/+\z/, '')
service_uri.path = '/' if service_uri.path.blank?

clean_service = service_uri.to_s

logger.debug("Cleaned dirty service URL '#{dirty_service}' to '#{clean_service}'") if dirty_service != clean_service
Expand Down
1 change: 1 addition & 0 deletions lib/casino_core/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
module CASinoCore
module Model
autoload :LoginTicket, 'casino_core/model/login_ticket.rb'
autoload :ServiceRule, 'casino_core/model/service_rule.rb'
autoload :ServiceTicket, 'casino_core/model/service_ticket.rb'
autoload :ProxyGrantingTicket, 'casino_core/model/proxy_granting_ticket.rb'
autoload :ProxyTicket, 'casino_core/model/proxy_ticket.rb'
Expand Down
28 changes: 28 additions & 0 deletions lib/casino_core/model/service_rule.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
require 'casino_core/model'

class CASinoCore::Model::ServiceRule < ActiveRecord::Base
attr_accessible :enabled, :order, :name, :url, :regex
validates :name, presence: true
validates :url, uniqueness: true, presence: true

def self.allowed?(service_url)
rules = self.where(enabled: true)
if rules.empty?
true
else
rules.any? { |rule| rule.allows?(service_url) }
end
end

def allows?(service_url)
if self.regex?
regex = Regexp.new self.url, true
if regex =~ service_url
return true
end
elsif self.url == service_url
return true
end
false
end
end
13 changes: 11 additions & 2 deletions lib/casino_core/processor/api/service_ticket_provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class CASinoCore::Processor::API::ServiceTicketProvider < CASinoCore::Processor
# The service ticket (and nothing else) should be displayed.
# * `#invalid_ticket_granting_ticket_via_api`: No argument. The application should respond with status "400 Bad Request"
# * `#no_service_provided_via_api`: No argument. The application should respond with status "400 Bad Request"
# * `#service_not_allowed_via_api`: The user tried to access a service that this CAS server is not allowed to serve.
#
# @param [String] ticket_granting_ticket ticket_granting_ticket supplied by the user in the URL
# @param [Hash] parameters parameters supplied by user (`service` in particular)
Expand All @@ -37,8 +38,12 @@ def fetch_valid_ticket_granting_ticket
def handle_ticket_granting_ticket
case
when (@service_url and @ticket_granting_ticket)
create_service_ticket
callback_granted_service_ticket
begin
create_service_ticket
callback_granted_service_ticket
rescue ServiceNotAllowedError
callback_service_not_allowed
end
when (@service_url and not @ticket_granting_ticket)
callback_invalid_tgt
when (not @service_url and @ticket_granting_ticket)
Expand All @@ -62,4 +67,8 @@ def callback_empty_service
@listener.no_service_provided_via_api
end

def callback_service_not_allowed
@listener.service_not_allowed_via_api(clean_service_url @service_url)
end

end
48 changes: 22 additions & 26 deletions lib/casino_core/processor/login_credential_acceptor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,39 @@ class CASinoCore::Processor::LoginCredentialAcceptor < CASinoCore::Processor
# The second argument (String) is the ticket-granting ticket. It should be stored in a cookie named "tgt".
# * `#invalid_login_ticket` and `#invalid_login_credentials`: The first argument is a LoginTicket.
# See {CASinoCore::Processor::LoginCredentialRequestor} for details.
# * `#service_not_allowed`: The user tried to access a service that this CAS server is not allowed to serve.
#
# @param [Hash] params parameters supplied by user
# @param [Hash] cookies cookies supplied by user
# @param [String] user_agent user-agent delivered by the client
def process(params = nil, cookies = nil, user_agent = nil)
params ||= {}
cookies ||= {}
if login_ticket_valid?(params[:lt])
authentication_result = validate_login_credentials(params[:username], params[:password])
if !authentication_result.nil?
ticket_granting_ticket = acquire_ticket_granting_ticket(authentication_result, user_agent)
url = unless params[:service].nil?
acquire_service_ticket(ticket_granting_ticket, params[:service], true).service_with_ticket_url
end
@listener.user_logged_in(url, ticket_granting_ticket.ticket)
else
@listener.invalid_login_credentials(acquire_login_ticket)
end
def process(params = nil, user_agent = nil)
@params = params || {}
@user_agent = user_agent
if login_ticket_valid?(@params[:lt])
authenticate_user
else
@listener.invalid_login_ticket(acquire_login_ticket)
end
end

private
def login_ticket_valid?(lt)
ticket = CASinoCore::Model::LoginTicket.find_by_ticket lt
if ticket.nil?
logger.info "Login ticket '#{lt}' not found"
false
elsif ticket.created_at < CASinoCore::Settings.login_ticket[:lifetime].seconds.ago
logger.info "Login ticket '#{ticket.ticket}' expired"
false
def authenticate_user
authentication_result = validate_login_credentials(@params[:username], @params[:password])
if !authentication_result.nil?
user_logged_in(authentication_result)
else
logger.debug "Login ticket '#{ticket.ticket}' successfully validated"
ticket.delete
true
@listener.invalid_login_credentials(acquire_login_ticket)
end
end

def user_logged_in(authentication_result)
begin
ticket_granting_ticket = acquire_ticket_granting_ticket(authentication_result, @user_agent)
url = unless @params[:service].nil?
acquire_service_ticket(ticket_granting_ticket, @params[:service], true).service_with_ticket_url
end
@listener.user_logged_in(url, ticket_granting_ticket.ticket)
rescue ServiceNotAllowedError => e
@listener.service_not_allowed(clean_service_url @params[:service])
end
end
end
60 changes: 45 additions & 15 deletions lib/casino_core/processor/login_credential_requestor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,57 @@ class CASinoCore::Processor::LoginCredentialRequestor < CASinoCore::Processor
# The method will call one of the following methods on the listener:
# * `#user_logged_in`: The first argument (String) is the URL (if any), the user should be redirected to.
# * `#user_not_logged_in`: The first argument is a LoginTicket. It should be stored in a hidden field with name "lt".
# * `#service_not_allowed`: The user tried to access a service that this CAS server is not allowed to serve.
#
# @param [Hash] params parameters supplied by user
# @param [Hash] cookies cookies supplied by user
# @param [String] user_agent user-agent delivered by the client
def process(params = nil, cookies = nil, user_agent = nil)
params ||= {}
cookies ||= {}
request_env ||= {}
if !params[:renew] && (ticket_granting_ticket = find_valid_ticket_granting_ticket(cookies[:tgt], user_agent))
service_url_with_ticket = unless params[:service].nil?
acquire_service_ticket(ticket_granting_ticket, params[:service], true).service_with_ticket_url
end
@listener.user_logged_in(service_url_with_ticket)
@params = params || {}
@cookies = cookies || {}
@user_agent = user_agent || {}
if check_service_allowed
handle_allowed_service
end
end

private
def handle_allowed_service
if !@params[:renew] && (@ticket_granting_ticket = find_valid_ticket_granting_ticket(@cookies[:tgt], @user_agent))
handle_logged_in
else
handle_not_logged_in
end
end

def handle_logged_in
service_url_with_ticket = unless @params[:service].nil?
acquire_service_ticket(@ticket_granting_ticket, @params[:service], true).service_with_ticket_url
end
@listener.user_logged_in(service_url_with_ticket)
end

def handle_not_logged_in
if gateway_request?
# we actually lie to the listener to simplify things
@listener.user_logged_in(@params[:service])
else
login_ticket = acquire_login_ticket
@listener.user_not_logged_in(login_ticket)
end
end

def check_service_allowed
service_url = clean_service_url(@params[:service]) unless @params[:service].nil?
if service_url.nil? || CASinoCore::Model::ServiceRule.allowed?(service_url)
true
else
if params[:gateway] == 'true' && params[:service]
# we actually lie to the listener to simplify things
@listener.user_logged_in(params[:service])
else
login_ticket = acquire_login_ticket
@listener.user_not_logged_in(login_ticket)
end
@listener.service_not_allowed(service_url)
false
end
end

def gateway_request?
@params[:gateway] == 'true' && @params[:service]
end
end
1 change: 1 addition & 0 deletions lib/casino_core/rake_tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def load_tasks
%w(
database
cleanup
service_rule
).each do |task|
load "casino_core/tasks/#{task}.rake"
end
Expand Down
Loading

0 comments on commit 93bdc1f

Please sign in to comment.