vibes-rubycas-client 2.3.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.rvmrc +1 -0
  2. data/.source_index +0 -0
  3. data/CHANGELOG.txt +1 -0
  4. data/Gemfile +15 -0
  5. data/Gemfile.lock +22 -0
  6. data/History.txt +192 -0
  7. data/LICENSE.txt +26 -0
  8. data/README.rdoc +321 -0
  9. data/Rakefile +53 -0
  10. data/VERSION +1 -0
  11. data/examples/merb/.gitignore +18 -0
  12. data/examples/merb/README.textile +12 -0
  13. data/examples/merb/Rakefile +35 -0
  14. data/examples/merb/merb.thor +2020 -0
  15. data/examples/merb/merb_auth_cas.rb +67 -0
  16. data/examples/merb/spec/spec_helper.rb +24 -0
  17. data/examples/rails/README +16 -0
  18. data/examples/rails/app/controllers/advanced_example_controller.rb +31 -0
  19. data/examples/rails/app/controllers/application.rb +2 -0
  20. data/examples/rails/app/controllers/simple_example_controller.rb +16 -0
  21. data/examples/rails/app/views/advanced_example/index.html.erb +13 -0
  22. data/examples/rails/app/views/advanced_example/my_account.html.erb +11 -0
  23. data/examples/rails/app/views/simple_example/index.html.erb +6 -0
  24. data/examples/rails/config/boot.rb +109 -0
  25. data/examples/rails/config/environment.rb +39 -0
  26. data/examples/rails/config/environments/development.rb +17 -0
  27. data/examples/rails/config/environments/production.rb +22 -0
  28. data/examples/rails/config/environments/test.rb +22 -0
  29. data/examples/rails/config/initializers/inflections.rb +10 -0
  30. data/examples/rails/config/initializers/mime_types.rb +5 -0
  31. data/examples/rails/config/initializers/new_rails_defaults.rb +17 -0
  32. data/examples/rails/config/routes.rb +4 -0
  33. data/examples/rails/log/development.log +946 -0
  34. data/examples/rails/log/production.log +0 -0
  35. data/examples/rails/log/server.log +0 -0
  36. data/examples/rails/log/test.log +0 -0
  37. data/examples/rails/script/about +4 -0
  38. data/examples/rails/script/console +3 -0
  39. data/examples/rails/script/server +3 -0
  40. data/lib/casclient.rb +89 -0
  41. data/lib/casclient/client.rb +271 -0
  42. data/lib/casclient/frameworks/merb/filter.rb +105 -0
  43. data/lib/casclient/frameworks/merb/strategy.rb +110 -0
  44. data/lib/casclient/frameworks/rails/cas_proxy_callback_controller.rb +76 -0
  45. data/lib/casclient/frameworks/rails/filter.rb +415 -0
  46. data/lib/casclient/responses.rb +197 -0
  47. data/lib/casclient/tickets.rb +38 -0
  48. data/lib/vibes-rubycas-client.rb +1 -0
  49. data/vibes-rubycas-client.gemspec +100 -0
  50. metadata +198 -0
