gunark-rubycas-client 2.0.99

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/README.txt ADDED
@@ -0,0 +1,291 @@
1
+ = RubyCAS-Client
2
+
3
+ Author:: 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
+ Copyright:: (c) 2008 Urbacon Ltd.
5
+ License:: GNU Lesser General Public License v2.1 (LGPL 2.1)
6
+ Websites:: http://code.google.com/p/rubycas-client
7
+ http://github.com/gunark/rubycas-client
8
+ http://rubyforge.org/projects/rubycas-client
9
+
10
+
11
+
12
+ === RubyCAS-Client is a Ruby client library for Yale's Central Authentication Service (CAS) protocol.
13
+
14
+ CAS provides a secure single sign on solution for web-based applications. The user logs in to your
15
+ organization's CAS server, and is automatically authenticated for all other CAS-enabled applications.
16
+
17
+ For general information about the open CAS protocol, please have a look at http://www.ja-sig.org/products/cas.
18
+
19
+ If your organization does not already have a CAS server, you may be interested in RubyCAS-Client's sister project,
20
+ RubyCAS-Server[http://code.google.com/p/rubycas-server/].
21
+
22
+
23
+ == Getting help and reporting problems
24
+
25
+ If you need help, try posting to the RubyCAS discussion group at http://groups.google.com/group/rubycas-server.
26
+
27
+ To report problems, please use the Google Code issue tracker at http://code.google.com/p/rubycas-client/issues/list.
28
+
29
+
30
+ == Installation
31
+
32
+ You can download the latest version of RubyCAS-Client from the project's rubyforge page at
33
+ http://rubyforge.org/projects/rubycas-client.
34
+
35
+ However, it is easier to install the CAS client into a Ruby on Rails app as a plugin:
36
+
37
+ cd <your rails app>
38
+ ./script/plugin install http://rubycas-client.googlecode.com/svn/trunk/rubycas-client
39
+
40
+ Alternatively, the library is also installable as a RubyGem[http://rubygems.org]:
41
+
42
+ gem install rubycas-client
43
+
44
+ If your Rails application is under Subversion control, you can also install the plugin as an svn:external, ensuring that
45
+ you always have the latest bleeding-edge version of RubyCAS-Client:
46
+
47
+ ./script/plugin install -x http://rubycas-client.googlecode.com/svn/trunk/rubycas-client
48
+
49
+
50
+ == Usage Examples
51
+
52
+ Although RubyCAS-Client can be used with other web Frameworks (for example Camping), the following examples
53
+ are aimed at {Ruby on Rails}[http://rubyonrails.org].
54
+
55
+ ==== Using RubyCAS-Client in Rails controllers
56
+
57
+ <i>Note that from this point on we are assuming that you have a working CAS server up and running!</i>
58
+
59
+ After installing RubyCAS-Client as a plugin (see above), add the following to your app's <tt>config/environment.rb</tt>
60
+ (make sure that you put it at the bottom of the file, *after* the Rails Initializer):
61
+
62
+ CASClient::Frameworks::Rails::Filter.configure(
63
+ :cas_base_url => "https://cas.example.foo/"
64
+ )
65
+
66
+ (Change the <tt>:cas_base_url</tt> value to your CAS server's base URL; also note that many CAS servers are configured
67
+ with a base URL that looks more like "https://cas.example.foo/cas".)
68
+
69
+ Then, in your <tt>app/controllers/application.rb</tt> (or in whichever controller you want to add the CAS filter for):
70
+
71
+ before_filter CASClient::Frameworks::Rails::Filter
72
+
73
+ That's it. You should now find that you are redirected to your CAS login page whenever you try to access any action
74
+ in your protected controller. You can of course qualify the <tt>before_filter</tt> as you would with any other ActionController
75
+ filter. For example:
76
+
77
+ before_filter CASClient::Frameworks::Rails::Filter, :except => [ :unprotected_action, :another_unprotected_action ]
78
+
79
+ <b>Once the user has been authenticated, their authenticated username is available under <tt>session[:cas_user]</tt>,</b>
80
+ If you want to do something with this username (for example load a user record from the database), you can append another
81
+ filter method that checks for this value and does whatever you need it to do.
82
+
83
+ <b>Note:</b> If Rails complains about missing constants, try adding this before the CASClient configuration:
84
+
85
+ require 'casclient'
86
+ require 'casclient/frameworks/rails/filter'
87
+
88
+
89
+ ==== A more complicated example
90
+
91
+ Here is a more complicated configuration showing most of the configuration options along with their default values
92
+ (this does not show proxy options, which are covered in the next section):
93
+
94
+ # enable detailed CAS logging
95
+ cas_logger = CASClient::Logger.new(RAILS_ROOT+'/log/cas.log')
96
+ cas_logger.level = Logger::DEBUG
97
+
98
+ CASClient::Frameworks::Rails::Filter.configure(
99
+ :cas_base_url => "https://cas.example.foo/",
100
+ :login_url => "https://cas.example.foo/login",
101
+ :logout_url => "https://cas.example.foo/logout",
102
+ :validate_url => "https://cas.example.foo/proxyValidate",
103
+ :username_session_key => :cas_user,
104
+ :extra_attributes_session_key => :cas_extra_attributes
105
+ :logger => cas_logger,
106
+ :authenticate_on_every_request => true
107
+ )
108
+
109
+ Note that normally it is not necessary to specify <tt>:login_url</tt>, <tt>:logout_url</tt>, and <tt>:validate_url</tt>.
110
+ These values are automatically set to standard CAS defaults based on the given <tt>:cas_base_url</tt>.
111
+
112
+ The <tt>:username_session_key</tt> value determines the key under which you can find the CAS username in the Rails session hash.
113
+
114
+ Any additional info that the CAS server might have supplied about the user during authentication will be found under the
115
+ <tt>:extra_attributes_session_key</tt> value in the Rails session hash (i.e. given the above configuration, you would find this
116
+ info under <tt>session[:cas_extra_attributes]</tt>).
117
+
118
+ An arbitrary Logger instance can be given as the :logger parameter. In the example above we log all CAS activity to a
119
+ <tt>log/cas.log</tt> file in your Rails app's directory.
120
+
121
+ ==== Re-authenticating on every request (i.e. the "single sign-out problem")
122
+
123
+ By default, the Rails filter will only authenticate with the CAS server when no session[:cas_user] value exists. Once the user
124
+ has been authenticated, no further CAS forwarding is done until the user's session is wiped. This saves you
125
+ the trouble of having to do this check yourself (since in most cases it is not advisable to go through the CAS server
126
+ on every request -- this is slow and would potentially lead to problems, for example for AJAX requests). However,
127
+ the disadvantage is that the filter no longer checks to make sure that the user's CAS session is still actually open.
128
+ In other words it is possible for the user's authentication session to be closed on the CAS server without the
129
+ client application knowing about it.
130
+
131
+ In the future RubyCAS-Client will support the new "Single Sign-Out" functionality in CAS 3.1, allowing the server to
132
+ notify the client application that the CAS session is closed, but for now it is up to you to handle this by, for example,
133
+ by wiping the local <tt>session[:cas_user]</tt> value periodically to force a CAS re-check.
134
+
135
+ Alternatively, it is possible to disable this authentication persistence behaviour by setting the <tt>:authenticate_on_every_request</tt>
136
+ configuration option to true as in the example above.
137
+
138
+
139
+ ==== Defining a 'logout' action
140
+
141
+ Your Rails application's controller(s) will probably have some sort of logout function. Here you can do any necessary local
142
+ cleanup, and then call <tt>CASClient::Frameworks::Rails::Filter.logout(controller)</tt>. For example:
143
+
144
+ class ApplicationController < ActionController::Base
145
+
146
+ # ...
147
+
148
+ def logout
149
+ # optionally do some local cleanup here
150
+ # ...
151
+
152
+ CASClient::Frameworks::Rails::Filter.logout(self)
153
+ end
154
+ end
155
+
156
+ By default, the logout method will clear the local Rails session, do some local CAS cleanup, and redirect to the CAS
157
+ logout page. Additionally, the <tt>request.referer</tt> value from the <tt>controller</tt> instance is passed to the
158
+ CAS server as a 'destination' parameter. This allows RubyCAS server to provide a follow-up login page allowing
159
+ the user to log back in to the service they just logged out from using a different username and password. Other
160
+ CAS server implemenations may use this 'destination' parameter in different ways.
161
+
162
+ ==== Gatewayed (i.e. optional) authentication
163
+
164
+ "Gatewaying" essentially allows for optional CAS authentication. Users who already have a pre-existing CAS SSO session
165
+ will be automatically authenticated for the gatewayed service, while those who do not will be allowed to access the service
166
+ without authentication. This is useful for example when you want to show some additional private content on a homepage to
167
+ authenticated users, but also want anonymous users to be able to access the page without first logging in.
168
+
169
+ To allow users to access a page without authenticatin, simply use <tt>CASClient::Frameworks::Rails::GatewayFilter</tt>
170
+ in place of <tt>CASClient::Frameworks::Rails::Filter</tt> in your controller. For example, you may want to require
171
+ CAS authentication for all actions in a controller except the index action:
172
+
173
+ class ExampleController < ApplicationController
174
+ before_filter CASClient::Frameworks::Rails::GatewayFilter, :only => :index
175
+ before_filter CASClient::Frameworks::Rails::Filter, :except => :index
176
+
177
+ # ...
178
+ end
179
+
180
+
181
+ ==== How to act as a CAS proxy
182
+
183
+ CAS 2.0 has a built-in mechanism that allows a CAS-authenticated application to pass on its authentication to other applications.
184
+ 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
185
+ various other sites that run independently of the portal system (but are always accessed via the portal). The exact mechanism
186
+ behind this is rather complicated so I won't go over it here. If you wish to learn more about CAS proxying, a great walkthrough
187
+ is available at http://www.ja-sig.org/wiki/display/CAS/Proxy+CAS+Walkthrough.
188
+
189
+ RubyCAS-Client fully supports proxying, so a CAS-protected Rails application can act as a CAS proxy.
190
+
191
+ Additionally, RubyCAS-Client comes with a controller that can act as a CAS proxy callback receiver. This is necessary because
192
+ when your application requests to act as a CAS proxy, the CAS server must contact your application to deposit the proxy-granting-ticket
193
+ (PGT). Note that in this case the CAS server CONTACTS YOU, rather than you contacting the CAS server (as in all other CAS operations).
194
+
195
+ Confused? Don't worry, you don't really have to understand this to use it. To enable your Rails app to act as a CAS proxy,
196
+ all you need to do is this:
197
+
198
+ In your <tt>config/environment.rb</tt>:
199
+
200
+ # enable detailed CAS logging for easier troubleshooting
201
+ cas_logger = CASClient::Logger.new(RAILS_ROOT+'/log/cas.log')
202
+ cas_logger.level = Logger::DEBUG
203
+
204
+ CASClient::Frameworks::Rails::Filter.configure(
205
+ :cas_base_url => "https://cas.example.foo/",
206
+ :proxy_retrieval_url => "https://cas-proxy-callback.example.foo/cas_proxy_callback/retrieve_pgt",
207
+ :proxy_callback_url => "https://cas-proxy-callback.example.foo/cas_proxy_callback/receive_pgt",
208
+ :logger => cas_logger
209
+ )
210
+
211
+ In <tt>config/routes.rb</tt> make sure that you have a route that will allow requests to /cas_proxy_callback/:action to be routed to the
212
+ CasProxyCallbackController. This should work as-is with the standard Rails routes setup, but if you have disabled the default
213
+ route, you should add the following:
214
+
215
+ map.cas_proxy_callback 'cas_proxy_callback/:action', :controller => 'cas_proxy_callback'
216
+
217
+ Now here's a big giant caveat: <b>your CAS callback application and your CAS proxy application must run on separate Rails servers</b>.
218
+ In other words, if you want a Rails app to act as a CAS ticket-granting proxy, the cas_proxy_callback controller
219
+ must run on a different server. This is because Rails does not properly support handling of concurrent requests. The CAS proxy mechanism
220
+ acts in such a way that if your proxy application and your callback controller were on the same server
221
+ you would end up with a deadlock (the CAS server would be waiting for its callback to be accepted by your Rails server,
222
+ but your Rails server wouldn't respond to the CAS server's callback until the CAS server responded back first).
223
+
224
+ The simplest workaround is this:
225
+
226
+ 1. Create an empty rails app (i.e. something like <tt>rails cas_proxy_callback</tt>)
227
+ 2. Make sure that you have the CAS plugin installed. If you installed it as a gem, you don't have to do anything since
228
+ it is already installed. If you want to install as a plugin, see the instructions in the "Installing" section above.
229
+ 3. Make sure that the server is up and running, and configure your proxy_callback_url and proxy_retrieval_url to point
230
+ to the new server as described above (or rather, make Pound point to the new server, if that's how you're handling https).
231
+
232
+ That's it. The proxy_callback_controller doesn't require any additional configuration. It doesn't access the database
233
+ or anything of that sort.
234
+
235
+ Once your user logs in to CAS via your application, you can do the following to obtain a service ticket that can then be used
236
+ to authenticate another application:
237
+
238
+ service_uri = "http://some-other-application.example.foo"
239
+ proxy_granting_ticket = session[:cas_pgt]
240
+ ticket = CASClient::Frameworks::Rails::Filter.client.request_proxy_ticket(service_uri, proxy_granting_ticket).ticket
241
+
242
+ <tt>ticket</tt> should now contain a valid service ticket. You can use it to authenticate other services by sending it and
243
+ the service URI as parameters to your target application:
244
+
245
+ http://some-other-application.example.foo?service=#{CGI.encode(ticket.target_service)}&ticket=#{ticket.proxy_ticket}
246
+
247
+ This is of course assuming that http://some-other-application.example.foo is also protected by the CAS filter.
248
+ Note that you should always URI-encode your service parameter inside URIs!
249
+
250
+ Note that #request_proxy_ticket returns a CASClient::ProxyTicket object, which is why we need to call #ticket on it
251
+ to retrieve the actual service ticket string.
252
+
253
+ ===== Additional proxying notes and caveats
254
+
255
+ <b>The proxy url must be an https address.</b> Otherwise CAS will refuse to communicate with it. This means that if you are using
256
+ the bundled cas_proxy_callback controller, you will have to host your application on an https-enabled server. This can be a bit
257
+ tricky with Rails. WEBrick's SSL support is difficult to configure, and Mongrel doesn't support SSL at all. One workaround is to
258
+ use a reverse proxy like Pound[http://www.apsis.ch/pound/], which will accept https connections and locally re-route them
259
+ to your Rails application. Also, note that <i>self-signed SSL certificates likely won't work</i>. You will probably need to use
260
+ a real certificate purchased from a trusted CA authority (there are ways around this, but good luck :)
261
+
262
+
263
+ == SSL Support
264
+
265
+ Make sure you have the Ruby OpenSSL library installed. Otherwise you may get errors like:
266
+
267
+ no such file to load -- net/https
268
+
269
+ To install the library on an Debian/Ubuntu system:
270
+
271
+ sudo apt-get install libopenssl-ruby
272
+
273
+ For other platforms you'll have to figure it out yourself.
274
+
275
+
276
+
277
+ == License
278
+
279
+ This program is free software; you can redistribute it and/or modify
280
+ it under the terms of the GNU Lesser General Public License as published by
281
+ the Free Software Foundation; either version 2 of the License, or
282
+ (at your option) any later version.
283
+
284
+ This program is distributed in the hope that it will be useful,
285
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
286
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
287
+ GNU General Public License for more details.
288
+
289
+ You should have received a copy of the GNU Lesser General Public License
290
+ along with this program (see the file called LICENSE); if not, write to the
291
+ Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
data/Rakefile ADDED
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/testtask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/rdoctask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+ require 'fileutils'
10
+ require 'hoe'
11
+ include FileUtils
12
+ require File.join(File.dirname(__FILE__), 'lib', 'casclient', 'version')
13
+
14
+ AUTHOR = ["Matt Zukowski", "Matt Walker"] # can also be an array of Authors
15
+ EMAIL = "matt at roughest dot net"
16
+ DESCRIPTION = "Client library for the Central Authentication Service (CAS) protocol."
17
+ GEM_NAME = "rubycas-client" # what ppl will type to install your gem
18
+ RUBYFORGE_PROJECT = "rubycas-client" # The unix name for your project
19
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
20
+
21
+ ENV['NODOT'] = '1'
22
+
23
+ NAME = "rubycas-client"
24
+ REV = nil
25
+ #REV = `svn info`[/Revision: (\d+)/, 1] rescue nil
26
+ VERS = ENV['VERSION'] || (CASClient::VERSION::STRING + (REV ? ".#{REV}" : ""))
27
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
28
+ RDOC_OPTS = ['--quiet', '--title', "rubycas-client documentation",
29
+ "--opname", "index.html",
30
+ "--line-numbers",
31
+ "--main", "README",
32
+ "--inline-source"]
33
+
34
+ class Hoe
35
+ def extra_deps
36
+ @extra_deps.reject { |x| Array(x).first == 'hoe' }
37
+ end
38
+ end
39
+
40
+ # Generate all the Rake tasks
41
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
42
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
43
+ p.author = AUTHOR
44
+ p.description = DESCRIPTION
45
+ p.email = EMAIL
46
+ p.summary = DESCRIPTION
47
+ p.url = HOMEPATH
48
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
49
+ p.test_globs = ["test/**/*_test.rb"]
50
+ p.clean_globs = CLEAN #An array of file patterns to delete on clean.
51
+
52
+ # == Optional
53
+ #p.changes - A description of the release's latest changes.
54
+ #p.extra_deps - An array of rubygem dependencies.
55
+ #p.spec_extras - A hash of extra values to set in the gemspec.
56
+ p.extra_deps = ['activesupport']
57
+ end
58
+
59
+ desc 'Build and install rubycas-client'
60
+ task :install do
61
+ system "gem build rubycas-client.gemspec"
62
+ system "sudo gem install rubycas-client-#{VERS}.gem"
63
+ end
data/init.rb ADDED
@@ -0,0 +1,6 @@
1
+ # This file makes it possible to install RubyCAS-Client as a Rails plugin.
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__))+'/lib'
4
+
5
+ require 'casclient'
6
+ require 'casclient/frameworks/rails/filter'
@@ -0,0 +1,256 @@
1
+ module CASClient
2
+ # The client brokers all HTTP transactions with the CAS server.
3
+ class Client
4
+ attr_reader :cas_base_url
5
+ attr_reader :log, :username_session_key, :extra_attributes_session_key
6
+ attr_writer :login_url, :validate_url, :proxy_url, :logout_url, :service_url
7
+ attr_accessor :proxy_callback_url, :proxy_retrieval_url
8
+
9
+ def initialize(conf = nil)
10
+ configure(conf) if conf
11
+ end
12
+
13
+ def configure(conf)
14
+ raise ArgumentError, "Missing :cas_base_url parameter!" unless conf[:cas_base_url]
15
+
16
+ @cas_base_url = conf[:cas_base_url].gsub(/\/$/, '')
17
+
18
+ @login_url = conf[:login_url]
19
+ @logout_url = conf[:logout_url]
20
+ @validate_url = conf[:validate_url]
21
+ @proxy_url = conf[:proxy_url]
22
+ @service_url = conf[:service_url]
23
+ @proxy_callback_url = conf[:proxy_callback_url]
24
+ @proxy_retrieval_url = conf[:proxy_retrieval_url]
25
+
26
+ @username_session_key = conf[:username_session_key] || :cas_user
27
+ @extra_attributes_session_key = conf[:extra_attributes_session_key] || :cas_extra_attributes
28
+
29
+ @log = CASClient::LoggerWrapper.new
30
+ @log.set_real_logger(conf[:logger]) if conf[:logger]
31
+ end
32
+
33
+ def login_url
34
+ @login_url || (cas_base_url + "/login")
35
+ end
36
+
37
+ def validate_url
38
+ @validate_url || (cas_base_url + "/proxyValidate")
39
+ end
40
+
41
+ # Returns the CAS server's logout url.
42
+ #
43
+ # If a logout_url has not been explicitly configured,
44
+ # the default is cas_base_url + "/logout".
45
+ #
46
+ # destination_url:: Set this if you want the user to be
47
+ # able to immediately log back in. Generally
48
+ # you'll want to use something like <tt>request.referer</tt>.
49
+ # Note that the above behaviour describes RubyCAS-Server
50
+ # -- other CAS server implementations might use this
51
+ # parameter differently (or not at all).
52
+ # follow_url:: This satisfies section 2.3.1 of the CAS protocol spec.
53
+ # See http://www.ja-sig.org/products/cas/overview/protocol
54
+ def logout_url(destination_url = nil, follow_url = nil)
55
+ url = @logout_url || (cas_base_url + "/logout")
56
+
57
+ if destination_url
58
+ # if present, remove the 'ticket' parameter from the destination_url
59
+ duri = URI.parse(destination_url)
60
+ h = duri.query ? query_to_hash(duri.query) : {}
61
+ h.delete('ticket')
62
+ duri.query = hash_to_query(h)
63
+ destination_url = duri.to_s.gsub(/\?$/, '')
64
+ end
65
+
66
+ if destination_url || follow_url
67
+ uri = URI.parse(url)
68
+ h = uri.query ? query_to_hash(uri.query) : {}
69
+ h['destination'] = destination_url if destination_url
70
+ h['url'] = follow_url if follow_url
71
+ uri.query = hash_to_query(h)
72
+ uri.to_s
73
+ else
74
+ url
75
+ end
76
+ end
77
+
78
+ def proxy_url
79
+ @proxy_url || (cas_base_url + "/proxy")
80
+ end
81
+
82
+ def validate_service_ticket(st)
83
+ uri = URI.parse(validate_url)
84
+ h = uri.query ? query_to_hash(uri.query) : {}
85
+ h['service'] = st.service
86
+ h['ticket'] = st.ticket
87
+ h['renew'] = 1 if st.renew
88
+ h['pgtUrl'] = proxy_callback_url if proxy_callback_url
89
+ uri.query = hash_to_query(h)
90
+
91
+ st.response = request_cas_response(uri, ValidationResponse)
92
+
93
+ return st
94
+ end
95
+ alias validate_proxy_ticket validate_service_ticket
96
+
97
+ # Returns true if the configured CAS server is up and responding;
98
+ # false otherwise.
99
+ def cas_server_is_up?
100
+ uri = URI.parse(login_url)
101
+
102
+ log.debug "Checking if CAS server at URI '#{uri}' is up..."
103
+
104
+ https = Net::HTTP.new(uri.host, uri.port)
105
+ https.use_ssl = (uri.scheme == 'https')
106
+
107
+ begin
108
+ raw_res = https.start do |conn|
109
+ conn.get("#{uri.path}?#{uri.query}")
110
+ end
111
+ rescue Errno::ECONNREFUSED => e
112
+ log.warn "CAS server did not respond! (#{e.inspect})"
113
+ return false
114
+ end
115
+
116
+ log.debug "CAS server responded with #{raw_res.inspect}:\n#{raw_res.body}"
117
+
118
+ return raw_res.kind_of?(Net::HTTPSuccess)
119
+ end
120
+
121
+ # Requests a login using the given credentials for the given service;
122
+ # returns a LoginResponse object.
123
+ def login_to_service(credentials, service)
124
+ lt = request_login_ticket
125
+
126
+ data = credentials.merge(
127
+ :lt => lt,
128
+ :service => service
129
+ )
130
+
131
+ res = submit_data_to_cas(login_url, data)
132
+ CASClient::LoginResponse.new(res)
133
+ end
134
+
135
+ # Requests a login ticket from the CAS server for use in a login request;
136
+ # returns a LoginTicket object.
137
+ #
138
+ # This only works with RubyCAS-Server, since obtaining login
139
+ # tickets in this manner is not part of the official CAS spec.
140
+ def request_login_ticket
141
+ uri = URI.parse(login_url+'Ticket')
142
+ https = Net::HTTP.new(uri.host, uri.port)
143
+ https.use_ssl = (uri.scheme == 'https')
144
+ res = https.post(uri.path, ';')
145
+
146
+ raise CASException, res.body unless res.kind_of? Net::HTTPSuccess
147
+
148
+ res.body.strip
149
+ end
150
+
151
+ # Requests a proxy ticket from the CAS server for the given service
152
+ # using the given pgt (proxy granting ticket); returns a ProxyTicket
153
+ # object.
154
+ #
155
+ # The pgt required to request a proxy ticket is obtained as part of
156
+ # a ValidationResponse.
157
+ def request_proxy_ticket(pgt, target_service)
158
+ uri = URI.parse(proxy_url)
159
+ h = uri.query ? query_to_hash(uri.query) : {}
160
+ h['pgt'] = pgt.ticket
161
+ h['targetService'] = target_service
162
+ uri.query = hash_to_query(h)
163
+
164
+ pr = request_cas_response(uri, ProxyResponse)
165
+
166
+ pt = ProxyTicket.new(pr.proxy_ticket, target_service)
167
+ pt.response = pr
168
+
169
+ return pt
170
+ end
171
+
172
+ def retrieve_proxy_granting_ticket(pgt_iou)
173
+ uri = URI.parse(proxy_retrieval_url)
174
+ uri.query = (uri.query ? uri.query + "&" : "") + "pgtIou=#{CGI.escape(pgt_iou)}"
175
+ retrieve_url = uri.to_s
176
+
177
+ log.debug "Retrieving PGT for PGT IOU #{pgt_iou.inspect} from #{retrieve_url.inspect}"
178
+
179
+ # https = Net::HTTP.new(uri.host, uri.port)
180
+ # https.use_ssl = (uri.scheme == 'https')
181
+ # res = https.post(uri.path, ';')
182
+ uri = URI.parse(uri) unless uri.kind_of? URI
183
+ https = Net::HTTP.new(uri.host, uri.port)
184
+ https.use_ssl = (uri.scheme == 'https')
185
+ res = https.start do |conn|
186
+ conn.get("#{uri.path}?#{uri.query}")
187
+ end
188
+
189
+
190
+ raise CASException, res.body unless res.kind_of? Net::HTTPSuccess
191
+
192
+ ProxyGrantingTicket.new(res.body.strip, pgt_iou)
193
+ end
194
+
195
+ def add_service_to_login_url(service_url)
196
+ uri = URI.parse(login_url)
197
+ uri.query = (uri.query ? uri.query + "&" : "") + "service=#{CGI.escape(service_url)}"
198
+ uri.to_s
199
+ end
200
+
201
+ private
202
+ # Fetches a CAS response of the given type from the given URI.
203
+ # Type should be either ValidationResponse or ProxyResponse.
204
+ def request_cas_response(uri, type)
205
+ log.debug "Requesting CAS response for URI #{uri}"
206
+
207
+ uri = URI.parse(uri) unless uri.kind_of? URI
208
+ https = Net::HTTP.new(uri.host, uri.port)
209
+ https.use_ssl = (uri.scheme == 'https')
210
+
211
+ begin
212
+ raw_res = https.start do |conn|
213
+ conn.get("#{uri.path}?#{uri.query}")
214
+ end
215
+ rescue Errno::ECONNREFUSED => e
216
+ log.error "CAS server did not respond! (#{e.inspect})"
217
+ raise "The CAS authentication server at #{uri} is not responding!"
218
+ end
219
+
220
+ # We accept responses of type 422 since RubyCAS-Server generates these
221
+ # in response to requests from the client that are processable but contain
222
+ # invalid CAS data (for example an invalid service ticket).
223
+ if raw_res.kind_of?(Net::HTTPSuccess) || raw_res.code.to_i == 422
224
+ log.debug "CAS server responded with #{raw_res.inspect}:\n#{raw_res.body}"
225
+ else
226
+ log.error "CAS server responded with an error! (#{raw_res.inspect})"
227
+ raise "The CAS authentication server at #{uri} responded with an error (#{raw_res.inspect})!"
228
+ end
229
+
230
+ type.new(raw_res.body)
231
+ end
232
+
233
+ # Submits some data to the given URI and returns a Net::HTTPResponse.
234
+ def submit_data_to_cas(uri, data)
235
+ uri = URI.parse(uri) unless uri.kind_of? URI
236
+ req = Net::HTTP::Post.new(uri.path)
237
+ req.set_form_data(data, ';')
238
+ https = Net::HTTP.new(uri.host, uri.port)
239
+ https.use_ssl = (uri.scheme == 'https')
240
+ https.start {|conn| conn.request(req) }
241
+ end
242
+
243
+ def query_to_hash(query)
244
+ CGI.parse(query)
245
+ end
246
+
247
+ def hash_to_query(hash)
248
+ pairs = []
249
+ hash.each do |k, vals|
250
+ vals = [vals] unless vals.kind_of? Array
251
+ vals.each {|v| pairs << "#{CGI.escape(k)}=#{CGI.escape(v)}"}
252
+ end
253
+ pairs.join("&")
254
+ end
255
+ end
256
+ end