rubycas-client 2.2.1 → 2.3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/Gemfile +13 -0
  2. data/Gemfile.lock +30 -0
  3. data/History.txt +21 -0
  4. data/README.rdoc +24 -20
  5. data/Rakefile +43 -54
  6. data/VERSION +1 -0
  7. data/examples/rails/README +16 -0
  8. data/examples/rails/app/controllers/advanced_example_controller.rb +31 -0
  9. data/examples/rails/app/controllers/application.rb +2 -0
  10. data/examples/rails/app/controllers/simple_example_controller.rb +16 -0
  11. data/examples/rails/app/views/advanced_example/index.html.erb +13 -0
  12. data/examples/rails/app/views/advanced_example/my_account.html.erb +11 -0
  13. data/examples/rails/app/views/simple_example/index.html.erb +6 -0
  14. data/examples/rails/config/boot.rb +109 -0
  15. data/examples/rails/config/environment.rb +39 -0
  16. data/examples/rails/config/environments/development.rb +17 -0
  17. data/examples/rails/config/environments/production.rb +22 -0
  18. data/examples/rails/config/environments/test.rb +22 -0
  19. data/examples/rails/config/initializers/inflections.rb +10 -0
  20. data/examples/rails/config/initializers/mime_types.rb +5 -0
  21. data/examples/rails/config/initializers/new_rails_defaults.rb +17 -0
  22. data/examples/rails/config/routes.rb +4 -0
  23. data/examples/rails/log/development.log +946 -0
  24. data/examples/rails/log/production.log +0 -0
  25. data/examples/rails/log/server.log +0 -0
  26. data/examples/rails/log/test.log +0 -0
  27. data/examples/rails/script/about +4 -0
  28. data/examples/rails/script/console +3 -0
  29. data/examples/rails/script/server +3 -0
  30. data/lib/casclient/client.rb +49 -36
  31. data/lib/casclient/frameworks/rails/cas_proxy_callback_controller.rb +5 -39
  32. data/lib/casclient/frameworks/rails/filter.rb +86 -113
  33. data/lib/casclient/responses.rb +29 -16
  34. data/lib/casclient/tickets/storage/active_record_ticket_store.rb +67 -0
  35. data/lib/casclient/tickets/storage.rb +167 -0
  36. data/lib/casclient/tickets.rb +3 -3
  37. data/lib/casclient.rb +3 -2
  38. data/lib/rubycas-client.rb +1 -5
  39. data/rails_generators/active_record_ticket_store/USAGE +9 -0
  40. data/rails_generators/active_record_ticket_store/active_record_ticket_store_generator.rb +29 -0
  41. data/rails_generators/active_record_ticket_store/templates/README +1 -0
  42. data/rails_generators/active_record_ticket_store/templates/migration.rb +24 -0
  43. data/rubycas-client.gemspec +103 -0
  44. data/test/teststrap.rb +10 -0
  45. data/test/units/casclient/frameworks/rails/filter_test.rb +184 -0
  46. metadata +148 -47
  47. data/Manifest.txt +0 -23
  48. data/examples/merb/README.textile +0 -12
  49. data/examples/merb/Rakefile +0 -35
  50. data/examples/merb/merb.thor +0 -2020
  51. data/examples/merb/merb_auth_cas.rb +0 -67
  52. data/examples/merb/spec/spec_helper.rb +0 -24
  53. data/init.rb +0 -6
  54. data/lib/casclient/frameworks/merb/filter.rb +0 -105
  55. data/lib/casclient/frameworks/merb/strategy.rb +0 -110
  56. data/lib/casclient/version.rb +0 -9
  57. data/setup.rb +0 -1585
File without changes
File without changes
File without changes
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
4
+ require 'commands/about'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/console'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'commands/server'
@@ -1,8 +1,9 @@
1
1
  module CASClient
2
2
  # The client brokers all HTTP transactions with the CAS server.
3
3
  class Client
4
- attr_reader :cas_base_url
4
+ attr_reader :cas_base_url, :cas_destination_logout_param_name
5
5
  attr_reader :log, :username_session_key, :extra_attributes_session_key
6
+ attr_reader :ticket_store
6
7
  attr_writer :login_url, :validate_url, :proxy_url, :logout_url, :service_url
7
8
  attr_accessor :proxy_callback_url, :proxy_retrieval_url
8
9
 
