rubycas-client 0.11.0 → 0.12.0
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/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
|