authlogic 1.0.0 → 1.1.0

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 (54) hide show
  1. data/CHANGELOG.rdoc +19 -0
  2. data/Manifest +29 -15
  3. data/README.rdoc +17 -15
  4. data/Rakefile +1 -1
  5. data/authlogic.gemspec +7 -7
  6. data/lib/authlogic.rb +21 -4
  7. data/lib/authlogic/controller_adapters/abstract_adapter.rb +19 -4
  8. data/lib/authlogic/controller_adapters/merb_adapter.rb +0 -27
  9. data/lib/authlogic/controller_adapters/rails_adapter.rb +0 -14
  10. data/lib/authlogic/crypto_providers/sha1.rb +24 -0
  11. data/lib/authlogic/crypto_providers/sha512.rb +30 -0
  12. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic.rb +89 -0
  13. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb +144 -0
  14. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/logged_in.rb +41 -0
  15. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb +62 -0
  16. data/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance.rb +83 -0
  17. data/lib/authlogic/orm_adapters/active_record_adapter/authenticates_many.rb +58 -0
  18. data/lib/authlogic/{active_record/scoped_session.rb → session/authenticates_many_association.rb} +12 -3
  19. data/lib/authlogic/session/base.rb +63 -93
  20. data/lib/authlogic/session/callbacks.rb +15 -3
  21. data/lib/authlogic/session/config.rb +130 -26
  22. data/lib/authlogic/session/cookies.rb +39 -0
  23. data/lib/authlogic/session/openid.rb +106 -0
  24. data/lib/authlogic/session/params.rb +28 -0
  25. data/lib/authlogic/session/session.rb +33 -0
  26. data/lib/authlogic/testing/shoulda_macros.rb +17 -0
  27. data/lib/authlogic/version.rb +1 -1
  28. data/test/fixtures/users.yml +2 -2
  29. data/{test_libs → test/libs}/aes128_crypto_provider.rb +0 -0
  30. data/{test_libs → test/libs}/mock_controller.rb +7 -0
  31. data/{test_libs → test/libs}/mock_cookie_jar.rb +0 -0
  32. data/{test_libs → test/libs}/mock_request.rb +0 -0
  33. data/{test_libs → test/libs}/ordered_hash.rb +0 -0
  34. data/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_test.rb +217 -0
  35. data/test/orm_adapters_tests/active_record_adapter_tests/authenticates_many_test.rb +32 -0
  36. data/test/session_tests/active_record_trickery_test.rb +14 -0
  37. data/test/session_tests/authenticates_many_association_test.rb +20 -0
  38. data/test/session_tests/base_test.rb +264 -0
  39. data/test/session_tests/config_test.rb +165 -0
  40. data/test/session_tests/cookies_test.rb +32 -0
  41. data/test/session_tests/params_test.rb +16 -0
  42. data/test/session_tests/scopes_test.rb +60 -0
  43. data/test/session_tests/session_test.rb +45 -0
  44. data/test/test_helper.rb +14 -5
  45. metadata +57 -29
  46. data/lib/authlogic/active_record/acts_as_authentic.rb +0 -297
  47. data/lib/authlogic/active_record/authenticates_many.rb +0 -56
  48. data/lib/authlogic/sha512_crypto_provider.rb +0 -18
  49. data/test/active_record_acts_as_authentic_test.rb +0 -213
  50. data/test/active_record_authenticates_many_test.rb +0 -28
  51. data/test/user_session_active_record_trickery_test.rb +0 -12
  52. data/test/user_session_base_test.rb +0 -316
  53. data/test/user_session_config_test.rb +0 -144
  54. data/test/user_session_scopes_test.rb +0 -19
@@ -4,10 +4,10 @@ module Authlogic
4
4
  #
5
5
  # Just like in ActiveRecord you have before_save, before_validation, etc. You have similar callbacks with Authlogic, see all callbacks below.
6
6
  module Callbacks