@@ -15,7 +16,14 @@ module CASClient
15
16
 
16
17
  raise ArgumentError, "Missing :cas_base_url parameter!" unless conf[:cas_base_url]
17
18
 
19
+ if conf.has_key?("encode_extra_attributes_as")
20
+ unless (conf[:encode_extra_attributes_as] == :json || conf[:encode_extra_attributes_as] == :yaml)
21
+ raise ArgumentError, "Unkown Value for :encode_extra_attributes_as parameter! Allowed options are json or yaml - #{conf[:encode_extra_attributes_as]}"
22
+ end
23
+ end
24
+
18
25
  @cas_base_url = conf[:cas_base_url].gsub(/\/$/, '')
26
+ @cas_destination_logout_param_name = conf[:cas_destination_logout_param_name]
19
27
 
20
28
  @login_url = conf[:login_url]
21
29
  @logout_url = conf[:logout_url]
@@ -24,15 +32,23 @@ module CASClient
24
32
  @service_url = conf[:service_url]
25
33
  @force_ssl_verification = conf[:force_ssl_verification]
26
34
  @proxy_callback_url = conf[:proxy_callback_url]
27
- @proxy_retrieval_url = conf[:proxy_retrieval_url]
28
35
 
29
36
  @username_session_key = conf[:username_session_key] || :cas_user
30
37
  @extra_attributes_session_key = conf[:extra_attributes_session_key] || :cas_extra_attributes
38
+ @ticket_store_class = conf[:ticket_store] || CASClient::Tickets::Storage::LocalDirTicketStore
39
+ @ticket_store = @ticket_store_class.new conf[:ticket_store_config]
40
+ raise CASException, "The Ticket Store is not a subclass of AbstractTicketStore, it is a #{@ticket_store_class}" unless @ticket_store.kind_of? CASClient::Tickets::Storage::AbstractTicketStore
31
41
 
32
42
  @log = CASClient::LoggerWrapper.new
33
43
  @log.set_real_logger(conf[:logger]) if conf[:logger]
44
+ @ticket_store.log = @log
45
+ @conf_options = conf
34
46
  end
35
47
 
48
+ def cas_destination_logout_param_name
49
+ @cas_destination_logout_param_name || "destination"
50
+ end
51
+
36
52
  def login_url
37
53
  @login_url || (cas_base_url + "/login")
38
54
  end
@@ -69,7 +85,7 @@ module CASClient
69
85
  if destination_url || follow_url
70
86
  uri = URI.parse(url)
71
87
  h = uri.query ? query_to_hash(uri.query) : {}
72
- h['destination'] = destination_url if destination_url
88
+ h[cas_destination_logout_param_name] = destination_url if destination_url
73
89
  h['url'] = follow_url if follow_url
74
90
  uri.query = hash_to_query(h)
75
91
  uri.to_s
@@ -87,11 +103,17 @@ module CASClient
87
103
  h = uri.query ? query_to_hash(uri.query) : {}
88
104
  h['service'] = st.service
89
105
  h['ticket'] = st.ticket
90
- h['renew'] = 1 if st.renew
106
+ h['renew'] = "1" if st.renew
91
107
  h['pgtUrl'] = proxy_callback_url if proxy_callback_url
92
108
  uri.query = hash_to_query(h)
93
109
 
94
- st.response = request_cas_response(uri, ValidationResponse)
110
+ response = request_cas_response(uri, ValidationResponse)
111
+ st.user = response.user
112
+ st.extra_attributes = response.extra_attributes
113
+ st.pgt_iou = response.pgt_iou
114
+ st.success = response.is_success?
115
+ st.failure_code = response.failure_code
116
+ st.failure_message = response.failure_message
95
117
 
96
118
  return st
97
119
  end
@@ -133,7 +155,13 @@ module CASClient
133
155
  )
134
156
 
135
157
  res = submit_data_to_cas(login_url, data)
136
- CASClient::LoginResponse.new(res)
158
+ response = CASClient::LoginResponse.new(res)
159
+
160
+ if response.is_success?
161
+ log.info("Login was successful for ticket: #{response.ticket.inspect}.")
162
+ end
163
+
164
+ return response
137
165
  end
138
166
 
139
167
  # Requests a login ticket from the CAS server for use in a login request;
@@ -166,37 +194,22 @@ module CASClient
166
194
  h['targetService'] = target_service
167
195
  uri.query = hash_to_query(h)