@@ -0,0 +1,110 @@
1
+ # The 'cas' strategy attempts to login users based on the CAS protocol
2
+ # http://www.ja-sig.org/products/cas/overview/background/index.html
3
+ #
4
+ # install the rubycas-client gem
5
+ # http://rubyforge.org/projects/rubycas-client/
6
+ #
7
+ require 'casclient'
8
+ class Merb::Authentication
9
+ module Strategies
10
+ class CAS < Merb::Authentication::Strategy
11
+
12
+ include CASClient
13
+
14
+ def run!
15
+ @client ||= Client.new(config)
16
+
17
+ service_ticket = read_ticket
18
+
19
+ cas_login_url = @client.add_service_to_login_url(service_url)
20
+
21
+ last_service_ticket = session[:cas_last_valid_ticket]
22
+ if (service_ticket && last_service_ticket &&
23
+ last_service_ticket.ticket == service_ticket.ticket &&
24
+ last_service_ticket.service == service_ticket.service)
25
+
26
+ # warn() rather than info() because we really shouldn't be re-validating the same ticket.
27
+ # The only time when this is acceptable is if the user manually does a refresh and the ticket
28
+ # happens to be in the URL.
29
+ log.warn("Reusing previously validated ticket since the new ticket and service are the same.")
30
+ service_ticket = last_service_ticket
31
+ elsif last_service_ticket &&
32
+ !config[:authenticate_on_every_request] &&
33
+ session[@client.username_session_key]
34
+ # Re-use the previous ticket if the user already has a local CAS session (i.e. if they were already
35
+ # previously authenticated for this service). This is to prevent redirection to the CAS server on every
36
+ # request.
37
+ # This behaviour can be disabled (so that every request is routed through the CAS server) by setting
38
+ # the :authenticate_on_every_request config option to false.
39
+ log.debug "Existing local CAS session detected for #{session[@client.username_session_key].inspect}. "+
40
+ "Previous ticket #{last_service_ticket.ticket.inspect} will be re-used."
41
+ service_ticket = last_service_ticket
42
+ end
43
+
44
+ if service_ticket
45
+ @client.validate_service_ticket(service_ticket) unless service_ticket.has_been_validated?
46
+ validation_response = service_ticket.response
47
+
48
+ if service_ticket.is_valid?
49
+ log.info("Ticket #{service_ticket.inspect} for service #{service_ticket.service.inspect} " +
50
+ "belonging to user #{validation_response.user.inspect} is VALID.")
51
+
52
+ session[@client.username_session_key] = validation_response.user
53
+ session[@client.extra_attributes_session_key] = validation_response.extra_attributes
54
+
55
+ # Store the ticket in the session to avoid re-validating the same service
56
+ # ticket with the CAS server.
57
+ session[:cas_last_valid_ticket] = service_ticket
58
+ return true
59
+ else
60
+ log.warn("Ticket #{service_ticket.ticket.inspect} failed validation -- " +
61
+ "#{validation_response.failure_code}: #{validation_response.failure_message}")
62
+ redirect!(cas_login_url)
63
+ return false
64
+ end
65
+ else
66
+ log.warn("No ticket -- redirecting to #{cas_login_url}")
67
+ redirect!(cas_login_url)
68
+ return false
69
+ end
70
+ end
71
+
72
+ def read_ticket
73
+ ticket = request.params[:ticket]
74
+
75
+ return nil unless ticket
76
+
77
+ log.debug("Request contains ticket #{ticket.inspect}.")
78
+
79
+ if ticket =~ /^PT-/
80
+ ProxyTicket.new(ticket, service_url, request.params[:renew])
81
+ else
82
+ ServiceTicket.new(ticket, service_url, request.params[:renew])
83
+ end
84
+ end
85
+
86
+ def service_url
87
+ if config[:service_url]
88
+ log.debug("Using explicitly set service url: #{config[:service_url]}")
89
+ return config[:service_url]
90
+ end
91
+
92
+ params = request.params.dup
93
+ params.delete(:ticket)
94
+ service_url = "#{request.protocol}://#{request.host}" + request.path
95
+ log.debug("Guessed service url: #{service_url.inspect}")
96
+ return service_url
97
+ end
98
+
99
+ def config
100
+ ::Merb::Plugins.config[:"rubycas-client"]
101
+ end
102
+
103
+ def log
104
+ ::Merb.logger
105
+ end
106
+
107
+ end # CAS
108
+ end # Strategies
109
+ end
110
+
@@ -0,0 +1,76 @@
1
+ require 'pstore'
2
+
3
+ # Rails controller that responds to proxy generating ticket callbacks from the CAS server and allows
4
+ # for retrieval of those PGTs.
5
+ class CasProxyCallbackController < ActionController::Base
6
+
7
+ # Receives a proxy granting ticket from the CAS server and stores it in the database.
8
+ # Note that this action should ALWAYS be called via https, otherwise you have a gaping security hole.
9
+ # In fact, the JA-SIG implementation of the CAS server will refuse to send PGTs to non-https URLs.
10
+ def receive_pgt
11
+ #FIXME: these checks don't work because REMOTE_HOST doesn't work consistently under all web servers (for example it doesn't work at all under mongrel)
12
+ # ... need to find a reliable way to check if the request came through from a reverse HTTPS proxy -- until then I'm disabling this check
13
+ #render_error "PGTs can be received only via HTTPS or local connections." and return unless
14
+ # request.ssl? or request.env['REMOTE_HOST'] == "127.0.0.1"
15
+
16
+ pgtIou = params['pgtIou']
17
+
18
+ # CAS Protocol spec says that the argument should be called 'pgt', but the JA-SIG CAS server seems to use pgtId.
19
+ # To accomodate this, we check for both parameters, although 'pgt' takes precedence over 'pgtId'.
20
+ pgtId = params['pgt'] || params['pgtId']
21
+
22
+ # We need to render a response with HTTP status code 200 when no pgtIou/pgtId is specified because CAS seems first
23
+ # call the action without any parameters (maybe to check if the server responds correctly)
24
+ render :text => "Okay, the server is up, but please specify a pgtIou and pgtId." and return unless pgtIou and pgtId
25
+
26
+ # TODO: pstore contents should probably be encrypted...
27
+ pstore = open_pstore
28
+
29
+ pstore.transaction do
30
+ pstore[pgtIou] = pgtId
31
+ end
32
+
33
+ render :text => "PGT received. Thank you!" and return
34
+ end
35
+
36
+ # Retreives a proxy granting ticket, sends it to output, and deletes the pgt from session storage.
37
+ # Note that this action should ALWAYS be called via https, otherwise you have a gaping security hole --
38
+ # in fact, the action will not work if the request is not made via SSL or is not local (we allow for local
39
+ # non-SSL requests since this allows for the use of reverse HTTPS proxies like Pound).
40
+ def retrieve_pgt
41
+ #render_error "You can only retrieve PGTs via HTTPS or local connections." and return unless
42
+ # request.ssl? or request.env['REMOTE_HOST'] == "127.0.0.1"
43
+
44
+ pgtIou = params['pgtIou']
45
+
46
+ render_error "No pgtIou specified. Cannot retreive the pgtId." and return unless pgtIou
47
+
48
+ pstore = open_pstore
49
+
50
+ pgt = nil
51
+ pstore.transaction do
52
+ pgt = pstore[pgtIou]
53
+ end
54
+
55
+ if not pgt
56
+ render_error "Invalid pgtIou specified. Perhaps this pgt has already been retrieved?" and return
57
+ end
58
+
59
+ render :text => pgt
60
+
61
+ # TODO: need to periodically clean the storage, otherwise it will just keep growing
62
+ pstore.transaction do
63
+ pstore.delete pgtIou
64
+ end
65
+ end
66
+
67
+ private
68
+ def render_error(msg)
69
+ # Note that the error messages are mostly just for debugging, since the CAS server never reads them.
70
+ render :text => msg, :status => 500
71
+ end
72
+
73
+ def open_pstore
74
+ PStore.new("#{RAILS_ROOT}/tmp/cas_pgt.pstore")
75
+ end
76
+ end
@@ -0,0 +1,415 @@
1
+ module CASClient
2
+ module Frameworks
3
+ module Rails
4
+ class Filter
5
+ cattr_reader :config, :log, :client
6
+
7
+ # These are initialized when you call configure.
8
+ @@config = nil
9
+ @@client = nil
10
+ @@log = nil
11
+ @@fake_user = nil
12
+ @@fake_extra_attributes = nil
13
+
14
+ class << self
15
+ def filter(controller)
16
+ raise "Cannot use the CASClient filter because it has not yet been configured." if config.nil?
17
+
18
+ if @@fake_user
19
+ controller.session[client.username_session_key] = @@fake_user
20
+ controller.session[:casfilteruser] = @@fake_user
21
+ controller.session[client.extra_attributes_session_key] = @@fake_extra_attributes
22
+ return true
23
+ end
24
+
25
+
26
+ last_st = controller.session[:cas_last_valid_ticket]
27
+
28
+ if single_sign_out(controller)
29
+ controller.send(:render, :text => "CAS Single-Sign-Out request intercepted.")
30
+ return false
31
+ end
32
+
33
+ st = read_ticket(controller)
34
+
35
+ is_new_session = true
36
+
37
+ if st && last_st &&
38
+ last_st.ticket == st.ticket &&
39
+ last_st.service == st.service
40
+ # warn() rather than info() because we really shouldn't be re-validating the same ticket.
41
+ # The only situation where this is acceptable is if the user manually does a refresh and
42
+ # the same ticket happens to be in the URL.
43
+ log.warn("Re-using previously validated ticket since the ticket id and service are the same.")
44
+ st = last_st
45
+ is_new_session = false
46
+ elsif last_st &&
47
+ !config[:authenticate_on_every_request] &&
48
+ controller.session[client.username_session_key]
49
+ # Re-use the previous ticket if the user already has a local CAS session (i.e. if they were already
50
+ # previously authenticated for this service). This is to prevent redirection to the CAS server on every
51
+ # request.
52
+ #
53
+ # This behaviour can be disabled (so that every request is routed through the CAS server) by setting
54
+ # the :authenticate_on_every_request config option to true. However, this is not desirable since
55
+ # it will almost certainly break POST request, AJAX calls, etc.
56
+ log.debug "Existing local CAS session detected for #{controller.session[client.username_session_key].inspect}. "+
57
+ "Previous ticket #{last_st.ticket.inspect} will be re-used."
58
+ st = last_st
59
+ is_new_session = false
60
+ end
61
+
62
+ if st
63
+ client.validate_service_ticket(st) unless st.has_been_validated?
64
+ vr = st.response
65
+
66
+ if st.is_valid?
67
+ if is_new_session
68
+ log.info("Ticket #{st.ticket.inspect} for service #{st.service.inspect} belonging to user #{vr.user.inspect} is VALID.")
69
+ controller.session[client.username_session_key] = vr.user.dup
70
+ controller.session[client.extra_attributes_session_key] = HashWithIndifferentAccess.new(vr.extra_attributes) if vr.extra_attributes
71
+
72
+ if vr.extra_attributes
73
+ log.debug("Extra user attributes provided along with ticket #{st.ticket.inspect}: #{vr.extra_attributes.inspect}.")
74
+ end
75
+
76
+ # RubyCAS-Client 1.x used :casfilteruser as it's username session key,
77
+ # so we need to set this here to ensure compatibility with configurations
78
+ # built around the old client.
79
+ controller.session[:casfilteruser] = vr.user
80
+
81
+ if config[:enable_single_sign_out]
82
+ f = store_service_session_lookup(st, controller.request.session_options[:id] || controller.session.session_id)
83
+ log.debug("Wrote service session lookup file to #{f.inspect} with session id #{controller.request.session_options[:id] || controller.session.session_id.inspect}.")
84
+ end
85
+ end
86
+
87
+ # Store the ticket in the session to avoid re-validating the same service
88
+ # ticket with the CAS server.
89
+ controller.session[:cas_last_valid_ticket] = st
90
+
91
+ if vr.pgt_iou
92
+ unless controller.session[:cas_pgt] && controller.session[:cas_pgt].ticket && controller.session[:cas_pgt].iou == vr.pgt_iou
93
+ log.info("Receipt has a proxy-granting ticket IOU. Attempting to retrieve the proxy-granting ticket...")
94
+ pgt = client.retrieve_proxy_granting_ticket(vr.pgt_iou)
95
+
96
+ if pgt
97
+ log.debug("Got PGT #{pgt.ticket.inspect} for PGT IOU #{pgt.iou.inspect}. This will be stored in the session.")
98
+ controller.session[:cas_pgt] = pgt
99
+ # For backwards compatibility with RubyCAS-Client 1.x configurations...
100
+ controller.session[:casfilterpgt] = pgt
101
+ else
102
+ log.error("Failed to retrieve a PGT for PGT IOU #{vr.pgt_iou}!")
103
+ end
104
+ else
105
+ log.info("PGT is present in session and PGT IOU #{vr.pgt_iou} matches the saved PGT IOU. Not retrieving new PGT.")
106
+ end
107
+
108
+ end
109
+
110
+ return true
111
+ else
112
+ log.warn("Ticket #{st.ticket.inspect} failed validation -- #{vr.failure_code}: #{vr.failure_message}")
113
+ unauthorized!(controller, vr)
114
+ return false
115
+ end
116
+ else # no service ticket was present in the request
117
+ if returning_from_gateway?(controller)
118
+ log.info "Returning from CAS gateway without authentication."
119
+
120
+ # unset, to allow for the next request to be authenticated if necessary
121
+ controller.session[:cas_sent_to_gateway] = false
122
+
123
+ if use_gatewaying?
124
+ log.info "This CAS client is configured to use gatewaying, so we will permit the user to continue without authentication."
125
+ controller.session[client.username_session_key] = nil
126
+ return true
127
+ else
128
+ log.warn "The CAS client is NOT configured to allow gatewaying, yet this request was gatewayed. Something is not right!"
129
+ end
130
+ end
131
+
132
+ unauthorized!(controller)
133
+ return false
134
+ end
135
+ rescue OpenSSL::SSL::SSLError
136
+ log.error("SSL Error: hostname was not match with the server certificate. You can try to disable the ssl verification with a :force_ssl_verification => false in your configurations file.")
137
+ unauthorized!(controller)
138
+ return false
139
+ end
140
+
141
+ def configure(config)
142
+ @@config = config
143
+ @@config[:logger] = RAILS_DEFAULT_LOGGER unless @@config[:logger]
144
+ @@client = CASClient::Client.new(config)
145
+ @@log = client.log
146
+ end
147
+
148
+ # used to allow faking for testing
149
+ # with cucumber and other tools.
150
+ # use like
151
+ # CASClient::Frameworks::Rails::Filter.fake("homer")
152
+ # you can also fake extra attributes by including a second parameter
153
+ # CASClient::Frameworks::Rails::Filter.fake("homer", {:roles => ['dad', 'husband']})
154
+ def fake(username, extra_attributes = nil)
155
+ @@fake_user = username
156
+ @@fake_extra_attributes = extra_attributes
157
+ end
158
+
159
+ def use_gatewaying?
160
+ @@config[:use_gatewaying]
161
+ end
162
+
163
+ # Returns the login URL for the current controller.
164
+ # Useful when you want to provide a "Login" link in a GatewayFilter'ed
165
+ # action.
166
+ def login_url(controller)
167
+ service_url = read_service_url(controller)
168
+ url = client.add_service_to_login_url(service_url)
169
+ log.debug("Generated login url: #{url}")
170
+ return url
171
+ end
172
+
173
+ # allow controllers to reuse the existing config to auto-login to
174
+ # the service
175
+ #
176
+ # Use this from within a controller. Pass the controller, the
177
+ # login-credentials and the path that you want the user
178
+ # resdirected to on success.
179
+ #
180
+ # When writing a login-action you must check the return-value of
181
+ # the response to see if it failed!
182
+ #
183
+ # If it worked - you need to redirect the user to the service -
184
+ # path, because that has the ticket that will *actually* log them
185
+ # into your system
186
+ #
187
+ # example:
188
+ # def autologin
189
+ # resp = CASClient::Frameworks::Rails::Filter.login_to_service(self, credentials, dashboard_url)
190
+ # if resp.is_faiulure?
191
+ # flash[:error] = 'Login failed'
192
+ # render :action => 'login'
193
+ # else
194
+ # return redirect_to(@resp.service_redirect_url)
195
+ # end
196
+ # end
197
+ def login_to_service(controller, credentials, return_path)
198
+ resp = @@client.login_to_service(credentials, return_path)
199
+ if resp.is_failure?
200
+ log.info("Validation failed for service #{return_path.inspect} reason: '#{resp.failure_message}'")
201
+ else
202
+ log.info("Ticket #{resp.ticket.inspect} for service #{return_path.inspect} is VALID.")
203
+ end
204
+
205
+ resp
206
+ end
207
+
208
+ # Clears the given controller's local Rails session, does some local
209
+ # CAS cleanup, and redirects to the CAS logout page. Additionally, the
210
+ # <tt>request.referer</tt> value from the <tt>controller</tt> instance
211
+ # is passed to the CAS server as a 'destination' parameter. This
212
+ # allows RubyCAS server to provide a follow-up login page allowing
213
+ # the user to log back in to the service they just logged out from
214
+ # using a different username and password. Other CAS server
215
+ # implemenations may use this 'destination' parameter in different
216
+ # ways.
217
+ # If given, the optional <tt>service</tt> URL overrides
218
+ # <tt>request.referer</tt>.
219
+ def logout(controller, service = nil)
220
+ referer = service || controller.request.referer
221
+ st = controller.session[:cas_last_valid_ticket]
222
+ delete_service_session_lookup(st) if st
223
+ controller.send(:reset_session)
224
+ controller.send(:redirect_to, client.logout_url(referer))
225
+ end
226
+
227
+ def unauthorized!(controller, vr = nil)
228
+ if controller.params[:format] == "xml"
229
+ if vr
230
+ controller.send(:render, :xml => "<errors><error>#{vr.failure_message}</error></errors>", :status => 401)
231
+ else
232
+ controller.send(:head, 401)
233
+ end
234
+ else
235
+ redirect_to_cas_for_authentication(controller)
236
+ end
237
+ end
238
+
239
+ def redirect_to_cas_for_authentication(controller)
240
+ redirect_url = login_url(controller)
241
+
242
+ if use_gatewaying?
243
+ controller.session[:cas_sent_to_gateway] = true
244
+ redirect_url << "&gateway=true"
245
+ else
246
+ controller.session[:cas_sent_to_gateway] = false
247
+ end
248
+
249
+ if controller.session[:previous_redirect_to_cas] &&
250
+ controller.session[:previous_redirect_to_cas] > (Time.now - 1.second)
251
+ log.warn("Previous redirect to the CAS server was less than a second ago. The client at #{controller.request.remote_ip.inspect} may be stuck in a redirection loop!")
252
+ controller.session[:cas_validation_retry_count] ||= 0
253
+
254
+ if controller.session[:cas_validation_retry_count] > 3
255
+ log.error("Redirection loop intercepted. Client at #{controller.request.remote_ip.inspect} will be redirected back to login page and forced to renew authentication.")
256
+ redirect_url += "&renew=1&redirection_loop_intercepted=1"
257
+ end
258
+
259
+ controller.session[:cas_validation_retry_count] += 1
260
+ else
261
+ controller.session[:cas_validation_retry_count] = 0
262
+ end
263
+ controller.session[:previous_redirect_to_cas] = Time.now
264
+
265
+ log.debug("Redirecting to #{redirect_url.inspect}")
266
+ controller.send(:redirect_to, redirect_url)
267
+ end
268
+
269
+ private
270
+ def single_sign_out(controller)
271
+
272
+ # Avoid calling raw_post (which may consume the post body) if
273
+ # this seems to be a file upload
274
+ if content_type = controller.request.headers["CONTENT_TYPE"] &&
275
+ content_type =~ %r{^multipart/}
276
+ return false
277
+ end
278
+
279
+ if controller.request.post? &&
280
+ controller.params['logoutRequest'] &&
281
+ controller.params['logoutRequest'] =~
282
+ %r{^<samlp:LogoutRequest.*?<samlp:SessionIndex>(.*)</samlp:SessionIndex>}m
283
+ # TODO: Maybe check that the request came from the registered CAS server? Although this might be
284
+ # pointless since it's easily spoofable...
285
+ si = $~[1]
286
+
287
+ unless config[:enable_single_sign_out]
288
+ log.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)."
289
+ return false
290
+ end
291
+
292
+ log.debug "Intercepted single-sign-out request for CAS session #{si.inspect}."
293
+
294
+ begin
295
+ required_sess_store = ActiveRecord::SessionStore
296
+ current_sess_store = ActionController::Base.session_store
297
+ rescue NameError
298
+ # for older versions of Rails (prior to 2.3)
299
+ required_sess_store = CGI::Session::ActiveRecordStore
300
+ current_sess_store = ActionController::Base.session_options[:database_manager]
301
+ end
302
+
303
+
304
+ if current_sess_store == required_sess_store
305
+ session_id = read_service_session_lookup(si)
306
+
307
+ if session_id
308
+ session = current_sess_store::Session.find_by_session_id(session_id)
309
+ if session
310
+ session.destroy
311
+ log.debug("Destroyed #{session.inspect} for session #{session_id.inspect} corresponding to service ticket #{si.inspect}.")
312
+ else
313
+ log.debug("Data for session #{session_id.inspect} was not found. It may have already been cleared by a local CAS logout request.")
314
+ end
315
+
316
+ log.info("Single-sign-out for session #{session_id.inspect} completed successfuly.")
317
+ else
318
+ log.warn("Couldn't destroy session with SessionIndex #{si} because no corresponding session id could be looked up.")
319
+ end
320
+ else
321
+ log.error "Cannot process logout request because this Rails application's session store is "+
322
+ " #{current_sess_store.name.inspect}. Single Sign-Out only works with the "+
323
+ " #{required_sess_store.name.inspect} session store."
324
+ end
325
+
326
+ # Return true to indicate that a single-sign-out request was detected
327
+ # and that further processing of the request is unnecessary.
328
+ return true
329
+ end
330
+
331
+ # This is not a single-sign-out request.
332
+ return false
333
+ end
334
+
335
+ def read_ticket(controller)
336
+ ticket = controller.params[:ticket]
337
+
338
+ return nil unless ticket
339
+
340
+ log.debug("Request contains ticket #{ticket.inspect}.")
341
+
342
+ if ticket =~ /^PT-/
343
+ ProxyTicket.new(ticket, read_service_url(controller), controller.params[:renew])
344
+ else
345
+ ServiceTicket.new(ticket, read_service_url(controller), controller.params[:renew])
346
+ end
347
+ end
348
+
349
+ def returning_from_gateway?(controller)
350
+ controller.session[:cas_sent_to_gateway]
351
+ end
352
+
353
+ def read_service_url(controller)
354
+ if config[:service_url]
355
+ log.debug("Using explicitly set service url: #{config[:service_url]}")
356
+ return config[:service_url]
357
+ end
358
+
359
+ params = controller.params.dup
360
+ params.delete(:ticket)
361
+ service_url = controller.url_for(params)
362
+ log.debug("Guessed service url: #{service_url.inspect}")
363
+ return service_url
364
+ end
365
+
366
+ # Creates a file in tmp/sessions linking a SessionTicket
367
+ # with the local Rails session id. The file is named
368
+ # cas_sess.<session ticket> and its text contents is the corresponding
369
+ # Rails session id.
370
+ # Returns the filename of the lookup file created.
371
+ def store_service_session_lookup(st, sid)
372
+ st = st.ticket if st.kind_of? ServiceTicket
373
+ f = File.new(filename_of_service_session_lookup(st), 'w')
374
+ f.write(sid)
375
+ f.close
376
+ return f.path
377
+ end
378
+
379
+ # Returns the local Rails session ID corresponding to the given
380
+ # ServiceTicket. This is done by reading the contents of the
381
+ # cas_sess.<session ticket> file created in a prior call to
382
+ # #store_service_session_lookup.
383
+ def read_service_session_lookup(st)
384
+ st = st.ticket if st.kind_of? ServiceTicket
385
+ ssl_filename = filename_of_service_session_lookup(st)
386
+ return File.exists?(ssl_filename) && IO.read(ssl_filename)
387
+ end
388
+
389
+ # Removes a stored relationship between a ServiceTicket and a local
390
+ # Rails session id. This should be called when the session is being
391
+ # closed.
392
+ #
393
+ # See #store_service_session_lookup.
394
+ def delete_service_session_lookup(st)
395
+ st = st.ticket if st.kind_of? ServiceTicket
396
+ ssl_filename = filename_of_service_session_lookup(st)
397
+ File.delete(ssl_filename) if File.exists?(ssl_filename)
398
+ end
399
+
400
+ # Returns the path and filename of the service session lookup file.
401
+ def filename_of_service_session_lookup(st)
402
+ st = st.ticket if st.kind_of? ServiceTicket
403
+ return "#{RAILS_ROOT}/tmp/sessions/cas_sess.#{st}"
404
+ end
405
+ end
406
+ end
407
+
408
+ class GatewayFilter < Filter
409
+ def self.use_gatewaying?
410
+ return true unless @@config[:use_gatewaying] == false
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end