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.
- data/Gemfile +13 -0
- data/Gemfile.lock +30 -0
- data/History.txt +21 -0
- data/README.rdoc +24 -20
- data/Rakefile +43 -54
- data/VERSION +1 -0
- data/examples/rails/README +16 -0
- data/examples/rails/app/controllers/advanced_example_controller.rb +31 -0
- data/examples/rails/app/controllers/application.rb +2 -0
- data/examples/rails/app/controllers/simple_example_controller.rb +16 -0
- data/examples/rails/app/views/advanced_example/index.html.erb +13 -0
- data/examples/rails/app/views/advanced_example/my_account.html.erb +11 -0
- data/examples/rails/app/views/simple_example/index.html.erb +6 -0
- data/examples/rails/config/boot.rb +109 -0
- data/examples/rails/config/environment.rb +39 -0
- data/examples/rails/config/environments/development.rb +17 -0
- data/examples/rails/config/environments/production.rb +22 -0
- data/examples/rails/config/environments/test.rb +22 -0
- data/examples/rails/config/initializers/inflections.rb +10 -0
- data/examples/rails/config/initializers/mime_types.rb +5 -0
- data/examples/rails/config/initializers/new_rails_defaults.rb +17 -0
- data/examples/rails/config/routes.rb +4 -0
- data/examples/rails/log/development.log +946 -0
- data/examples/rails/log/production.log +0 -0
- data/examples/rails/log/server.log +0 -0
- data/examples/rails/log/test.log +0 -0
- data/examples/rails/script/about +4 -0
- data/examples/rails/script/console +3 -0
- data/examples/rails/script/server +3 -0
- data/lib/casclient/client.rb +49 -36
- data/lib/casclient/frameworks/rails/cas_proxy_callback_controller.rb +5 -39
- data/lib/casclient/frameworks/rails/filter.rb +86 -113
- data/lib/casclient/responses.rb +29 -16
- data/lib/casclient/tickets/storage/active_record_ticket_store.rb +67 -0
- data/lib/casclient/tickets/storage.rb +167 -0
- data/lib/casclient/tickets.rb +3 -3
- data/lib/casclient.rb +3 -2
- data/lib/rubycas-client.rb +1 -5
- data/rails_generators/active_record_ticket_store/USAGE +9 -0
- data/rails_generators/active_record_ticket_store/active_record_ticket_store_generator.rb +29 -0
- data/rails_generators/active_record_ticket_store/templates/README +1 -0
- data/rails_generators/active_record_ticket_store/templates/migration.rb +24 -0
- data/rubycas-client.gemspec +103 -0
- data/test/teststrap.rb +10 -0
- data/test/units/casclient/frameworks/rails/filter_test.rb +184 -0
- metadata +148 -47
- data/Manifest.txt +0 -23
- data/examples/merb/README.textile +0 -12
- data/examples/merb/Rakefile +0 -35
- data/examples/merb/merb.thor +0 -2020
- data/examples/merb/merb_auth_cas.rb +0 -67
- data/examples/merb/spec/spec_helper.rb +0 -24
- data/init.rb +0 -6
- data/lib/casclient/frameworks/merb/filter.rb +0 -105
- data/lib/casclient/frameworks/merb/strategy.rb +0 -110
- data/lib/casclient/version.rb +0 -9
- data/setup.rb +0 -1585
File without changes
|
File without changes
|
File without changes
|
data/lib/casclient/client.rb
CHANGED
@@ -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[
|
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
|
-
|
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
|
-
|
197
|
+
response = request_cas_response(uri, ProxyResponse)
|
170
198
|
|
171
|
-
pt = ProxyTicket.new(
|
172
|
-
pt.
|
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
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
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("#{
|
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
|
38
|
-
|
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
|
-
|
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.
|
57
|
-
|
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 #{
|
68
|
-
controller.session[client.username_session_key] =
|
69
|
-
controller.session[client.extra_attributes_session_key] = HashWithIndifferentAccess.new(
|
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
|
72
|
-
log.debug("Extra user attributes provided along with ticket #{st.ticket.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] =
|
74
|
+
controller.session[:casfilteruser] = st.user
|
79
75
|
|
80
76
|
if config[:enable_single_sign_out]
|
81
|
-
|
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
|
91
|
-
unless controller.session[:cas_pgt] && controller.session[:cas_pgt].ticket && controller.session[:cas_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(
|
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 #{
|
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 #{
|
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 -- #{
|
112
|
-
unauthorized!(controller,
|
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] =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
242
|
-
|
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
|