168
196
 
169
- pr = request_cas_response(uri, ProxyResponse)
197
+ response = request_cas_response(uri, ProxyResponse)
170
198
 
171
- pt = ProxyTicket.new(pr.proxy_ticket, target_service)
172
- pt.response = pr
199
+ pt = ProxyTicket.new(response.proxy_ticket, target_service)
200
+ pt.success = response.is_success?
201
+ pt.failure_code = response.failure_code
202
+ pt.failure_message = response.failure_message
173
203
 
174
204
  return pt
175
205
  end
176
206
 
177
207
  def retrieve_proxy_granting_ticket(pgt_iou)
178
- uri = URI.parse(proxy_retrieval_url)
179
- uri.query = (uri.query ? uri.query + "&" : "") + "pgtIou=#{CGI.escape(pgt_iou)}"
180
- retrieve_url = uri.to_s
181
-
182
- log.debug "Retrieving PGT for PGT IOU #{pgt_iou.inspect} from #{retrieve_url.inspect}"
183
-
184
- # https = Net::HTTP.new(uri.host, uri.port)
185
- # https.use_ssl = (uri.scheme == 'https')
186
- # res = https.post(uri.path, ';')
187
- uri = URI.parse(uri) unless uri.kind_of? URI
188
- https = Net::HTTP.new(uri.host, uri.port)
189
- https.use_ssl = (uri.scheme == 'https')
190
- https.verify_mode = (@force_ssl_verification ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE)
191
-
192
- res = https.start do |conn|
193
- conn.get("#{uri.path}?#{uri.query}")
194
- end
195
-
196
-
197
- raise CASException, res.body unless res.kind_of? Net::HTTPSuccess
198
-
199
- ProxyGrantingTicket.new(res.body.strip, pgt_iou)
208
+ pgt = @ticket_store.retrieve_pgt(pgt_iou)
209
+
210
+ raise CASException, "Couldn't find pgt for pgt_iou #{pgt_iou}" unless pgt
211
+
212
+ ProxyGrantingTicket.new(pgt, pgt_iou)
200
213
  end
201
214
 
202
215
  def add_service_to_login_url(service_url)
@@ -208,7 +221,7 @@ module CASClient
208
221
  private
209
222
  # Fetches a CAS response of the given type from the given URI.
210
223
  # Type should be either ValidationResponse or ProxyResponse.
211
- def request_cas_response(uri, type)
224
+ def request_cas_response(uri, type, options={})
212
225
  log.debug "Requesting CAS response for URI #{uri}"
213
226
 
214
227
  uri = URI.parse(uri) unless uri.kind_of? URI
@@ -234,8 +247,8 @@ module CASClient
234
247
  log.error "CAS server responded with an error! (#{raw_res.inspect})"
235
248
  raise "The CAS authentication server at #{uri} responded with an error (#{raw_res.inspect})!"
236
249
  end
237
-
238
- type.new(raw_res.body)
250
+
251
+ type.new(raw_res.body, @conf_options)
239
252
  end
240
253
 
241
254
  # Submits some data to the given URI and returns a Net::HTTPResponse.
@@ -257,9 +270,9 @@ module CASClient
257
270
  pairs = []
258
271
  hash.each do |k, vals|
259
272
  vals = [vals] unless vals.kind_of? Array
260
- vals.each {|v| pairs << "#{CGI.escape(k)}=#{CGI.escape(v)}"}
273
+ vals.each {|v| pairs << (v.nil? ? CGI.escape(k) : "#{CGI.escape(k)}=#{CGI.escape(v)}")}
261
274
  end
262
275
  pairs.join("&")
263
276
  end
264
277
  end
265
- end
278
+ end
@@ -1,5 +1,3 @@
1
- require 'pstore'
2
-
3
1
  # Rails controller that responds to proxy generating ticket callbacks from the CAS server and allows
4
2
  # for retrieval of those PGTs.
5
3
  class CasProxyCallbackController < ActionController::Base
@@ -24,46 +22,14 @@ class CasProxyCallbackController < ActionController::Base
24
22
  render :text => "Okay, the server is up, but please specify a pgtIou and pgtId." and return unless pgtIou and pgtId
25
23
 
26
24
  # TODO: pstore contents should probably be encrypted...
27
- pstore = open_pstore
28
-
29
- pstore.transaction do
30
- pstore[pgtIou] = pgtId
31
- end
32
25
 
