rubycas-client 2.2.1 → 2.3.0.rc1

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