rubycas-client-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 University of Toronto
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,43 @@
1
+ RubyCAS-Client-Rails
2
+ ====================
3
+
4
+ Rails plugin for using the RubyCAS-Client as a controller filter.
5
+
6
+ This uses a Railtie, so will only work with Rails 3.0 and up.
7
+
8
+
9
+ Example
10
+ =======
11
+
12
+ First, install the `rubycas-client` gem:
13
+
14
+ gem install rubycas-client
15
+
16
+ Next, clone the `rubycas-client-rails` code from Github (this will eventually
17
+ be released as a gem too).
18
+
19
+ Now add both to your `Gemfile`:
20
+
21
+ gem 'rubycas-client'
22
+ gem 'rubycas-client-rails', :path => '/path/where/you/cloned/rubycas-client-rails'
23
+
24
+ In your `application.rb` add:
25
+
26
+ config.rubycas.cas_base_url = 'https://cas.example.com/'
27
+
28
+ Finally, to enable the CAS filter for a controller:
29
+
30
+ class MyController < ApplicationController
31
+
32
+ before_filter RubyCAS::Filter
33
+
34
+ Many other configuration options are available. For example you can instruct
35
+ the client to log its actions to the default Rails logger using:
36
+
37
+ config.rubycas.logger = Rails.logger
38
+
39
+ See the (outdated) documentation at [http://rubycas-client.rubyforge.org/](http://rubycas-client.rubyforge.org/)
40
+ for a full list of config options.
41
+
42
+
43
+ Copyright (c) 2011 University of Toronto, released under the MIT license
@@ -0,0 +1,23 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the rubycas_client_rails plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the rubycas_client_rails plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'RubycasClientRails'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ # Include hook code here
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,424 @@
1
+
2
+ require 'casclient'
3
+
4
+ module RubyCAS
5
+ class Railtie < Rails::Railtie
6
+ config.rubycas = ActiveSupport::OrderedOptions.new
7
+
8
+ initializer 'rubycas.initialize' do |app|
9
+ RubyCAS::Filter.setup(config.rubycas)
10
+ end
11
+ end
12
+
13
+ class Filter
14
+ cattr_reader :config, :log, :client
15
+
16
+ # These are initialized when you call setup.
17
+ @@client = nil
18
+ @@log = nil
19
+ @@fake_user = nil
20
+ @@fake_extra_attributes = nil
21
+
22
+ class << self
23
+ def setup(config)
24
+ @@config = config
25
+ @@config[:logger] = Rails.logger unless @@config[:logger]
26
+ @@client = CASClient::Client.new(@@config)
27
+ @@log = @@client.log
28
+ end
29
+
30
+ def filter(controller)
31
+ raise "Cannot use the CASClient filter because it has not yet been configured." if config.nil?
32
+
33
+ if @@fake_user
34
+ controller.session[client.username_session_key] = @@fake_user
35
+ controller.session[:casfilteruser] = @@fake_user
36
+ controller.session[client.extra_attributes_session_key] = @@fake_extra_attributes
37
+ return true
38
+ end
39
+
40
+
41
+ last_st = controller.session[:cas_last_valid_ticket]
42
+
43
+ if single_sign_out(controller)
44
+ controller.send(:render, :text => "CAS Single-Sign-Out request intercepted.")
45
+ return false
46
+ end
47
+
48
+ st = read_ticket(controller)
49
+
50
+ is_new_session = true
51
+
52
+ if st && last_st &&
53
+ last_st.ticket == st.ticket &&
54
+ last_st.service == st.service
55
+ # warn() rather than info() because we really shouldn't be re-validating the same ticket.
56
+ # The only situation where this is acceptable is if the user manually does a refresh and
57
+ # the same ticket happens to be in the URL.
58
+ log.warn("Re-using previously validated ticket since the ticket id and service are the same.")
59
+ st = last_st
60
+ is_new_session = false
61
+ elsif last_st &&
62
+ !config[:authenticate_on_every_request] &&
63
+ controller.session[client.username_session_key]
64
+ # Re-use the previous ticket if the user already has a local CAS session (i.e. if they were already
65
+ # previously authenticated for this service). This is to prevent redirection to the CAS server on every
66
+ # request.
67
+ #
68
+ # This behaviour can be disabled (so that every request is routed through the CAS server) by setting
69
+ # the :authenticate_on_every_request config option to true. However, this is not desirable since
70
+ # it will almost certainly break POST request, AJAX calls, etc.
71
+ log.debug "Existing local CAS session detected for #{controller.session[client.username_session_key].inspect}. "+
72
+ "Previous ticket #{last_st.ticket.inspect} will be re-used."
73
+ st = last_st
74
+ is_new_session = false
75
+ end
76
+
77
+ if st
78
+ client.validate_service_ticket(st) unless st.has_been_validated?
79
+ vr = st.response
80
+
81
+ if st.is_valid?
82
+ if is_new_session
83
+ log.info("Ticket #{st.ticket.inspect} for service #{st.service.inspect} belonging to user #{vr.user.inspect} is VALID.")
84
+ controller.session[client.username_session_key] = vr.user.dup
85
+ controller.session[client.extra_attributes_session_key] = HashWithIndifferentAccess.new(vr.extra_attributes) if vr.extra_attributes
86
+
87
+ if vr.extra_attributes
88
+ log.debug("Extra user attributes provided along with ticket #{st.ticket.inspect}: #{vr.extra_attributes.inspect}.")
89
+ end
90
+
91
+ # RubyCAS-Client 1.x used :casfilteruser as it's username session key,
92
+ # so we need to set this here to ensure compatibility with configurations
93
+ # built around the old client.
94
+ controller.session[:casfilteruser] = vr.user
95
+
96
+ if config[:enable_single_sign_out]
97
+ f = store_service_session_lookup(st, controller.request.session_options[:id] || controller.session.session_id)
98
+ log.debug("Wrote service session lookup file to #{f.inspect} with session id #{controller.request.session_options[:id] || controller.session.session_id.inspect}.")
99
+ end
100
+ end
101
+
102
+ # Store the ticket in the session to avoid re-validating the same service
103
+ # ticket with the CAS server.
104
+ controller.session[:cas_last_valid_ticket] = st
105
+
106
+ if vr.pgt_iou
107
+ unless controller.session[:cas_pgt] && controller.session[:cas_pgt].ticket && controller.session[:cas_pgt].iou == vr.pgt_iou
108
+ log.info("Receipt has a proxy-granting ticket IOU. Attempting to retrieve the proxy-granting ticket...")
109
+ pgt = client.retrieve_proxy_granting_ticket(vr.pgt_iou)
110
+
111
+ if pgt
112
+ log.debug("Got PGT #{pgt.ticket.inspect} for PGT IOU #{pgt.iou.inspect}. This will be stored in the session.")
113
+ controller.session[:cas_pgt] = pgt
114
+ # For backwards compatibility with RubyCAS-Client 1.x configurations...
115
+ controller.session[:casfilterpgt] = pgt
116
+ else
117
+ log.error("Failed to retrieve a PGT for PGT IOU #{vr.pgt_iou}!")
118
+ end
119
+ else
120
+ log.info("PGT is present in session and PGT IOU #{vr.pgt_iou} matches the saved PGT IOU. Not retrieving new PGT.")
121
+ end
122
+
123
+ end
124
+
125
+ return true
126
+ else
127
+ log.warn("Ticket #{st.ticket.inspect} failed validation -- #{vr.failure_code}: #{vr.failure_message}")
128
+ unauthorized!(controller, vr)
129
+ return false
130
+ end
131
+ else # no service ticket was present in the request
132
+ if returning_from_gateway?(controller)
133
+ log.info "Returning from CAS gateway without authentication."
134
+
135
+ # unset, to allow for the next request to be authenticated if necessary
136
+ controller.session[:cas_sent_to_gateway] = false
137
+
138
+ if use_gatewaying?
139
+ log.info "This CAS client is configured to use gatewaying, so we will permit the user to continue without authentication."
140
+ controller.session[client.username_session_key] = nil
141
+ return true
142
+ else
143
+ log.warn "The CAS client is NOT configured to allow gatewaying, yet this request was gatewayed. Something is not right!"
144
+ end
145
+ end
146
+
147
+ unauthorized!(controller)
148
+ return false
149
+ end
150
+ rescue OpenSSL::SSL::SSLError
151
+ log.error("SSL Error: hostname was not match with the server certificate. You can try to disable the ssl verification with a :force_ssl_verification => false in your configurations file.")
152
+ unauthorized!(controller)
153
+ return false
154
+ end
155
+
156
+ # used to allow faking for testing
157
+ # with cucumber and other tools.
158
+ # use like
159
+ # CASClient::Frameworks::Rails::Filter.fake("homer")
160
+ # you can also fake extra attributes by including a second parameter
161
+ # CASClient::Frameworks::Rails::Filter.fake("homer", {:roles => ['dad', 'husband']})
162
+ def fake(username, extra_attributes = nil)
163
+ @@fake_user = username
164
+ @@fake_extra_attributes = extra_attributes
165
+ end
166
+
167
+ def use_gatewaying?
168
+ @@config[:use_gatewaying]
169
+ end
170
+
171
+ # Returns the login URL for the current controller.
172
+ # Useful when you want to provide a "Login" link in a GatewayFilter'ed
173
+ # action.
174
+ def login_url(controller)
175
+ service_url = read_service_url(controller)
176
+ url = client.add_service_to_login_url(service_url)
177
+ log.debug("Generated login url: #{url}")
178
+ return url
179
+ end
180
+
181
+ # allow controllers to reuse the existing config to auto-login to
182
+ # the service
183
+ #
184
+ # Use this from within a controller. Pass the controller, the
185
+ # login-credentials and the path that you want the user
186
+ # resdirected to on success.
187
+ #
188
+ # When writing a login-action you must check the return-value of
189
+ # the response to see if it failed!
190
+ #
191
+ # If it worked - you need to redirect the user to the service -
192
+ # path, because that has the ticket that will *actually* log them
193
+ # into your system
194
+ #
195
+ # example:
196
+ # def autologin
197
+ # resp = CASClient::Frameworks::Rails::Filter.login_to_service(self, credentials, dashboard_url)
198
+ # if resp.is_faiulure?
199
+ # flash[:error] = 'Login failed'
200
+ # render :action => 'login'
201
+ # else
202
+ # return redirect_to(@resp.service_redirect_url)
203
+ # end
204
+ # end
205
+ def login_to_service(controller, credentials, return_path)
206
+ resp = @@client.login_to_service(credentials, return_path)
207
+ if resp.is_failure?
208
+ log.info("Validation failed for service #{return_path.inspect} reason: '#{resp.failure_message}'")
209
+ else
210
+ log.info("Ticket #{resp.ticket.inspect} for service #{return_path.inspect} is VALID.")
211
+ end
212
+
213
+ resp
214
+ end
215
+
216
+ # Clears the given controller's local Rails session, does some local
217
+ # CAS cleanup, and redirects to the CAS logout page. Additionally, the
218
+ # <tt>request.referer</tt> value from the <tt>controller</tt> instance
219
+ # is passed to the CAS server as a 'destination' parameter. This
220
+ # allows RubyCAS server to provide a follow-up login page allowing
221
+ # the user to log back in to the service they just logged out from
222
+ # using a different username and password. Other CAS server
223
+ # implemenations may use this 'destination' parameter in different
224
+ # ways.
225
+ # If given, the optional <tt>service</tt> URL overrides
226
+ # <tt>request.referer</tt>.
227
+ def logout(controller, service = nil)
228
+ referer = service || controller.request.referer
229
+ st = controller.session[:cas_last_valid_ticket]
230
+ delete_service_session_lookup(st) if st
231
+ controller.send(:reset_session)
232
+ controller.send(:redirect_to, client.logout_url(referer))
233
+ end
234
+
235
+ def unauthorized!(controller, vr = nil)
236
+ if controller.params[:format] == "xml"
237
+ if vr
238
+ controller.send(:render, :xml => "<errors><error>#{vr.failure_message}</error></errors>", :status => 401)
239
+ else
240
+ controller.send(:head, 401)
241
+ end
242
+ else
243
+ redirect_to_cas_for_authentication(controller)
244
+ end
245
+ end
246
+
247
+ def redirect_to_cas_for_authentication(controller)
248
+ redirect_url = login_url(controller)
249
+
250
+ if use_gatewaying?
251
+ controller.session[:cas_sent_to_gateway] = true
252
+ redirect_url << "&gateway=true"
253
+ else
254
+ controller.session[:cas_sent_to_gateway] = false
255
+ end
256
+
257
+ if controller.session[:previous_redirect_to_cas] &&
258
+ controller.session[:previous_redirect_to_cas] > (Time.now - 1.second)
259
+ log.warn("Previous redirect to the CAS server was less than a second ago. The client at #{controller.request.remote_ip.inspect} may be stuck in a redirection loop!")
260
+ controller.session[:cas_validation_retry_count] ||= 0
261
+
262
+ if controller.session[:cas_validation_retry_count] > 3
263
+ log.error("Redirection loop intercepted. Client at #{controller.request.remote_ip.inspect} will be redirected back to login page and forced to renew authentication.")
264
+ redirect_url += "&renew=1&redirection_loop_intercepted=1"
265
+ end
266
+
267
+ controller.session[:cas_validation_retry_count] += 1
268
+ else
269
+ controller.session[:cas_validation_retry_count] = 0
270
+ end
271
+ controller.session[:previous_redirect_to_cas] = Time.now
272
+
273
+ log.debug("Redirecting to #{redirect_url.inspect}")
274
+ controller.send(:redirect_to, redirect_url)
275
+ end
276
+
277
+ private
278
+ def single_sign_out(controller)
279
+
280
+ # Avoid calling raw_post (which may consume the post body) if
281
+ # this seems to be a file upload
282
+ if content_type = controller.request.headers["CONTENT_TYPE"] &&
283
+ content_type =~ %r{^multipart/}
284
+ return false
285
+ end
286
+
287
+ if controller.request.post? &&
288
+ controller.params['logoutRequest'] &&
289
+ controller.params['logoutRequest'] =~
290
+ %r{^<samlp:LogoutRequest.*?<samlp:SessionIndex>(.*)</samlp:SessionIndex>}m
291
+ # TODO: Maybe check that the request came from the registered CAS server? Although this might be
292
+ # pointless since it's easily spoofable...
293
+ si = $~[1]
294
+
295
+ unless config[:enable_single_sign_out]
296
+ log.warn "Ignoring single-sign-out request for CAS session #{si.inspect} because ssout functionality is not enabled (see the :enable_single_sign_out config option)."
297
+ return false
298
+ end
299
+
300
+ log.debug "Intercepted single-sign-out request for CAS session #{si.inspect}."
301
+
302
+ begin
303
+ required_sess_store = ActiveRecord::SessionStore
304
+ current_sess_store = ActionController::Base.session_store
305
+ rescue NameError
306
+ # for older versions of Rails (prior to 2.3)
307
+ required_sess_store = CGI::Session::ActiveRecordStore
308
+ current_sess_store = ActionController::Base.session_options[:database_manager]
309
+ end
310
+
311
+
312
+ if current_sess_store == required_sess_store
313
+ session_id = read_service_session_lookup(si)
314
+
315
+ if session_id
316
+ session = current_sess_store::Session.find_by_session_id(session_id)
317
+ if session
318
+ session.destroy
319
+ log.debug("Destroyed #{session.inspect} for session #{session_id.inspect} corresponding to service ticket #{si.inspect}.")
320
+ else
321
+ log.debug("Data for session #{session_id.inspect} was not found. It may have already been cleared by a local CAS logout request.")
322
+ end
323
+
324
+ log.info("Single-sign-out for session #{session_id.inspect} completed successfuly.")
325
+ else
326
+ log.warn("Couldn't destroy session with SessionIndex #{si} because no corresponding session id could be looked up.")
327
+ end
328
+ else
329
+ log.error "Cannot process logout request because this Rails application's session store is "+
330
+ " #{current_sess_store.name.inspect}. Single Sign-Out only works with the "+
331
+ " #{required_sess_store.name.inspect} session store."
332
+ end
333
+
334
+ # Return true to indicate that a single-sign-out request was detected
335
+ # and that further processing of the request is unnecessary.
336
+ return true
337
+ end
338
+
339
+ # This is not a single-sign-out request.
340
+ return false
341
+ end
342
+
343
+ def read_ticket(controller)
344
+ # Note that we are now deleting the 'ticket' and 'renew' parameters, since they really
345
+ # have no business getting passed on to the controller action.
346
+
347
+ ticket = controller.params.delete(:ticket)
348
+
349
+ return nil unless ticket
350
+
351
+ log.debug("Request contains ticket #{ticket.inspect}.")
352
+
353
+ if ticket =~ /^PT-/
354
+ CASClient::ProxyTicket.new(ticket, read_service_url(controller), controller.params.delete(:renew))
355
+ else
356
+ CASClient::ServiceTicket.new(ticket, read_service_url(controller), controller.params.delete(:renew))
357
+ end
358
+ end
359
+
360
+ def returning_from_gateway?(controller)
361
+ controller.session[:cas_sent_to_gateway]
362
+ end
363
+
364
+ def read_service_url(controller)
365
+ if config[:service_url]
366
+ log.debug("Using explicitly set service url: #{config[:service_url]}")
367
+ return config[:service_url]
368
+ end
369
+
370
+ params = controller.params.dup
371
+ params.delete(:ticket)
372
+ service_url = controller.url_for(params)
373
+ log.debug("Guessed service url: #{service_url.inspect}")
374
+ return service_url
375
+ end
376
+
377
+ # Creates a file in tmp/sessions linking a SessionTicket
378
+ # with the local Rails session id. The file is named
379
+ # cas_sess.<session ticket> and its text contents is the corresponding
380
+ # Rails session id.
381
+ # Returns the filename of the lookup file created.
382
+ def store_service_session_lookup(st, sid)
383
+ st = st.ticket if st.kind_of? CASClient::ServiceTicket
384
+ f = File.new(filename_of_service_session_lookup(st), 'w')
385
+ f.write(sid)
386
+ f.close
387
+ return f.path
388
+ end
389
+
390
+ # Returns the local Rails session ID corresponding to the given
391
+ # ServiceTicket. This is done by reading the contents of the
392
+ # cas_sess.<session ticket> file created in a prior call to
393
+ # #store_service_session_lookup.
394
+ def read_service_session_lookup(st)
395
+ st = st.ticket if st.kind_of? CASClient::ServiceTicket
396
+ ssl_filename = filename_of_service_session_lookup(st)
397
+ return File.exists?(ssl_filename) && IO.read(ssl_filename)
398
+ end
399
+
400
+ # Removes a stored relationship between a ServiceTicket and a local
401
+ # Rails session id. This should be called when the session is being
402
+ # closed.
403
+ #
404
+ # See #store_service_session_lookup.
405
+ def delete_service_session_lookup(st)
406
+ st = st.ticket if st.kind_of? CASClient::ServiceTicket
407
+ ssl_filename = filename_of_service_session_lookup(st)
408
+ File.delete(ssl_filename) if File.exists?(ssl_filename)
409
+ end
410
+
411
+ # Returns the path and filename of the service session lookup file.
412
+ def filename_of_service_session_lookup(st)
413
+ st = st.ticket if st.kind_of? CASClient::ServiceTicket
414
+ return "#{Rails.root}/tmp/sessions/cas_sess.#{st}"
415
+ end
416
+ end
417
+
418
+ class GatewayFilter < Filter
419
+ def self.use_gatewaying?
420
+ return true unless @@config[:use_gatewaying] == false
421
+ end
422
+ end
423
+ end
424
+ end
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{rubycas-client-rails}
3
+ s.version = "0.1.0"
4
+
5
+ s.authors = ["Matt Zukowski"]
6
+ s.date = %q{2011-08-13}
7
+ s.description = %q{Rails plugin for using the RubyCAS-Client as a controller filter.}
8
+ s.summary = %q{RubyCAS-Client Railtie for Rails 3.0.}
9
+ s.email = %q{matt dot zukowski at utoronto dot ca}
10
+ s.files = `git ls-files`.split("\n")
11
+ s.homepage = %q{http://rubycas-client.rubyforge.org}
12
+ s.rdoc_options = ["--main", "README.txt"]
13
+ s.require_paths = ["lib"]
14
+ s.rubyforge_project = %q{rubycas-client}
15
+
16
+ s.add_dependency('rails', '>= 3.0.0')
17
+ s.add_dependency('rubycas-client', '>= 2.2.0')
18
+ end
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubycas-client-rails
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Matt Zukowski
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-13 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rails
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 3
32
+ - 0
33
+ - 0
34
+ version: 3.0.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: rubycas-client
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 7
46
+ segments:
47
+ - 2
48
+ - 2
49
+ - 0
50
+ version: 2.2.0
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ description: Rails plugin for using the RubyCAS-Client as a controller filter.
54
+ email: matt dot zukowski at utoronto dot ca
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files: []
60
+
61
+ files:
62
+ - MIT-LICENSE
63
+ - README.markdown
64
+ - Rakefile
65
+ - init.rb
66
+ - install.rb
67
+ - lib/rubycas-client-rails.rb
68
+ - rubycas-client-rails.gemspec
69
+ - uninstall.rb
70
+ has_rdoc: true
71
+ homepage: http://rubycas-client.rubyforge.org
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --main
77
+ - README.txt
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project: rubycas-client
101
+ rubygems_version: 1.4.2
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: RubyCAS-Client Railtie for Rails 3.0.
105
+ test_files: []
106
+