26
+ casclient = CASClient::Frameworks::Rails::Filter.client
27
+
28
+ casclient.ticket_store.save_pgt_iou(pgtIou, pgtId)
29
+
33
30
  render :text => "PGT received. Thank you!" and return
34
31
  end
35
32
 
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
33
  private
68
34
  def render_error(msg)
69
35
  # Note that the error messages are mostly just for debugging, since the CAS server never reads them.
@@ -71,6 +37,6 @@ class CasProxyCallbackController < ActionController::Base
71
37
  end
72
38
 
73
39
  def open_pstore
74
- PStore.new("#{RAILS_ROOT}/tmp/cas_pgt.pstore")
40
+ PStore.new("#{::Rails.root}/tmp/cas_pgt.pstore")
75
41
  end
76
42
  end
@@ -2,14 +2,14 @@ module CASClient
2
2
  module Frameworks
3
3
  module Rails
4
4
  class Filter
5
- cattr_reader :config, :log, :client
5
+ cattr_reader :config, :log, :client, :fake_user, :fake_extra_attribues
6
6
 
7
7
  # These are initialized when you call configure.
8
8
  @@config = nil
9
9
  @@client = nil
10
10
  @@log = nil
11
11
  @@fake_user = nil
12
-
12
+ @@fake_extra_attributes = nil
13
13
 
14
14
  class << self
15
15
  def filter(controller)
@@ -18,11 +18,12 @@ module CASClient
18
18
  if @@fake_user
19
19
  controller.session[client.username_session_key] = @@fake_user
20
20
  controller.session[:casfilteruser] = @@fake_user
21
+ controller.session[client.extra_attributes_session_key] = @@fake_extra_attributes if @@fake_extra_attributes
21
22
  return true
22
23
  end
23
24
 
24
-
25
25
  last_st = controller.session[:cas_last_valid_ticket]
26
+ last_st_service = controller.session[:cas_last_valid_ticket_service]
26
27
 
27
28
  if single_sign_out(controller)
28
29
  controller.send(:render, :text => "CAS Single-Sign-Out request intercepted.")
@@ -31,17 +32,14 @@ module CASClient
31
32
 
32
33
  st = read_ticket(controller)
33
34
 
34
- is_new_session = true
35
-
36
35
  if st && last_st &&
37
- last_st.ticket == st.ticket &&
38
- last_st.service == st.service
36
+ last_st == st.ticket &&
37
+ last_st_service == st.service
39
38
  # warn() rather than info() because we really shouldn't be re-validating the same ticket.
40
39
  # The only situation where this is acceptable is if the user manually does a refresh and
41
40
  # the same ticket happens to be in the URL.
42
41
  log.warn("Re-using previously validated ticket since the ticket id and service are the same.")
43
- st = last_st
44
- is_new_session = false
42
+ return true
45
43
  elsif last_st &&
46
44
  !config[:authenticate_on_every_request] &&
47
45
  controller.session[client.username_session_key]
@@ -53,44 +51,42 @@ module CASClient
53
51
  # the :authenticate_on_every_request config option to true. However, this is not desirable since
54
52
  # it will almost certainly break POST request, AJAX calls, etc.
55
53
  log.debug "Existing local CAS session detected for #{controller.session[client.username_session_key].inspect}. "+
56
- "Previous ticket #{last_st.ticket.inspect} will be re-used."
57
- st = last_st
58
- is_new_session = false
54
+ "Previous ticket #{last_st.inspect} will be re-used."
55
+ return true
59
56
  end
60
57
 
61
58
  if st
62
59
  client.validate_service_ticket(st) unless st.has_been_validated?
63
- vr = st.response
64
60
 
65
61
  if st.is_valid?
66
- if is_new_session
67
- log.info("Ticket #{st.ticket.inspect} for service #{st.service.inspect} belonging to user #{vr.user.inspect} is VALID.")
68
- controller.session[client.username_session_key] = vr.user.dup
69
- controller.session[client.extra_attributes_session_key] = HashWithIndifferentAccess.new(vr.extra_attributes)
62
+ #if is_new_session
63
+ log.info("Ticket #{st.ticket.inspect} for service #{st.service.inspect} belonging to user #{st.user.inspect} is VALID.")
64
+ controller.session[client.username_session_key] = st.user.dup
65
+ controller.session[client.extra_attributes_session_key] = HashWithIndifferentAccess.new(st.extra_attributes) if st.extra_attributes
70
66
 
