authlogic 0.10.4

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of authlogic might be problematic. Click here for more details.

Files changed (102) hide show
  1. data/CHANGELOG.rdoc +47 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest +100 -0
  4. data/README.rdoc +292 -0
  5. data/Rakefile +15 -0
  6. data/authlogic.gemspec +38 -0
  7. data/init.rb +1 -0
  8. data/lib/authlogic.rb +25 -0
  9. data/lib/authlogic/active_record/acts_as_authentic.rb +265 -0
  10. data/lib/authlogic/active_record/authenticates_many.rb +19 -0
  11. data/lib/authlogic/active_record/scoped_session.rb +28 -0
  12. data/lib/authlogic/controller_adapters/abstract_adapter.rb +25 -0
  13. data/lib/authlogic/controller_adapters/rails_adapter.rb +39 -0
  14. data/lib/authlogic/session/active_record_trickery.rb +26 -0
  15. data/lib/authlogic/session/base.rb +510 -0
  16. data/lib/authlogic/session/callbacks.rb +56 -0
  17. data/lib/authlogic/session/config.rb +237 -0
  18. data/lib/authlogic/session/errors.rb +18 -0
  19. data/lib/authlogic/sha512_crypto_provider.rb +18 -0
  20. data/lib/authlogic/version.rb +56 -0
  21. data/test_app/README +256 -0
  22. data/test_app/Rakefile +10 -0
  23. data/test_app/app/controllers/application.rb +72 -0
  24. data/test_app/app/controllers/companies_controller.rb +2 -0
  25. data/test_app/app/controllers/user_sessions_controller.rb +25 -0
  26. data/test_app/app/controllers/users_controller.rb +61 -0
  27. data/test_app/app/helpers/application_helper.rb +3 -0
  28. data/test_app/app/helpers/companies_helper.rb +2 -0
  29. data/test_app/app/helpers/user_sessions_helper.rb +2 -0
  30. data/test_app/app/helpers/users_helper.rb +2 -0
  31. data/test_app/app/models/company.rb +4 -0
  32. data/test_app/app/models/project.rb +3 -0
  33. data/test_app/app/models/user.rb +5 -0
  34. data/test_app/app/models/user_session.rb +3 -0
  35. data/test_app/app/views/layouts/application.html.erb +27 -0
  36. data/test_app/app/views/user_sessions/new.html.erb +15 -0
  37. data/test_app/app/views/users/_form.erb +15 -0
  38. data/test_app/app/views/users/edit.html.erb +8 -0
  39. data/test_app/app/views/users/new.html.erb +8 -0
  40. data/test_app/app/views/users/show.html.erb +29 -0
  41. data/test_app/config/boot.rb +109 -0
  42. data/test_app/config/database.yml +19 -0
  43. data/test_app/config/environment.rb +69 -0
  44. data/test_app/config/environments/development.rb +17 -0
  45. data/test_app/config/environments/production.rb +22 -0
  46. data/test_app/config/environments/test.rb +22 -0
  47. data/test_app/config/initializers/inflections.rb +10 -0
  48. data/test_app/config/initializers/mime_types.rb +5 -0
  49. data/test_app/config/initializers/new_rails_defaults.rb +17 -0
  50. data/test_app/config/routes.rb +11 -0
  51. data/test_app/db/development.sqlite3 +0 -0
  52. data/test_app/db/migrate/20081023040052_create_users.rb +20 -0
  53. data/test_app/db/migrate/20081103003828_create_companies.rb +14 -0
  54. data/test_app/db/migrate/20081103003834_create_projects.rb +18 -0
  55. data/test_app/db/schema.rb +46 -0
  56. data/test_app/db/test.sqlite3 +0 -0
  57. data/test_app/doc/README_FOR_APP +2 -0
  58. data/test_app/public/404.html +30 -0
  59. data/test_app/public/422.html +30 -0
  60. data/test_app/public/500.html +30 -0
  61. data/test_app/public/dispatch.cgi +10 -0
  62. data/test_app/public/dispatch.fcgi +24 -0
  63. data/test_app/public/dispatch.rb +10 -0
  64. data/test_app/public/favicon.ico +0 -0
  65. data/test_app/public/images/rails.png +0 -0
  66. data/test_app/public/javascripts/application.js +2 -0
  67. data/test_app/public/javascripts/controls.js +963 -0
  68. data/test_app/public/javascripts/dragdrop.js +972 -0
  69. data/test_app/public/javascripts/effects.js +1120 -0
  70. data/test_app/public/javascripts/prototype.js +4225 -0
  71. data/test_app/public/robots.txt +5 -0
  72. data/test_app/public/stylesheets/scaffold.css +62 -0
  73. data/test_app/script/about +4 -0
  74. data/test_app/script/console +3 -0
  75. data/test_app/script/dbconsole +3 -0
  76. data/test_app/script/destroy +3 -0
  77. data/test_app/script/generate +3 -0
  78. data/test_app/script/performance/benchmarker +3 -0
  79. data/test_app/script/performance/profiler +3 -0
  80. data/test_app/script/performance/request +3 -0
  81. data/test_app/script/plugin +3 -0
  82. data/test_app/script/process/inspector +3 -0
  83. data/test_app/script/process/reaper +3 -0
  84. data/test_app/script/process/spawner +3 -0
  85. data/test_app/script/runner +3 -0
  86. data/test_app/script/server +3 -0
  87. data/test_app/test/fixtures/companies.yml +7 -0
  88. data/test_app/test/fixtures/projects.yml +4 -0
  89. data/test_app/test/fixtures/users.yml +21 -0
  90. data/test_app/test/functional/companies_controller_test.rb +8 -0
  91. data/test_app/test/functional/user_sessions_controller_test.rb +36 -0
  92. data/test_app/test/functional/users_controller_test.rb +8 -0
  93. data/test_app/test/integration/company_user_session_stories_test.rb +46 -0
  94. data/test_app/test/integration/user_sesion_stories_test.rb +105 -0
  95. data/test_app/test/integration/user_session_config_test.rb +24 -0
  96. data/test_app/test/integration/user_session_test.rb +161 -0
  97. data/test_app/test/test_helper.rb +81 -0
  98. data/test_app/test/unit/account_test.rb +8 -0
  99. data/test_app/test/unit/company_test.rb +8 -0
  100. data/test_app/test/unit/project_test.rb +8 -0
  101. data/test_app/test/unit/user_test.rb +80 -0
  102. metadata +201 -0
