cassy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright 2011 YOURNAME
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,4 @@
1
+ # Cassy
2
+
3
+ This project is designed to be a Rails 3.0 engine that uses a large portion of the code from the [rubycas-server][https://github.com/gunark/rubycas-server] project. Certain portions of this code belong to the rubycas-server project owners.
4
+
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ Bundler::GemHelper.install_tasks
12
+
13
+ task :default => :spec
@@ -0,0 +1,35 @@
1
+ module Cassy
2
+ extend ActiveSupport::Autoload
3
+
4
+ autoload :CAS
5
+ autoload :Utils
6
+ autoload :Engine
7
+
8
+ def self.draw_routes
9
+ Rails.application.routes.draw do
10
+ scope(:path => "cas") do
11
+ get 'login', :to => "cassy/sessions#new"
12
+ post 'login', :to => "cassy/sessions#create"
13
+
14
+ get 'logout', :to => "cassy/sessions#destroy"
15
+
16
+ get 'serviceValidate', :to => "cassy/sessions#service_validate"
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.root
22
+ Pathname.new(File.dirname(__FILE__) + "../..")
23
+ end
24
+
25
+ # Just an easier way to get to the configuration for the engine
26
+ def self.config
27
+ Cassy::Engine.config.configuration
28
+ end
29
+
30
+ class AuthenticatorError < Exception
31
+ end
32
+ end
33
+
34
+ require 'cassy/authenticators'
35
+ require 'cassy/engine'
@@ -0,0 +1,9 @@
1
+ module Cassy
2
+ module Authenticators
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Base
6
+ autoload :Devise
7
+ autoload :Test
8
+ end
9
+ end
@@ -0,0 +1,69 @@
1
+ module Cassy
2
+ module Authenticators
3
+ class Base
4
+ class << self
5
+ attr_accessor :options
6
+ attr_reader :username # make this accessible so that we can pick up any
7
+ # transformations done within the authenticator
8
+ end
9
+
10
+ # This is called at server startup.
11
+ # Any class-wide initializiation for the authenticator should be done here.
12
+ # (e.g. establish database connection).
13
+ # You can leave this empty if you don't need to set up anything.
14
+ def self.setup(options)
15
+ end
16
+
17
+ # This is called prior to #validate (i.e. each time the user tries to log in).
18
+ # Any per-instance initialization for the authenticator should be done here.
19
+ #
20
+ # By default this makes the authenticator options hash available for #validate
21
+ # under @options and initializes @extra_attributes to an empty hash.
22
+ def self.configure(options)
23
+ raise ArgumentError, "options must be a HashWithIndifferentAccess" unless options.kind_of? HashWithIndifferentAccess
24
+ @options = options.dup
25
+ @extra_attributes = {}
26
+ end
27
+
28
+ # Override this to implement your authentication credential validation.
29
+ # This is called each time the user tries to log in. The credentials hash
30
+ # holds the credentials as entered by the user (generally under :username
31
+ # and :password keys; :service and :request are also included by default)
32
+ #
33
+ # Note that the standard credentials can be read in to instance variables
34
+ # by calling #read_standard_credentials.
35
+ def validate(credentials)
36
+ raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
37
+ end
38
+
39
+ def extra_attributes
40
+ @extra_attributes
41
+ end
42
+
43
+ protected
44
+ def self.read_standard_credentials(credentials)
45
+ @username = credentials[:username]
46
+ @password = credentials[:password]
47
+ @service = credentials[:service]
48
+ @request = credentials[:request]
49
+ end
50
+
51
+ def extra_attributes_to_extract
52
+ if @options[:extra_attributes].kind_of? Array
53
+ attrs = @options[:extra_attributes]
54
+ elsif @options[:extra_attributes].kind_of? String
55
+ attrs = @options[:extra_attributes].split(',').collect{|col| col.strip}
56
+ else
57
+ $LOG.error("Can't figure out attribute list from #{@options[:extra_attributes].inspect}. This must be an Aarray of column names or a comma-separated list.")
58
+ attrs = []
59
+ end
60
+
61
+ $LOG.debug("#{self.class.name} will try to extract the following extra_attributes: #{attrs.inspect}")
62
+ return attrs
63
+ end
64
+ end
65
+ end
66
+
67
+ class AuthenticatorError < Exception
68
+ end
69
+ end
@@ -0,0 +1,12 @@
1
+ module Cassy
2
+ module Authenticators
3
+ class Devise < Base
4
+ def self.validate(credentials)
5
+ # Find the user with the given email
6
+ user = User.find_by_email(credentials[:username])
7
+ # Did we find a user, and is their password valid?
8
+ user && user.valid_password?(credentials[:password])
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+ require 'cassy/authenticators/base'
3
+
4
+ # Dummy authenticator used for testing.
5
+ # Accepts any username as valid as long as the password is "testpassword"; otherwise authentication fails.
6
+ # Raises an AuthenticationError when username is "do_error" (this is useful to test the Exception
7
+ # handling functionality).
8
+ class Cassy::Authenticators::Test < Cassy::Authenticators::Base
9
+ def self.validate(credentials)
10
+ read_standard_credentials(credentials)
11
+
12
+ raise Cassy::AuthenticatorError, "Username is 'do_error'!" if @username == 'do_error'
13
+
14
+ # @extra_attributes[:test_utf_string] = "Ютф"
15
+ # @extra_attributes[:test_numeric] = 123.45
16
+ # @extra_attributes[:test_serialized] = {:foo => 'bar', :alpha => [1,2,3]}
17
+
18
+ valid_password = options[:password] || "testpassword"
19
+
20
+ return @password == valid_password
21
+ end
22
+ end
@@ -0,0 +1,315 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+
4
+ require 'cassy/models'
5
+ require 'cassy/utils'
6
+
7
+ # Encapsulates CAS functionality. This module is meant to be included in
8
+ # the Cassy::Controllers module.
9
+ module Cassy
10
+ module CAS
11
+
12
+ def settings
13
+ Cassy.config
14
+ end
15
+
16
+ def generate_login_ticket
17
+ # 3.5 (login ticket)
18
+ lt = Cassy::LoginTicket.new
19
+ lt.ticket = "LT-" + Cassy::Utils.random_string
20
+
21
+ lt.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
22
+ lt.save!
23
+ logger.debug("Generated login ticket '#{lt.ticket}' for client at '#{lt.client_hostname}'")
24
+ lt
25
+ end
26
+
27
+ # Creates a TicketGrantingTicket for the given username. This is done when the user logs in
28
+ # for the first time to establish their SSO session (after their credentials have been validated).
29
+ #
30
+ # The optional 'extra_attributes' parameter takes a hash of additional attributes
31
+ # that will be sent along with the username in the CAS response to subsequent
32
+ # validation requests from clients.
33
+ def generate_ticket_granting_ticket(username)
34
+ # 3.6 (ticket granting cookie/ticket)
35
+ tgt = Cassy::TicketGrantingTicket.new
36
+ tgt.ticket = "TGC-" + Cassy::Utils.random_string
37
+ tgt.username = username
38
+ tgt.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
39
+ tgt.save!
40
+ tgt
41
+ end
42
+
43
+ def generate_service_ticket(service, username, tgt)
44
+ # 3.1 (service ticket)
45
+ st = ServiceTicket.new
46
+ st.ticket = "ST-" + Cassy::Utils.random_string
47
+ st.service = service
48
+ st.username = username
49
+ st.granted_by_tgt_id = tgt.id
50
+ st.client_hostname = env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_HOST'] || env['REMOTE_ADDR']
51
+ st.save!
52
+ logger.debug("Generated service ticket '#{st.ticket}' for service '#{st.service}'" +
53
+ " for user '#{st.username}' at '#{st.client_hostname}'")
54
+ st
55
+ end
56
+
57
+ def generate_proxy_ticket(target_service, pgt)
58
+ # 3.2 (proxy ticket)
59
+ pt = ProxyTicket.new
60
+ pt.ticket = "PT-" + Cassy::Utils.random_string
61
+ pt.service = target_service
62
+ pt.username = pgt.service_ticket.username
63
+ pt.granted_by_pgt_id = pgt.id
64
+ pt.granted_by_tgt_id = pgt.service_ticket.granted_by_tgt.id
65
+ pt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
66
+ pt.save!
67
+ logger.debug("Generated proxy ticket '#{pt.ticket}' for target service '#{pt.service}'" +
68
+ " for user '#{pt.username}' at '#{pt.client_hostname}' using proxy-granting" +
69
+ " ticket '#{pgt.ticket}'")
70
+ pt
71
+ end
72
+
73
+ def generate_proxy_granting_ticket(pgt_url, st)
74
+ uri = URI.parse(pgt_url)
75
+ https = Net::HTTP.new(uri.host,uri.port)
76
+ https.use_ssl = true
77
+
78
+ # Here's what's going on here:
79
+ #
80
+ # 1. We generate a ProxyGrantingTicket (but don't store it in the database just yet)
81
+ # 2. Deposit the PGT and it's associated IOU at the proxy callback URL.
82
+ # 3. If the proxy callback URL responds with HTTP code 200, store the PGT and return it;
83
+ # otherwise don't save it and return nothing.
84
+ #
85
+ https.start do |conn|
86
+ path = uri.path.empty? ? '/' : uri.path
87
+ path += '?' + uri.query unless (uri.query.nil? || uri.query.empty?)
88
+
89
+ pgt = ProxyGrantingTicket.new
90
+ pgt.ticket = "PGT-" + Cassy::Utils.random_string(60)
91
+ pgt.iou = "PGTIOU-" + Cassy::Utils.random_string(57)
92
+ pgt.service_ticket_id = st.id
93
+ pgt.client_hostname = @env['HTTP_X_FORWARDED_FOR'] || @env['REMOTE_HOST'] || @env['REMOTE_ADDR']
94
+
95
+ # FIXME: The CAS protocol spec says to use 'pgt' as the parameter, but in practice
96
+ # the JA-SIG and Yale server implementations use pgtId. We'll go with the
97
+ # in-practice standard.
98
+ path += (uri.query.nil? || uri.query.empty? ? '?' : '&') + "pgtId=#{pgt.ticket}&pgtIou=#{pgt.iou}"
99
+
100
+ response = conn.request_get(path)
101
+ # TODO: follow redirects... 2.5.4 says that redirects MAY be followed
102
+ # NOTE: The following response codes are valid according to the JA-SIG implementation even without following redirects
103
+
104
+ if %w(200 202 301 302 304).include?(response.code)
105
+ # 3.4 (proxy-granting ticket IOU)
106
+ pgt.save!
107
+ logger.debug "PGT generated for pgt_url '#{pgt_url}': #{pgt.inspect}"
108
+ pgt
109
+ else
110
+ logger.warn "PGT callback server responded with a bad result code '#{response.code}'. PGT will not be stored."
111
+ nil
112
+ end
113
+ end
114
+ end
115
+
116
+ def validate_login_ticket(ticket)
117
+ logger.debug("Validating login ticket '#{ticket}'")
118
+
119
+ success = false
120
+ if ticket.nil?
121
+ error = "Your login request did not include a login ticket. There may be a problem with the authentication system."
122
+ logger.warn "Missing login ticket."
123
+ elsif lt = LoginTicket.find_by_ticket(ticket)
124
+ if lt.consumed?
125
+ error = "The login ticket you provided has already been used up. Please try logging in again."
126
+ logger.warn "Login ticket '#{ticket}' previously used up"
127
+ elsif Time.now - lt.created_on < settings[:maximum_unused_login_ticket_lifetime]
128
+ logger.info "Login ticket '#{ticket}' successfully validated"
129
+ else
130
+ error = "You took too long to enter your credentials. Please try again."
131
+ logger.warn "Expired login ticket '#{ticket}'"
132
+ end
133
+ else
134
+ error = "The login ticket you provided is invalid. There may be a problem with the authentication system."
135
+ logger.warn "Invalid login ticket '#{ticket}'"
136
+ end
137
+
138
+ lt.consume! if lt
139
+
140
+ error
141
+ end
142
+
143
+ def validate_ticket_granting_ticket(ticket)
144
+ logger.debug("Validating ticket granting ticket '#{ticket}'")
145
+
146
+ if ticket.nil?
147
+ error = "No ticket granting ticket given."
148
+ logger.debug error
149
+ elsif tgt = TicketGrantingTicket.find_by_ticket(ticket)
150
+ if settings.config[:maximum_session_lifetime] && Time.now - tgt.created_on > settings.config[:maximum_session_lifetime]
151
+ tgt.destroy
152
+ error = "Your session has expired. Please log in again."
153
+ logger.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' expired."
154
+ else
155
+ logger.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' successfully validated."
156
+ end
157
+ else
158
+ error = "Invalid ticket granting ticket '#{ticket}' (no matching ticket found in the database)."
159
+ logger.warn(error)
160
+ end
161
+
162
+ [tgt, error]
163
+ end
164
+
165
+ def validate_service_ticket(service, ticket, allow_proxy_tickets = false)
166
+ logger.debug "Validating service/proxy ticket '#{ticket}' for service '#{service}'"
167
+
168
+ if service.nil? or ticket.nil?
169
+ error = Error.new(:INVALID_REQUEST, "Ticket or service parameter was missing in the request.")
170
+ logger.warn "#{error.code} - #{error.message}"
171
+ elsif st = ServiceTicket.find_by_ticket(ticket)
172
+ if st.consumed?
173
+ error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' has already been used up.")
174
+ logger.warn "#{error.code} - #{error.message}"
175
+ elsif st.kind_of?(Cassy::ProxyTicket) && !allow_proxy_tickets
176
+ error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' is a proxy ticket, but only service tickets are allowed here.")
177
+ logger.warn "#{error.code} - #{error.message}"
178
+ elsif Time.now - st.created_on > settings[:maximum_unused_service_ticket_lifetime]
179
+ error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' has expired.")
180
+ logger.warn "Ticket '#{ticket}' has expired."
181
+ elsif !st.matches_service? service
182
+ error = Error.new(:INVALID_SERVICE, "The ticket '#{ticket}' belonging to user '#{st.username}' is valid,"+
183
+ " but the requested service '#{service}' does not match the service '#{st.service}' associated with this ticket.")
184
+ logger.warn "#{error.code} - #{error.message}"
185
+ else
186
+ logger.info("Ticket '#{ticket}' for service '#{service}' for user '#{st.username}' successfully validated.")
187
+ end
188
+ else
189
+ error = Error.new(:INVALID_TICKET, "Ticket '#{ticket}' not recognized.")
190
+ logger.warn("#{error.code} - #{error.message}")
191
+ end
192
+
193
+ if st
194
+ st.consume!
195
+ end
196
+
197
+
198
+ [st, error]
199
+ end
200
+
201
+ def validate_proxy_ticket(service, ticket)
202
+ pt, error = validate_service_ticket(service, ticket, true)
203
+
204
+ if pt.kind_of?(Cassy::ProxyTicket) && !error
205
+ if not pt.granted_by_pgt
206
+ error = Error.new(:INTERNAL_ERROR, "Proxy ticket '#{pt}' belonging to user '#{pt.username}' is not associated with a proxy granting ticket.")
207
+ elsif not pt.granted_by_pgt.service_ticket
208
+ error = Error.new(:INTERNAL_ERROR, "Proxy granting ticket '#{pt.granted_by_pgt}'"+
209
+ " (associated with proxy ticket '#{pt}' and belonging to user '#{pt.username}' is not associated with a service ticket.")
210
+ end
211
+ end
212
+
213
+ [pt, error]
214
+ end
215
+
216
+ def validate_proxy_granting_ticket(ticket)
217
+ if ticket.nil?
218
+ error = Error.new(:INVALID_REQUEST, "pgt parameter was missing in the request.")
219
+ logger.warn("#{error.code} - #{error.message}")
220
+ elsif pgt = ProxyGrantingTicket.find_by_ticket(ticket)
221
+ if pgt.service_ticket
222
+ logger.info("Proxy granting ticket '#{ticket}' belonging to user '#{pgt.service_ticket.username}' successfully validated.")
223
+ else
224
+ error = Error.new(:INTERNAL_ERROR, "Proxy granting ticket '#{ticket}' is not associated with a service ticket.")
225
+ logger.error("#{error.code} - #{error.message}")
226
+ end
227
+ else
228
+ error = Error.new(:BAD_PGT, "Invalid proxy granting ticket '#{ticket}' (no matching ticket found in the database).")
229
+ logger.warn("#{error.code} - #{error.message}")
230
+ end
231
+
232
+ [pgt, error]
233
+ end
234
+
235
+ # Takes an existing ServiceTicket object (presumably pulled from the database)
236
+ # and sends a POST with logout information to the service that the ticket
237
+ # was generated for.
238
+ #
239
+ # This makes possible the "single sign-out" functionality added in CAS 3.1.
240
+ # See http://www.ja-sig.org/wiki/display/CASUM/Single+Sign+Out
241
+ def send_logout_notification_for_service_ticket(st)
242
+ uri = URI.parse(st.service)
243
+ uri.path = '/' if uri.path.empty?
244
+ time = Time.now
245
+ rand = Cassy::Utils.random_string
246
+
247
+ begin
248
+ response = Net::HTTP.post_form(uri, {'logoutRequest' => URI.escape(%{<samlp:LogoutRequest ID="#{rand}" Version="2.0" IssueInstant="#{time.rfc2822}">
249
+ <saml:NameID></saml:NameID>
250
+ <samlp:SessionIndex>#{st.ticket}</samlp:SessionIndex>
251
+ </samlp:LogoutRequest>})})
252
+ if response.kind_of? Net::HTTPSuccess
253
+ logger.info "Logout notification successfully posted to #{st.service.inspect}."
254
+ return true
255
+ else
256
+ logger.error "Service #{st.service.inspect} responed to logout notification with code '#{response.code}'!"
257
+ return false
258
+ end
259
+ rescue Exception => e
260
+ logger.error "Failed to send logout notification to service #{st.service.inspect} due to #{e}"
261
+ return false
262
+ end
263
+ end
264
+
265
+ def service_uri_with_ticket(service, st)
266
+ raise ArgumentError, "Second argument must be a ServiceTicket!" unless st.kind_of? Cassy::ServiceTicket
267
+
268
+ # This will choke with a URI::InvalidURIError if service URI is not properly URI-escaped...
269
+ # This exception is handled further upstream (i.e. in the controller).
270
+ service_uri = URI.parse(service)
271
+
272
+ if service.include? "?"
273
+ if service_uri.query.empty?
274
+ query_separator = ""
275
+ else
276
+ query_separator = "&"
277
+ end
278
+ else
279
+ query_separator = "?"
280
+ end
281
+
282
+ service_with_ticket = service + query_separator + "ticket=" + st.ticket
283
+ service_with_ticket
284
+ end
285
+
286
+ # Strips CAS-related parameters from a service URL and normalizes it,
287
+ # removing trailing / and ?. Also converts any spaces to +.
288
+ #
289
+ # For example, "http://google.com?ticket=12345" will be returned as
290
+ # "http://google.com". Also, "http://google.com/" would be returned as
291
+ # "http://google.com".
292
+ #
293
+ # Note that only the first occurance of each CAS-related parameter is
294
+ # removed, so that "http://google.com?ticket=12345&ticket=abcd" would be
295
+ # returned as "http://google.com?ticket=abcd".
296
+ def clean_service_url(dirty_service)
297
+ return dirty_service if dirty_service.blank?
298
+ clean_service = dirty_service.dup
299
+ ['service', 'ticket', 'gateway', 'renew'].each do |p|
300
+ clean_service.sub!(Regexp.new("&?#{p}=[^&]*"), '')
301
+ end
302
+
303
+ clean_service.gsub!(/[\/\?&]$/, '') # remove trailing ?, /, or &
304
+ clean_service.gsub!('?&', '?')
305
+ clean_service.gsub!(' ', '+')
306
+
307
+ logger.debug("Cleaned dirty service URL #{dirty_service.inspect} to #{clean_service.inspect}") if
308
+ dirty_service != clean_service
309
+
310
+ return clean_service
311
+ end
312
+ module_function :clean_service_url
313
+
314
+ end
315
+ end
@@ -0,0 +1,10 @@
1
+ module Cassy
2
+ class Engine < Rails::Engine
3
+
4
+ config.config_file = ENV["RUBYCAS_CONFIG_FILE"] || "/etc/rubycas-server/config.yml"
5
+
6
+ config.after_initialize do
7
+ config.configuration = HashWithIndifferentAccess.new(YAML.load_file(config.config_file))
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ require 'cassy'
2
+
3
+ Dir[Cassy.root + "app/models/cassy/*.rb"].each do |file|
4
+ require f
5
+ end
@@ -0,0 +1,32 @@
1
+ require 'crypt-isaac'
2
+
3
+ # Misc utility function used throughout by the RubyCAS-Server.
4
+ module Cassy
5
+ module Utils
6
+ def random_string(max_length = 29)
7
+ rg = Crypt::ISAAC.new
8
+ max = 4294619050
9
+ r = "#{Time.now.to_i}r%X%X%X%X%X%X%X%X" %
10
+ [rg.rand(max), rg.rand(max), rg.rand(max), rg.rand(max),
11
+ rg.rand(max), rg.rand(max), rg.rand(max), rg.rand(max)]
12
+ r[0..max_length-1]
13
+ end
14
+ module_function :random_string
15
+
16
+ def log_controller_action(controller, params)
17
+ $LOG << "\n"
18
+
19
+ /`(.*)'/.match(caller[1])
20
+ method = $~[1]
21
+
22
+ if params.respond_to? :dup
23
+ params2 = params.dup
24
+ params2['password'] = '******' if params2['password']
25
+ else
26
+ params2 = params
27
+ end
28
+ $LOG.debug("Processing #{controller}::#{method} #{params2.inspect}")
29
+ end
30
+ module_function :log_controller_action
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :cassy do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cassy
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.0
6
+ platform: ruby
7
+ authors:
8
+ - ryan@rubyx.com
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-16 00:00:00 +10:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: crypt-isaac
18
+ requirement: &id001 !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: &id002 !ruby/object:Gem::Requirement
30
+ none: false
31
+ requirements:
32
+ - - "="
33
+ - !ruby/object:Gem::Version
34
+ version: 3.0.7
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec-rails
40
+ requirement: &id003 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.6.0
46
+ type: :development
47
+ prerelease: false
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: capybara
51
+ requirement: &id004 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: "1.0"
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: database_cleaner
62
+ requirement: &id005 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: sqlite3
73
+ requirement: &id006 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: "0"
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: launchy
84
+ requirement: &id007 !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
94
+ name: devise
95
+ requirement: &id008 !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ~>
99
+ - !ruby/object:Gem::Version
100
+ version: "1.3"
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: *id008
104
+ description: An engine that provides a CAS server to the application it's included within.
105
+ email:
106
+ executables: []
107
+
108
+ extensions: []
109
+
110
+ extra_rdoc_files: []
111
+
112
+ files:
113
+ - lib/cassy/authenticators/base.rb
114
+ - lib/cassy/authenticators/devise.rb
115
+ - lib/cassy/authenticators/test.rb
116
+ - lib/cassy/authenticators.rb
117
+ - lib/cassy/cas.rb
118
+ - lib/cassy/engine.rb
119
+ - lib/cassy/models.rb
120
+ - lib/cassy/utils.rb
121
+ - lib/cassy.rb
122
+ - lib/tasks/cassy_tasks.rake
123
+ - MIT-LICENSE
124
+ - Rakefile
125
+ - README.markdown
126
+ has_rdoc: true
127
+ homepage:
128
+ licenses: []
129
+
130
+ post_install_message:
131
+ rdoc_options: []
132
+
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ none: false
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ hash: 3009467735426729429
141
+ segments:
142
+ - 0
143
+ version: "0"
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ hash: 3009467735426729429
150
+ segments:
151
+ - 0
152
+ version: "0"
153
+ requirements: []
154
+
155
+ rubyforge_project:
156
+ rubygems_version: 1.6.2
157
+ signing_key:
158
+ specification_version: 3
159
+ summary: Insert Cassy summary.
160
+ test_files: []
161
+