7
- CALLBACKS = %w(before_create after_create before_destroy after_destroy before_save after_save before_update after_update before_validation after_validation)
7
+ CALLBACKS = %w(before_create after_create before_destroy after_destroy before_find after_find before_save after_save before_update after_update before_validation after_validation)
8
8
 
9
9
  def self.included(base) #:nodoc:
10
- [:destroy, :save, :validate].each do |method|
10
+ [:destroy, :find_record, :save, :validate].each do |method|
11
11
  base.send :alias_method_chain, method, :callbacks
12
12
  end
13
13
 
@@ -15,7 +15,7 @@ module Authlogic
15
15
  base.define_callbacks *CALLBACKS
16
16
  end
17
17
 
18
- # Run the following callbacks:
18
+ # Runs the following callbacks:
19
19
  #
20
20
  # before_destroy
21
21
  # destroy
@@ -27,6 +27,18 @@ module Authlogic
27
27
  result
28
28
  end
29
29
 
30
+ # Runs the following callbacks:
31
+ #
32
+ # before_find
33
+ # find_record
34
+ # after_find # if a record was found
35
+ def find_record_with_callbacks
36
+ run_callbacks(:before_find)
37
+ result = find_record_without_callbacks
38
+ run_callbacks(:after_find) if result
39
+ result
40
+ end
41
+
30
42
  # Runs the following callbacks:
31
43
  #
32
44
  # before_save
@@ -6,9 +6,11 @@ module Authlogic
6
6
  klass.send(:include, InstanceMethods)
7
7
  end
8
8
 
9
- # = Config
9
+ # = Session Config
10
10
  #
11
- # Configuration is simple. The configuration options are just class methods. Just put this in your config/initializers directory
11
+ # This deals with configuration for your session. If you are wanting to configure your model please look at Authlogic::ORMAdapters::ActiveRecord::ActsAsAuthentic
12
+ #
13
+ # Configuration for your session is simple. The configuration options are just class methods. Just put this in your config/initializers directory
12
14
  #
13
15
  # UserSession.configure do |config|
14
16
  # config.authenticate_with = User
@@ -46,6 +48,9 @@ module Authlogic
46
48
  # The name of the cookie or the key in the cookies hash. Be sure and use a unique name. If you have multiple sessions and they use the same cookie it will cause problems.
47
49
  # Also, if a id is set it will be inserted into the beginning of the string. Exmaple:
48
50
  #
51
+ # session = UserSession.new
52
+ # session.cookie_key => "user_credentials"
53
+ #
49
54
  # session = UserSession.new(:super_high_secret)
50
55
  # session.cookie_key => "super_high_secret_user_credentials"
51
56
  #
@@ -60,12 +65,17 @@ module Authlogic
60
65
  end
61
66
  alias_method :cookie_key=, :cookie_key
62
67
 
63
- # The name of the method used to find the record by the login. What's nifty about this is that you can do anything in your method, Authlogic will just pass you the login.
68
+ # Authlogic tries to validate the credentials passed to it. One part of validation is actually finding the user and making sure it exists. What method it uses the do this is up to you.
69
+ #
70
+ # Let's say you have a UserSession that is authenticating a User. By default UserSession will call User.find_by_login(login). You can change what method UserSession calls by specifying it here. Then
71
+ # in your User model you can make that method do anything you want, giving you complete control of how users are found by the UserSession.
64
72
  #
65
- # Let's say you allow users to login by username or email. Set this to "find_login", or whatever method you want. Then in your model create a class method like:
73
+ # Let's take an example: You want to allow users to login by username or email. Set this to the name of the class method that does this in the User model. Let's call it "find_by_username_or_email"
66
74
  #
67
- # def self.find_login(login)
68
- # find_by_login(login) || find_by_email(login)
75
+ # class User < ActiveRecord::Base
76
+ # def self.find_by_username_or_email(login)
77
+ # find_by_username(login) || find_by_email(login)
78
+ # end
69
79
  # end
70
80
  #
71
81
  # * <tt>Default:</tt> "find_by_#{login_field}"
@@ -79,13 +89,28 @@ module Authlogic
79
89
  end
