rubycas-client 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +18 -0
- data/README +14 -11
- data/init.rb +2 -2
- data/lib/cas.rb +17 -12
- data/lib/cas_auth.rb +31 -21
- data/lib/cas_proxy_callback_controller.rb +5 -3
- metadata +2 -2
data/CHANGES
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
= RubyCAS-Client Changelog
|
2
2
|
|
3
|
+
== Version 0.12.0
|
4
|
+
|
5
|
+
* Prior to redirecting to the CAS login page, the client now stores the
|
6
|
+
current service URI in a session variable. This value is used to
|
7
|
+
validate the service ticket after the user comes back from the CAS
|
8
|
+
server's login page. This should address issues where redirection
|
9
|
+
from the CAS server resulted in a slightly different URI from the original
|
10
|
+
one used prior to login redirection (for example due to variations in the
|
11
|
+
way routing rules are applied by the server).
|
12
|
+
* The client now handles malformed CAS server responses more gracefully.
|
13
|
+
This makes debugging a malfunctioning CAS server somewhat easier.
|
14
|
+
* When receiving a proxy-granting ticket, the cas_proxy_callback_controller
|
15
|
+
can now take a parameter called 'pgt' (which is what ought to be used
|
16
|
+
according to the published CAS spec) or 'pgtId' (which is what the JA-SIG
|
17
|
+
CAS server uses).
|
18
|
+
* Logging has been somewhat quieted down. Many messages that were previously
|
19
|
+
logged as INFO are now logged as DEBUG.
|
20
|
+
|
3
21
|
== Version 0.11.0
|
4
22
|
|
5
23
|
* Added this changelog to advise users of major changes to the library.
|
data/README
CHANGED
@@ -30,29 +30,30 @@ Alternatively, the library is also available as a gem, which can be installed by
|
|
30
30
|
|
31
31
|
gem install rubycas-client
|
32
32
|
|
33
|
-
If your Rails application is under
|
34
|
-
you
|
33
|
+
If your Rails application is under Subversion control, you can also install the plugin as an svn:external, which will ensure that
|
34
|
+
you always have the latest version of RubyCAS-Client:
|
35
35
|
|
36
36
|
./script/plugin install -x http://rubycas-client.googlecode.com/svn/trunk/rubycas-client
|
37
37
|
|
38
|
-
Please contact the developers via the {
|
38
|
+
Please contact the developers via the {RubyForge page}[http://rubyforge.org/projects/rubycas-client] if you have bug fixes
|
39
39
|
or enhancements you would like to contribute back.
|
40
40
|
|
41
41
|
== Examples
|
42
42
|
|
43
|
-
====
|
43
|
+
==== Using RubyCAS-Client in Rails controllers
|
44
44
|
|
45
|
-
|
46
|
-
you'll need to <tt>require 'cas_auth'</tt> and <tt>require 'cas_proxy_callback_controller'</tt>):
|
45
|
+
<i>Note that from this point on we are assuming that you have a working CAS server up and running at an https URI.</i>
|
47
46
|
|
47
|
+
Somewhere in your <tt>config/environment.rb</tt> file add this:
|
48
|
+
|
49
|
+
require 'cas_auth'
|
48
50
|
CAS::Filter.cas_base_url = "https://login.example.com/cas"
|
49
51
|
|
50
|
-
You will also
|
52
|
+
You will also need to specify the server name where your CAS-protected app is running (i.e. the hostname of the app you are
|
53
|
+
adding CAS protection to, not the CAS server):
|
51
54
|
|
52
55
|
CAS::Filter.server_name = "yourapplication.example.com:3000"
|
53
56
|
|
54
|
-
The above setting might not be necessary if your application is running on the standard port 80.
|
55
|
-
|
56
57
|
Then, in your <tt>app/controllers/application.rb</tt> (or in whatever controller you want to add the CAS filter for):
|
57
58
|
|
58
59
|
before_filter CAS::Filter
|
@@ -68,7 +69,7 @@ filter. For example:
|
|
68
69
|
user record from the database), you can append another filter method that checks for this value and does whatever you need
|
69
70
|
it to do.
|
70
71
|
|
71
|
-
==== A more complicated example
|
72
|
+
==== A more complicated example
|
72
73
|
|
73
74
|
Here is a more complicated configuration showing most of the configuration options (this does not show proxy options however,
|
74
75
|
which are covered in the next section):
|
@@ -81,8 +82,10 @@ which are covered in the next section):
|
|
81
82
|
CAS::Filter.gateway = false # act as cas gateway? see http://www.ja-sig.org/products/cas/overview/protocol
|
82
83
|
CAS::Filter.session_username = :casfilteruser # this is the hash in the session where the authenticated username will be stored
|
83
84
|
|
85
|
+
Note that in this example we explicitly specified the login and validate URLs instead of letting RubyCAS-Client figure them out
|
86
|
+
based on <tt>CAS::Filter.cas_base_url</tt>.
|
84
87
|
|
85
|
-
==== How to act as a CAS proxy
|
88
|
+
==== How to act as a CAS proxy
|
86
89
|
|
87
90
|
CAS 2.0 has a built-in mechanism that allows a CAS-authenticated application to pass on its authentication to other applications.
|
88
91
|
An example where this is useful might be a portal site, where the user logs in to a central website and then gets forwarded to
|
data/init.rb
CHANGED
@@ -5,13 +5,13 @@ require 'cas_proxy_callback_controller'
|
|
5
5
|
#CAS::Filter.logger = RAILS_DEFAULT_LOGGER if !RAILS_DEFAULT_LOGGER.nil?
|
6
6
|
#CAS::Filter.logger = config.logger if !config.logger.nil?
|
7
7
|
|
8
|
-
CAS::Filter.logger = CAS::Logger.new("#{RAILS_ROOT}/log/cas_client_#{RAILS_ENV}.log"
|
8
|
+
CAS::Filter.logger = CAS::Logger.new("#{RAILS_ROOT}/log/cas_client_#{RAILS_ENV}.log")
|
9
9
|
CAS::Filter.logger.formatter = CAS::Logger::Formatter.new
|
10
10
|
|
11
11
|
#if RAILS_ENV == "production"
|
12
12
|
# CAS::Filter.logger.level = Logger::WARN
|
13
13
|
#else
|
14
|
-
CAS::Filter.logger.level = Logger::DEBUG
|
14
|
+
# CAS::Filter.logger.level = Logger::DEBUG
|
15
15
|
#end
|
16
16
|
|
17
17
|
|
data/lib/cas.rb
CHANGED
@@ -8,6 +8,8 @@ module CAS
|
|
8
8
|
end
|
9
9
|
class ValidationException < CASException
|
10
10
|
end
|
11
|
+
class MalformedServerResponseException < CASException
|
12
|
+
end
|
11
13
|
|
12
14
|
class Receipt
|
13
15
|
attr_accessor :validate_url, :pgt_iou, :primary_authentication, :proxy_callback_url, :proxy_list, :user_name
|
@@ -55,14 +57,12 @@ module CAS
|
|
55
57
|
end
|
56
58
|
|
57
59
|
class AbstractCASResponse
|
60
|
+
attr_reader :error_code, :error_message, :successful_authentication
|
58
61
|
|
59
62
|
def self.retrieve(uri_str)
|
60
|
-
# puts uri_str
|
61
63
|
prs = URI.parse(uri_str)
|
62
|
-
# puts prs.inspect
|
63
64
|
https = Net::HTTP.new(prs.host,prs.port)
|
64
|
-
|
65
|
-
https.use_ssl=true
|
65
|
+
https.use_ssl = true
|
66
66
|
https.start { |conn|
|
67
67
|
# TODO: make sure that HTTP status code in the response is 200... maybe throw exception if is 500?
|
68
68
|
conn.get("#{prs.path}?#{prs.query}").body.strip
|
@@ -71,17 +71,24 @@ module CAS
|
|
71
71
|
|
72
72
|
protected
|
73
73
|
def parse_unsuccessful(elm)
|
74
|
-
# puts "unsuccessful"
|
75
74
|
@error_message = elm.text.strip
|
76
75
|
@error_code = elm.attributes["code"].strip
|
77
76
|
@successful_authentication = false
|
78
77
|
end
|
79
78
|
|
80
79
|
def parse(str)
|
81
|
-
|
82
|
-
|
80
|
+
begin
|
81
|
+
doc = REXML::Document.new str
|
82
|
+
rescue REXML::ParseException => e
|
83
|
+
raise MalformedServerResponseException, "BAD RESPONSE FROM CAS SERVER:\n#{str}\n\nEXCEPTION:\n#{e}"
|
84
|
+
end
|
85
|
+
|
86
|
+
unless doc.elements && doc.elements["cas:serviceResponse"]
|
87
|
+
raise MalformedServerResponseException, "BAD RESPONSE FROM CAS SERVER:\n#{str}\n\nXML DOC:\n#{doc.inspect}"
|
88
|
+
end
|
89
|
+
|
83
90
|
resp = doc.elements["cas:serviceResponse"].elements[1]
|
84
|
-
|
91
|
+
|
85
92
|
if successful_response? resp
|
86
93
|
parse_successful(resp)
|
87
94
|
else
|
@@ -92,7 +99,7 @@ module CAS
|
|
92
99
|
|
93
100
|
class ServiceTicketValidator < AbstractCASResponse
|
94
101
|
attr_accessor :validate_url, :proxy_callback_url, :renew, :service_ticket, :service
|
95
|
-
attr_reader :pgt_iou, :user, :
|
102
|
+
attr_reader :pgt_iou, :user, :entire_response
|
96
103
|
|
97
104
|
def renewed?
|
98
105
|
renew
|
@@ -154,7 +161,7 @@ module CAS
|
|
154
161
|
protected
|
155
162
|
def parse_successful(elm)
|
156
163
|
super(elm)
|
157
|
-
|
164
|
+
|
158
165
|
proxies = elm.elements["cas:proxies"]
|
159
166
|
if proxies
|
160
167
|
proxies.elements.each("cas:proxy") { |prox|
|
@@ -171,9 +178,7 @@ module CAS
|
|
171
178
|
|
172
179
|
def request
|
173
180
|
url_building = "#{proxy_url}#{(url_building =~ /\?/)?'&':'?'}pgt=#{pgt}&targetService=#{CGI.escape(target_service)}"
|
174
|
-
# puts "REQUESTING:"+url_building
|
175
181
|
@@entire_response = ServiceTicketValidator.retrieve url_building
|
176
|
-
# puts @@entire_response.to_s
|
177
182
|
parse @@entire_response
|
178
183
|
end
|
179
184
|
|
data/lib/cas_auth.rb
CHANGED
@@ -106,26 +106,27 @@ module CAS
|
|
106
106
|
if !@@logout_url && @@login_url =~ %r{^(.+?)/[^/]*$}
|
107
107
|
@@logout_url = "#{$1}/logout"
|
108
108
|
end
|
109
|
-
logger.
|
109
|
+
logger.debug "Created logout url: #{@@logout_url}"
|
110
110
|
end
|
111
111
|
|
112
112
|
def logout_url(controller)
|
113
113
|
create_logout_url unless @@logout_url
|
114
114
|
url = redirect_url(controller,@@logout_url)
|
115
|
-
logger.
|
115
|
+
logger.debug "Logout url is: #{url}"
|
116
116
|
url
|
117
117
|
end
|
118
118
|
|
119
119
|
def logout_url=(url)
|
120
120
|
@@logout_url = url
|
121
|
-
logger.
|
121
|
+
logger.debug "Initialized logout url to: #{url}"
|
122
122
|
end
|
123
123
|
|
124
124
|
def cas_base_url=(url)
|
125
|
+
url.gsub!(/\/$/, '')
|
125
126
|
CAS::Filter.login_url = "#{url}/login"
|
126
127
|
CAS::Filter.validate_url = "#{url}/proxyValidate"
|
127
128
|
CAS::Filter.proxy_url = "#{url}/proxy"
|
128
|
-
logger.
|
129
|
+
logger.debug "Initialized CAS base url to: #{url}"
|
129
130
|
end
|
130
131
|
|
131
132
|
def fake
|
@@ -162,17 +163,18 @@ module CAS
|
|
162
163
|
def filter_r(controller)
|
163
164
|
logger.break
|
164
165
|
logger.info("Using real CAS filter in controller: #{controller}")
|
165
|
-
|
166
|
-
|
167
|
-
|
166
|
+
|
167
|
+
session_receipt = controller.session[:casfilterreceipt]
|
168
|
+
session_ticket = controller.session[:caslastticket]
|
169
|
+
ticket = controller.params[:ticket]
|
168
170
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
171
|
+
is_valid = false
|
172
|
+
|
173
|
+
if ticket and (!session_ticket or session_ticket != ticket)
|
174
|
+
log.info "A ticket parameter was given in the URI: #{ticket} and "+
|
175
|
+
(!session_ticket ? "there is no previous ticket for this session" :
|
176
|
+
"the ticket is different than the previous ticket, which was #{session_ticket}")
|
177
|
+
|
176
178
|
receipt = get_receipt_for_ticket(ticket, controller)
|
177
179
|
|
178
180
|
if receipt && validate_receipt(receipt)
|
@@ -188,16 +190,16 @@ module CAS
|
|
188
190
|
log.debug("Got PGT #{pgt} for PGT IOU #{receipt.pgt_iou}. This will be stored in the session.")
|
189
191
|
controller.session[:casfilterpgt] = pgt
|
190
192
|
else
|
191
|
-
log.
|
193
|
+
log.error("Failed to retrieve a PGT for PGT IOU #{receipt.pgt_iou}!")
|
192
194
|
end
|
193
195
|
end
|
194
196
|
|
195
197
|
is_valid = true
|
196
198
|
else
|
197
199
|
if receipt
|
198
|
-
log.warn "get_receipt_for_ticket() for ticket #{ticket} did not return a receipt!"
|
199
|
-
else
|
200
200
|
log.warn "Receipt was invalid for ticket #{ticket}!"
|
201
|
+
else
|
202
|
+
log.warn "get_receipt_for_ticket() for ticket #{ticket} did not return a receipt!"
|
201
203
|
end
|
202
204
|
end
|
203
205
|
|
@@ -241,6 +243,7 @@ module CAS
|
|
241
243
|
logger.info "This request is successfully CAS authenticated for user #{controller.session[@@session_username]}!"
|
242
244
|
return true
|
243
245
|
else
|
246
|
+
controller.session[:service] = service_url(controller)
|
244
247
|
logger.info "This request is NOT CAS authenticated, so we will redirect to the login page at: #{redirect_url(controller)}"
|
245
248
|
controller.send :redirect_to, redirect_url(controller) and return false
|
246
249
|
end
|
@@ -262,7 +265,7 @@ module CAS
|
|
262
265
|
if r.proxy_ticket
|
263
266
|
logger.info("Got proxy ticket #{r.proxy_ticket} for service #{r.target_service}")
|
264
267
|
else
|
265
|
-
logger.warn("Did not receive a proxy ticket for service #{r.target_service}!")
|
268
|
+
logger.warn("Did not receive a proxy ticket for service #{r.target_service}! Reason: #{r.error_code}: #{r.error_message}")
|
266
269
|
end
|
267
270
|
|
268
271
|
return r
|
@@ -323,7 +326,7 @@ module CAS
|
|
323
326
|
pv = ProxyTicketValidator.new
|
324
327
|
pv.validate_url = @@validate_url
|
325
328
|
pv.service_ticket = ticket
|
326
|
-
pv.service = service_url(controller)
|
329
|
+
pv.service = controller.session[:service] || service_url(controller)
|
327
330
|
pv.renew = @@renew
|
328
331
|
pv.proxy_callback_url = @@proxy_callback_url
|
329
332
|
receipt = nil
|
@@ -332,6 +335,9 @@ module CAS
|
|
332
335
|
receipt = Receipt.new(pv)
|
333
336
|
rescue AuthenticationException => e
|
334
337
|
logger.warn("Getting a receipt for the ProxyTicketValidator threw an exception: #{e}")
|
338
|
+
rescue MalformedServerResponseException => e
|
339
|
+
logger.error("CAS Server returned malformed response:\n\n#{e}")
|
340
|
+
raise e
|
335
341
|
end
|
336
342
|
logger.debug "Receipt is: #{receipt.inspect}"
|
337
343
|
receipt
|
@@ -340,13 +346,17 @@ module CAS
|
|
340
346
|
def self.service_url(controller)
|
341
347
|
unclean = @@service_url || guess_service(controller)
|
342
348
|
clean = remove_ticket_from_service_uri(unclean)
|
343
|
-
logger.debug("Service URI
|
349
|
+
logger.debug("Service URI without ticket is: #{clean}")
|
344
350
|
clean
|
345
351
|
end
|
346
352
|
|
347
353
|
# FIXME: this method is really poorly named :(
|
348
354
|
def self.redirect_url(controller,url=@@login_url)
|
349
|
-
"#{url}?service=#{CGI.escape(service_url(controller))}" +
|
355
|
+
"#{url}?service=#{CGI.escape(service_url(controller))}" +
|
356
|
+
((@@renew)? "&renew=true":"") +
|
357
|
+
((@@gateway)? "&gateway=true":"") +
|
358
|
+
((@@query_string.blank?)? "" : "&" +
|
359
|
+
(@@query_string.collect { |k,v| "#{k}=#{v}"}.join("&")))
|
350
360
|
end
|
351
361
|
|
352
362
|
def self.guess_service(controller)
|
@@ -14,11 +14,13 @@ class CasProxyCallbackController < ActionController::Base
|
|
14
14
|
# request.ssl? or request.env['REMOTE_HOST'] == "127.0.0.1"
|
15
15
|
|
16
16
|
pgtIou = params['pgtIou']
|
17
|
-
|
17
|
+
|
18
|
+
# CAS Protocol spec says that the argument should be called 'pgt', but the JA-SIG CAS server seems to use pgtId.
|
19
|
+
# To accomodate this, we check for both parameters, although 'pgt' takes precedence over 'pgtId'.
|
20
|
+
pgtId = params['pgt'] || params['pgtId']
|
18
21
|
|
19
22
|
# We need to render a response with HTTP status code 200 when no pgtIou/pgtId is specified because CAS seems first
|
20
|
-
# call the action without any parameters (maybe to check if the server responds correctly)
|
21
|
-
# this time with the required params.
|
23
|
+
# call the action without any parameters (maybe to check if the server responds correctly)
|
22
24
|
render :text => "Okay, the server is up, but please specify a pgtIou and pgtId." and return unless pgtIou and pgtId
|
23
25
|
|
24
26
|
# TODO: pstore contents should probably be encrypted...
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rubycas-client
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.12.0
|
7
|
+
date: 2007-04-04 00:00:00 -04:00
|
8
8
|
summary: Client library for the CAS single-sign-on protocol.
|
9
9
|
require_paths:
|
10
10
|
- lib
|