71
- if vr.extra_attributes
72
- log.debug("Extra user attributes provided along with ticket #{st.ticket.inspect}: #{vr.extra_attributes.inspect}.")
67
+ if st.extra_attributes
68
+ log.debug("Extra user attributes provided along with ticket #{st.ticket.inspect}: #{st.extra_attributes.inspect}.")
73
69
  end
74
70
 
75
71
  # RubyCAS-Client 1.x used :casfilteruser as it's username session key,
76
72
  # so we need to set this here to ensure compatibility with configurations
77
73
  # built around the old client.
78
- controller.session[:casfilteruser] = vr.user
74
+ controller.session[:casfilteruser] = st.user
79
75
 
80
76
  if config[:enable_single_sign_out]
81
- f = store_service_session_lookup(st, controller.request.session_options[:id] || controller.session.session_id)
82
- log.debug("Wrote service session lookup file to #{f.inspect} with session id #{controller.request.session_options[:id] || controller.session.session_id.inspect}.")
77
+ client.ticket_store.store_service_session_lookup(st, controller)
83
78
  end
84
- end
79
+ #end
85
80
 
86
81
  # Store the ticket in the session to avoid re-validating the same service
87
82
  # ticket with the CAS server.
88
- controller.session[:cas_last_valid_ticket] = st
83
+ controller.session[:cas_last_valid_ticket] = st.ticket
84
+ controller.session[:cas_last_valid_ticket_service] = st.service
89
85
 
90
- if vr.pgt_iou
91
- unless controller.session[:cas_pgt] && controller.session[:cas_pgt].ticket && controller.session[:cas_pgt].iou == vr.pgt_iou
86
+ if st.pgt_iou
87
+ unless controller.session[:cas_pgt] && controller.session[:cas_pgt].ticket && controller.session[:cas_pgt].iou == st.pgt_iou
92
88
  log.info("Receipt has a proxy-granting ticket IOU. Attempting to retrieve the proxy-granting ticket...")
93
- pgt = client.retrieve_proxy_granting_ticket(vr.pgt_iou)
89
+ pgt = client.retrieve_proxy_granting_ticket(st.pgt_iou)
94
90
 
95
91
  if pgt
96
92
  log.debug("Got PGT #{pgt.ticket.inspect} for PGT IOU #{pgt.iou.inspect}. This will be stored in the session.")
@@ -98,18 +94,16 @@ module CASClient
98
94
  # For backwards compatibility with RubyCAS-Client 1.x configurations...
99
95
  controller.session[:casfilterpgt] = pgt
100
96
  else
101
- log.error("Failed to retrieve a PGT for PGT IOU #{vr.pgt_iou}!")
97
+ log.error("Failed to retrieve a PGT for PGT IOU #{st.pgt_iou}!")
102
98
  end
103
99
  else
104
- log.info("PGT is present in session and PGT IOU #{vr.pgt_iou} matches the saved PGT IOU. Not retrieving new PGT.")
100
+ log.info("PGT is present in session and PGT IOU #{st.pgt_iou} matches the saved PGT IOU. Not retrieving new PGT.")
105
101
  end
106
-
107
102
  end
108
-
109
103
  return true
110
104
  else
111
- log.warn("Ticket #{st.ticket.inspect} failed validation -- #{vr.failure_code}: #{vr.failure_message}")
112
- unauthorized!(controller, vr)
105
+ log.warn("Ticket #{st.ticket.inspect} failed validation -- #{st.failure_code}: #{st.failure_message}")
106
+ unauthorized!(controller, st)
113
107
  return false
114
108
  end
115
109
  else # no service ticket was present in the request
@@ -121,6 +115,7 @@ module CASClient
121
115
 
122
116
  if use_gatewaying?
123
117
  log.info "This CAS client is configured to use gatewaying, so we will permit the user to continue without authentication."
118
+ controller.session[client.username_session_key] = nil
124
119
  return true
125
120
  else
126
121
  log.warn "The CAS client is NOT configured to allow gatewaying, yet this request was gatewayed. Something is not right!"
@@ -138,7 +133,7 @@ module CASClient
138
133
 
139
134
  def configure(config)
140
135
  @@config = config
141
- @@config[:logger] = RAILS_DEFAULT_LOGGER unless @@config[:logger]
136
+ @@config[:logger] = ::Rails.logger unless @@config[:logger]
142
137
  @@client = CASClient::Client.new(config)