80
90
  alias_method :find_by_login_method=, :find_by_login_method
81
91
 
82
- # Calling UserSession.find tries to find the user session by session, then cookie, then basic http auth. This option allows you to change the order or remove any of these.
92
+ # Once the user confirms their openid Authlogic tries to find the record with that openod. This is the method it called on the record's
93
+ # class to find the record by the openid.
83
94
  #
84
- # * <tt>Default:</tt> [:session, :cookie, :http_auth]
95
+ # * <tt>Default:</tt> "find_by_#{openid_field}"
96
+ # * <tt>Accepts:</tt> Symbol or String
97
+ def find_by_openid_method(value = nil)
98
+ if value.nil?
99
+ read_inheritable_attribute(:find_by_openid_method) || find_by_openid_method("find_by_#{openid_field}")
100
+ else
101
+ write_inheritable_attribute(:find_by_openid_method, value)
102
+ end
103
+ end
104
+ alias_method :find_by_openid_method=, :find_by_openid_method
105
+
106
+ # Calling UserSession.find tries to find the user session by session, then cookie, then params, and finally by basic http auth.
107
+ # This option allows you to change the order or remove any of these.
108
+ #
109
+ # * <tt>Default:</tt> [:session, :cookie, :params, :http_auth]
85
110
  # * <tt>Accepts:</tt> Array, and can only use any of the 3 options above
86
111
  def find_with(*values)
87
112
  if values.blank?
88
- read_inheritable_attribute(:find_with) || find_with(:session, :cookie, :http_auth)
113
+ read_inheritable_attribute(:find_with) || find_with(:session, :cookie, :params, :http_auth)
89
114
  else
90
115
  values.flatten!
91
116
  write_inheritable_attribute(:find_with, values)
@@ -93,28 +118,94 @@ module Authlogic
93
118
  end
94
119
  alias_method :find_with=, :find_with
95
120
 
96
- # The name of the method you want Authlogic to create for storing the login / username. Keep in mind this is just for your Authlogic::Session, if you want it can be something completely different
97
- # than the field in your model. So if you wanted people to login with a field called "login" and then find users by email this is compeltely doable. See the find_by_login_method configuration option for
98
- # more details.
121
+ # Every time a session is found the last_request_at field for that record is updatd with the current time, if that field exists. If you want to limit how frequent that field is updated specify the threshold
122
+ # here. For example, if your user is making a request every 5 seconds, and you feel this is too frequent, and feel a minute is a good threashold. Set this to 1.minute. Once a minute has passed in between
123
+ # requests the field will be updated.
124
+ #
125
+ # * <tt>Default:</tt> 0
126
+ # * <tt>Accepts:</tt> integer representing time in seconds
127
+ def last_request_at_threshold(value = nil)
128
+ if value.nil?
129
+ read_inheritable_attribute(:last_request_at_threshold) || last_request_at_threshold(0)
130
+ else
131
+ write_inheritable_attribute(:last_request_at_threshold, value)
132
+ end
133
+ end
134
+ alias_method :last_request_at_threshold=, :last_request_at_threshold
135
+
136
+ # The name of the method you want Authlogic to create for storing the login / username. Keep in mind this is just for your
137
+ # Authlogic::Session, if you want it can be something completely different than the field in your model. So if you wanted people to
138
+ # login with a field called "login" and then find users by email this is compeltely doable. See the find_by_login_method configuration
139
+ # option for more details.
99
140
  #
100
141
  # * <tt>Default:</tt> Guesses based on the model columns, tries login, username, and email. If none are present it defaults to login
101
142
  # * <tt>Accepts:</tt> Symbol or String
102
143
  def login_field(value = nil)
103
144
  if value.nil?
104
- read_inheritable_attribute(:login_field) || login_field((klass.column_names.include?("login") && :login) || (klass.column_names.include?("username") && :username) || (klass.column_names.include?("email") && :email) || :login)
145
+ read_inheritable_attribute(:login_field) || login_field(klass.login_field)
105
146
  else
