cassy 1.0.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.
@@ -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
+