143
138
  @@log = client.log
144
139
  end
@@ -147,8 +142,11 @@ module CASClient
147
142
  # with cucumber and other tools.
148
143
  # use like
149
144
  # CASClient::Frameworks::Rails::Filter.fake("homer")
150
- def fake(username)
145
+ # you can also fake extra attributes by including a second parameter
146
+ # CASClient::Frameworks::Rails::Filter.fake("homer", {:roles => ['dad', 'husband']})
147
+ def fake(username, extra_attributes = nil)
151
148
  @@fake_user = username
149
+ @@fake_extra_attributes = extra_attributes
152
150
  end
153
151
 
154
152
  def use_gatewaying?
@@ -164,6 +162,41 @@ module CASClient
164
162
  log.debug("Generated login url: #{url}")
165
163
  return url
166
164
  end
165
+
166
+ # allow controllers to reuse the existing config to auto-login to
167
+ # the service
168
+ #
169
+ # Use this from within a controller. Pass the controller, the
170
+ # login-credentials and the path that you want the user
171
+ # resdirected to on success.
172
+ #
173
+ # When writing a login-action you must check the return-value of
174
+ # the response to see if it failed!
175
+ #
176
+ # If it worked - you need to redirect the user to the service -
177
+ # path, because that has the ticket that will *actually* log them
178
+ # into your system
179
+ #
180
+ # example:
181
+ # def autologin
182
+ # resp = CASClient::Frameworks::Rails::Filter.login_to_service(self, credentials, dashboard_url)
183
+ # if resp.is_faiulure?
184
+ # flash[:error] = 'Login failed'
185
+ # render :action => 'login'
186
+ # else
187
+ # return redirect_to(@resp.service_redirect_url)
188
+ # end
189
+ # end
190
+ def login_to_service(controller, credentials, return_path)
191
+ resp = @@client.login_to_service(credentials, return_path)
192
+ if resp.is_failure?
193
+ log.info("Validation failed for service #{return_path.inspect} reason: '#{resp.failure_message}'")
194
+ else
195
+ log.info("Ticket #{resp.ticket.inspect} for service #{return_path.inspect} is VALID.")
196
+ end
197
+
198
+ resp
199
+ end
167
200
 
168
201
  # Clears the given controller's local Rails session, does some local
169
202
  # CAS cleanup, and redirects to the CAS logout page. Additionally, the
@@ -179,17 +212,25 @@ module CASClient
179
212
  def logout(controller, service = nil)
180
213
  referer = service || controller.request.referer
181
214
  st = controller.session[:cas_last_valid_ticket]
182
- delete_service_session_lookup(st) if st
215
+ @@client.ticket_store.cleanup_service_session_lookup(st) if st
183
216
  controller.send(:reset_session)
184
217
  controller.send(:redirect_to, client.logout_url(referer))
185
218
  end
186
219
 
187
220
  def unauthorized!(controller, vr = nil)
188
- if controller.params[:format] == "xml"
221
+ format = controller.request.format.to_sym
222
+ format = (format == :js ? :json : format)
223
+ case format
224
+ when :xml, :json
189
225
  if vr
190
- controller.send(:render, :xml => "<errors><error>#{vr.failure_message}</error></errors>", :status => 401)
226
+ case format
227
+ when :xml
228
+ controller.send(:render, :xml => { :error => vr.failure_message }.to_xml(:root => 'errors'), :status => :unauthorized)
229
+ when :json
230
+ controller.send(:render, :json => { :errors => { :error => vr.failure_message }}, :status => :unauthorized)
231
+ end
191
232
  else
192
- controller.send(:head, 401)
233
+ controller.send(:head, :unauthorized)
193
234
  end
194
235
  else
195
236
  redirect_to_cas_for_authentication(controller)
@@ -238,8 +279,10 @@ module CASClient
238
279
 
239
280
  if controller.request.post? &&
240
281
  controller.params['logoutRequest'] &&