106
147
  write_inheritable_attribute(:login_field, value)
107
148
  end
108
149
  end
109
150
  alias_method :login_field=, :login_field
110
151
 
152
+ # The name of the method you want Authlogic to create for storing the openid url. Keep in mind this is just for your Authlogic::Session,
153
+ # if you want it can be something completely different than the field in your model. So if you wanted people to login with a field called
154
+ # "openid_url" and then find users by openid this is compeltely doable. See the find_by_openid_method configuration option for
155
+ # more details.
156
+ #
157
+ # * <tt>Default:</tt> Guesses based on the model columns, tries openid, openid_url, identity_url.
158
+ # * <tt>Accepts:</tt> Symbol or String
159
+ def openid_field(value = nil)
160
+ if value.nil?
161
+ read_inheritable_attribute(:openid_field) || openid_field((klass.column_names.include?("openid") && :openid) || (klass.column_names.include?("openid_url") && :openid_url) || (klass.column_names.include?("identity_url") && :identity_url))
162
+ else
163
+ write_inheritable_attribute(:openid_field, value)
164
+ end
165
+ end
166
+ alias_method :openid_field=, :openid_field
167
+
168
+ # The name of the method you want Authlogic to create for storing the openid url. Keep in mind this is just for your Authlogic::Session,
169
+ # if you want it can be something completely different than the field in your model. So if you wanted people to login with a field called
170
+ # "openid_url" and then find users by openid this is compeltely doable. See the find_by_openid_method configuration option for
171
+ # more details.
172
+ #
173
+ # * <tt>Default:</tt> Guesses based on the model columns, tries openid, openid_url, identity_url.
174
+ # * <tt>Accepts:</tt> Symbol or String
175
+ def openid_file_store_path(value = nil)
176
+ if value.nil?
177
+ read_inheritable_attribute(:openid_file_store_path) || openid_file_store_path((defined?(RAILS_ROOT) && RAILS_ROOT + "/tmp/openids") || (defined?(Merb) && Merb.root + "/tmp/openids"))
178
+ else
179
+ write_inheritable_attribute(:openid_file_store_path, value)
180
+ end
181
+ end
182
+ alias_method :openid_file_store_path=, :openid_file_store_path
183
+
184
+ # Works exactly like cookie_key, but for params. So a user can login via params just like a cookie or a session. Your URK would look like:
185
+ #
186
+ # http://www.domain.com?user_credentials=fdsfdfd32jfksdjfdksl
187
+ #
188
+ # You can change the "user_credentials" key above with this configuration option. Keep in mind, just like cookie_key, if you supply an id
189
+ # the id will be appended to the front.
190
+ #
191
+ # * <tt>Default:</tt> cookie_key
192
+ # * <tt>Accepts:</tt> String
193
+ def params_key(value = nil)
194
+ if value.nil?
195
+ read_inheritable_attribute(:params_key) || params_key(cookie_key)
196
+ else
197
+ write_inheritable_attribute(:params_key, value)
198
+ end
199
+ end
200
+ alias_method :params_key=, :params_key
201
+
111
202
  # Works exactly like login_field, but for the password instead.
112
203
  #
113
204
  # * <tt>Default:</tt> Guesses based on the model columns, tries password and pass. If none are present it defaults to password
114
205
  # * <tt>Accepts:</tt> Symbol or String
115
206
  def password_field(value = nil)
116
207
  if value.nil?
117
- read_inheritable_attribute(:password_field) || password_field((klass.column_names.include?("password") && :password) || (klass.column_names.include?("pass") && :pass) || :password)
208
+ read_inheritable_attribute(:password_field) || password_field(klass.password_field)
118
209
  else
119
210
  write_inheritable_attribute(:password_field, value)
120
211
  end
@@ -155,14 +246,7 @@ module Authlogic
155
246
  # * <tt>Accepts:</tt> Symbol or String
156
247
  def remember_token_field(value = nil)
157
248
  if value.nil?
