vibes-rubycas-client 2.3.0.alpha → 2.3.0.alpha2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -2
- data/Gemfile.lock +5 -2
- data/History.txt +21 -0
- data/README.rdoc +14 -12
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/casclient/client.rb +19 -26
- data/lib/casclient/frameworks/rails/cas_proxy_callback_controller.rb +5 -31
- data/lib/casclient/frameworks/rails/filter.rb +15 -48
- data/lib/casclient/responses.rb +12 -13
- data/lib/casclient/tickets/storage.rb +131 -0
- data/lib/casclient.rb +1 -0
- data/test/teststrap.rb +7 -0
- data/test/units/casclient/frameworks/rails/filter_test.rb +32 -0
- data/vibes-rubycas-client.gemspec +11 -8
- metadata +27 -12
- data/.source_index +0 -0
- data/lib/casclient/frameworks/merb/filter.rb +0 -105
- data/lib/casclient/frameworks/merb/strategy.rb +0 -110
data/Gemfile
CHANGED
@@ -6,10 +6,11 @@ source "http://rubygems.org"
|
|
6
6
|
# Add dependencies to develop your gem here.
|
7
7
|
# Include everything needed to run rake, tests, features, etc.
|
8
8
|
group :development do
|
9
|
-
gem "
|
9
|
+
gem "riot"
|
10
|
+
gem "rr"
|
10
11
|
gem "bundler", "~> 1.0.0"
|
11
12
|
gem "jeweler", "~> 1.6.2"
|
12
|
-
gem "rcov"
|
13
|
+
gem "rcov"
|
13
14
|
end
|
14
15
|
|
15
16
|
gem "activesupport", "~> 2.3.11"
|
data/Gemfile.lock
CHANGED
data/History.txt
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
= RubyCAS-Client Changelog
|
2
2
|
|
3
|
+
== Version 2.3.0 :: Prerelease
|
4
|
+
|
5
|
+
* New Functionality
|
6
|
+
* Add configuration option to expect complex extra attributes to be encoded
|
7
|
+
in json instead of yaml
|
8
|
+
* Split out storage mechanism for single sign out and proxy ticket storage so
|
9
|
+
that it is modular
|
10
|
+
|
11
|
+
* Changes to existing functionality
|
12
|
+
* Change gem building from hoe to jeweler
|
13
|
+
* expect extra attributes to be nested under a cas:attributes elemenet to
|
14
|
+
improve compatibility with other extra attribute implementations
|
15
|
+
* Unauthorized requests to URLs ending in .json now show an JSON formatted
|
16
|
+
response
|
17
|
+
|
18
|
+
* Bug Fixes
|
19
|
+
* Fixed bug introduced by upstream patch that broke proxy ticket validation
|
20
|
+
when using extra attributes
|
21
|
+
* Fixed bug where extra attributes key was set on the session with a null
|
22
|
+
value when faking with no extra attributes
|
23
|
+
|
3
24
|
== Version 2.2.1 :: 2010-06-24
|
4
25
|
|
5
26
|
* Removed a 3rd party patch to the logging mechanism that broke the client under
|
data/README.rdoc
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
= RubyCAS-Client
|
2
2
|
|
3
|
-
|
3
|
+
Authors:: Matt Campbell and Rahul Joshi, forked from original project by Matt Zukowski <matt AT roughest DOT net>; inspired by code by Ola Bini <ola.bini AT ki DOT se> and Matt Walker <mwalker AT tamu DOT edu>
|
4
4
|
Copyright:: Portions contributed by Matt Zukowski are copyright (c) 2009 Urbacon Ltd.
|
5
|
+
Protions contributed by Matt Campbell and Rahul Joshi are copyright (c) 2011 Vibes Media LLC.
|
5
6
|
Other portions are copyright of their respective authors.
|
6
7
|
License:: MIT License
|
7
|
-
Websites:: http://github.com/
|
8
|
-
http://
|
9
|
-
http://
|
10
|
-
|
8
|
+
Websites:: http://github.com/vibes/rubycas-client
|
9
|
+
http://github.com/vibes/rubycas-client/wiki
|
10
|
+
http://rubydoc.info/github/vibes/rubycas-client/master/frames
|
11
11
|
|
12
12
|
|
13
13
|
=== RubyCAS-Client is a Ruby client library for Yale's Central Authentication Service (CAS) protocol.
|
@@ -234,7 +234,6 @@ In your <tt>config/environment.rb</tt>:
|
|
234
234
|
|
235
235
|
CASClient::Frameworks::Rails::Filter.configure(
|
236
236
|
:cas_base_url => "https://cas.example.foo/",
|
237
|
-
:proxy_retrieval_url => "https://cas-proxy-callback.example.foo/cas_proxy_callback/retrieve_pgt",
|
238
237
|
:proxy_callback_url => "https://cas-proxy-callback.example.foo/cas_proxy_callback/receive_pgt",
|
239
238
|
:logger => cas_logger
|
240
239
|
)
|
@@ -254,12 +253,9 @@ but your Rails server wouldn't respond to the CAS server's callback until the CA
|
|
254
253
|
|
255
254
|
The simplest workaround is this:
|
256
255
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
3. Make sure that the server is up and running, and configure your proxy_callback_url and proxy_retrieval_url to point
|
261
|
-
to the new server as described above (or rather, make Pound point to the new server, if that's how you're handling https).
|
262
|
-
|
256
|
+
Run rails using a server that handles multiple concurrent requests. In development, you can use Phusion Passenger Standalone,
|
257
|
+
POW (http://pow.cx/), unicorn and many others. In production, I imagine you already support multiple concurrent requests.
|
258
|
+
|
263
259
|
That's it. The proxy_callback_controller doesn't require any additional configuration. It doesn't access the database
|
264
260
|
or anything of that sort.
|
265
261
|
|
@@ -315,6 +311,12 @@ In your test or Cucumber step definition, simply fake out CAS.
|
|
315
311
|
This functionality was present in the original version of this plugin.
|
316
312
|
The value of the username is stored in session[:cas_user] (or the user specified field) and session[:casfilteruser] for backwards-compatibility.
|
317
313
|
|
314
|
+
If you need to fake out extra attributes, you can do so like this:
|
315
|
+
|
316
|
+
CASClient::Frameworks::Rails::Filter.fake("homer", {:role => "user", :email => "homer@test.foo"})
|
317
|
+
|
318
|
+
And the extra attributes will get put in the proper place in the session.
|
319
|
+
|
318
320
|
== License
|
319
321
|
|
320
322
|
RubyCAS-Client is licensed for use under the terms of the MIT License.
|
data/Rakefile
CHANGED
@@ -27,8 +27,8 @@ Jeweler::RubygemsDotOrgTasks.new
|
|
27
27
|
|
28
28
|
require 'rake/testtask'
|
29
29
|
Rake::TestTask.new(:test) do |test|
|
30
|
-
test.libs << '
|
31
|
-
test.pattern = 'test
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
32
|
test.verbose = true
|
33
33
|
end
|
34
34
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.3.0.
|
1
|
+
2.3.0.alpha2
|
data/lib/casclient/client.rb
CHANGED
@@ -3,6 +3,7 @@ module CASClient
|
|
3
3
|
class Client
|
4
4
|
attr_reader :cas_base_url
|
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,6 +16,12 @@ 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(/\/$/, '')
|
19
26
|
|
20
27
|
@login_url = conf[:login_url]
|
@@ -24,13 +31,16 @@ module CASClient
|
|
24
31
|
@service_url = conf[:service_url]
|
25
32
|
@force_ssl_verification = conf[:force_ssl_verification]
|
26
33
|
@proxy_callback_url = conf[:proxy_callback_url]
|
27
|
-
@proxy_retrieval_url = conf[:proxy_retrieval_url]
|
28
34
|
|
29
35
|
@username_session_key = conf[:username_session_key] || :cas_user
|
30
36
|
@extra_attributes_session_key = conf[:extra_attributes_session_key] || :cas_extra_attributes
|
37
|
+
@ticket_store_class = conf[:ticket_store] || CASClient::Tickets::Storage::LocalDirTicketStore
|
38
|
+
@ticket_store = @ticket_store_class.new conf[:ticket_store_config]
|
39
|
+
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
40
|
|
32
41
|
@log = CASClient::LoggerWrapper.new
|
33
42
|
@log.set_real_logger(conf[:logger]) if conf[:logger]
|
43
|
+
@conf_options = conf
|
34
44
|
end
|
35
45
|
|
36
46
|
def login_url
|
@@ -181,28 +191,11 @@ module CASClient
|
|
181
191
|
end
|
182
192
|
|
183
193
|
def retrieve_proxy_granting_ticket(pgt_iou)
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
# https = Net::HTTP.new(uri.host, uri.port)
|
191
|
-
# https.use_ssl = (uri.scheme == 'https')
|
192
|
-
# res = https.post(uri.path, ';')
|
193
|
-
uri = URI.parse(uri) unless uri.kind_of? URI
|
194
|
-
https = Net::HTTP.new(uri.host, uri.port)
|
195
|
-
https.use_ssl = (uri.scheme == 'https')
|
196
|
-
https.verify_mode = (@force_ssl_verification ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE)
|
197
|
-
|
198
|
-
res = https.start do |conn|
|
199
|
-
conn.get("#{uri.path}?#{uri.query}")
|
200
|
-
end
|
201
|
-
|
202
|
-
|
203
|
-
raise CASException, res.body unless res.kind_of? Net::HTTPSuccess
|
204
|
-
|
205
|
-
ProxyGrantingTicket.new(res.body.strip, pgt_iou)
|
194
|
+
pgt = @ticket_store.retrieve_pgt(pgt_iou)
|
195
|
+
|
196
|
+
raise CASException, "Couldn't find pgt for pgt_iou #{pgt_iou}" unless pgt
|
197
|
+
|
198
|
+
ProxyGrantingTicket.new(pgt, pgt_iou)
|
206
199
|
end
|
207
200
|
|
208
201
|
def add_service_to_login_url(service_url)
|
@@ -214,7 +207,7 @@ module CASClient
|
|
214
207
|
private
|
215
208
|
# Fetches a CAS response of the given type from the given URI.
|
216
209
|
# Type should be either ValidationResponse or ProxyResponse.
|
217
|
-
def request_cas_response(uri, type)
|
210
|
+
def request_cas_response(uri, type, options={})
|
218
211
|
log.debug "Requesting CAS response for URI #{uri}"
|
219
212
|
|
220
213
|
uri = URI.parse(uri) unless uri.kind_of? URI
|
@@ -240,8 +233,8 @@ module CASClient
|
|
240
233
|
log.error "CAS server responded with an error! (#{raw_res.inspect})"
|
241
234
|
raise "The CAS authentication server at #{uri} responded with an error (#{raw_res.inspect})!"
|
242
235
|
end
|
243
|
-
|
244
|
-
type.new(raw_res.body)
|
236
|
+
|
237
|
+
type.new(raw_res.body, @conf_options)
|
245
238
|
end
|
246
239
|
|
247
240
|
# Submits some data to the given URI and returns a Net::HTTPResponse.
|
@@ -24,6 +24,11 @@ class CasProxyCallbackController < ActionController::Base
|
|
24
24
|
render :text => "Okay, the server is up, but please specify a pgtIou and pgtId." and return unless pgtIou and pgtId
|
25
25
|
|
26
26
|
# TODO: pstore contents should probably be encrypted...
|
27
|
+
|
28
|
+
casclient = CASClient::Frameworks::Rails::Filter.client
|
29
|
+
|
30
|
+
casclient.ticket_store.save_pgt_iou(pgtIou, pgtId)
|
31
|
+
|
27
32
|
pstore = open_pstore
|
28
33
|
|
29
34
|
pstore.transaction do
|
@@ -33,37 +38,6 @@ class CasProxyCallbackController < ActionController::Base
|
|
33
38
|
render :text => "PGT received. Thank you!" and return
|
34
39
|
end
|
35
40
|
|
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
41
|
private
|
68
42
|
def render_error(msg)
|
69
43
|
# Note that the error messages are mostly just for debugging, since the CAS server never reads them.
|
@@ -18,11 +18,10 @@ 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
|
21
|
+
controller.session[client.extra_attributes_session_key] = @@fake_extra_attributes if @@fake_extra_attributes
|
22
22
|
return true
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
25
|
last_st = controller.session[:cas_last_valid_ticket]
|
27
26
|
|
28
27
|
if single_sign_out(controller)
|
@@ -79,7 +78,7 @@ module CASClient
|
|
79
78
|
controller.session[:casfilteruser] = vr.user
|
80
79
|
|
81
80
|
if config[:enable_single_sign_out]
|
82
|
-
f = store_service_session_lookup(st, controller.request.session_options[:id] || controller.session.session_id)
|
81
|
+
f = @@client.ticket_store.store_service_session_lookup(st, controller.request.session_options[:id] || controller.session.session_id)
|
83
82
|
log.debug("Wrote service session lookup file to #{f.inspect} with session id #{controller.request.session_options[:id] || controller.session.session_id.inspect}.")
|
84
83
|
end
|
85
84
|
end
|
@@ -219,17 +218,25 @@ module CASClient
|
|
219
218
|
def logout(controller, service = nil)
|
220
219
|
referer = service || controller.request.referer
|
221
220
|
st = controller.session[:cas_last_valid_ticket]
|
222
|
-
delete_service_session_lookup(st) if st
|
221
|
+
@@client.ticket_store.delete_service_session_lookup(st) if st
|
223
222
|
controller.send(:reset_session)
|
224
223
|
controller.send(:redirect_to, client.logout_url(referer))
|
225
224
|
end
|
226
225
|
|
227
226
|
def unauthorized!(controller, vr = nil)
|
228
|
-
|
227
|
+
format = controller.request.format.to_sym
|
228
|
+
format = (format == :js ? :json : format)
|
229
|
+
case format
|
230
|
+
when :xml, :json
|
229
231
|
if vr
|
230
|
-
|
232
|
+
case format
|
233
|
+
when :xml
|
234
|
+
controller.send(:render, :xml => { :error => vr.failure_message }.to_xml(:root => 'errors'), :status => :unauthorized)
|
235
|
+
when :json
|
236
|
+
controller.send(:render, :json => { :errors => { :error => vr.failure_message }}, :status => :unauthorized)
|
237
|
+
end
|
231
238
|
else
|
232
|
-
controller.send(:head,
|
239
|
+
controller.send(:head, :unauthorized)
|
233
240
|
end
|
234
241
|
else
|
235
242
|
redirect_to_cas_for_authentication(controller)
|
@@ -302,7 +309,7 @@ module CASClient
|
|
302
309
|
|
303
310
|
|
304
311
|
if current_sess_store == required_sess_store
|
305
|
-
session_id = read_service_session_lookup(si)
|
312
|
+
session_id = @@client.ticket_store.read_service_session_lookup(si)
|
306
313
|
|
307
314
|
if session_id
|
308
315
|
session = current_sess_store::Session.find_by_session_id(session_id)
|
@@ -362,46 +369,6 @@ module CASClient
|
|
362
369
|
log.debug("Guessed service url: #{service_url.inspect}")
|
363
370
|
return service_url
|
364
371
|
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
372
|
end
|
406
373
|
end
|
407
374
|
|
data/lib/casclient/responses.rb
CHANGED
@@ -31,7 +31,8 @@ module CASClient
|
|
31
31
|
|
32
32
|
attr_reader :protocol, :user, :pgt_iou, :proxies, :extra_attributes
|
33
33
|
|
34
|
-
def initialize(raw_text)
|
34
|
+
def initialize(raw_text, options={})
|
35
|
+
conf_options = options
|
35
36
|
parse(raw_text)
|
36
37
|
end
|
37
38
|
|
@@ -66,23 +67,22 @@ module CASClient
|
|
66
67
|
end
|
67
68
|
|
68
69
|
@extra_attributes = {}
|
69
|
-
@xml.elements.to_a('//cas:authenticationSuccess/*').each do |el|
|
70
|
+
@xml.elements.to_a('//cas:authenticationSuccess/cas:attributes/*').each do |el|
|
70
71
|
# generating the hash requires prefixes to be defined, so add all of the namespaces
|
71
72
|
el.namespaces.each {|k,v| el.add_namespace(k,v)}
|
72
|
-
@extra_attributes.merge!(Hash.from_xml(el.to_s))
|
73
|
+
@extra_attributes.merge!(Hash.from_xml(el.to_s))
|
73
74
|
end
|
74
75
|
|
75
76
|
# unserialize extra attributes
|
76
77
|
@extra_attributes.each do |k, v|
|
77
|
-
Rails.logger.debug "#{k.inspect} => #{v.inspect}"
|
78
78
|
if v.blank?
|
79
79
|
@extra_attributes[k] = nil
|
80
|
+
elsif v.kind_of?(String)
|
81
|
+
@extra_attributes[k] = v
|
82
|
+
elsif conf.has_key?('encode_extra_attributes_as') && conf_options[:encode_extra_attributes_as] == :json
|
83
|
+
@extra_attributes[k] = JSON.parse(v)
|
80
84
|
else
|
81
|
-
|
82
|
-
@extra_attributes[k] = JSON.parse(v)
|
83
|
-
rescue JSON::ParserError => e
|
84
|
-
@extra_attributes[k] = v
|
85
|
-
end
|
85
|
+
@extra_attributes[k] = YAML.load(v)
|
86
86
|
end
|
87
87
|
end
|
88
88
|
elsif is_failure?
|
@@ -92,9 +92,8 @@ module CASClient
|
|
92
92
|
# this should never happen, since the response should already have been recognized as invalid
|
93
93
|
raise BadResponseException, "BAD CAS RESPONSE:\n#{raw_text.inspect}\n\nXML DOC:\n#{doc.inspect}"
|
94
94
|
end
|
95
|
-
|
96
95
|
end
|
97
|
-
|
96
|
+
|
98
97
|
def is_success?
|
99
98
|
(instance_variable_defined?(:@valid) && @valid) || (protocol > 1.0 && xml.name == "authenticationSuccess")
|
100
99
|
end
|
@@ -111,7 +110,7 @@ module CASClient
|
|
111
110
|
|
112
111
|
attr_reader :proxy_ticket
|
113
112
|
|
114
|
-
def initialize(raw_text)
|
113
|
+
def initialize(raw_text, options={})
|
115
114
|
parse(raw_text)
|
116
115
|
end
|
117
116
|
|
@@ -149,7 +148,7 @@ module CASClient
|
|
149
148
|
attr_reader :tgt, :ticket, :service_redirect_url
|
150
149
|
attr_reader :failure_message
|
151
150
|
|
152
|
-
def initialize(http_response = nil)
|
151
|
+
def initialize(http_response = nil, options={})
|
153
152
|
parse_http_response(http_response) if http_response
|
154
153
|
end
|
155
154
|
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module CASClient
|
2
|
+
module Tickets
|
3
|
+
module Storage
|
4
|
+
class AbstractTicketStore
|
5
|
+
def store_service_session_lookup(st, sid)
|
6
|
+
raise 'Implement this in a subclass!'
|
7
|
+
end
|
8
|
+
|
9
|
+
def read_servcie_session_lookup(st)
|
10
|
+
raise 'Implement this in a subclass!'
|
11
|
+
end
|
12
|
+
|
13
|
+
def delete_service_session_lookup(st)
|
14
|
+
raise 'Implement this in a subclass!'
|
15
|
+
end
|
16
|
+
|
17
|
+
def save_pgt_iou(pgt_iou, pgt)
|
18
|
+
raise 'Implement this in a subclass!'
|
19
|
+
end
|
20
|
+
|
21
|
+
def retrieve_pgt(pgt_iou)
|
22
|
+
raise 'Implement this in a subclass!'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# A Ticket Store that keeps it's tickets in a directory on the local filesystem.
|
27
|
+
# Service tickets are stored under tmp/sessions by default
|
28
|
+
# and Proxy Granting Tickets and their IOUs are stored in tmp/cas_pgt.pstore
|
29
|
+
# This Ticket Store works fine for small sites but will most likely have
|
30
|
+
# concurrency problems under heavy load. It also requires that all your
|
31
|
+
# worker processes have access to a shared file system.
|
32
|
+
#
|
33
|
+
# This ticket store takes the following config parameters
|
34
|
+
# :storage_dir - The directory to store data in. Defaults to RAILS_ROOT/tmp
|
35
|
+
# :service_session_lookup_dir - The directory to store Service Ticket/Session ID files in. Defaults to :storage_dir/sessions
|
36
|
+
# :pgt_store_path - The location to store the pgt PStore file. Defaults to :storage_dir/cas_pgt.pstore
|
37
|
+
class LocalDirTicketStore < AbstractTicketStore
|
38
|
+
require 'pstore'
|
39
|
+
|
40
|
+
def initialize(config={})
|
41
|
+
config ||= {}
|
42
|
+
@tmp_dir = config[:storage_dir] || "#{RAILS_ROOT}/tmp"
|
43
|
+
@service_session_lookup_dir = config[:service_session_lookup_dir] || "#{@tmp_dir}/sessions"
|
44
|
+
@pgt_store_path = config[:pgt_store_path] || "#{@tmp_dir}/cas_pgt.pstore"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Creates a file in tmp/sessions linking a SessionTicket
|
48
|
+
# with the local Rails session id. The file is named
|
49
|
+
# cas_sess.<session ticket> and its text contents is the corresponding
|
50
|
+
# Rails session id.
|
51
|
+
# Returns the filename of the lookup file created.
|
52
|
+
def store_service_session_lookup(st, sid)
|
53
|
+
raise CASException, "No service_ticket specified." unless st
|
54
|
+
raise CASException, "No session_id specified." unless sid
|
55
|
+
|
56
|
+
st = st.ticket if st.kind_of? ServiceTicket
|
57
|
+
f = File.new(filename_of_service_session_lookup(st), 'w')
|
58
|
+
f.write(sid)
|
59
|
+
f.close
|
60
|
+
return f.path
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the local Rails session ID corresponding to the given
|
64
|
+
# ServiceTicket. This is done by reading the contents of the
|
65
|
+
# cas_sess.<session ticket> file created in a prior call to
|
66
|
+
# #store_service_session_lookup.
|
67
|
+
def read_service_session_lookup(st)
|
68
|
+
raise CASException, "No service_ticket specified." unless st
|
69
|
+
|
70
|
+
st = st.ticket if st.kind_of? ServiceTicket
|
71
|
+
ssl_filename = filename_of_service_session_lookup(st)
|
72
|
+
return File.exists?(ssl_filename) && IO.read(ssl_filename)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Removes a stored relationship between a ServiceTicket and a local
|
76
|
+
# Rails session id. This should be called when the session is being
|
77
|
+
# closed.
|
78
|
+
#
|
79
|
+
# See #store_service_session_lookup.
|
80
|
+
def delete_service_session_lookup(st)
|
81
|
+
raise CASException, "No service_ticket specified." unless st
|
82
|
+
|
83
|
+
st = st.ticket if st.kind_of? ServiceTicket
|
84
|
+
ssl_filename = filename_of_service_session_lookup(st)
|
85
|
+
File.delete(ssl_filename) if File.exists?(ssl_filename)
|
86
|
+
end
|
87
|
+
|
88
|
+
def save_pgt_iou(pgt_iou, pgt)
|
89
|
+
# TODO: pstore contents should probably be encrypted...
|
90
|
+
pstore = open_pstore
|
91
|
+
|
92
|
+
pstore.transaction do
|
93
|
+
pstore[pgt_iou] = pgt
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def retrieve_pgt(pgt_iou)
|
98
|
+
raise CASException, "No pgt_iou specified. Cannot retrieve the pgt." unless pgt_iou
|
99
|
+
|
100
|
+
pstore = open_pstore
|
101
|
+
|
102
|
+
pgt = nil
|
103
|
+
pstore.transaction do
|
104
|
+
pgt = pstore[pgt_iou]
|
105
|
+
end
|
106
|
+
|
107
|
+
raise CASException, "Invalid pgt_iou specified. Perhaps this pgt has already been retrieved?" unless pgt
|
108
|
+
|
109
|
+
# TODO: need to periodically clean the storage, otherwise it will just keep growing
|
110
|
+
pstore.transaction do
|
111
|
+
pstore.delete pgt_iou
|
112
|
+
end
|
113
|
+
|
114
|
+
pgt
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# Returns the path and filename of the service session lookup file.
|
120
|
+
def filename_of_service_session_lookup(st)
|
121
|
+
st = st.ticket if st.kind_of? ServiceTicket
|
122
|
+
return "#{@service_session_lookup_dir}/cas_sess.#{st}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def open_pstore
|
126
|
+
PStore.new(@pgt_store_path)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/casclient.rb
CHANGED
data/test/teststrap.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'teststrap'
|
2
|
+
require 'casclient/frameworks/rails/filter'
|
3
|
+
|
4
|
+
context CASClient::Frameworks::Rails::Filter do
|
5
|
+
helper(:controller_with_session) do |session|
|
6
|
+
controller = Object.new
|
7
|
+
stub(controller).session {session}
|
8
|
+
controller
|
9
|
+
end
|
10
|
+
setup do
|
11
|
+
CASClient::Frameworks::Rails::Filter.configure(
|
12
|
+
:cas_base_url => 'http://test.local/',
|
13
|
+
:logger => stub!
|
14
|
+
)
|
15
|
+
end
|
16
|
+
context "that has fake called with a username" do
|
17
|
+
setup { CASClient::Frameworks::Rails::Filter.fake('tester@test.com') }
|
18
|
+
should 'set the session user on #filter' do
|
19
|
+
setup { Hash.new }
|
20
|
+
CASClient::Frameworks::Rails::Filter.filter(controller_with_session(topic))
|
21
|
+
topic
|
22
|
+
end.equals :cas_user => 'tester@test.com', :casfilteruser => 'tester@test.com'
|
23
|
+
end
|
24
|
+
context "that has fake called with a username and attributes" do
|
25
|
+
setup { CASClient::Frameworks::Rails::Filter.fake('tester@test.com', {:test => 'stuff', :this => 'that'}) }
|
26
|
+
should 'set the session user and attributes on #filter' do
|
27
|
+
setup { Hash.new }
|
28
|
+
CASClient::Frameworks::Rails::Filter.filter(controller_with_session(topic))
|
29
|
+
topic
|
30
|
+
end.equals :cas_user => 'tester@test.com', :casfilteruser => 'tester@test.com', :cas_extra_attributes => {:test => 'stuff', :this => 'that' }
|
31
|
+
end
|
32
|
+
end
|
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{vibes-rubycas-client}
|
8
|
-
s.version = "2.3.0.
|
8
|
+
s.version = "2.3.0.alpha2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Matt Campbell", "Rahul Joshi", "Matt Zukowski", "Matt Walker"]
|
12
|
-
s.date = %q{2011-06-
|
12
|
+
s.date = %q{2011-06-11}
|
13
13
|
s.description = %q{We've taken the rubycas-client and added some enterprisey features and improved compatibility with JASIG's CAS server}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE.txt",
|
@@ -17,7 +17,6 @@ Gem::Specification.new do |s|
|
|
17
17
|
]
|
18
18
|
s.files = [
|
19
19
|
".rvmrc",
|
20
|
-
".source_index",
|
21
20
|
"CHANGELOG.txt",
|
22
21
|
"Gemfile",
|
23
22
|
"Gemfile.lock",
|
@@ -57,13 +56,14 @@ Gem::Specification.new do |s|
|
|
57
56
|
"examples/rails/script/server",
|
58
57
|
"lib/casclient.rb",
|
59
58
|
"lib/casclient/client.rb",
|
60
|
-
"lib/casclient/frameworks/merb/filter.rb",
|
61
|
-
"lib/casclient/frameworks/merb/strategy.rb",
|
62
59
|
"lib/casclient/frameworks/rails/cas_proxy_callback_controller.rb",
|
63
60
|
"lib/casclient/frameworks/rails/filter.rb",
|
64
61
|
"lib/casclient/responses.rb",
|
65
62
|
"lib/casclient/tickets.rb",
|
63
|
+
"lib/casclient/tickets/storage.rb",
|
66
64
|
"lib/vibes-rubycas-client.rb",
|
65
|
+
"test/teststrap.rb",
|
66
|
+
"test/units/casclient/frameworks/rails/filter_test.rb",
|
67
67
|
"vibes-rubycas-client.gemspec"
|
68
68
|
]
|
69
69
|
s.homepage = %q{http://github.com/vibes/rubycas-client}
|
@@ -78,20 +78,23 @@ Gem::Specification.new do |s|
|
|
78
78
|
|
79
79
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
80
80
|
s.add_runtime_dependency(%q<activesupport>, ["~> 2.3.11"])
|
81
|
-
s.add_development_dependency(%q<
|
81
|
+
s.add_development_dependency(%q<riot>, [">= 0"])
|
82
|
+
s.add_development_dependency(%q<rr>, [">= 0"])
|
82
83
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
83
84
|
s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
|
84
85
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
85
86
|
else
|
86
87
|
s.add_dependency(%q<activesupport>, ["~> 2.3.11"])
|
87
|
-
s.add_dependency(%q<
|
88
|
+
s.add_dependency(%q<riot>, [">= 0"])
|
89
|
+
s.add_dependency(%q<rr>, [">= 0"])
|
88
90
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
89
91
|
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
90
92
|
s.add_dependency(%q<rcov>, [">= 0"])
|
91
93
|
end
|
92
94
|
else
|
93
95
|
s.add_dependency(%q<activesupport>, ["~> 2.3.11"])
|
94
|
-
s.add_dependency(%q<
|
96
|
+
s.add_dependency(%q<riot>, [">= 0"])
|
97
|
+
s.add_dependency(%q<rr>, [">= 0"])
|
95
98
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
96
99
|
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
97
100
|
s.add_dependency(%q<rcov>, [">= 0"])
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vibes-rubycas-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: -
|
4
|
+
hash: -3702664408
|
5
5
|
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 2
|
8
8
|
- 3
|
9
9
|
- 0
|
10
10
|
- alpha
|
11
|
-
|
11
|
+
- 2
|
12
|
+
version: 2.3.0.alpha2
|
12
13
|
platform: ruby
|
13
14
|
authors:
|
14
15
|
- Matt Campbell
|
@@ -19,7 +20,7 @@ autorequire:
|
|
19
20
|
bindir: bin
|
20
21
|
cert_chain: []
|
21
22
|
|
22
|
-
date: 2011-06-
|
23
|
+
date: 2011-06-11 00:00:00 -05:00
|
23
24
|
default_executable:
|
24
25
|
dependencies:
|
25
26
|
- !ruby/object:Gem::Dependency
|
@@ -49,12 +50,26 @@ dependencies:
|
|
49
50
|
segments:
|
50
51
|
- 0
|
51
52
|
version: "0"
|
52
|
-
name:
|
53
|
+
name: riot
|
53
54
|
version_requirements: *id002
|
54
55
|
prerelease: false
|
55
56
|
- !ruby/object:Gem::Dependency
|
56
57
|
type: :development
|
57
58
|
requirement: &id003 !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
name: rr
|
68
|
+
version_requirements: *id003
|
69
|
+
prerelease: false
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
type: :development
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
58
73
|
none: false
|
59
74
|
requirements:
|
60
75
|
- - ~>
|
@@ -66,11 +81,11 @@ dependencies:
|
|
66
81
|
- 0
|
67
82
|
version: 1.0.0
|
68
83
|
name: bundler
|
69
|
-
version_requirements: *
|
84
|
+
version_requirements: *id004
|
70
85
|
prerelease: false
|
71
86
|
- !ruby/object:Gem::Dependency
|
72
87
|
type: :development
|
73
|
-
requirement: &
|
88
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
74
89
|
none: false
|
75
90
|
requirements:
|
76
91
|
- - ~>
|
@@ -82,11 +97,11 @@ dependencies:
|
|
82
97
|
- 2
|
83
98
|
version: 1.6.2
|
84
99
|
name: jeweler
|
85
|
-
version_requirements: *
|
100
|
+
version_requirements: *id005
|
86
101
|
prerelease: false
|
87
102
|
- !ruby/object:Gem::Dependency
|
88
103
|
type: :development
|
89
|
-
requirement: &
|
104
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
90
105
|
none: false
|
91
106
|
requirements:
|
92
107
|
- - ">="
|
@@ -96,7 +111,7 @@ dependencies:
|
|
96
111
|
- 0
|
97
112
|
version: "0"
|
98
113
|
name: rcov
|
99
|
-
version_requirements: *
|
114
|
+
version_requirements: *id006
|
100
115
|
prerelease: false
|
101
116
|
description: We've taken the rubycas-client and added some enterprisey features and improved compatibility with JASIG's CAS server
|
102
117
|
email:
|
@@ -109,7 +124,6 @@ extra_rdoc_files:
|
|
109
124
|
- README.rdoc
|
110
125
|
files:
|
111
126
|
- .rvmrc
|
112
|
-
- .source_index
|
113
127
|
- CHANGELOG.txt
|
114
128
|
- Gemfile
|
115
129
|
- Gemfile.lock
|
@@ -149,13 +163,14 @@ files:
|
|
149
163
|
- examples/rails/script/server
|
150
164
|
- lib/casclient.rb
|
151
165
|
- lib/casclient/client.rb
|
152
|
-
- lib/casclient/frameworks/merb/filter.rb
|
153
|
-
- lib/casclient/frameworks/merb/strategy.rb
|
154
166
|
- lib/casclient/frameworks/rails/cas_proxy_callback_controller.rb
|
155
167
|
- lib/casclient/frameworks/rails/filter.rb
|
156
168
|
- lib/casclient/responses.rb
|
157
169
|
- lib/casclient/tickets.rb
|
170
|
+
- lib/casclient/tickets/storage.rb
|
158
171
|
- lib/vibes-rubycas-client.rb
|
172
|
+
- test/teststrap.rb
|
173
|
+
- test/units/casclient/frameworks/rails/filter_test.rb
|
159
174
|
- vibes-rubycas-client.gemspec
|
160
175
|
has_rdoc: true
|
161
176
|
homepage: http://github.com/vibes/rubycas-client
|
data/.source_index
DELETED
Binary file
|
@@ -1,105 +0,0 @@
|
|
1
|
-
module CASClient
|
2
|
-
module Frameworks
|
3
|
-
module Merb
|
4
|
-
module Filter
|
5
|
-
attr_reader :client
|
6
|
-
|
7
|
-
def cas_filter
|
8
|
-
@client ||= CASClient::Client.new(config)
|
9
|
-
|
10
|
-
service_ticket = read_ticket(self)
|
11
|
-
|
12
|
-
cas_login_url = client.add_service_to_login_url(read_service_url(self))
|
13
|
-
|
14
|
-
last_service_ticket = session[:cas_last_valid_ticket]
|
15
|
-
if (service_ticket && last_service_ticket &&
|
16
|
-
last_service_ticket.ticket == service_ticket.ticket &&
|
17
|
-
last_service_ticket.service == service_ticket.service)
|
18
|
-
|
19
|
-
# warn() rather than info() because we really shouldn't be re-validating the same ticket.
|
20
|
-
# The only time when this is acceptable is if the user manually does a refresh and the ticket
|
21
|
-
# happens to be in the URL.
|
22
|
-
log.warn("Reusing previously validated ticket since the new ticket and service are the same.")
|
23
|
-
service_ticket = last_service_ticket
|
24
|
-
elsif last_service_ticket &&
|
25
|
-
!config[:authenticate_on_every_request] &&
|
26
|
-
session[client.username_session_key]
|
27
|
-
# Re-use the previous ticket if the user already has a local CAS session (i.e. if they were already
|
28
|
-
# previously authenticated for this service). This is to prevent redirection to the CAS server on every
|
29
|
-
# request.
|
30
|
-
# This behaviour can be disabled (so that every request is routed through the CAS server) by setting
|
31
|
-
# the :authenticate_on_every_request config option to false.
|
32
|
-
log.debug "Existing local CAS session detected for #{session[client.username_session_key].inspect}. "+
|
33
|
-
"Previous ticket #{last_service_ticket.ticket.inspect} will be re-used."
|
34
|
-
service_ticket = last_service_ticket
|
35
|
-
end
|
36
|
-
|
37
|
-
if service_ticket
|
38
|
-
client.validate_service_ticket(service_ticket) unless service_ticket.has_been_validated?
|
39
|
-
validation_response = service_ticket.response
|
40
|
-
|
41
|
-
if service_ticket.is_valid?
|
42
|
-
log.info("Ticket #{service_ticket.inspect} for service #{service_ticket.service.inspect} " +
|
43
|
-
"belonging to user #{validation_response.user.inspect} is VALID.")
|
44
|
-
|
45
|
-
session[client.username_session_key] = validation_response.user
|
46
|
-
session[client.extra_attributes_session_key] = validation_response.extra_attributes
|
47
|
-
|
48
|
-
# Store the ticket in the session to avoid re-validating the same service
|
49
|
-
# ticket with the CAS server.
|
50
|
-
session[:cas_last_valid_ticket] = service_ticket
|
51
|
-
return true
|
52
|
-
else
|
53
|
-
log.warn("Ticket #{service_ticket.ticket.inspect} failed validation -- " +
|
54
|
-
"#{validation_response.failure_code}: #{validation_response.failure_message}")
|
55
|
-
redirect cas_login_url
|
56
|
-
throw :halt
|
57
|
-
end
|
58
|
-
else
|
59
|
-
log.warn("No ticket -- redirecting to #{cas_login_url}")
|
60
|
-
redirect cas_login_url
|
61
|
-
throw :halt
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
# Copied from Rails adapter
|
67
|
-
def read_ticket(controller)
|
68
|
-
ticket = controller.params[:ticket]
|
69
|
-
|
70
|
-
return nil unless ticket
|
71
|
-
|
72
|
-
log.debug("Request contains ticket #{ticket.inspect}.")
|
73
|
-
|
74
|
-
if ticket =~ /^PT-/
|
75
|
-
ProxyTicket.new(ticket, read_service_url(controller), controller.params[:renew])
|
76
|
-
else
|
77
|
-
ServiceTicket.new(ticket, read_service_url(controller), controller.params[:renew])
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
# Also copied from Rails adapter
|
82
|
-
def read_service_url(controller)
|
83
|
-
if config[:service_url]
|
84
|
-
log.debug("Using explicitly set service url: #{config[:service_url]}")
|
85
|
-
return config[:service_url]
|
86
|
-
end
|
87
|
-
|
88
|
-
params = controller.params.dup
|
89
|
-
params.delete(:ticket)
|
90
|
-
service_url = request.protocol + '://' + request.host / controller.url(params.to_hash.symbolize_keys!)
|
91
|
-
log.debug("Guessed service url: #{service_url.inspect}")
|
92
|
-
return service_url
|
93
|
-
end
|
94
|
-
|
95
|
-
def log
|
96
|
-
::Merb.logger
|
97
|
-
end
|
98
|
-
|
99
|
-
def config
|
100
|
-
::Merb::Plugins.config[:"rubycas-client"]
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
@@ -1,110 +0,0 @@
|
|
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
|
-
|