241
- controller.params['logoutRequest'] =~
242
- %r{^<samlp:LogoutRequest.*?<samlp:SessionIndex>(.*)</samlp:SessionIndex>}m
282
+ #This next line checks the logoutRequest value for both its regular and URI.escape'd form. I couldn't get
283
+ #it to work without URI.escaping it from rubycas server's side, this way it will work either way.
284
+ [controller.params['logoutRequest'],URI.unescape(controller.params['logoutRequest'])].find{|xml| xml =~
285
+ %r{^<samlp:LogoutRequest.*?<samlp:SessionIndex>(.*)</samlp:SessionIndex>}m}
243
286
  # TODO: Maybe check that the request came from the registered CAS server? Although this might be
244
287
  # pointless since it's easily spoofable...
245
288
  si = $~[1]
@@ -250,38 +293,8 @@ module CASClient
250
293
  end
251
294
 
252
295
  log.debug "Intercepted single-sign-out request for CAS session #{si.inspect}."
253
-
254
- begin
255
- required_sess_store = ActiveRecord::SessionStore
256
- current_sess_store = ActionController::Base.session_store
257
- rescue NameError
258
- # for older versions of Rails (prior to 2.3)
259
- required_sess_store = CGI::Session::ActiveRecordStore
260
- current_sess_store = ActionController::Base.session_options[:database_manager]
261
- end
262
296
 
263
-
264
- if current_sess_store == required_sess_store
265
- session_id = read_service_session_lookup(si)
266
-
267
- if session_id
268
- session = current_sess_store::Session.find_by_session_id(session_id)
269
- if session
270
- session.destroy
271
- log.debug("Destroyed #{session.inspect} for session #{session_id.inspect} corresponding to service ticket #{si.inspect}.")
272
- else
273
- log.debug("Data for session #{session_id.inspect} was not found. It may have already been cleared by a local CAS logout request.")
274
- end
275
-
276
- log.info("Single-sign-out for session #{session_id.inspect} completed successfuly.")
277
- else
278
- log.warn("Couldn't destroy session with SessionIndex #{si} because no corresponding session id could be looked up.")
279
- end
280
- else
281
- log.error "Cannot process logout request because this Rails application's session store is "+
282
- " #{current_sess_store.name.inspect}. Single Sign-Out only works with the "+
283
- " #{required_sess_store.name.inspect} session store."
284
- end
297
+ @@client.ticket_store.process_single_sign_out(si)
285
298
 
286
299
  # Return true to indicate that a single-sign-out request was detected
287
300
  # and that further processing of the request is unnecessary.
@@ -322,49 +335,9 @@ module CASClient
322
335
  log.debug("Guessed service url: #{service_url.inspect}")
323
336
  return service_url
324
337
  end
325
-
326
- # Creates a file in tmp/sessions linking a SessionTicket
327
- # with the local Rails session id. The file is named
328
- # cas_sess.<session ticket> and its text contents is the corresponding
329
- # Rails session id.
330
- # Returns the filename of the lookup file created.
331
- def store_service_session_lookup(st, sid)
332
- st = st.ticket if st.kind_of? ServiceTicket
333
- f = File.new(filename_of_service_session_lookup(st), 'w')
334
- f.write(sid)
335
- f.close
336
- return f.path
337
- end
338
-
339
- # Returns the local Rails session ID corresponding to the given
340
- # ServiceTicket. This is done by reading the contents of the
341
- # cas_sess.<session ticket> file created in a prior call to
342
- # #store_service_session_lookup.
343
- def read_service_session_lookup(st)
344
- st = st.ticket if st.kind_of? ServiceTicket
345
- ssl_filename = filename_of_service_session_lookup(st)
346
- return File.exists?(ssl_filename) && IO.read(ssl_filename)
347
- end
348
-
349
- # Removes a stored relationship between a ServiceTicket and a local
350
- # Rails session id. This should be called when the session is being
351
- # closed.
352
- #
353
- # See #store_service_session_lookup.
354
- def delete_service_session_lookup(st)
355
- st = st.ticket if st.kind_of? ServiceTicket
356
- ssl_filename = filename_of_service_session_lookup(st)
357
- File.delete(ssl_filename) if File.exists?(ssl_filename)
358
- end
359
-
360
- # Returns the path and filename of the service session lookup file.
361
- def filename_of_service_session_lookup(st)
362
- st = st.ticket if st.kind_of? ServiceTicket
363
- return "#{RAILS_ROOT}/tmp/sessions/cas_sess.#{st}"
364
- end
365
338
  end
366
339
  end
367
-
340
+
368
341
  class GatewayFilter < Filter
369
342
  def self.use_gatewaying?
370
343
  return true unless @@config[:use_gatewaying] == false