158
- read_inheritable_attribute(:remember_token_field) ||
159
- remember_token_field(
160
- (klass.column_names.include?("remember_token") && :remember_token) ||
161
- (klass.column_names.include?("remember_key") && :remember_key) ||
162
- (klass.column_names.include?("cookie_token") && :cookie_token) ||
163
- (klass.column_names.include?("cookie_key") && :cookie_key) ||
164
- :remember_token
165
- )
249
+ read_inheritable_attribute(:remember_token_field) || remember_token_field(klass.remember_token_field)
166
250
  else
167
251
  write_inheritable_attribute(:remember_token_field, value)
168
252
  end
@@ -198,21 +282,36 @@ module Authlogic
198
282
 
199
283
  module InstanceMethods # :nodoc:
200
284
  def cookie_key
201
- key_parts = [id, scope[:id], self.class.cookie_key].compact
202
- key_parts.join("_")
285
+ build_key(self.class.cookie_key)
203
286
  end
204
287
 
205
288
  def find_by_login_method
206
289
  self.class.find_by_login_method
207
290
  end
208
291
 
292
+ def find_by_openid_method
293
+ self.class.find_by_openid_method
294
+ end
295
+
209
296
  def find_with
210
297
  self.class.find_with
211
298
  end
299
+
300
+ def last_request_at_threshold
301
+ self.class.last_request_at_threshold
302
+ end
212
303
 
213
304
  def login_field
214
305
  self.class.login_field
215
306
  end
307
+
308
+ def openid_field
309
+ self.class.openid_field
310
+ end
311
+
312
+ def params_key
313
+ build_key(self.class.params_key)
314
+ end
216
315
 
217
316
  def password_field
218
317
  self.class.password_field
@@ -228,13 +327,18 @@ module Authlogic
228
327
  end
229
328
 
230
329
  def session_key
231
- key_parts = [id, scope[:id], self.class.session_key].compact
232
- key_parts.join("_")
330
+ build_key(self.class.session_key)
233
331
  end
234
332
 
235
333
  def verify_password_method
236
334
  self.class.verify_password_method
237
335
  end
336
+
337
+ private
338
+ def build_key(last_part)
339
+ key_parts = [id, scope[:id], last_part].compact
340
+ key_parts.join("_")
341
+ end
238
342
  end
239
343
  end
240
344
  end
