ror-rubycas-server 1.0.a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +292 -0
- data/Gemfile +2 -0
- data/LICENSE +26 -0
- data/README.textile +129 -0
- data/Rakefile +1 -0
- data/bin/rubycas-server +16 -0
- data/lib/casserver.rb +11 -0
- data/lib/casserver/authenticators/active_directory_ldap.rb +19 -0
- data/lib/casserver/authenticators/authlogic_crypto_providers/aes256.rb +43 -0
- data/lib/casserver/authenticators/authlogic_crypto_providers/bcrypt.rb +92 -0
- data/lib/casserver/authenticators/authlogic_crypto_providers/md5.rb +34 -0
- data/lib/casserver/authenticators/authlogic_crypto_providers/sha1.rb +59 -0
- data/lib/casserver/authenticators/authlogic_crypto_providers/sha512.rb +50 -0
- data/lib/casserver/authenticators/base.rb +67 -0
- data/lib/casserver/authenticators/client_certificate.rb +47 -0
- data/lib/casserver/authenticators/google.rb +58 -0
- data/lib/casserver/authenticators/ldap.rb +147 -0
- data/lib/casserver/authenticators/ntlm.rb +88 -0
- data/lib/casserver/authenticators/open_id.rb +22 -0
- data/lib/casserver/authenticators/sql.rb +133 -0
- data/lib/casserver/authenticators/sql_authlogic.rb +93 -0
- data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
- data/lib/casserver/authenticators/sql_md5.rb +19 -0
- data/lib/casserver/authenticators/sql_rest_auth.rb +85 -0
- data/lib/casserver/authenticators/test.rb +22 -0
- data/lib/casserver/cas.rb +315 -0
- data/lib/casserver/localization.rb +91 -0
- data/lib/casserver/model.rb +270 -0
- data/lib/casserver/options_hash.rb +44 -0
- data/lib/casserver/server.rb +706 -0
- data/lib/casserver/utils.rb +32 -0
- data/lib/casserver/views/_login_form.erb +42 -0
- data/lib/casserver/views/layout.erb +18 -0
- data/lib/casserver/views/login.erb +30 -0
- data/lib/casserver/views/proxy.builder +12 -0
- data/lib/casserver/views/proxy_validate.builder +25 -0
- data/lib/casserver/views/service_validate.builder +18 -0
- data/lib/casserver/views/validate.erb +2 -0
- data/po/de_DE/rubycas-server.po +127 -0
- data/po/es_ES/rubycas-server.po +123 -0
- data/po/fr_FR/rubycas-server.po +128 -0
- data/po/ja_JP/rubycas-server.po +126 -0
- data/po/pl_PL/rubycas-server.po +123 -0
- data/po/pt_BR/rubycas-server.po +123 -0
- data/po/ru_RU/rubycas-server.po +118 -0
- data/po/rubycas-server.pot +112 -0
- data/po/zh_CN/rubycas-server.po +113 -0
- data/po/zh_TW/rubycas-server.po +113 -0
- data/public/themes/cas.css +121 -0
- data/public/themes/notice.png +0 -0
- data/public/themes/ok.png +0 -0
- data/public/themes/simple/bg.png +0 -0
- data/public/themes/simple/favicon.png +0 -0
- data/public/themes/simple/login_box_bg.png +0 -0
- data/public/themes/simple/logo.png +0 -0
- data/public/themes/simple/theme.css +28 -0
- data/public/themes/urbacon/bg.png +0 -0
- data/public/themes/urbacon/login_box_bg.png +0 -0
- data/public/themes/urbacon/logo.png +0 -0
- data/public/themes/urbacon/theme.css +33 -0
- data/public/themes/warning.png +0 -0
- data/resources/init.d.sh +58 -0
- data/rubycas-server.gemspec +57 -0
- data/setup.rb +1585 -0
- data/spec/alt_config.yml +50 -0
- data/spec/authenticators/ldap_spec.rb +53 -0
- data/spec/casserver_spec.rb +141 -0
- data/spec/database.yml +5 -0
- data/spec/default_config.yml +73 -0
- data/spec/model_spec.rb +42 -0
- data/spec/options_hash_spec.rb +146 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +90 -0
- data/spec/utils_spec.rb +53 -0
- data/tasks/bundler.rake +4 -0
- data/tasks/db/migrate.rake +12 -0
- data/tasks/localization.rake +13 -0
- data/tasks/spec.rake +10 -0
- metadata +356 -0
@@ -0,0 +1,91 @@
|
|
1
|
+
require "gettext"
|
2
|
+
require "gettext/cgi"
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module CASServer
|
6
|
+
module Localization
|
7
|
+
def self.included(mod)
|
8
|
+
mod.module_eval do
|
9
|
+
include GetText
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
include GetText
|
14
|
+
bindtextdomain("rubycas-server", :path => File.join(File.dirname(File.expand_path(__FILE__)), "../../locale"))
|
15
|
+
|
16
|
+
def determine_locale(request)
|
17
|
+
source = nil
|
18
|
+
lang = case
|
19
|
+
when !request.params['lang'].blank?
|
20
|
+
source = "'lang' request variable"
|
21
|
+
request.cookies['lang'] = request.params['lang']
|
22
|
+
request.params['lang']
|
23
|
+
when !request.cookies['lang'].blank?
|
24
|
+
source = "'lang' cookie"
|
25
|
+
request.cookies['lang']
|
26
|
+
when !request.env['HTTP_ACCEPT_LANGUAGE'].blank?
|
27
|
+
source = "'HTTP_ACCEPT_LANGUAGE' header"
|
28
|
+
lang = request.env['HTTP_ACCEPT_LANGUAGE']
|
29
|
+
when !request.env['HTTP_USER_AGENT'].blank? && request.env['HTTP_USER_AGENT'] =~ /[^a-z]([a-z]{2}(-[a-z]{2})?)[^a-z]/i
|
30
|
+
source = "'HTTP_USER_AGENT' header"
|
31
|
+
$~[1]
|
32
|
+
# when !$CONF['default_locale'].blank?
|
33
|
+
# source = "'default_locale' config option"
|
34
|
+
# $CONF[:default_locale]
|
35
|
+
else
|
36
|
+
source = "default"
|
37
|
+
"en"
|
38
|
+
end
|
39
|
+
|
40
|
+
$LOG.debug "Detected locale is #{lang.inspect} (from #{source})"
|
41
|
+
|
42
|
+
lang.gsub!('_','-')
|
43
|
+
|
44
|
+
# TODO: Need to confirm that this method of splitting the accepted
|
45
|
+
# language string is correct.
|
46
|
+
if lang =~ /[,;\|]/
|
47
|
+
langs = lang.split(/[,;\|]/)
|
48
|
+
else
|
49
|
+
langs = [lang]
|
50
|
+
end
|
51
|
+
|
52
|
+
# TODO: This method of selecting the desired language might not be
|
53
|
+
# standards-compliant. For example, http://www.w3.org/TR/ltli/
|
54
|
+
# suggests that de-de and de-*-DE might be acceptable identifiers
|
55
|
+
# for selecting various wildcards. The algorithm below does not
|
56
|
+
# currently support anything like this.
|
57
|
+
|
58
|
+
available = available_locales
|
59
|
+
|
60
|
+
if available.length == 1
|
61
|
+
$LOG.warn "Only the #{available.first.inspect} localization is available. You should run `rake localization:mo` to compile support for additional languages!"
|
62
|
+
elsif available.length == 0 # this should never actually happen
|
63
|
+
$LOG.error "No localizations available! Run `rake localization:mo` to compile support for additional languages."
|
64
|
+
end
|
65
|
+
|
66
|
+
# Try to pick a locale exactly matching the desired identifier, otherwise
|
67
|
+
# fall back to locale without region (i.e. given "en-US; de-DE", we would
|
68
|
+
# first look for "en-US", then "en", then "de-DE", then "de").
|
69
|
+
|
70
|
+
chosen_lang = nil
|
71
|
+
langs.each do |l|
|
72
|
+
a = available.find{ |a| a =~ Regexp.new("\\A#{l}\\Z", 'i') ||
|
73
|
+
a =~ Regexp.new("#{l}-\w*", 'i') }
|
74
|
+
if a
|
75
|
+
chosen_lang = a
|
76
|
+
break
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
chosen_lang = "en" if chosen_lang.blank?
|
81
|
+
|
82
|
+
$LOG.debug "Chosen locale is #{chosen_lang.inspect}"
|
83
|
+
|
84
|
+
return chosen_lang
|
85
|
+
end
|
86
|
+
|
87
|
+
def available_locales
|
88
|
+
(Dir.glob(File.join(File.dirname(File.expand_path(__FILE__)), "../../locale/[a-z]*")).map{|path| File.basename(path)} << "en").uniq.collect{|l| l.gsub('_','-')}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/base'
|
3
|
+
|
4
|
+
module CASServer::Model
|
5
|
+
|
6
|
+
module Consumable
|
7
|
+
def consume!
|
8
|
+
self.consumed = Time.now
|
9
|
+
self.save!
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.included(mod)
|
13
|
+
mod.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def cleanup(max_lifetime, max_unconsumed_lifetime)
|
18
|
+
transaction do
|
19
|
+
conditions = ["created_on < ? OR (consumed IS NULL AND created_on < ?)",
|
20
|
+
Time.now - max_lifetime,
|
21
|
+
Time.now - max_unconsumed_lifetime]
|
22
|
+
puts all.count
|
23
|
+
expired_tickets_count = count(:conditions => conditions)
|
24
|
+
|
25
|
+
$LOG.debug("Destroying #{expired_tickets_count} expired #{self.name.demodulize}"+
|
26
|
+
"#{'s' if expired_tickets_count > 1}.") if expired_tickets_count > 0
|
27
|
+
|
28
|
+
destroy_all(conditions)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Base < ActiveRecord::Base
|
35
|
+
end
|
36
|
+
|
37
|
+
class Ticket < Base
|
38
|
+
def to_s
|
39
|
+
ticket
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.cleanup(max_lifetime)
|
43
|
+
transaction do
|
44
|
+
conditions = ["created_on < ?", Time.now - max_lifetime]
|
45
|
+
expired_tickets_count = count(:conditions => conditions)
|
46
|
+
|
47
|
+
$LOG.debug("Destroying #{expired_tickets_count} expired #{self.name.demodulize}"+
|
48
|
+
"#{'s' if expired_tickets_count > 1}.") if expired_tickets_count > 0
|
49
|
+
|
50
|
+
destroy_all(conditions)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class LoginTicket < Ticket
|
56
|
+
set_table_name 'casserver_lt'
|
57
|
+
include Consumable
|
58
|
+
end
|
59
|
+
|
60
|
+
class ServiceTicket < Ticket
|
61
|
+
set_table_name 'casserver_st'
|
62
|
+
include Consumable
|
63
|
+
|
64
|
+
belongs_to :granted_by_tgt,
|
65
|
+
:class_name => 'CASServer::Model::TicketGrantingTicket',
|
66
|
+
:foreign_key => :granted_by_tgt_id
|
67
|
+
has_one :proxy_granting_ticket,
|
68
|
+
:foreign_key => :created_by_st_id
|
69
|
+
|
70
|
+
def matches_service?(service)
|
71
|
+
CASServer::CAS.clean_service_url(self.service) ==
|
72
|
+
CASServer::CAS.clean_service_url(service)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class ProxyTicket < ServiceTicket
|
77
|
+
belongs_to :granted_by_pgt,
|
78
|
+
:class_name => 'CASServer::Model::ProxyGrantingTicket',
|
79
|
+
:foreign_key => :granted_by_pgt_id
|
80
|
+
end
|
81
|
+
|
82
|
+
class TicketGrantingTicket < Ticket
|
83
|
+
set_table_name 'casserver_tgt'
|
84
|
+
|
85
|
+
serialize :extra_attributes
|
86
|
+
|
87
|
+
has_many :granted_service_tickets,
|
88
|
+
:class_name => 'CASServer::Model::ServiceTicket',
|
89
|
+
:foreign_key => :granted_by_tgt_id
|
90
|
+
end
|
91
|
+
|
92
|
+
class ProxyGrantingTicket < Ticket
|
93
|
+
set_table_name 'casserver_pgt'
|
94
|
+
belongs_to :service_ticket
|
95
|
+
has_many :granted_proxy_tickets,
|
96
|
+
:class_name => 'CASServer::Model::ProxyTicket',
|
97
|
+
:foreign_key => :granted_by_pgt_id
|
98
|
+
end
|
99
|
+
|
100
|
+
class Error
|
101
|
+
attr_reader :code, :message
|
102
|
+
|
103
|
+
def initialize(code, message)
|
104
|
+
@code = code
|
105
|
+
@message = message
|
106
|
+
end
|
107
|
+
|
108
|
+
def to_s
|
109
|
+
message
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# class CreateCASServer < V 0.1
|
114
|
+
# def self.up
|
115
|
+
# if ActiveRecord::Base.connection.table_alias_length > 30
|
116
|
+
# $LOG.info("Creating database with long table names...")
|
117
|
+
#
|
118
|
+
# create_table :casserver_login_tickets, :force => true do |t|
|
119
|
+
# t.column :ticket, :string, :null => false
|
120
|
+
# t.column :created_on, :timestamp, :null => false
|
121
|
+
# t.column :consumed, :datetime, :null => true
|
122
|
+
# t.column :client_hostname, :string, :null => false
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# create_table :casserver_service_tickets, :force => true do |t|
|
126
|
+
# t.column :ticket, :string, :null => false
|
127
|
+
# t.column :service, :string, :null => false
|
128
|
+
# t.column :created_on, :timestamp, :null => false
|
129
|
+
# t.column :consumed, :datetime, :null => true
|
130
|
+
# t.column :client_hostname, :string, :null => false
|
131
|
+
# t.column :username, :string, :null => false
|
132
|
+
# t.column :type, :string, :null => false
|
133
|
+
# t.column :proxy_granting_ticket_id, :integer, :null => true
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# create_table :casserver_ticket_granting_tickets, :force => true do |t|
|
137
|
+
# t.column :ticket, :string, :null => false
|
138
|
+
# t.column :created_on, :timestamp, :null => false
|
139
|
+
# t.column :client_hostname, :string, :null => false
|
140
|
+
# t.column :username, :string, :null => false
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# create_table :casserver_proxy_granting_tickets, :force => true do |t|
|
144
|
+
# t.column :ticket, :string, :null => false
|
145
|
+
# t.column :created_on, :timestamp, :null => false
|
146
|
+
# t.column :client_hostname, :string, :null => false
|
147
|
+
# t.column :iou, :string, :null => false
|
148
|
+
# t.column :service_ticket_id, :integer, :null => false
|
149
|
+
# end
|
150
|
+
# end
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# def self.down
|
154
|
+
# if ActiveRecord::Base.connection.table_alias_length > 30
|
155
|
+
# drop_table :casserver_proxy_granting_tickets
|
156
|
+
# drop_table :casserver_ticket_granting_tickets
|
157
|
+
# drop_table :casserver_service_tickets
|
158
|
+
# drop_table :casserver_login_tickets
|
159
|
+
# end
|
160
|
+
# end
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# # Oracle table names cannot exceed 30 chars...
|
164
|
+
# # See http://code.google.com/p/rubycas-server/issues/detail?id=15
|
165
|
+
# class ShortenTableNames < V 0.5
|
166
|
+
# def self.up
|
167
|
+
# if ActiveRecord::Base.connection.table_alias_length > 30
|
168
|
+
# $LOG.info("Shortening table names")
|
169
|
+
# rename_table :casserver_login_tickets, :casserver_lt
|
170
|
+
# rename_table :casserver_service_tickets, :casserver_st
|
171
|
+
# rename_table :casserver_ticket_granting_tickets, :casserver_tgt
|
172
|
+
# rename_table :casserver_proxy_granting_tickets, :casserver_pgt
|
173
|
+
# else
|
174
|
+
# create_table :casserver_lt, :force => true do |t|
|
175
|
+
# t.column :ticket, :string, :null => false
|
176
|
+
# t.column :created_on, :timestamp, :null => false
|
177
|
+
# t.column :consumed, :datetime, :null => true
|
178
|
+
# t.column :client_hostname, :string, :null => false
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# create_table :casserver_st, :force => true do |t|
|
182
|
+
# t.column :ticket, :string, :null => false
|
183
|
+
# t.column :service, :string, :null => false
|
184
|
+
# t.column :created_on, :timestamp, :null => false
|
185
|
+
# t.column :consumed, :datetime, :null => true
|
186
|
+
# t.column :client_hostname, :string, :null => false
|
187
|
+
# t.column :username, :string, :null => false
|
188
|
+
# t.column :type, :string, :null => false
|
189
|
+
# t.column :proxy_granting_ticket_id, :integer, :null => true
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# create_table :casserver_tgt, :force => true do |t|
|
193
|
+
# t.column :ticket, :string, :null => false
|
194
|
+
# t.column :created_on, :timestamp, :null => false
|
195
|
+
# t.column :client_hostname, :string, :null => false
|
196
|
+
# t.column :username, :string, :null => false
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
# create_table :casserver_pgt, :force => true do |t|
|
200
|
+
# t.column :ticket, :string, :null => false
|
201
|
+
# t.column :created_on, :timestamp, :null => false
|
202
|
+
# t.column :client_hostname, :string, :null => false
|
203
|
+
# t.column :iou, :string, :null => false
|
204
|
+
# t.column :service_ticket_id, :integer, :null => false
|
205
|
+
# end
|
206
|
+
# end
|
207
|
+
# end
|
208
|
+
#
|
209
|
+
# def self.down
|
210
|
+
# if ActiveRecord::Base.connection.table_alias_length > 30
|
211
|
+
# rename_table :casserver_lt, :cassserver_login_tickets
|
212
|
+
# rename_table :casserver_st, :casserver_service_tickets
|
213
|
+
# rename_table :casserver_tgt, :casserver_ticket_granting_tickets
|
214
|
+
# rename_table :casserver_pgt, :casserver_proxy_granting_tickets
|
215
|
+
# else
|
216
|
+
# drop_table :casserver_pgt
|
217
|
+
# drop_table :casserver_tgt
|
218
|
+
# drop_table :casserver_st
|
219
|
+
# drop_table :casserver_lt
|
220
|
+
# end
|
221
|
+
# end
|
222
|
+
# end
|
223
|
+
#
|
224
|
+
# class AddTgtToSt < V 0.7
|
225
|
+
# def self.up
|
226
|
+
# add_column :casserver_st, :tgt_id, :integer, :null => true
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# def self.down
|
230
|
+
# remove_column :casserver_st, :tgt_id, :integer
|
231
|
+
# end
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# class ChangeServiceToText < V 0.71
|
235
|
+
# def self.up
|
236
|
+
# # using change_column to change the column type from :string to :text
|
237
|
+
# # doesn't seem to work, at least under MySQL, so we drop and re-create
|
238
|
+
# # the column instead
|
239
|
+
# remove_column :casserver_st, :service
|
240
|
+
# say "WARNING: All existing service tickets are being deleted."
|
241
|
+
# add_column :casserver_st, :service, :text
|
242
|
+
# end
|
243
|
+
#
|
244
|
+
# def self.down
|
245
|
+
# change_column :casserver_st, :service, :string
|
246
|
+
# end
|
247
|
+
# end
|
248
|
+
#
|
249
|
+
# class AddExtraAttributes < V 0.72
|
250
|
+
# def self.up
|
251
|
+
# add_column :casserver_tgt, :extra_attributes, :text
|
252
|
+
# end
|
253
|
+
#
|
254
|
+
# def self.down
|
255
|
+
# remove_column :casserver_tgt, :extra_attributes
|
256
|
+
# end
|
257
|
+
# end
|
258
|
+
#
|
259
|
+
# class RenamePgtForeignKeys < V 0.80
|
260
|
+
# def self.up
|
261
|
+
# rename_column :casserver_st, :proxy_granting_ticket_id, :granted_by_pgt_id
|
262
|
+
# rename_column :casserver_st, :tgt_id, :granted_by_tgt_id
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# def self.down
|
266
|
+
# rename_column :casserver_st, :granted_by_pgt_id, :proxy_granting_ticket_id
|
267
|
+
# rename_column :casserver_st, :granted_by_tgt_id, :tgt_id
|
268
|
+
# end
|
269
|
+
# end
|
270
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "active_support"
|
2
|
+
require "active_record"
|
3
|
+
class OptionsHash < HashWithIndifferentAccess
|
4
|
+
|
5
|
+
class EnvironmentMissing < Exception
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :environment
|
9
|
+
|
10
|
+
def load_config file
|
11
|
+
load file do |file|
|
12
|
+
merge! HashWithIndifferentAccess.new(YAML.load file )[@environment]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_database_config file
|
17
|
+
load file do |file|
|
18
|
+
self[:database] = HashWithIndifferentAccess.new(YAML.load file )[@environment]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def inherit_authenticator_database!
|
23
|
+
if self[:authenticator][:database] == "inherit"
|
24
|
+
self[:authenticator][:database] = self[:database]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def load file
|
32
|
+
raise EnvironmentMissing if @environment.nil?
|
33
|
+
|
34
|
+
case file
|
35
|
+
when File
|
36
|
+
# do nothing..
|
37
|
+
when String
|
38
|
+
file = File.open file
|
39
|
+
else
|
40
|
+
raise ArgumentError
|
41
|
+
end
|
42
|
+
yield file
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,706 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'casserver/localization'
|
3
|
+
require 'casserver/utils'
|
4
|
+
require 'casserver/cas'
|
5
|
+
require "casserver/options_hash"
|
6
|
+
|
7
|
+
require 'logger'
|
8
|
+
$LOG ||= Logger.new(STDOUT)
|
9
|
+
|
10
|
+
module CASServer
|
11
|
+
class Server < Sinatra::Base
|
12
|
+
CONFIG_FILE = ENV['CONFIG_FILE'] || "/etc/rubycas-server/config.yml"
|
13
|
+
|
14
|
+
include CASServer::CAS # CAS protocol helpers
|
15
|
+
include Localization
|
16
|
+
|
17
|
+
set :app_file, __FILE__
|
18
|
+
set :public, Proc.new { settings.config[:public_dir] || File.join(root, "..", "..", "public") }
|
19
|
+
|
20
|
+
config = OptionsHash.new(
|
21
|
+
:maximum_unused_login_ticket_lifetime => 5.minutes,
|
22
|
+
:maximum_unused_service_ticket_lifetime => 5.minutes, # CAS Protocol Spec, sec. 3.2.1 (recommended expiry time)
|
23
|
+
:maximum_session_lifetime => 2.days, # all tickets are deleted after this period of time
|
24
|
+
:log => {:file => 'casserver.log', :level => 'DEBUG'},
|
25
|
+
:uri_path => ""
|
26
|
+
)
|
27
|
+
config.environment = environment
|
28
|
+
set :config, config
|
29
|
+
|
30
|
+
def self.uri_path
|
31
|
+
config[:uri_path]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Strip the config.uri_path from the request.path_info...
|
35
|
+
# FIXME: do we really need to override all of Sinatra's #static! to make this happen?
|
36
|
+
def static!
|
37
|
+
return if (public_dir = settings.public).nil?
|
38
|
+
public_dir = File.expand_path(public_dir)
|
39
|
+
|
40
|
+
path = File.expand_path(public_dir + unescape(request.path_info.gsub(/^#{settings.config[:uri_path]}/,'')))
|
41
|
+
return if path[0, public_dir.length] != public_dir
|
42
|
+
return unless File.file?(path)
|
43
|
+
|
44
|
+
env['sinatra.static_file'] = path
|
45
|
+
send_file path, :disposition => nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.run!(options={})
|
49
|
+
set options
|
50
|
+
|
51
|
+
handler = detect_rack_handler
|
52
|
+
handler_name = handler.name.gsub(/.*::/, '')
|
53
|
+
|
54
|
+
puts "== RubyCAS-Server is starting up " +
|
55
|
+
"on port #{config[:port] || port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
|
56
|
+
|
57
|
+
begin
|
58
|
+
opts = handler_options
|
59
|
+
rescue Exception => e
|
60
|
+
print_cli_message e, :error
|
61
|
+
raise e
|
62
|
+
end
|
63
|
+
|
64
|
+
handler.run self, opts do |server|
|
65
|
+
[:INT, :TERM].each { |sig| trap(sig) { quit!(server, handler_name) } }
|
66
|
+
set :running, true
|
67
|
+
end
|
68
|
+
rescue Errno::EADDRINUSE => e
|
69
|
+
puts "== Something is already running on port #{port}!"
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.quit!(server, handler_name)
|
73
|
+
## Use thins' hard #stop! if available, otherwise just #stop
|
74
|
+
server.respond_to?(:stop!) ? server.stop! : server.stop
|
75
|
+
puts "\n== RubyCAS-Server is shutting down" unless handler_name =~/cgi/i
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.print_cli_message(msg, type = :info)
|
79
|
+
if respond_to?(:config) && config && config[:quiet]
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
if type == :error
|
84
|
+
io = $stderr
|
85
|
+
prefix = "!!! "
|
86
|
+
else
|
87
|
+
io = $stdout
|
88
|
+
prefix = ">>> "
|
89
|
+
end
|
90
|
+
|
91
|
+
io.puts
|
92
|
+
io.puts "#{prefix}#{msg}"
|
93
|
+
io.puts
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.load_config_file(config_file)
|
97
|
+
begin
|
98
|
+
config_file = File.open(config_file)
|
99
|
+
rescue Errno::ENOENT => e
|
100
|
+
|
101
|
+
print_cli_message "Config file #{config_file} does not exist!", :error
|
102
|
+
print_cli_message "Would you like the default config file copied to #{config_file.inspect}? [y/N]"
|
103
|
+
if gets.strip.downcase == 'y'
|
104
|
+
require 'fileutils'
|
105
|
+
default_config = File.dirname(__FILE__) + '/../../config/config.example.yml'
|
106
|
+
|
107
|
+
if !File.exists?(File.dirname(config_file))
|
108
|
+
print_cli_message "Creating config directory..."
|
109
|
+
FileUtils.mkdir_p(File.dirname(config_file), :verbose => true)
|
110
|
+
end
|
111
|
+
|
112
|
+
print_cli_message "Copying #{default_config.inspect} to #{config_file.inspect}..."
|
113
|
+
FileUtils.cp(default_config, config_file, :verbose => true)
|
114
|
+
print_cli_message "The default config has been copied. You should now edit it and try starting again."
|
115
|
+
exit
|
116
|
+
else
|
117
|
+
print_cli_message "Cannot start RubyCAS-Server without a valid config file.", :error
|
118
|
+
raise e
|
119
|
+
end
|
120
|
+
rescue Errno::EACCES => e
|
121
|
+
print_cli_message "Config file #{config_file.inspect} is not readable (permission denied)!", :error
|
122
|
+
raise e
|
123
|
+
rescue => e
|
124
|
+
print_cli_message "Config file #{config_file.inspect} could not be read!", :error
|
125
|
+
raise e
|
126
|
+
end
|
127
|
+
|
128
|
+
# This is the meat of the options hash functionality
|
129
|
+
config.load_config config_file
|
130
|
+
if File.exists? "config/database.yml"
|
131
|
+
config.load_database_config "config/database.yml"
|
132
|
+
end
|
133
|
+
config.inherit_authenticator_database!
|
134
|
+
|
135
|
+
set :server, config[:server] || 'webrick'
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.reconfigure!(config)
|
139
|
+
config.each do |key, val|
|
140
|
+
self.config[key] = val
|
141
|
+
end
|
142
|
+
init_database!
|
143
|
+
init_logger!
|
144
|
+
init_authenticators!
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.handler_options
|
148
|
+
handler_options = {
|
149
|
+
:Host => bind || config[:bind_address],
|
150
|
+
:Port => config[:port] || 443
|
151
|
+
}
|
152
|
+
|
153
|
+
handler_options.merge(handler_ssl_options).to_hash.symbolize_keys!
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.handler_ssl_options
|
157
|
+
return {} unless config[:ssl_cert]
|
158
|
+
|
159
|
+
cert_path = config[:ssl_cert]
|
160
|
+
key_path = config[:ssl_key] || config[:ssl_cert]
|
161
|
+
|
162
|
+
unless cert_path.nil? && key_path.nil?
|
163
|
+
raise "The ssl_cert and ssl_key options cannot be used with mongrel. You will have to run your " +
|
164
|
+
" server behind a reverse proxy if you want SSL under mongrel." if
|
165
|
+
config[:server] == 'mongrel'
|
166
|
+
|
167
|
+
raise "The specified certificate file #{cert_path.inspect} does not exist or is not readable. " +
|
168
|
+
" Your 'ssl_cert' configuration setting must be a path to a valid " +
|
169
|
+
" ssl certificate." unless
|
170
|
+
File.exists? cert_path
|
171
|
+
|
172
|
+
raise "The specified key file #{key_path.inspect} does not exist or is not readable. " +
|
173
|
+
" Your 'ssl_key' configuration setting must be a path to a valid " +
|
174
|
+
" ssl private key." unless
|
175
|
+
File.exists? key_path
|
176
|
+
|
177
|
+
require 'openssl'
|
178
|
+
require 'webrick/https'
|
179
|
+
|
180
|
+
cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
|
181
|
+
key = OpenSSL::PKey::RSA.new(File.read(key_path))
|
182
|
+
|
183
|
+
{
|
184
|
+
:SSLEnable => true,
|
185
|
+
:SSLVerifyClient => ::OpenSSL::SSL::VERIFY_NONE,
|
186
|
+
:SSLCertificate => cert,
|
187
|
+
:SSLPrivateKey => key
|
188
|
+
}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.init_authenticators!
|
193
|
+
auth = []
|
194
|
+
|
195
|
+
if config[:authenticator].nil?
|
196
|
+
print_cli_message "No authenticators have been configured. Please double-check your config file (#{CONFIG_FILE.inspect}).", :error
|
197
|
+
exit 1
|
198
|
+
end
|
199
|
+
|
200
|
+
begin
|
201
|
+
# attempt to instantiate the authenticator
|
202
|
+
config[:authenticator] = [config[:authenticator]] unless config[:authenticator].instance_of? Array
|
203
|
+
config[:authenticator].each { |authenticator| auth << authenticator[:class].constantize}
|
204
|
+
rescue NameError
|
205
|
+
if config[:authenticator].instance_of? Array
|
206
|
+
config[:authenticator].each do |authenticator|
|
207
|
+
if !authenticator[:source].nil?
|
208
|
+
# config.yml explicitly names source file
|
209
|
+
require authenticator[:source]
|
210
|
+
else
|
211
|
+
# the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
|
212
|
+
auth_rb = authenticator[:class].underscore.gsub('cas_server/', '')
|
213
|
+
require 'casserver/'+auth_rb
|
214
|
+
end
|
215
|
+
auth << authenticator[:class].constantize
|
216
|
+
end
|
217
|
+
else
|
218
|
+
if config[:authenticator][:source]
|
219
|
+
# config.yml explicitly names source file
|
220
|
+
require config[:authenticator][:source]
|
221
|
+
else
|
222
|
+
# the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
|
223
|
+
auth_rb = config[:authenticator][:class].underscore.gsub('cas_server/', '')
|
224
|
+
require 'casserver/'+auth_rb
|
225
|
+
end
|
226
|
+
|
227
|
+
auth << config[:authenticator][:class].constantize
|
228
|
+
config[:authenticator] = [config[:authenticator]]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
auth.zip(config[:authenticator]).each_with_index{ |auth_conf, index|
|
233
|
+
authenticator, conf = auth_conf
|
234
|
+
$LOG.debug "About to setup #{authenticator} with #{conf.inspect}..."
|
235
|
+
authenticator.setup(conf.merge('auth_index' => index)) if authenticator.respond_to?(:setup)
|
236
|
+
$LOG.debug "Done setting up #{authenticator}."
|
237
|
+
}
|
238
|
+
|
239
|
+
set :auth, auth
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.init_logger!
|
243
|
+
if config[:log]
|
244
|
+
if $LOG && config[:log][:file]
|
245
|
+
print_cli_message "Redirecting RubyCAS-Server log to #{config[:log][:file]}"
|
246
|
+
#$LOG.close
|
247
|
+
$LOG = Logger.new(config[:log][:file])
|
248
|
+
end
|
249
|
+
$LOG.level = Logger.const_get(config[:log][:level]) if config[:log][:level]
|
250
|
+
end
|
251
|
+
|
252
|
+
if config[:db_log]
|
253
|
+
if $LOG && config[:db_log][:file]
|
254
|
+
$LOG.debug "Redirecting ActiveRecord log to #{config[:log][:file]}"
|
255
|
+
#$LOG.close
|
256
|
+
ActiveRecord::Base.logger = Logger.new(config[:db_log][:file])
|
257
|
+
end
|
258
|
+
ActiveRecord::Base.logger.level = Logger.const_get(config[:db+log][:level]) if config[:db_log][:level]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def self.init_database!
|
263
|
+
#CASServer::Model::Base.establish_connection(config[:database])
|
264
|
+
$LOG.debug "About to connect to database. Database config - #{config[:database].inspect}"
|
265
|
+
ActiveRecord::Base.establish_connection(config[:database])
|
266
|
+
|
267
|
+
unless config[:disable_auto_migrations]
|
268
|
+
print_cli_message "Running migrations to make sure your database schema is up to date..."
|
269
|
+
prev_db_log = ActiveRecord::Base.logger
|
270
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
271
|
+
ActiveRecord::Migration.verbose = true
|
272
|
+
ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + "#{ENV["APP_ROOT"]}/db/migrate")
|
273
|
+
ActiveRecord::Base.logger = prev_db_log
|
274
|
+
print_cli_message "Your database is now up to date."
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
configure do
|
279
|
+
load_config_file(CONFIG_FILE)
|
280
|
+
init_logger!
|
281
|
+
init_database!
|
282
|
+
init_authenticators!
|
283
|
+
end
|
284
|
+
|
285
|
+
before do
|
286
|
+
GetText.locale = determine_locale(request)
|
287
|
+
content_type :html, 'charset' => 'utf-8'
|
288
|
+
@theme = settings.config[:theme]
|
289
|
+
@organization = settings.config[:organization]
|
290
|
+
@uri_path = settings.config[:uri_path]
|
291
|
+
@infoline = settings.config[:infoline]
|
292
|
+
@custom_views = settings.config[:custom_views]
|
293
|
+
end
|
294
|
+
|
295
|
+
# The #.#.# comments (e.g. "2.1.3") refer to section numbers in the CAS protocol spec
|
296
|
+
# under http://www.ja-sig.org/products/cas/overview/protocol/index.html
|
297
|
+
|
298
|
+
# 2.1 :: Login
|
299
|
+
|
300
|
+
# 2.1.1
|
301
|
+
get "#{uri_path}/login" do
|
302
|
+
CASServer::Utils::log_controller_action(self.class, params)
|
303
|
+
|
304
|
+
# make sure there's no caching
|
305
|
+
headers['Pragma'] = 'no-cache'
|
306
|
+
headers['Cache-Control'] = 'no-store'
|
307
|
+
headers['Expires'] = (Time.now - 1.year).rfc2822
|
308
|
+
|
309
|
+
# optional params
|
310
|
+
@service = clean_service_url(params['service'])
|
311
|
+
@renew = params['renew']
|
312
|
+
@gateway = params['gateway'] == 'true' || params['gateway'] == '1'
|
313
|
+
|
314
|
+
if tgc = request.cookies['tgt']
|
315
|
+
tgt, tgt_error = validate_ticket_granting_ticket(tgc)
|
316
|
+
end
|
317
|
+
|
318
|
+
if tgt and !tgt_error
|
319
|
+
@message = {:type => 'notice',
|
320
|
+
:message => _("You are currently logged in as '%s'. If this is not you, please log in below.") % tgt.username }
|
321
|
+
end
|
322
|
+
|
323
|
+
if params['redirection_loop_intercepted']
|
324
|
+
@message = {:type => 'mistake',
|
325
|
+
:message => _("The client and server are unable to negotiate authentication. Please try logging in again later.")}
|
326
|
+
end
|
327
|
+
|
328
|
+
begin
|
329
|
+
if @service
|
330
|
+
if !@renew && tgt && !tgt_error
|
331
|
+
st = generate_service_ticket(@service, tgt.username, tgt)
|
332
|
+
service_with_ticket = service_uri_with_ticket(@service, st)
|
333
|
+
$LOG.info("User '#{tgt.username}' authenticated based on ticket granting cookie. Redirecting to service '#{@service}'.")
|
334
|
+
redirect service_with_ticket, 303 # response code 303 means "See Other" (see Appendix B in CAS Protocol spec)
|
335
|
+
elsif @gateway
|
336
|
+
$LOG.info("Redirecting unauthenticated gateway request to service '#{@service}'.")
|
337
|
+
redirect @service, 303
|
338
|
+
end
|
339
|
+
elsif @gateway
|
340
|
+
$LOG.error("This is a gateway request but no service parameter was given!")
|
341
|
+
@message = {:type => 'mistake',
|
342
|
+
:message => _("The server cannot fulfill this gateway request because no service parameter was given.")}
|
343
|
+
end
|
344
|
+
rescue URI::InvalidURIError
|
345
|
+
$LOG.error("The service '#{@service}' is not a valid URI!")
|
346
|
+
@message = {:type => 'mistake',
|
347
|
+
:message => _("The target service your browser supplied appears to be invalid. Please contact your system administrator for help.")}
|
348
|
+
end
|
349
|
+
|
350
|
+
lt = generate_login_ticket
|
351
|
+
|
352
|
+
$LOG.debug("Rendering login form with lt: #{lt}, service: #{@service}, renew: #{@renew}, gateway: #{@gateway}")
|
353
|
+
|
354
|
+
@lt = lt.ticket
|
355
|
+
|
356
|
+
#$LOG.debug(env)
|
357
|
+
|
358
|
+
# If the 'onlyLoginForm' parameter is specified, we will only return the
|
359
|
+
# login form part of the page. This is useful for when you want to
|
360
|
+
# embed the login form in some external page (as an IFRAME, or otherwise).
|
361
|
+
# The optional 'submitToURI' parameter can be given to explicitly set the
|
362
|
+
# action for the form, otherwise the server will try to guess this for you.
|
363
|
+
if params.has_key? 'onlyLoginForm'
|
364
|
+
if @env['HTTP_HOST']
|
365
|
+
guessed_login_uri = "http#{@env['HTTPS'] && @env['HTTPS'] == 'on' ? 's' : ''}://#{@env['REQUEST_URI']}#{self / '/login'}"
|
366
|
+
else
|
367
|
+
guessed_login_uri = nil
|
368
|
+
end
|
369
|
+
|
370
|
+
@form_action = params['submitToURI'] || guessed_login_uri
|
371
|
+
|
372
|
+
if @form_action
|
373
|
+
render :login_form
|
374
|
+
else
|
375
|
+
status 500
|
376
|
+
render _("Could not guess the CAS login URI. Please supply a submitToURI parameter with your request.")
|
377
|
+
end
|
378
|
+
else
|
379
|
+
render :erb, :login
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
|
384
|
+
# 2.2
|
385
|
+
post "#{uri_path}/login" do
|
386
|
+
Utils::log_controller_action(self.class, params)
|
387
|
+
|
388
|
+
# 2.2.1 (optional)
|
389
|
+
@service = clean_service_url(params['service'])
|
390
|
+
|
391
|
+
# 2.2.2 (required)
|
392
|
+
@username = params['username']
|
393
|
+
@password = params['password']
|
394
|
+
@lt = params['lt']
|
395
|
+
|
396
|
+
# Remove leading and trailing widespace from username.
|
397
|
+
@username.strip! if @username
|
398
|
+
|
399
|
+
if @username && settings.config[:downcase_username]
|
400
|
+
$LOG.debug("Converting username #{@username.inspect} to lowercase because 'downcase_username' option is enabled.")
|
401
|
+
@username.downcase!
|
402
|
+
end
|
403
|
+
|
404
|
+
if error = validate_login_ticket(@lt)
|
405
|
+
@message = {:type => 'mistake', :message => error}
|
406
|
+
# generate another login ticket to allow for re-submitting the form
|
407
|
+
@lt = generate_login_ticket.ticket
|
408
|
+
status 500
|
409
|
+
render :erb, :login
|
410
|
+
end
|
411
|
+
|
412
|
+
# generate another login ticket to allow for re-submitting the form after a post
|
413
|
+
@lt = generate_login_ticket.ticket
|
414
|
+
|
415
|
+
$LOG.debug("Logging in with username: #{@username}, lt: #{@lt}, service: #{@service}, auth: #{settings.auth.inspect}")
|
416
|
+
|
417
|
+
credentials_are_valid = false
|
418
|
+
extra_attributes = {}
|
419
|
+
successful_authenticator = nil
|
420
|
+
begin
|
421
|
+
auth_index = 0
|
422
|
+
settings.auth.each do |auth_class|
|
423
|
+
auth = auth_class.new
|
424
|
+
|
425
|
+
auth_config = settings.config[:authenticator][auth_index]
|
426
|
+
# pass the authenticator index to the configuration hash in case the authenticator needs to know
|
427
|
+
# it splace in the authenticator queue
|
428
|
+
auth.configure(auth_config.merge('auth_index' => auth_index))
|
429
|
+
|
430
|
+
credentials_are_valid = auth.validate(
|
431
|
+
:username => @username,
|
432
|
+
:password => @password,
|
433
|
+
:service => @service,
|
434
|
+
:request => @env
|
435
|
+
)
|
436
|
+
if credentials_are_valid
|
437
|
+
extra_attributes.merge!(auth.extra_attributes) unless auth.extra_attributes.blank?
|
438
|
+
successful_authenticator = auth
|
439
|
+
break
|
440
|
+
end
|
441
|
+
|
442
|
+
auth_index += 1
|
443
|
+
end
|
444
|
+
|
445
|
+
if credentials_are_valid
|
446
|
+
$LOG.info("Credentials for username '#{@username}' successfully validated using #{successful_authenticator.class.name}.")
|
447
|
+
$LOG.debug("Authenticator provided additional user attributes: #{extra_attributes.inspect}") unless extra_attributes.blank?
|
448
|
+
|
449
|
+
# 3.6 (ticket-granting cookie)
|
450
|
+
tgt = generate_ticket_granting_ticket(@username, extra_attributes)
|
451
|
+
response.set_cookie('tgt', tgt.to_s)
|
452
|
+
|
453
|
+
$LOG.debug("Ticket granting cookie '#{request.cookies['tgt'].inspect}' granted to #{@username.inspect}")
|
454
|
+
|
455
|
+
if @service.blank?
|
456
|
+
$LOG.info("Successfully authenticated user '#{@username}' at '#{tgt.client_hostname}'. No service param was given, so we will not redirect.")
|
457
|
+
@message = {:type => 'confirmation', :message => _("You have successfully logged in.")}
|
458
|
+
else
|
459
|
+
@st = generate_service_ticket(@service, @username, tgt)
|
460
|
+
|
461
|
+
begin
|
462
|
+
service_with_ticket = service_uri_with_ticket(@service, @st)
|
463
|
+
|
464
|
+
$LOG.info("Redirecting authenticated user '#{@username}' at '#{@st.client_hostname}' to service '#{@service}'")
|
465
|
+
redirect service_with_ticket, 303 # response code 303 means "See Other" (see Appendix B in CAS Protocol spec)
|
466
|
+
rescue URI::InvalidURIError
|
467
|
+
$LOG.error("The service '#{@service}' is not a valid URI!")
|
468
|
+
@message = {
|
469
|
+
:type => 'mistake',
|
470
|
+
:message => _("The target service your browser supplied appears to be invalid. Please contact your system administrator for help.")
|
471
|
+
}
|
472
|
+
end
|
473
|
+
end
|
474
|
+
else
|
475
|
+
$LOG.warn("Invalid credentials given for user '#{@username}'")
|
476
|
+
@message = {:type => 'mistake', :message => _("Incorrect username or password.")}
|
477
|
+
status 401
|
478
|
+
end
|
479
|
+
rescue CASServer::AuthenticatorError => e
|
480
|
+
$LOG.error(e)
|
481
|
+
# generate another login ticket to allow for re-submitting the form
|
482
|
+
@lt = generate_login_ticket.ticket
|
483
|
+
@message = {:type => 'mistake', :message => _(e.to_s)}
|
484
|
+
status 401
|
485
|
+
end
|
486
|
+
|
487
|
+
render :erb, :login
|
488
|
+
end
|
489
|
+
|
490
|
+
get /^#{uri_path}\/?$/ do
|
491
|
+
redirect "#{config['uri_path']}/login", 303
|
492
|
+
end
|
493
|
+
|
494
|
+
|
495
|
+
# 2.3
|
496
|
+
|
497
|
+
# 2.3.1
|
498
|
+
get "#{uri_path}/logout" do
|
499
|
+
CASServer::Utils::log_controller_action(self.class, params)
|
500
|
+
|
501
|
+
# The behaviour here is somewhat non-standard. Rather than showing just a blank
|
502
|
+
# "logout" page, we take the user back to the login page with a "you have been logged out"
|
503
|
+
# message, allowing for an opportunity to immediately log back in. This makes it
|
504
|
+
# easier for the user to log out and log in as someone else.
|
505
|
+
@service = clean_service_url(params['service'] || params['destination'])
|
506
|
+
@continue_url = params['url']
|
507
|
+
|
508
|
+
@gateway = params['gateway'] == 'true' || params['gateway'] == '1'
|
509
|
+
|
510
|
+
tgt = CASServer::Model::TicketGrantingTicket.find_by_ticket(request.cookies['tgt'])
|
511
|
+
|
512
|
+
response.delete_cookie 'tgt'
|
513
|
+
|
514
|
+
if tgt
|
515
|
+
CASServer::Model::TicketGrantingTicket.transaction do
|
516
|
+
$LOG.debug("Deleting Service/Proxy Tickets for '#{tgt}' for user '#{tgt.username}'")
|
517
|
+
tgt.granted_service_tickets.each do |st|
|
518
|
+
send_logout_notification_for_service_ticket(st) if config[:enable_single_sign_out]
|
519
|
+
# TODO: Maybe we should do some special handling if send_logout_notification_for_service_ticket fails?
|
520
|
+
# (the above method returns false if the POST results in a non-200 HTTP response).
|
521
|
+
$LOG.debug "Deleting #{st.class.name.demodulize} #{st.ticket.inspect} for service #{st.service}."
|
522
|
+
st.destroy
|
523
|
+
end
|
524
|
+
|
525
|
+
pgts = CASServer::Model::ProxyGrantingTicket.find(:all,
|
526
|
+
:conditions => [CASServer::Model::Base.connection.quote_table_name(CASServer::Model::ServiceTicket.table_name)+".username = ?", tgt.username],
|
527
|
+
:include => :service_ticket)
|
528
|
+
pgts.each do |pgt|
|
529
|
+
$LOG.debug("Deleting Proxy-Granting Ticket '#{pgt}' for user '#{pgt.service_ticket.username}'")
|
530
|
+
pgt.destroy
|
531
|
+
end
|
532
|
+
|
533
|
+
$LOG.debug("Deleting #{tgt.class.name.demodulize} '#{tgt}' for user '#{tgt.username}'")
|
534
|
+
tgt.destroy
|
535
|
+
end
|
536
|
+
|
537
|
+
$LOG.info("User '#{tgt.username}' logged out.")
|
538
|
+
else
|
539
|
+
$LOG.warn("User tried to log out without a valid ticket-granting ticket.")
|
540
|
+
end
|
541
|
+
|
542
|
+
@message = {:type => 'confirmation', :message => _("You have successfully logged out.")}
|
543
|
+
|
544
|
+
@message[:message] +=_(" Please click on the following link to continue:") if @continue_url
|
545
|
+
|
546
|
+
@lt = generate_login_ticket
|
547
|
+
|
548
|
+
if @gateway && @service
|
549
|
+
redirect @service, 303
|
550
|
+
elsif @continue_url
|
551
|
+
render :erb, :logout
|
552
|
+
else
|
553
|
+
render :erb, :login
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
|
558
|
+
# 2.4
|
559
|
+
|
560
|
+
# 2.4.1
|
561
|
+
get "#{uri_path}/validate" do
|
562
|
+
CASServer::Utils::log_controller_action(self.class, params)
|
563
|
+
|
564
|
+
# required
|
565
|
+
@service = clean_service_url(params['service'])
|
566
|
+
@ticket = params['ticket']
|
567
|
+
# optional
|
568
|
+
@renew = params['renew']
|
569
|
+
|
570
|
+
st, @error = validate_service_ticket(@service, @ticket)
|
571
|
+
@success = st && !@error
|
572
|
+
|
573
|
+
@username = st.username if @success
|
574
|
+
|
575
|
+
status response_status_from_error(@error) if @error
|
576
|
+
|
577
|
+
render :erb, :validate, :layout => false
|
578
|
+
end
|
579
|
+
|
580
|
+
|
581
|
+
# 2.5
|
582
|
+
|
583
|
+
# 2.5.1
|
584
|
+
get "#{uri_path}/serviceValidate" do
|
585
|
+
CASServer::Utils::log_controller_action(self.class, params)
|
586
|
+
|
587
|
+
# required
|
588
|
+
@service = clean_service_url(params['service'])
|
589
|
+
@ticket = params['ticket']
|
590
|
+
# optional
|
591
|
+
@renew = params['renew']
|
592
|
+
|
593
|
+
st, @error = validate_service_ticket(@service, @ticket)
|
594
|
+
@success = st && !@error
|
595
|
+
|
596
|
+
if @success
|
597
|
+
@username = st.username
|
598
|
+
if @pgt_url
|
599
|
+
pgt = generate_proxy_granting_ticket(@pgt_url, st)
|
600
|
+
@pgtiou = pgt.iou if pgt
|
601
|
+
end
|
602
|
+
@extra_attributes = st.granted_by_tgt.extra_attributes || {}
|
603
|
+
end
|
604
|
+
|
605
|
+
status response_status_from_error(@error) if @error
|
606
|
+
|
607
|
+
render :builder, :proxy_validate
|
608
|
+
end
|
609
|
+
|
610
|
+
|
611
|
+
# 2.6
|
612
|
+
|
613
|
+
# 2.6.1
|
614
|
+
get "#{uri_path}/proxyValidate" do
|
615
|
+
CASServer::Utils::log_controller_action(self.class, params)
|
616
|
+
|
617
|
+
# required
|
618
|
+
@service = clean_service_url(params['service'])
|
619
|
+
@ticket = params['ticket']
|
620
|
+
# optional
|
621
|
+
@pgt_url = params['pgtUrl']
|
622
|
+
@renew = params['renew']
|
623
|
+
|
624
|
+
@proxies = []
|
625
|
+
|
626
|
+
t, @error = validate_proxy_ticket(@service, @ticket)
|
627
|
+
@success = t && !@error
|
628
|
+
|
629
|
+
@extra_attributes = {}
|
630
|
+
if @success
|
631
|
+
@username = t.username
|
632
|
+
|
633
|
+
if t.kind_of? CASServer::Model::ProxyTicket
|
634
|
+
@proxies << t.granted_by_pgt.service_ticket.service
|
635
|
+
end
|
636
|
+
|
637
|
+
if @pgt_url
|
638
|
+
pgt = generate_proxy_granting_ticket(@pgt_url, t)
|
639
|
+
@pgtiou = pgt.iou if pgt
|
640
|
+
end
|
641
|
+
|
642
|
+
@extra_attributes = t.granted_by_tgt.extra_attributes || {}
|
643
|
+
end
|
644
|
+
|
645
|
+
status response_status_from_error(@error) if @error
|
646
|
+
|
647
|
+
render :builder, :proxy_validate
|
648
|
+
end
|
649
|
+
|
650
|
+
|
651
|
+
# 2.7
|
652
|
+
get "#{uri_path}/proxy" do
|
653
|
+
CASServer::Utils::log_controller_action(self.class, params)
|
654
|
+
|
655
|
+
# required
|
656
|
+
@ticket = params['pgt']
|
657
|
+
@target_service = params['targetService']
|
658
|
+
|
659
|
+
pgt, @error = validate_proxy_granting_ticket(@ticket)
|
660
|
+
@success = pgt && !@error
|
661
|
+
|
662
|
+
if @success
|
663
|
+
@pt = generate_proxy_ticket(@target_service, pgt)
|
664
|
+
end
|
665
|
+
|
666
|
+
status response_status_from_error(@error) if @error
|
667
|
+
|
668
|
+
render :builder, :proxy
|
669
|
+
end
|
670
|
+
|
671
|
+
|
672
|
+
|
673
|
+
# Helpers
|
674
|
+
|
675
|
+
def response_status_from_error(error)
|
676
|
+
case error.code.to_s
|
677
|
+
when /^INVALID_/, 'BAD_PGT'
|
678
|
+
422
|
679
|
+
when 'INTERNAL_ERROR'
|
680
|
+
500
|
681
|
+
else
|
682
|
+
500
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
def serialize_extra_attribute(builder, key, value)
|
687
|
+
if value.kind_of?(String)
|
688
|
+
builder.tag! key, value
|
689
|
+
elsif value.kind_of?(Numeric)
|
690
|
+
builder.tag! key, value.to_s
|
691
|
+
else
|
692
|
+
builder.tag! key do
|
693
|
+
builder.cdata! value.to_yaml
|
694
|
+
end
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
def compile_template(engine, data, options, views)
|
699
|
+
super engine, data, options, @custom_views || views
|
700
|
+
rescue Errno::ENOENT
|
701
|
+
raise unless @custom_views
|
702
|
+
super engine, data, options, views
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|