vibes-rubycas-client 2.3.0.alpha

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 (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