@@ -0,0 +1,26 @@
1
+ module Authlogic
2
+ module Session
3
+ # = ActiveRecord Trickery
4
+ #
5
+ # Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not ActiveRecord. That's the goal here. This is useful for the various rails helper methods such as form_for, error_messages_for, etc.
6
+ # These helpers exptect various methods to be present. This adds in those methods into Authlogic.
7
+ module ActiveRecordTrickery
8
+ def self.included(klass) # :nodoc:
9
+ klass.extend ClassMethods
10
+ klass.send(:include, InstanceMethods)
11
+ end
12
+
13
+ module ClassMethods # :nodoc:
14
+ def human_attribute_name(attribute_key_name, options = {})
15
+ attribute_key_name.humanize
16
+ end
17
+ end
18
+
19
+ module InstanceMethods # :nodoc:
20
+ def new_record?
21
+ new_session?
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,510 @@
1
+ module Authlogic
2
+ module Session # :nodoc:
3
+ # = Base
4
+ #
5
+ # This is the muscle behind Authlogic. For detailed information on how to use this please refer to the README. For detailed method explanations see below.
6
+ class Base
7
+ include Config
8
+
9
+ class << self
10
+ # Returns true if a controller have been set and can be used properly. This MUST be set before anything can be done. Similar to how ActiveRecord won't allow you to do anything
11
+ # without establishing a DB connection. By default this is done for you automatically, but if you are using Authlogic in a unique way outside of rails, you need to assign a controller
12
+ # object to Authlogic via Authlogic::Session::Base.controller = obj.
13
+ def activated?
14
+ !controller.blank?
15
+ end
16
+
17
+ def controller=(value) # :nodoc:
18
+ controllers[Thread.current] = value
19
+ end
20
+
21
+ def controller # :nodoc:
22
+ controllers[Thread.current]
23
+ end
24
+
25
+ # A convenince method. The same as:
26
+ #
27
+ # session = UserSession.new
28
+ # session.create
29
+ def create(*args)
30
+ session = new(*args)
31
+ session.save
32
+ end
33
+
34
+ # Same as create but calls create!, which raises an exception when authentication fails
35
+ def create!(*args)
36
+ session = new(*args)
37
+ session.save!
38
+ end
39
+
40
+ # A convenience method for session.find_record. Finds your session by session, then cookie, and finally basic http auth. Perfect for that global before_filter to find your logged in user:
41
+ #
42
+ # before_filter :load_user
43
+ #
44
+ # def load_user
45
+ # @user_session = UserSession.find
46
+ # @current_user = @user_session && @user_session.record
47
+ # end
48
+ #
49
+ # Accepts a single parameter as the id. See initialize for more information on ids. Lastly, how it finds the session can be modified via configuration.
50
+ def find(id = nil)
51
+ args = [id].compact
52
+ session = new(*args)
53
+ return session if session.find_record
54
+ nil
55
+ end
56
+
57
+ def klass # :nodoc:
58
+ @klass ||=
59
+ if klass_name
60
+ klass_name.constantize
61
+ else
62
+ nil
63
+ end
64
+ end
65
+
66
+ def klass_name # :nodoc:
67
+ @klass_name ||=
68
+ if guessed_name = name.scan(/(.*)Session/)[0]
69
+ @klass_name = guessed_name[0]
70
+ end
71
+ end
72
+
73
+ # The current scope set, should be used in the block passed to with_scope.
74
+ def scope
75
+ scopes[Thread.current]
76
+ end
77
+
78
+ # Authentication can be scoped, but scoping authentication can get a little tricky. Checkout the section "Scoping" in the readme for more details.
79
+ #
80
+ # What with_scopes focuses on is scoping the query when finding the object and the name of the cookies.
81
+ #
82
+ # with_scope accepts a hash with any of the following options:
83
+ #
84
+ # * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find. This is used when trying to find the record.
85
+ # * <tt>id:</tt> see the id method above
86
+ #
87
+ # So you use it just like an ActiveRecord scope, essentially:
88
+ #
89
+ # UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
90
+ # UserSession.find
91
+ # end
92
+ #
93
+ # Eseentially what the above does is scope the searching of the object with the sql you provided. So instead of:
94
+ #
95
+ # User.find(:first, :conditions => "login = 'ben'")
96
+ #
97
+ # it would be:
98
+ #
99
+ # User.find(:first, :conditions => "login = 'ben' and account_id = 2")
100
+ #
101
+ # You will also notice the :id option. This works just like the id method. It scopes your cookies. So the name of your cookie will be:
102
+ #
103
+ # account_2_user_credentials
104
+ #
105
+ # instead of:
106
+ #
107
+ # user_credentials
108
+ #
109
+ # What is also nifty about scoping with an :id is that it merges your id's. So if you do:
110
+ #
111
+ # UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
112
+ # session = UserSession.new
113
+ # session.id = :secure
114
+ # end
115
+ #
116
+ # The name of your cookies will be:
117
+ #
118
+ # secure_account_2_user_credentials
119
+ def with_scope(options = {}, &block)
120
+ raise ArgumentError.new("You must provide a block") unless block_given?
121
+ self.scope = options
122
+ result = yield
123
+ self.scope = nil
124
+ result
125
+ end
126
+
127
+ private
128
+ def controllers
129
+ @@controllers ||= {}
130
+ end
131
+
132
+ def scope=(value)
133
+ scopes[Thread.current] = value
134
+ end
135
+
136
+ def scopes
137
+ @scopes ||= {}
138
+ end
139
+ end
140
+
141
+ attr_accessor :login_with, :new_session
142
+ attr_reader :record, :unauthorized_record
143
+ attr_writer :id, :scope
144
+
145
+ # You can initialize a session by doing any of the following:
146
+ #
147
+ # UserSession.new
148
+ # UserSession.new(login, password)
149
+ # UserSession.new(:login => login, :password => password)
150
+ # UserSession.new(User.first)
151
+ #
152
+ # If a user has more than one session you need to pass an id so that Authlogic knows how to differentiate the sessions. The id MUST be a Symbol.
153
+ #
154
+ # UserSession.new(:my_id)
155
+ # UserSession.new(login, password, :my_id)
156
+ # UserSession.new({:login => loing, :password => password}, :my_id)
157
+ # UserSession.new(User.first, :my_id)
158
+ #
159
+ # Ids are rarely used, but they can be useful. For example, what if users allow other users to login into their account via proxy? Now that user can "technically" be logged into 2 accounts at once.
160
+ # To solve this just pass a id called :proxy, or whatever you want. Authlogic will separate everything out.
161
+ def initialize(*args)
162
+ raise NotActivated.new(self) unless self.class.activated?
163
+
164
+ create_configurable_methods!
165
+
166
+ self.scope = self.class.scope
167
+ self.id = args.pop if args.last.is_a?(Symbol)
168
+
169
+ case args.size
170
+ when 1
171
+ credentials_or_record = args.first
172
+ case credentials_or_record
173
+ when Hash
174
+ self.credentials = credentials_or_record
175
+ else
176
+ self.unauthorized_record = credentials_or_record
177
+ end
178
+ else
179
+ send("#{login_field}=", args[0]) if args.size > 0
180
+ send("#{password_field}=", args[1]) if args.size > 1
181
+ self.remember_me = args[2] if args.size > 2
182
+ end
183
+ end
184
+
185
+ # Your login credentials in hash format. Usually {:login => "my login", :password => "<protected>"} depending on your configuration.
186
+ # Password is protected as a security measure. The raw password should never be publicly accessible.
187
+ def credentials
188
+ {login_field => send(login_field), password_field => "<Protected>"}
189
+ end
190
+
191
+ # Lets you set your loging and password via a hash format. This is "params" safe. It only allows for 3 keys: your login field name, password field name, and remember me.
192
+ def credentials=(values)
193
+ return if values.blank? || !values.is_a?(Hash)
194
+ values.symbolize_keys!
195
+ [login_field.to_sym, password_field.to_sym, :remember_me].each do |field|
196
+ next if !values.key?(field)
197
+ send("#{field}=", values[field])
198
+ end
199
+ end
200
+
201
+ # Resets everything, your errors, record, cookies, and session. Basically "logs out" a user.
202
+ def destroy
203
+ errors.clear
204
+ @record = nil
205
+ controller.cookies.delete cookie_key
206
+ controller.session[session_key] = nil
207
+ true
208
+ end
209
+
210
+ # The errors in Authlogic work JUST LIKE ActiveRecord. In fact, it uses the exact same ActiveRecord errors class. Use it the same way:
211
+ #
212
+ # === Example
213
+ #
214
+ # class UserSession
215
+ # before_validation :check_if_awesome
216
+ #
217
+ # private
218
+ # def check_if_awesome
219
+ # errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
220
+ # errors.add_to_base("You must be awesome to log in") unless record.awesome?
221
+ # end
222
+ # end
223
+ def errors
224
+ @errors ||= Errors.new(self)
225
+ end
226
+
227
+ # Attempts to find the record by session, then cookie, and finally basic http auth. See the class level find method if you are wanting to use this in a before_filter to persist your session.
228
+ def find_record
229
+ return record if record
230
+ find_with.each do |find_method|
231
+ if send("valid_#{find_method}?")
232
+ if record.class.column_names.include?("last_request_at")
233
+ record.last_request_at = Time.now
234
+ record.save_without_session_maintenance(false)
235
+ end
236
+ return record
237
+ end
238
+ end
239
+ nil
240
+ end
241
+
242
+ # Allows you to set a unique identifier for your session, so that you can have more than 1 session at a time. A good example when this might be needed is when you want to have a normal user session
243
+ # and a "secure" user session. The secure user session would be created only when they want to modify their billing information, or other sensative information. Similar to me.com. This requires 2
244
+ # user sessions. Just use an id for the "secure" session and you should be good.
245
+ #
246
+ # You can set the id a number of ways:
247
+ #
248
+ # session = Session.new(:secure)
249
+ # session = Session.new("username", "password", :secure)
250
+ # session = Session.new({:username => "username", :password => "password"}, :secure)
251
+ # session.id = :secure
252
+ #
253
+ # Just be sure and set your id before you validate / create / update your session.
254
+ def id
255
+ @id
256
+ end
257
+
258
+ def inspect # :nodoc:
259
+ details = {}
260
+ case login_with
261
+ when :unauthorized_record
262
+ details[:unauthorized_record] = "<protected>"
263
+ else
264
+ details[login_field.to_sym] = send(login_field)
265
+ details[password_field.to_sym] = "<protected>"
266
+ end
267
+ "#<#{self.class.name} #{details.inspect}>"
268
+ end
269
+
270
+ # Similar to ActiveRecord's new_record? Returns true if the session has not been saved yet.
271
+ def new_session?
272
+ new_session != false
273
+ end
274
+
275
+ def remember_me # :nodoc:
276
+ return @remember_me if @set_remember_me
277
+ @remember_me ||= self.class.remember_me
278
+ end
279
+
280
+ # Accepts a boolean as a flag to remember the session or not. Basically to expire the cookie at the end of the session or keep it for "remember_me_until".
281
+ def remember_me=(value)
282
+ @set_remember_me = true
283
+ @remember_me = value
284
+ end
285
+
286
+ # Allows users to be remembered via a cookie.
287
+ def remember_me?
288
+ remember_me == true || remember_me == "true" || remember_me == "1"
289
+ end
290
+
291
+ # When to expire the cookie. See remember_me_for configuration option to change this.
292
+ def remember_me_until
293
+ return unless remember_me?
294
+ remember_me_for.from_now
295
+ end
296
+
297
+ # See the class level with_scope method on information on scopes. with_scope essentialls sets this scope with the options passed and unsets it after the block executes.
298
+ def scope
299
+ @scope ||= {}
300
+ end
301
+
302
+ # Creates / updates a new user session for you. It does all of the magic:
303
+ #
304
+ # 1. validates
305
+ # 2. sets session
306
+ # 3. sets cookie
307
+ # 4. updates magic fields
308
+ def save
309
+ if valid?
310
+ update_session!
311
+ controller.cookies[cookie_key] = {
312
+ :value => record.send(remember_token_field),
313
+ :expires => remember_me_until
314
+ }
315
+
316
+ record.login_count = record.login_count + 1 if record.respond_to?(:login_count)
317
+
318
+ if record.respond_to?(:current_login_at)
319
+ record.last_login_at = record.current_login_at if record.respond_to?(:last_login_at)
320
+ record.current_login_at = Time.now
321
+ end
322
+
323
+ if record.respond_to?(:current_login_ip)
324
+ record.last_login_ip = record.current_login_ip if record.respond_to?(:last_login_ip)
325
+ record.current_login_ip = controller.request.remote_ip
326
+ end
327
+
328
+ record.save_without_session_maintenance(false)
329
+
330
+ self.new_session = false
331
+ self
332
+ end
333
+ end
334
+
335
+ # Same as save but raises an exception when authentication fails
336
+ def save!
337
+ result = save
338
+ raise SessionInvalid.new(self) unless result
339
+ result
340
+ end
341
+
342
+ # Sometimes you don't want to create a session via credentials (login and password). Maybe you already have the record. Just set this record to this and it will be authenticated when you try to validate
343
+ # the session. Basically this is another form of credentials, you are just skipping username and password validation.
344
+ def unauthorized_record=(value)
345
+ self.login_with = :unauthorized_record
346
+ @unauthorized_record = value
347
+ end
348
+
349
+ # Returns if the session is valid or not. Basically it means that a record could or could not be found. If the session is valid you will have a result when calling the "record" method. If it was unsuccessful
350
+ # you will not have a record.
351
+ def valid?
352
+ errors.clear
353
+ temp_record = validate_credentials
354
+ if errors.empty?
355
+ @record = temp_record
356
+ return true
357
+ end
358
+ false
359
+ end
360
+
361
+ # Tries to validate the session from information from a basic http auth, if it was provided.
362
+ def valid_http_auth?
363
+ controller.authenticate_with_http_basic do |login, password|
364
+ if !login.blank? && !password.blank?
365
+ send("#{login_method}=", login)
366
+ send("#{password_method}=", password)
367
+ result = valid?
368
+ if result
369
+ update_session!
370
+ return result
371
+ end
372
+ end
373
+ end
374
+
375
+ false
376
+ end
377
+
378
+ # Tries to validate the session from information in the cookie
379
+ def valid_cookie?
380
+ if cookie_credentials
381
+ self.unauthorized_record = search_for_record("find_by_#{remember_token_field}", cookie_credentials)
382
+ result = valid?
383
+ if result
384
+ update_session!
385
+ self.new_session = false
386
+ return result
387
+ end
388
+ end
389
+
390
+ false
391
+ end
392
+
393
+ # Tries to validate the session from information in the session
394
+ def valid_session?
395
+ if session_credentials
396
+ self.unauthorized_record = search_for_record("find_by_#{remember_token_field}", session_credentials)
397
+ result = valid?
398
+ if result
399
+ self.new_session = false
400
+ return result
401
+ end
402
+ end
403
+
404
+ false
405
+ end
406
+
407
+ private
408
+ def controller
409
+ self.class.controller
410
+ end
411
+
412
+ def cookie_credentials
413
+ controller.cookies[cookie_key]
414
+ end
415
+
416
+ def create_configurable_methods!
417
+ return if respond_to?(login_field) # already created these methods
418
+
419
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
420
+ attr_reader :#{login_field}
421
+
422
+ def #{login_field}=(value)
423
+ self.login_with = :credentials
424
+ @#{login_field} = value
425
+ end
426
+
427
+ def #{password_field}=(value)
428
+ self.login_with = :credentials
429
+ @#{password_field} = value
430
+ end
431
+
432
+ def #{password_field}; end
433
+
434
+ private
435
+ # The password should not be accessible publicly. This way forms using form_for don't fill the password with the attempted password. The prevent this we just create this method that is private.
436
+ def protected_#{password_field}
437
+ @#{password_field}
438
+ end
439
+ end_eval
440
+ end
441
+
442
+ def search_for_record(method, value)
443
+ klass.send(:with_scope, :find => (scope[:find_options] || {})) do
444
+ klass.send(method, value)
445
+ end
446
+ end
447
+
448
+ def klass
449
+ self.class.klass
450
+ end
451
+
452
+ def klass_name
453
+ self.class.klass_name
454
+ end
455
+
456
+ def session_credentials
457
+ controller.session[session_key]
458
+ end
459
+
460
+ def update_session!
461
+ controller.session[session_key] = record && record.send(remember_token_field)
462
+ end
463
+
464
+ def validate_credentials
465
+ temp_record = unauthorized_record
466
+
467
+ case login_with
468
+ when :credentials
469
+ errors.add(login_field, "can not be blank") if send(login_field).blank?
470
+ errors.add(password_field, "can not be blank") if send("protected_#{password_field}").blank?
471
+ return if errors.count > 0
472
+
473
+ temp_record = search_for_record(find_by_login_method, send(login_field))
474
+
475
+ if temp_record.blank?
476
+ errors.add(login_field, "was not found")
477
+ return
478
+ end
479
+
480
+ unless temp_record.send(verify_password_method, send("protected_#{password_field}"))
481
+ errors.add(password_field, "is invalid")
482
+ return
483
+ end
484
+ when :unauthorized_record
485
+ if temp_record.blank?
486
+ errors.add_to_base("You can not log in with a blank record.")
487
+ return
488
+ end
489
+
490
+ if temp_record.new_record?
491
+ errors.add_to_base("You can not login with a new record.") if temp_record.new_record?
492
+ return
493
+ end
494
+ else
495
+ errors.add_to_base("You must provide some form of credentials before logging in.")
496
+ return
497
+ end
498
+
499
+ [:active, :approved, :confirmed].each do |required_status|
500
+ if temp_record.respond_to?("#{required_status}?") && !temp_record.send("#{required_status}?")
501
+ errors.add_to_base("Your account has not been marked as #{required_status}")
502
+ return
503
+ end
504
+ end
505
+
506
+ temp_record
507
+ end
508
+ end
509
+ end
510
+ end