relevance-rubycas-server 0.6.99

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.
Files changed (58) hide show
  1. data/.loadpath +5 -0
  2. data/.project +17 -0
  3. data/CHANGELOG.txt +1 -0
  4. data/History.txt +223 -0
  5. data/LICENSE.txt +504 -0
  6. data/Manifest.txt +61 -0
  7. data/README.txt +25 -0
  8. data/Rakefile +60 -0
  9. data/bin/rubycas-server +26 -0
  10. data/bin/rubycas-server-ctl +22 -0
  11. data/config.example.yml +363 -0
  12. data/custom_views.example.rb +11 -0
  13. data/lib/casserver.rb +110 -0
  14. data/lib/casserver/authenticators/active_directory_ldap.rb +11 -0
  15. data/lib/casserver/authenticators/base.rb +47 -0
  16. data/lib/casserver/authenticators/ldap.rb +108 -0
  17. data/lib/casserver/authenticators/ntlm.rb +88 -0
  18. data/lib/casserver/authenticators/sql.rb +102 -0
  19. data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
  20. data/lib/casserver/authenticators/test.rb +15 -0
  21. data/lib/casserver/cas.rb +307 -0
  22. data/lib/casserver/conf.rb +112 -0
  23. data/lib/casserver/controllers.rb +436 -0
  24. data/lib/casserver/environment.rb +23 -0
  25. data/lib/casserver/models.rb +218 -0
  26. data/lib/casserver/postambles.rb +174 -0
  27. data/lib/casserver/utils.rb +30 -0
  28. data/lib/casserver/version.rb +9 -0
  29. data/lib/casserver/views.rb +235 -0
  30. data/lib/rubycas-server.rb +1 -0
  31. data/lib/rubycas-server/version.rb +1 -0
  32. data/lib/themes/cas.css +121 -0
  33. data/lib/themes/notice.png +0 -0
  34. data/lib/themes/ok.png +0 -0
  35. data/lib/themes/simple/bg.png +0 -0
  36. data/lib/themes/simple/login_box_bg.png +0 -0
  37. data/lib/themes/simple/logo.png +0 -0
  38. data/lib/themes/simple/theme.css +28 -0
  39. data/lib/themes/urbacon/bg.png +0 -0
  40. data/lib/themes/urbacon/login_box_bg.png +0 -0
  41. data/lib/themes/urbacon/logo.png +0 -0
  42. data/lib/themes/urbacon/theme.css +33 -0
  43. data/lib/themes/warning.png +0 -0
  44. data/misc/basic_cas_single_signon_mechanism_diagram.png +0 -0
  45. data/misc/basic_cas_single_signon_mechanism_diagram.svg +652 -0
  46. data/resources/init.d.sh +58 -0
  47. data/setup.rb +1585 -0
  48. data/test/test_cas.rb +33 -0
  49. data/test/test_casserver.rb +125 -0
  50. data/vendor/isaac_0.9.1/LICENSE +26 -0
  51. data/vendor/isaac_0.9.1/README +78 -0
  52. data/vendor/isaac_0.9.1/TODO +3 -0
  53. data/vendor/isaac_0.9.1/VERSIONS +3 -0
  54. data/vendor/isaac_0.9.1/crypt/ISAAC.rb +171 -0
  55. data/vendor/isaac_0.9.1/isaac.gemspec +39 -0
  56. data/vendor/isaac_0.9.1/setup.rb +596 -0
  57. data/vendor/isaac_0.9.1/test/TC_ISAAC.rb +76 -0
  58. metadata +158 -0