@@ -0,0 +1,39 @@
1
+ module Authlogic
2
+ module Session
3
+ # = Cookies
4
+ #
5
+ # Handles all authentication that deals with cookies, such as persisting a session and saving / destroying a session.
6
+ module Cookies
7
+ def self.included(klass)
8
+ klass.after_save :save_cookie
9
+ klass.after_destroy :destroy_cookie
10
+ end
11
+
12
+ # Tries to validate the session from information in the cookie
13
+ def valid_cookie?
14
+ if cookie_credentials
15
+ self.unauthorized_record = search_for_record("find_by_#{remember_token_field}", cookie_credentials)
16
+ return valid?
17
+ end
18
+
19
+ false
20
+ end
21
+
22
+ private
23
+ def cookie_credentials
24
+ controller.cookies[cookie_key]
25
+ end
26
+
27
+ def save_cookie
28
+ controller.cookies[cookie_key] = {
29
+ :value => record.send(remember_token_field),
30
+ :expires => remember_me_until
31
+ }
32
+ end
33
+
34
+ def destroy_cookie
35
+ controller.cookies.delete cookie_key
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,106 @@
1
+ module Authlogic
2
+ module Session
3
+ module OpenID
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ alias_method_chain :initialize, :openid
7
+ alias_method_chain :credentials=, :openid
8
+ alias_method_chain :create_configurable_methods!, :openid
9
+ before_validation :valid_openid?
10
+ attr_accessor :openid_response
11
+ end
12
+ end
13
+
14
+ def initialize_with_openid(*args)
15
+ initialize_without_openid(*args)
16
+ self.authenticating_with = :openid if openid_verification_complete?
17
+ end
18
+
19
+ def credentials_with_openid=(values)
20
+ result = self.credentials_without_openid = values
21
+ return result if openid_field.blank? || values.blank? || !values.is_a?(Hash) || values[:openid].blank?
22
+ self.openid = values[:openid]
23
+ result
24
+ end
25
+
26
+ # Returns true if logging in with openid. Credentials mean username and password.
27
+ def authenticating_with_openid?
28
+ authenticating_with == :openid
29
+ end
30
+
31
+ def verify_openid?
32
+ authenticating_with_openid? && controller.params[:openid_complete] != "1"
33
+ end
34
+
35
+ def openid_verified?
36
+ controller.params[:openid_complete] == "1"
37
+ end
38
+
39
+ def valid_openid?
40
+ return false if openid_field.blank?
41
+
42
+ if openid_verification_complete?
43
+ case openid_response.status
44
+ when OpenID::Consumer::SUCCESS
45
+
46
+ when OpenID::Consumer::CANCEL
47
+ errors.add_to_base("OpenID authentication was cancelled.")
48
+ when OpenID::Consumer::FAILURE
49
+ errors.add_to_base("OpenID authentication failed.")
50
+ when OpenID::Consumer::SETUP_NEEDED
51
+ errors.add_to_Base("OpenID authentication needs setup.")
52
+ end
53
+ else
54
+ if authenticating_with_openid?
55
+ if send(openid_field).blank?
56
+ errors.add(openid_field, "can not be blank")
57
+ return false
58
+ end
59
+
60
+ unless search_for_record(find_by_openid_method, send(openid_field))
61
+ errors.add(openid_field, "did not match any records in our database")
62
+ return false
63
+ end
64
+
65
+ begin
66
+ self.openid_response = openid_consumer.begin(send(openid_field))
67
+ rescue OpenID::OpenIDError => e
68
+ errors.add("The OpenID identifier #{send(openid_field)} could not be found: #{e}")
69
+ return false
70
+ end
71
+
72
+ sregreq = OpenID::SReg::Request.new
73
+ # required fields
74
+ #sregreq.request_fields(['email','nickname'], true)
75
+ # optional fields
76
+ #sregreq.request_fields(['dob', 'fullname'], false)
77
+ oidreq.add_extension(sregreq)
78
+ oidreq.return_to_args["openid_complete"] = 1
79
+ end
80
+ end
81
+ end
82
+
83
+ private
84
+ def create_configurable_methods_with_openid!
85
+ create_configurable_methods_without_openid!
86
+
87
+ return if openid_field.blank? || respond_to?(openid_field)
88
+
89
+ if openid_field
90
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
91
+ attr_reader :#{openid_field}
92
+
93
+ def #{openid_field}=(value)
94
+ self.authenticating_with = :openid
95
+ @#{openid_field} = value
96
+ end
97
+ end_eval
98
+ end
99
+ end
100
+
101
+ def openid_consumer
102
+ @openid_consumer ||= OpenID::Consumer.new(controller.session, OpenID::FilesystemStore.new(openid_file_store_path))
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,28 @@
1
+ module Authlogic
2
+ module Session
3
+ # = Params
4
+ #
5
+ # Tries to log the user in via params. Think about cookies and sessions. They are just hashes in your controller, so are params. People never
6
+ # look at params as an authentication option, but it can be useful for logging into private feeds. Logging in a user is as simple as:
7
+ #
8
+ # http://www.domain.com?user_credentials=[insert remember token here]
9
+ #
10
+ # The user_credentials is based on the name of your session, the above example assumes UserSession. Also, this can be modified via configuration.
11
+ module Params
12
+ # Tries to validate the session from information in the params token
13
+ def valid_params?
14
+ if params_credentials
15
+ self.unauthorized_record = search_for_record("find_by_#{remember_token_field}", params_credentials)
16
+ return valid?
17
+ end
18
+
19
+ false
20
+ end
21
+
22
+ private
23
+ def params_credentials
24
+ controller.params[params_key]
25
+ end
26
+ end
27
+ end
28
+ end