@@ -0,0 +1,75 @@
1
+ require 'casserver/authenticators/base'
2
+
3
+ require 'digest/sha1'
4
+ require 'digest/sha2'
5
+
6
+ $: << File.dirname(File.expand_path(__FILE__)) + "/../../../vendor/isaac_0.9.1"
7
+ require 'crypt/ISAAC'
8
+
9
+ begin
10
+ require 'active_record'
11
+ rescue LoadError
12
+ require 'rubygems'
13
+ require 'active_record'
14
+ end
15
+
16
+ # This is a more secure version of the SQL authenticator. Passwords are encrypted
17
+ # rather than being stored in plain text.
18
+ #
19
+ # Based on code contributed by Ben Mabey.
20
+ #
21
+ # Using this authenticator requires some configuration on the client side. Please see
22
+ # http://code.google.com/p/rubycas-server/wiki/UsingTheSQLEncryptedAuthenticator
23
+ class CASServer::Authenticators::SQLEncrypted < CASServer::Authenticators::Base
24
+
25
+ def validate(credentials)
26
+ read_standard_credentials(credentials)
27
+
28
+ raise CASServer::AuthenticatorError, "Cannot validate credentials because the authenticator hasn't yet been configured" unless @options
29
+ raise CASServer::AuthenticatorError, "Invalid authenticator configuration!" unless @options[:database]
30
+
31
+ CASUser.establish_connection @options[:database]
32
+ CASUser.set_table_name @options[:user_table] || "users"
33
+
34
+ username_column = @options[:username_column] || "username"
35
+
36
+ results = CASUser.find(:all, :conditions => ["#{username_column} = ?", @username])
37
+
38
+ if results.size > 0
39
+ $LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
40
+ user = results.first
41
+ return user.encrypted_password == user.encrypt(@password)
42
+ else
43
+ return false
44
+ end
45
+ end
46
+
47
+ # Include this module into your application's user model.
48
+ #
49
+ # Your model must have an 'encrypted_password' column where the password will be stored,
50
+ # and an 'encryption_salt' column that will be populated with a random string before
51
+ # the user record is first created.
52
+ module EncryptedPassword
53
+ def self.included(mod)
54
+ raise "#{self} should be inclued in an ActiveRecord class!" unless mod.respond_to?(:before_save)
55
+ mod.before_save :generate_encryption_salt
56
+ end
57
+
58
+ def encrypt(str)
59
+ Digest::SHA256.hexdigest("#{encryption_salt}::#{str}")
60
+ end
61
+
62
+ def password=(password)
63
+ self[:encrypted_password] = encrypt(password)
64
+ end
65
+
66
+ def generate_encryption_salt
67
+ self.encryption_salt = Digest::SHA1.hexdigest(Crypt::ISAAC.new.rand(2**31).to_s) unless
68
+ encryption_salt
69
+ end
70
+ end
71
+
72
+ class CASUser < ActiveRecord::Base
73
+ include EncryptedPassword
74
+ end
75
+ end
@@ -0,0 +1,15 @@
1
+ require 'casserver/authenticators/base'
2
+
3
+ # Dummy authenticator used for testing.
4
+ # Accepts "testuser" for username and "testpassword" for password; otherwise authentication fails.
5
+ # Raises an AuthenticationError when username is "do_error" (this is useful to test the Exception
6
+ # handling functionality).
7
+ class CASServer::Authenticators::Test < CASServer::Authenticators::Base
8
+ def validate(credentials)
9
+ read_standard_credentials(credentials)
10
+
11
+ raise CASServer::AuthenticatorError, "Username is 'do_error'!" if @username == 'do_error'
12
+
13
+ return @username == "testuser" && @password == "testpassword"
14
+ end
15
+ end
@@ -0,0 +1,307 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+
4
+ # Encapsulates CAS functionality. This module is meant to be included in
5
+ # the CASServer::Controllers module.
6
+ module CASServer::CAS
7
+
8
+ include CASServer::Models
9
+
10
+ def generate_login_ticket
11
+ # 3.5 (login ticket)
12
+ lt = LoginTicket.new
13
+ lt.ticket = "LT-" + CASServer::Utils.random_string
14
+ lt.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
15
+ lt.save!
16
+ $LOG.debug("Generated login ticket '#{lt.ticket}' for client" +
17
+ " at '#{lt.client_hostname}'")
18
+ lt
19
+ end
20
+
21
+ # Creates a TicketGrantingTicket for the given username. This is done when the user logs in
22
+ # for the first time to establish their SSO session (after their credentials have been validated).
23
+ #
24
+ # The optional 'extra_attributes' parameter takes a hash of additional attributes
25
+ # that will be sent along with the username in the CAS response to subsequent
26
+ # validation requests from clients.
27
+ def generate_ticket_granting_ticket(username, extra_attributes = {})
28
+ # 3.6 (ticket granting cookie/ticket)
29
+ tgt = TicketGrantingTicket.new
30
+ tgt.ticket = "TGC-" + CASServer::Utils.random_string
31
+ tgt.username = username
32
+ tgt.extra_attributes = extra_attributes
33
+ tgt.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
34
+ tgt.save!
35
+ $LOG.debug("Generated ticket granting ticket '#{tgt.ticket}' for user" +
36
+ " '#{tgt.username}' at '#{tgt.client_hostname}'" +
37
+ (extra_attributes.blank? ? "" : " with extra attributes #{extra_attributes.inspect}"))
38
+ tgt
39
+ end
40
+
41
+ def generate_service_ticket(service, username, tgt)
42
+ # 3.1 (service ticket)
43
+ st = ServiceTicket.new
44
+ st.ticket = "ST-" + CASServer::Utils.random_string
45
+ st.service = service
46
+ st.username = username
47
+ st.ticket_granting_ticket = tgt
48
+ st.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
49
+ st.save!
50
+ $LOG.debug("Generated service ticket '#{st.ticket}' for service '#{st.service}'" +
51
+ " for user '#{st.username}' at '#{st.client_hostname}'")
52
+ st
53
+ end
54
+
55
+ def generate_proxy_ticket(target_service, pgt)
56
+ # 3.2 (proxy ticket)
57
+ pt = ProxyTicket.new
58
+ pt.ticket = "PT-" + CASServer::Utils.random_string
59
+ pt.service = target_service
60
+ pt.username = pgt.service_ticket.username
61
+ pt.proxy_granting_ticket_id = pgt.id
62
+ pt.ticket_granting_ticket = pgt.service_ticket.ticket_granting_ticket
63
+ pt.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
64
+ pt.save!
65
+ $LOG.debug("Generated proxy ticket '#{pt.ticket}' for target service '#{pt.service}'" +
66
+ " for user '#{pt.username}' at '#{pt.client_hostname}' using proxy-granting" +
67
+ " ticket '#{pgt.ticket}'")
68
+ pt
69
+ end
70
+
71
+ def generate_proxy_granting_ticket(pgt_url, st)
72
+ uri = URI.parse(pgt_url)
73
+ https = Net::HTTP.new(uri.host,uri.port)
74
+ https.use_ssl = true
75
+
76
+ # Here's what's going on here:
77
+ #
78
+ # 1. We generate a ProxyGrantingTicket (but don't store it in the database just yet)
79
+ # 2. Deposit the PGT and it's associated IOU at the proxy callback URL.
80
+ # 3. If the proxy callback URL responds with HTTP code 200, store the PGT and return it;
81
+ # otherwise don't save it and return nothing.
82
+ #
83
+ https.start do |conn|
84
+ path = uri.path.empty? ? '/' : uri.path
85
+
86
+ pgt = ProxyGrantingTicket.new
87
+ pgt.ticket = "PGT-" + CASServer::Utils.random_string(60)
88
+ pgt.iou = "PGTIOU-" + CASServer::Utils.random_string(57)
89
+ pgt.service_ticket_id = st.id
90
+ pgt.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
91
+
92
+ # FIXME: The CAS protocol spec says to use 'pgt' as the parameter, but in practice
93
+ # the JA-SIG and Yale server implementations use pgtId. We'll go with the
94
+ # in-practice standard.
95
+ path += (uri.query.nil? || uri.query.empty? ? '?' : '&') + "pgtId=#{pgt.ticket}&pgtIou=#{pgt.iou}"
96
+
97
+ response = conn.request_get(path)
98
+ # TODO: follow redirects... 2.5.4 says that redirects MAY be followed
99
+
100
+ if response.code.to_i == 200
101
+ # 3.4 (proxy-granting ticket IOU)
102
+ pgt.save!
103
+ $LOG.debug "PGT generated for pgt_url '#{pgt_url}': #{pgt.inspect}"
104
+ pgt
105
+ else
106
+ $LOG.warn "PGT callback server responded with a bad result code '#{response.code}'. PGT will not be stored."
107
+ end
108
+ end
109
+ end
110
+
111
+ def validate_login_ticket(ticket)
112
+ $LOG.debug("Validating login ticket '#{ticket}'")
113
+
114
+ success = false
115
+ if ticket.nil?
116
+ error = "Your login request did not include a login ticket. There may be a problem with the authentication system."
117
+ $LOG.warn("Missing login ticket.")
118
+ elsif lt = LoginTicket.find_by_ticket(ticket)
119
+ if lt.consumed?
120
+ error = "The login ticket you provided has already been used up. Please try logging in again."
121
+ $LOG.warn("Login ticket '#{ticket}' previously used up")
122
+ elsif Time.now - lt.created_on < CASServer::Conf.login_ticket_expiry
123
+ $LOG.info("Login ticket '#{ticket}' successfully validated")
124
+ else
125
+ error = "Your login ticket has expired. Please try logging in again."
126
+ $LOG.warn("Expired login ticket '#{ticket}'")
127
+ end
128
+ else
129
+ error = "The login ticket you provided is invalid. Please try logging in again."
130
+ $LOG.warn("Invalid login ticket '#{ticket}'")
131
+ end
132
+
133
+ lt.consume! if lt
134
+
135
+ error
136
+ end
137
+
138
+ def validate_ticket_granting_ticket(ticket)
139
+ $LOG.debug("Validating ticket granting ticket '#{ticket}'")
140
+
141
+ if ticket.nil?
142
+ error = "No ticket granting ticket given."
143
+ $LOG.debug(error)
144
+ elsif tgt = TicketGrantingTicket.find_by_ticket(ticket)
145
+ if CASServer::Conf.expire_sessions && Time.now - tgt.created_on > CASServer::Conf.ticket_granting_ticket_expiry
146
+ error = "Your session has expired. Please log in again."
147
+ $LOG.info("Ticket granting ticket '#{ticket}' for user '#{tgt.username}' expired.")
148
+ else
149
+ $LOG.info("Ticket granting ticket '#{ticket}' for user '#{tgt.username}' successfully validated.")
150
+ end
151
+ else
152
+ error = "Invalid ticket granting ticket '#{ticket}' (no matching ticket found in the database)."
153
+ $LOG.warn(error)
154
+ end
155
+
156
+ [tgt, error]
157
+ end
158
+
159
+ def validate_service_ticket(service, ticket, allow_proxy_tickets = false)
160
+ $LOG.debug("Validating service/proxy ticket '#{ticket}' for service '#{service}'")
161
+
162
+ if service.nil? or ticket.nil?
163
+ error = Error.new("INVALID_REQUEST", "Ticket or service parameter was missing in the request.")
164
+ $LOG.warn("#{error.code} - #{error.message}")
165
+ elsif st = ServiceTicket.find_by_ticket(ticket)
166
+ if st.consumed?
167
+ error = Error.new("INVALID_TICKET", "Ticket '#{ticket}' has already been used up.")
168
+ $LOG.warn("#{error.code} - #{error.message}")
169
+ elsif st.kind_of?(CASServer::Models::ProxyTicket) && !allow_proxy_tickets
170
+ error = Error.new("INVALID_TICKET", "Ticket '#{ticket}' is a proxy ticket, but only service tickets are allowed here.")
171
+ $LOG.warn("#{error.code} - #{error.message}")
172
+ elsif Time.now - st.created_on > CASServer::Conf.service_ticket_expiry
173
+ error = Error.new("INVALID_TICKET", "Ticket '#{ticket}' has expired.")
174
+ $LOG.warn("Ticket '#{ticket}' has expired.")
175
+ elsif !st.matches_service? service
176
+ error = Error.new("INVALID_SERVICE", "The ticket '#{ticket}' belonging to user '#{st.username}' is valid,"+
177
+ " but the requested service '#{service}' does not match the service '#{st.service}' associated with this ticket.")
178
+ $LOG.warn("#{error.code} - #{error.message}")
179
+ else
180
+ $LOG.info("Ticket '#{ticket}' for service '#{service}' for user '#{st.username}' successfully validated.")
181
+ end
182
+ else
183
+ error = Error.new("INVALID_TICKET", "Ticket '#{ticket}' not recognized.")
184
+ $LOG.warn("#{error.code} - #{error.message}")
185
+ end
186
+
187
+ if st
188
+ st.consume!
189
+ end
190
+
191
+
192
+ [st, error]
193
+ end
194
+
195
+ def validate_proxy_ticket(service, ticket)
196
+ pt, error = validate_service_ticket(service, ticket, true)
197
+
198
+ if pt.kind_of?(CASServer::Models::ProxyTicket) && !error
199
+ if not pt.proxy_granting_ticket
200
+ error = Error.new("INTERNAL_ERROR", "Proxy ticket '#{pt}' belonging to user '#{pt.username}' is not associated with a proxy granting ticket.")
201
+ elsif not pt.proxy_granting_ticket.service_ticket
202
+ error = Error.new("INTERNAL_ERROR", "Proxy granting ticket '#{pt.proxy_granting_ticket}'"+
203
+ " (associated with proxy ticket '#{pt}' and belonging to user '#{pt.username}' is not associated with a service ticket.")
204
+ end
205
+ end
206
+
207
+ [pt, error]
208
+ end
209
+
210
+ def validate_proxy_granting_ticket(ticket)
211
+ if ticket.nil?
212
+ error = Error.new("INVALID_REQUEST", "pgt parameter was missing in the request.")
213
+ $LOG.warn("#{error.code} - #{error.message}")
214
+ elsif pgt = ProxyGrantingTicket.find_by_ticket(ticket)
215
+ if pgt.service_ticket
216
+ $LOG.info("Proxy granting ticket '#{ticket}' belonging to user '#{pgt.service_ticket.username}' successfully validated.")
217
+ else
218
+ error = Error.new("INTERNAL_ERROR", "Proxy granting ticket '#{ticket}' is not associated with a service ticket.")
219
+ $LOG.error("#{error.code} - #{error.message}")
220
+ end
221
+ else
222
+ error = Error.new("BAD_PGT", "Invalid proxy granting ticket '#{ticket}' (no matching ticket found in the database).")
223
+ $LOG.warn("#{error.code} - #{error.message}")
224
+ end
225
+
226
+ [pgt, error]
227
+ end
228
+
229
+ # Takes an existing ServiceTicket object (presumably pulled from the database)
230
+ # and sends a POST with logout information to the service that the ticket
231
+ # was generated for.
232
+ #
233
+ # This makes possible the "single sign-out" functionality added in CAS 3.1.
234
+ # See http://www.ja-sig.org/wiki/display/CASUM/Single+Sign+Out
235
+ def send_logout_notification_for_service_ticket(st)
236
+ uri = URI.parse(st.service)
237
+ http = Net::HTTP.new(uri.host,uri.port)
238
+ #http.use_ssl = true if uri.scheme = 'https'
239
+
240
+ http.start do |conn|
241
+ path = uri.path || '/'
242
+
243
+ time = Time.now
244
+ rand = CASServer::Utils.random_string
245
+
246
+ data = %{<samlp:LogoutRequest ID="#{rand}" Version="2.0" IssueInstant="#{time.rfc2822}">
247
+ <saml:NameID></saml:NameID>
248
+ <samlp:SessionIndex>#{st.ticket}</samlp:SessionIndex>
249
+ </samlp:LogoutRequest>}
250
+
251
+ response = conn.request_post(path, data)
252
+
253
+ if response.code.to_i == 200
254
+ $LOG.info "Logout notification successfully posted to #{st.service.inspect}."
255
+ return true
256
+ else
257
+ $LOG.error "Service #{st.service.inspect} responed to logout notification with code '#{response.code}'."
258
+ return false
259
+ end
260
+ end
261
+ end
262
+
263
+ def service_uri_with_ticket(service, st)
264
+ raise ArgumentError, "Second argument must be a ServiceTicket!" unless st.kind_of? CASServer::Models::ServiceTicket
265
+
266
+ # This will choke with a URI::InvalidURIError if service URI is not properly URI-escaped...
267
+ # This exception is handled further upstream (i.e. in the controller).
268
+ service_uri = URI.parse(service)
269
+
270
+ if service.include? "?"
271
+ if service_uri.query.empty?
272
+ query_separator = ""
273
+ else
274
+ query_separator = "&"
275
+ end
276
+ else
277
+ query_separator = "?"
278
+ end
279
+
280
+ service_with_ticket = service + query_separator + "ticket=" + st.ticket
281
+ service_with_ticket
282
+ end
283
+
284
+ # Strips CAS-related parameters from a service URL and normalizes it,
285
+ # removing trailing / and ?.
286
+ #
287
+ # For example, "http://google.com?ticket=12345" will be returned as
288
+ # "http://google.com". Also, "http://google.com/" would be returned as
289
+ # "http://google.com".
290
+ #
291
+ # Note that only the first occurance of each CAS-related parameter is
292
+ # removed, so that "http://google.com?ticket=12345&ticket=abcd" would be
293
+ # returned as "http://google.com?ticket=abcd".
294
+ def clean_service_url(dirty_service)
295
+ return dirty_service if dirty_service.blank?
296
+ clean_service = dirty_service.dup
297
+ ['service', 'ticket', 'gateway', 'renew'].each do |p|
298
+ clean_service.sub!(Regexp.new("#{p}=[^&]*"), '')
299
+ end
300
+
301
+ clean_service.gsub!(/[\/\?]$/, '')
302
+
303
+ return clean_service
304
+ end
305
+ module_function :clean_service_url
306
+
307
+ end
@@ -0,0 +1,112 @@
1
+ # load configuration
2
+
3
+ begin
4
+ if $CONFIG_FILE
5
+ conf_file = $CONFIG_FILE
6
+ else
7
+ conf_file = etc_conf = "/etc/rubycas-server/config.yml"
8
+ unless File.exists? conf_file
9
+ # can use local config.yml file in case we're running non-gem installation
10
+ conf_file = File.dirname(File.expand_path(__FILE__))+"/../../config.yml"
11
+ end
12
+ end
13
+
14
+ unless File.exists? conf_file
15
+ require 'fileutils'
16
+
17
+ example_conf_file = File.expand_path(File.dirname(File.expand_path(__FILE__))+"/../../config.example.yml")
18
+ puts "\nCAS SERVER NOT YET CONFIGURED!!!\n"
19
+ puts "\nAttempting to copy sample configuration from '#{example_conf_file}' to '#{etc_conf}'...\n"
20
+
21
+ begin
22
+ FileUtils.mkdir("/etc/rubycas-server") unless File.exists? "/etc/rubycas-server"
23
+ FileUtils.cp(example_conf_file, etc_conf)
24
+ rescue Errno::EACCES
25
+ puts "\nIt appears that you do not have permissions to create the '#{etc_conf}' file. Try running this command using sudo (as root).\n"
26
+ exit 2
27
+ rescue
28
+ puts "\nFor some reason the '#{etc_conf}' file could not be created. You'll have to copy the file manually." +
29
+ " Use '#{example_conf_file}' as a template.\n"
30
+ exit 2
31
+ end
32
+
33
+ puts "\nA sample configuration has been created for you in '#{etc_conf}'. Please edit this file to" +
34
+ " suit your needs and then run rubycas-server again.\n"
35
+ exit 1
36
+ end
37
+
38
+ loaded_conf = HashWithIndifferentAccess.new(YAML.load_file(conf_file))
39
+
40
+ if $CONF
41
+ $CONF = loaded_conf.merge $CONF
42
+ else
43
+ $CONF = loaded_conf
44
+ end
45
+
46
+ if $CONF[:authenticator].instance_of? Array
47
+ $CONF[:authenticator].each_index { |auth_index| $CONF[:authenticator][auth_index] = HashWithIndifferentAccess.new($CONF[:authenticator][auth_index])}
48
+ end
49
+
50
+ $AUTH = []
51
+ begin
52
+ # attempt to instantiate the authenticator
53
+ if $CONF[:authenticator].instance_of? Array
54
+ $CONF[:authenticator].each { |authenticator| $AUTH << authenticator[:class].constantize.new}
55
+ else
56
+ $AUTH << $CONF[:authenticator][:class].constantize.new
57
+ end
58
+ rescue NameError
59
+ if $CONF[:authenticator].instance_of? Array
60
+ $CONF[:authenticator].each do |authenticator|
61
+ if !authenticator[:source].nil?
62
+ # config.yml explicitly names source file
63
+ require authenticator[:source]
64
+ else
65
+ # the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
66
+ auth_rb = authenticator[:class].underscore.gsub('cas_server/', '')
67
+ require 'casserver/'+auth_rb
68
+ end
69
+ $AUTH << authenticator[:class].constantize.new
70
+ end
71
+ else
72
+ if !$CONF[:authenticator][:source].nil?
73
+ # config.yml explicitly names source file
74
+ require $CONF[:authenticator][:source]
75
+ else
76
+ # the authenticator class hasn't yet been loaded, so lets try to load it from the casserver/authenticators directory
77
+ auth_rb = $CONF[:authenticator][:class].underscore.gsub('cas_server/', '')
78
+ require 'casserver/'+auth_rb
79
+ end
80
+
81
+ $AUTH << $CONF[:authenticator][:class].constantize.new
82
+ end
83
+ end
84
+ rescue
85
+ raise "Your RubyCAS-Server configuration may be invalid."+
86
+ " Please double-check check your config.yml file."+
87
+ " Make sure that you are using spaces instead of tabs for your indentation!!" +
88
+ "\n\nUNDERLYING EXCEPTION:\n#{$!}"
89
+ end
90
+
91
+ module CASServer
92
+ module Conf
93
+ DEFAULTS = {
94
+ :expire_sessions => false,
95
+ :login_ticket_expiry => 5.minutes,
96
+ :service_ticket_expiry => 5.minutes, # CAS Protocol Spec, sec. 3.2.1 (recommended expiry time)
97
+ :proxy_granting_ticket_expiry => 48.hours,
98
+ :ticket_granting_ticket_expiry => 48.hours,
99
+ :log => {:file => 'casserver.log', :level => 'DEBUG'},
100
+ :uri_path => "/"
101
+ }
102
+
103
+ def [](key)
104
+ $CONF[key] || DEFAULTS[key]
105
+ end
106
+ module_function "[]".intern
107
+
108
+ def self.method_missing(method, *args)
109
+ self[method]
110
+ end
111
+ end
112
+ end