authlogic 2.0.5 → 2.0.6

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.

@@ -1,6 +1,24 @@
1
+ == 2.0.6
2
+
3
+ * Don't use second, use [1] instead so older rails systems don't complain.
4
+ * Update email regular expression to be less TLD specific: (?:[A-Z]{2,4}|museum|travel)
5
+ * Update shoulda macro for 2.0
6
+ * validates_length_of_password_confirmation_field_options defaults to validates_confirmation_of_password_field_options
7
+ * Use MockCookieJar in tests instead of a Hash.
8
+ * Cookies now store the record id as well, for faster lookup. Also to avoid the need to use sessions since sessions are lazily loaded in rails 2.3+
9
+ * Add configuration option for Authlogic::ActsAsAuthentic: ignore_blank_passwords
10
+ * Fix cookie_domain in rails adapter
11
+ * Make password and login fields optional. This allows you to have an alternate authentication method as your main authentication source. Such as OpenID, LDAP, or whatever you want.
12
+ * Reset the @password_changed instance variable after the record has been saved.
13
+ * Add referer and user_agent to mock requests for testing purposes.
14
+ * Add :case_sensitive => false to validates_uniqueness_of calls on the login and email fields.
15
+ * MockRequest not tries to use controller.env['REMOTE_ADDR'] for the IP address in tests.
16
+ * Add in custom find_with_email and find_with_login methods to perform case insensitive searches for databases that are case sensitive by default. This is only done if the :case_insensitive option for validates_uniqueness_of_login_field_options or validates_uniqueness_of_email_field_options is set to false. Which, as of this version, it is. If you are using MySQL this has been the default behavior all along. If you are using SQLite or Postgres this has NOT been the default behavior.
17
+ * Added in exception explaining that you are using the old configuration for acts_as_authentic with an example of the new format.
18
+
1
19
  == 2.0.5 released 2009-3-30
2
20
 
3
- * Stub out authenticate_with_http_basic for TestCase::Controller adapter.
21
+ * Stub out authenticate_with_http_basic for TestCase::ControllerAdapter.
4
22
  * Added second parameter for add_acts_as_authentic module to specify the position: append or prepend.
5
23
 
6
24
  == 2.0.4 released 2009-3-28
@@ -2,11 +2,11 @@
2
2
 
3
3
  Authlogic is a clean, simple, and unobtrusive ruby authentication solution.
4
4
 
5
- What inspired me to create Authlogic was the messiness of the current authentication solutions. Put simply, they just didn't feel right. They felt wrong because the logic was not organized properly. As you may know, a common misconception with the MVC design pattern is that the model "M" is only for data access logic, which is wrong. A model is a place for domain logic. This is why the RESTful design pattern and the current authentication solutions don't play nice. Authlogic solves this by placing the session maintenance logic into its own domain (aka "model"). Moving session maintenance into its own domain has its benefits:
5
+ What inspired me to create Authlogic was the messiness of the current authentication solutions. Put simply, they just didn't feel right, because the logic was not organized properly. As you may know, a common misconception with the MVC design pattern is that the model "M" is only for data access logic, which is wrong. A model is a place for domain logic. This is why the RESTful design pattern and the current authentication solutions don't play nice. Authlogic solves this by placing the session maintenance logic into its own domain (aka "model"). Moving session maintenance into its own domain has its benefits:
6
6
 
7
- 1. It's easier to update and stay current with the latest security practices. Since authlogic sits in between you and your session it can assist in keeping your security top notch. Such as upgrading your hashing algorithm, helping you transition to a new algorithm, etc. Also, Authlogic is a gem, which means you get all of these benefits easily, through a rubygems update.
7
+ 1. It's easier to update and stay current with the latest security practices. Since authlogic sits in between you and your session it can assist in keeping your security up to date. For example: upgrading your hashing algorithm, helping you transition to a new algorithm, etc. Since all of this logic is in the Authlogic library, staying up to date is as easy as updating the library.
8
8
  2. It ties everything together on the domain level. Take a new user registration for example, no reason to manually log the user in, authlogic handles this for you via callbacks. The same applies to a user changing their password. Authlogic handles maintaining the session for you.
9
- 3. Your application can stay clean and focused and free of redundant authentication code from app to app. Meaning generators are *NOT* necessary at all.
9
+ 3. Your application can stay clean, focused, and free of redundant authentication code from app to app. Meaning generators are *NOT* necessary. Not any more neccessary than any other control
10
10
  4. A byproduct of #3 is that you don't have to test the same code over and over in each of your apps. You don't test the internals of ActiveRecord in each of your apps, so why would you test the internals of Authlogic? It's already been thoroughly tested for you. Focus on your application, and get rid of the noise by testing your application specific code and not generated code that you didn't write.
11
11
  5. You get to write your own code, just like you do for any other model. Meaning the code you write is specific to your application, the way you want it, and more importantly you understand it.
12
12
  6. You are not restricted to a single session. Think about Apple's me.com, where they need you to authenticate a second time before changing your billing information. Why not just create a second session for this? It works just like your initial session. Then your billing controller can require an "ultra secure" session.
@@ -16,17 +16,26 @@ Authlogic can do all of this and much more, keep reading to see...
16
16
  == Helpful links
17
17
 
18
18
  * <b>Documentation:</b> http://authlogic.rubyforge.org
19
+ * <b>Live example with OpenID "add on" & source code:</b> http://authlogicexample.binarylogic.com
19
20
  * <b>Tutorial: Authlogic basic setup:</b> http://www.binarylogic.com/2008/11/3/tutorial-authlogic-basic-setup
20
21
  * <b>Tutorial: Reset passwords with Authlogic the RESTful way:</b> http://www.binarylogic.com/2008/11/16/tutorial-reset-passwords-with-authlogic
21
- * <b>Tutorial: Using OpenID with Authlogic:</b> http://www.binarylogic.com/2008/11/21/tutorial-using-openid-with-authlogic
22
- * <b>Live example of the tutorials above (with source):</b> http://authlogicexample.binarylogic.com
23
22
  * <b>Tutorial: Easily migrate from restful_authentication:</b> http://www.binarylogic.com/2008/11/23/tutorial-easily-migrate-from-restful_authentication-to-authlogic
24
23
  * <b>Tutorial: Upgrade passwords easily with Authlogic:</b> http://www.binarylogic.com/2008/11/23/tutorial-upgrade-passwords-easily-with-authlogic
25
24
  * <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/18752-authlogic
26
25
  * <b>Google group:</b> http://groups.google.com/group/authlogic
27
26
 
28
- **Before contacting me, please read:**
29
- If you find a bug or a problem please post it on lighthouse. If you need help with something, please use google groups. I check both regularly and get emails when anything happens, so that is the best place to get help. Please do not email me directly, with issues regarding Authlogic.
27
+ <b>Before contacting me, please read:</b>
28
+
29
+ If you find a bug or a problem please post it on lighthouse. If you need help with something, please use google groups. I check both regularly and get emails when anything happens, so that is the best place to get help.
30
+
31
+ Please do not email me directly with issues regarding Authlogic. This is an inefficient way to get help because it doesn't help people who have the same problem in the future.
32
+
33
+ == Authlogic "add ons"
34
+
35
+ * <b>Authlogic OpenID addon:</b> http://github.com/binarylogic/authlogic_openid
36
+ * <b>Authlogic LDAP addon:</b> http://github.com/binarylogic/authlogic_ldap
37
+
38
+ If you create one of your own, please let me know about it so I can add it to this list.
30
39
 
31
40
  == Documentation
32
41
 
@@ -184,7 +193,7 @@ This will create a file that looks similar to:
184
193
  The user model should have the following columns. The names of these columns can be changed with configuration. Better yet, Authlogic tries to guess these names by checking for the existence of common names. See the sub modules of Authlogic::Session for more details, but chances are you won't have to specify any configuration for your field names, even if they aren't the same names as below.
185
194
 
186
195
  t.string :login, :null => false # optional, you can use email instead, or both
187
- t.string :crypted_password, :null => false # required
196
+ t.string :crypted_password, :null => false # optional, see below
188
197
  t.string :password_salt, :null => false # optional, but highly recommended
189
198
  t.string :persistence_token, :null => false # required
190
199
  t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params
@@ -197,6 +206,8 @@ The user model should have the following columns. The names of these columns can
197
206
  t.string :current_login_ip # optional, see Authlogic::Session::MagicColumns
198
207
  t.string :last_login_ip # optional, see Authlogic::Session::MagicColumns
199
208
 
209
+ Notice the login and crypted_password fields are optional. If you prefer, you could use OpenID, LDAP, or whatever you want as your main authentication source and not even provide your own authentication system. I recommend providing your own as an option though. Your interface, such as the registration form, can dictate which method is the default. Lastly, adding 3rd party authentication methods should be as easy as installing an Authlogic "add on" gem. See "Authligic add ons" above.
210
+
200
211
  === 4. Set up your model
201
212
 
202
213
  Make sure you have a model that you will be authenticating with. Since we are using the User model it should look something like:
@@ -266,4 +277,4 @@ A lot of them forced me to name my password column as "this", or the key of my c
266
277
  I am not trying to "bash" any other authentication solutions. These are just my opinions, formulate your own opinion. I released Authlogic because I was "scratching my own itch". It has made my life easier and I enjoy using it, hopefully it does the same for you.
267
278
 
268
279
 
269
- Copyright (c) 2008 Ben Johnson of [Binary Logic](http://www.binarylogic.com), released under the MIT license
280
+ Copyright (c) 2009 {Ben Johnson of Binary Logic}(http://www.binarylogic.com), released under the MIT license
@@ -23,7 +23,7 @@ module Authlogic
23
23
  # end
24
24
  #
25
25
  # See the various sub modules for the configuration they provide.
26
- def acts_as_authentic(&block)
26
+ def acts_as_authentic(unsupported_options = nil, &block)
27
27
  # Stop all configuration if the DB is not set up
28
28
  begin
29
29
  column_names
@@ -31,6 +31,9 @@ module Authlogic
31
31
  return
32
32
  end
33
33
 
34
+ raise ArgumentError.new("You are using the old v1.X.X configuration method for Authlogic. Instead of " +
35
+ "passing a hash of configuration options to acts_as_authentic, pass a block: acts_as_authentic { |c| c.my_option = my_value }") if !unsupported_options.nil?
36
+
34
37
  yield self if block_given?
35
38
  acts_as_authentic_modules.each { |mod| include mod }
36
39
  end
@@ -73,9 +76,9 @@ module Authlogic
73
76
  end
74
77
  end
75
78
 
76
- def first_column_to_exist(*columns_to_check) # :nodoc:
79
+ def first_column_to_exist(*columns_to_check)
77
80
  columns_to_check.each { |column_name| return column_name.to_sym if column_names.include?(column_name.to_s) }
78
- columns_to_check.first ? columns_to_check.first.to_sym : nil
81
+ columns_to_check.first && columns_to_check.first.to_sym
79
82
  end
80
83
 
81
84
  end
@@ -19,7 +19,7 @@ module Authlogic
19
19
  # * <tt>Default:</tt> :email, if it exists
20
20
  # * <tt>Accepts:</tt> Symbol
21
21
  def email_field(value = nil)
22
- config(:email_field, value, first_column_to_exist(nil, :email))
22
+ config(:email_field, value, first_column_to_exist(nil, :email, :email_address))
23
23
  end
24
24
  alias_method :email_field=, :email_field
25
25
 
@@ -52,10 +52,10 @@ module Authlogic
52
52
 
53
53
  # A hash of options for the validates_uniqueness_of call for the email field. Allows you to change this however you want.
54
54
  #
55
- # * <tt>Default:</tt> {:scope => validations_scope, :if => "#{email_field}_changed?".to_sym}
55
+ # * <tt>Default:</tt> {:case_sensitive => false, :scope => validations_scope, :if => "#{email_field}_changed?".to_sym}
56
56
  # * <tt>Accepts:</tt> Hash of options accepted by validates_uniqueness_of
57
57
  def validates_uniqueness_of_email_field_options(value = nil)
58
- config(:validates_uniqueness_of_email_field_options, value, {:scope => validations_scope, :if => "#{email_field}_changed?".to_sym})
58
+ config(:validates_uniqueness_of_email_field_options, value, {:case_sensitive => false, :scope => validations_scope, :if => "#{email_field}_changed?".to_sym})
59
59
  end
60
60
  alias_method :validates_uniqueness_of_email_field_options=, :validates_uniqueness_of_email_field_options
61
61
 
@@ -64,7 +64,7 @@ module Authlogic
64
64
  return @email_regex if @email_regex
65
65
  email_name_regex = '[\w\.%\+\-]+'
66
66
  domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
67
- domain_tld_regex = '(?:[A-Z]{2}|aero|ag|asia|at|be|biz|ca|cc|cn|com|de|edu|eu|fm|gov|gs|jobs|jp|in|info|me|mil|mobi|museum|ms|name|net|nu|nz|org|tc|tw|tv|uk|us|vg|ws)'
67
+ domain_tld_regex = '(?:[A-Z]{2,4}|museum|travel)'
68
68
  @email_regex = /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
69
69
  end
70
70
  end
@@ -73,6 +73,8 @@ module Authlogic
73
73
  module Methods
74
74
  def self.included(klass)
75
75
  klass.class_eval do
76
+ extend ClassMethods
77
+
76
78
  if validate_email_field && email_field
77
79
  validates_length_of email_field, validates_length_of_email_field_options
78
80
  validates_format_of email_field, validates_format_of_email_field_options
@@ -80,6 +82,24 @@ module Authlogic
80
82
  end
81
83
  end
82
84
  end
85
+
86
+ # Class methods relating to the email field
87
+ module ClassMethods
88
+ # Calls alias_method if your email_field name is "out of the norm".
89
+ def self.included(klass)
90
+ klass.send(:alias_method, "find_with_email", "find_with_#{email_field}") if klass.email_field != :email
91
+ end
92
+
93
+ # Please see the find_with_login method in Authlogic::ActsAsAuthentic::Login module. It's the same exact thing
94
+ # but for the login field instead of the email field.
95
+ def find_with_email(email)
96
+ if validates_uniqueness_of_email_field_options[:case_sensitive] == false
97
+ first(:conditions => ["LOWER(#{quoted_table_name}.#{email_field}) = ?", email.downcase])
98
+ else
99
+ send("find_by_#{email_field}", email)
100
+ end
101
+ end
102
+ end
83
103
  end
84
104
  end
85
105
  end
@@ -27,27 +27,33 @@ module Authlogic
27
27
  # All methods for the logged in status feature seat.
28
28
  module Methods
29
29
  def self.included(klass)
30
+ return if !klass.column_names.include?("last_request_at")
31
+
30
32
  klass.class_eval do
33
+ include InstanceMethods
34
+
31
35
  named_scope :logged_in, lambda { {:conditions => ["last_request_at > ?", logged_in_timeout.seconds.ago]} }
32
36
  named_scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", logged_in_timeout.seconds.ago]} }
33
37
  end
34
38
  end
35
39
 
36
- # Returns true if the last_request_at > logged_in_timeout.
37
- def logged_in?
38
- raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at)
39
- !last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
40
- end
41
-
42
- # Opposite of logged_in?
43
- def logged_out?
44
- !logged_in?
45
- end
40
+ module InstanceMethods
41
+ # Returns true if the last_request_at > logged_in_timeout.
42
+ def logged_in?
43
+ raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at)
44
+ !last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
45
+ end
46
46
 
47
- private
48
- def logged_in_timeout
49
- self.class.logged_in_timeout
47
+ # Opposite of logged_in?
48
+ def logged_out?
49
+ !logged_in?
50
50
  end
51
+
52
+ private
53
+ def logged_in_timeout
54
+ self.class.logged_in_timeout
55
+ end
56
+ end
51
57
  end
52
58
  end
53
59
  end
@@ -49,18 +49,21 @@ module Authlogic
49
49
 
50
50
  # A hash of options for the validates_uniqueness_of call for the login field. Allows you to change this however you want.
51
51
  #
52
- # * <tt>Default:</tt> {:scope => validations_scope, :if => "#{login_field}_changed?".to_sym}
52
+ # * <tt>Default:</tt> {:case_sensitive => false, :scope => validations_scope, :if => "#{login_field}_changed?".to_sym}
53
53
  # * <tt>Accepts:</tt> Hash of options accepted by validates_uniqueness_of
54
54
  def validates_uniqueness_of_login_field_options(value = nil)
55
- config(:validates_uniqueness_of_login_field_options, value, {:scope => validations_scope, :if => "#{login_field}_changed?".to_sym})
55
+ config(:validates_uniqueness_of_login_field_options, value, {:case_sensitive => false, :scope => validations_scope, :if => "#{login_field}_changed?".to_sym})
56
56
  end
57
57
  alias_method :validates_uniqueness_of_login_field_options=, :validates_uniqueness_of_login_field_options
58
58
  end
59
59
 
60
60
  # All methods relating to the login field
61
61
  module Methods
62
+ # Adds in various validations, modules, etc.
62
63
  def self.included(klass)
63
64
  klass.class_eval do
65
+ extend ClassMethods
66
+
64
67
  if validate_login_field && login_field
65
68
  validates_length_of login_field, validates_length_of_login_field_options
66
69
  validates_format_of login_field, validates_format_of_login_field_options
@@ -68,6 +71,35 @@ module Authlogic
68
71
  end
69
72
  end
70
73
  end
74
+
75
+ # Class methods relating to the login field.
76
+ module ClassMethods
77
+ # Calls alias_method if your login_field name is "out of the norm".
78
+ def self.included(klass)
79
+ klass.send(:alias_method, "find_with_login", "find_with_#{login_field}") if klass.login_field != :login
80
+ end
81
+
82
+ # This method allows you to find a record with the given login. If you notice, with ActiveRecord you have the
83
+ # validates_uniqueness_of validation function. They give you a :case_sensitive option. I handle this in the same
84
+ # manner that they handle that. If you set false for the :case_sensitive option in validates_uniqueness_of_login_field_options
85
+ # this method will modify the query to look something like:
86
+ #
87
+ # first(:conditions => ["LOWER(#{quoted_table_name}.#{login_field}) = ?", login.downcase])
88
+ #
89
+ # If you don't specify this it calls the good old find_by_* method:
90
+ #
91
+ # find_by_login(login)
92
+ #
93
+ # The only reason I need to do the above is for Postgres and SQLite since they perform case sensitive searches with the
94
+ # find_by_* methods.
95
+ def find_with_login(login)
96
+ if validates_uniqueness_of_login_field_options[:case_sensitive] == false
97
+ first(:conditions => ["LOWER(#{quoted_table_name}.#{login_field}) = ?", login.downcase])
98
+ else
99
+ send("find_by_#{login_field}", login)
100
+ end
101
+ end
102
+ end
71
103
  end
72
104
  end
73
105
  end
@@ -18,7 +18,7 @@ module Authlogic
18
18
  # * <tt>Default:</tt> :crypted_password, :encrypted_password, :password_hash, or :pw_hash
19
19
  # * <tt>Accepts:</tt> Symbol
20
20
  def crypted_password_field(value = nil)
21
- config(:crypted_password_field, value, first_column_to_exist(:crypted_password, :encrypted_password, :password_hash, :pw_hash))
21
+ config(:crypted_password_field, value, first_column_to_exist(nil, :crypted_password, :encrypted_password, :password_hash, :pw_hash))
22
22
  end
23
23
  alias_method :crypted_password_field=, :crypted_password_field
24
24
 
@@ -31,6 +31,22 @@ module Authlogic
31
31
  end
32
32
  alias_method :password_salt_field=, :password_salt_field
33
33
 
34
+ # By default passwords are required when a record is new or the crypted_password is blank, but if both of these things
35
+ # are met a password is not required. In this case, blank passwords are ignored.
36
+ #
37
+ # Think about a profile page, where the user can edit all of their information, including changing their password.
38
+ # If they do not want to change their password they just leave the fields blank. This will try to set the password to
39
+ # a blank value, in which case is incorrect behavior. As such, Authlogic ignores this. But let's say you have a completely
40
+ # separate page for resetting passwords, you might not want to ignore blank passwords. If this is the case for you, then
41
+ # just set this value to false.
42
+ #
43
+ # * <tt>Default:</tt> true
44
+ # * <tt>Accepts:</tt> Boolean
45
+ def ignore_blank_passwords(value = nil)
46
+ config(:ignore_blank_passwords, value, true)
47
+ end
48
+ alias_method :ignore_blank_passwords=, :ignore_blank_passwords
49
+
34
50
  # Whether or not to validate the password field.
35
51
  #
36
52
  # * <tt>Default:</tt> true
@@ -60,10 +76,10 @@ module Authlogic
60
76
 
61
77
  # A hash of options for the validates_length_of call for the password_confirmation field. Allows you to change this however you want.
62
78
  #
63
- # * <tt>Default:</tt> {:minimum => 4, :if => :require_password_?}
79
+ # * <tt>Default:</tt> validates_length_of_password_field_options
64
80
  # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
65
81
  def validates_length_of_password_confirmation_field_options(value = nil)
66
- config(:validates_length_of_password_confirmation_field_options, value, {:minimum => 4, :if => :require_password?})
82
+ config(:validates_length_of_password_confirmation_field_options, value, validates_length_of_password_field_options)
67
83
  end
68
84
  alias_method :validates_length_of_password_confirmation_field_options=, :validates_length_of_password_confirmation_field_options
69
85
 
@@ -102,6 +118,7 @@ module Authlogic
102
118
  ]
103
119
 
104
120
  def self.included(klass)
121
+ return if !klass.column_names.include?(klass.crypted_password_field.to_s)
105
122
  klass.define_callbacks *METHODS
106
123
  end
107
124
 
@@ -118,112 +135,128 @@ module Authlogic
118
135
  # The methods related to the password field.
119
136
  module Methods
120
137
  def self.included(klass)
138
+ return if !klass.column_names.include?(klass.crypted_password_field.to_s)
139
+
121
140
  klass.class_eval do
141
+ include InstanceMethods
142
+
122
143
  if validate_password_field
123
144
  validates_length_of :password, validates_length_of_password_field_options
124
145
  validates_confirmation_of :password, validates_confirmation_of_password_field_options
125
146
  validates_length_of :password_confirmation, validates_length_of_password_confirmation_field_options
126
147
  end
148
+
149
+ after_save :reset_password_changed
127
150
  end
128
151
  end
129
152
 
130
- # The password
131
- def password
132
- @password
133
- end
153
+ module InstanceMethods
154
+ # The password
155
+ def password
156
+ @password
157
+ end
134
158
 
135
- # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
136
- # the password.
137
- def password=(pass)
138
- return if pass.blank?
139
- before_password_set
140
- @password = pass
141
- send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
142
- send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, act_like_restful_authentication? ? :restful_authentication : nil)))
143
- @password_changed = true
144
- after_password_set
145
- end
159
+ # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
160
+ # the password.
161
+ def password=(pass)
162
+ return if ignore_blank_passwords? && pass.blank?
163
+ before_password_set
164
+ @password = pass
165
+ send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
166
+ send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, act_like_restful_authentication? ? :restful_authentication : nil)))
167
+ @password_changed = true
168
+ after_password_set
169
+ end
146
170
 
147
- # Accepts a raw password to determine if it is the correct password or not.
148
- def valid_password?(attempted_password)
149
- return false if attempted_password.blank? || send(crypted_password_field).blank?
171
+ # Accepts a raw password to determine if it is the correct password or not.
172
+ def valid_password?(attempted_password)
173
+ return false if attempted_password.blank? || send(crypted_password_field).blank?
150
174
 
151
- before_password_verification
175
+ before_password_verification
152
176
 
153
- crypto_providers = [crypto_provider] + transition_from_crypto_providers
154
- crypto_providers.each_with_index do |encryptor, index|
155
- # The arguments_type of for the transitioning from restful_authentication
156
- arguments_type = (act_like_restful_authentication? && index == 0) ||
157
- (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
158
- :restful_authentication : nil
177
+ crypto_providers = [crypto_provider] + transition_from_crypto_providers
178
+ crypto_providers.each_with_index do |encryptor, index|
179
+ # The arguments_type of for the transitioning from restful_authentication
180
+ arguments_type = (act_like_restful_authentication? && index == 0) ||
181
+ (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
182
+ :restful_authentication : nil
159
183
 
160
- if encryptor.matches?(send(crypted_password_field), *encrypt_arguments(attempted_password, arguments_type))
161
- # If we are transitioning from an older encryption algorithm and the password is still using the old algorithm
162
- # then let's reset the password using the new algorithm. If the algorithm has a cost (BCrypt) and the cost has changed, update the password with
163
- # the new cost.
164
- if index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))
165
- self.password = attempted_password
166
- save(false)
167
- end
184
+ if encryptor.matches?(send(crypted_password_field), *encrypt_arguments(attempted_password, arguments_type))
185
+ # If we are transitioning from an older encryption algorithm and the password is still using the old algorithm
186
+ # then let's reset the password using the new algorithm. If the algorithm has a cost (BCrypt) and the cost has changed, update the password with
187
+ # the new cost.
188
+ if index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))
189
+ self.password = attempted_password
190
+ save(false)
191
+ end
168
192
 
169
- after_password_verification
193
+ after_password_verification
170
194
 
171
- return true
195
+ return true
196
+ end
172
197
  end
173
- end
174
198
 
175
- false
176
- end
199
+ false
200
+ end
177
201
 
178
- # Resets the password to a random friendly token.
179
- def reset_password
180
- friendly_token = Authlogic::Random.friendly_token
181
- self.password = friendly_token
182
- self.password_confirmation = friendly_token
183
- end
184
- alias_method :randomize_password, :reset_password
202
+ # Resets the password to a random friendly token.
203
+ def reset_password
204
+ friendly_token = Authlogic::Random.friendly_token
205
+ self.password = friendly_token
206
+ self.password_confirmation = friendly_token
207
+ end
208
+ alias_method :randomize_password, :reset_password
185
209
 
186
- # Resets the password to a random friendly token and then saves the record.
187
- def reset_password!
188
- reset_password
189
- save_without_session_maintenance(false)
190
- end
191
- alias_method :randomize_password!, :reset_password!
210
+ # Resets the password to a random friendly token and then saves the record.
211
+ def reset_password!
212
+ reset_password
213
+ save_without_session_maintenance(false)
214
+ end
215
+ alias_method :randomize_password!, :reset_password!
192
216
 
193
- private
194
- def encrypt_arguments(raw_password, arguments_type = nil)
195
- salt = password_salt_field ? send(password_salt_field) : nil
196
- case arguments_type
197
- when :restful_authentication
198
- [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
199
- else
200
- [raw_password, salt].compact
217
+ private
218
+ def encrypt_arguments(raw_password, arguments_type = nil)
219
+ salt = password_salt_field ? send(password_salt_field) : nil
220
+ case arguments_type
221
+ when :restful_authentication
222
+ [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
223
+ else
224
+ [raw_password, salt].compact
225
+ end
201
226
  end
202
- end
203
227
 
204
- def require_password?
205
- new_record? || password_changed? || send(crypted_password_field).blank?
206
- end
228
+ def require_password?
229
+ new_record? || password_changed? || send(crypted_password_field).blank?
230
+ end
207
231
 
208
- def password_changed?
209
- @password_changed == true
210
- end
232
+ def ignore_blank_passwords?
233
+ self.class.ignore_blank_passwords == true
234
+ end
211
235
 
212
- def crypted_password_field
213
- self.class.crypted_password_field
214
- end
236
+ def password_changed?
237
+ @password_changed == true
238
+ end
239
+
240
+ def reset_password_changed
241
+ @password_changed = false
242
+ end
215
243
 
216
- def password_salt_field
217
- self.class.password_salt_field
218
- end
244
+ def crypted_password_field
245
+ self.class.crypted_password_field
246
+ end
219
247
 
220
- def crypto_provider
221
- self.class.crypto_provider
222
- end
248
+ def password_salt_field
249
+ self.class.password_salt_field
250
+ end
223
251
 
224
- def transition_from_crypto_providers
225
- self.class.transition_from_crypto_providers
226
- end
252
+ def crypto_provider
253
+ self.class.crypto_provider
254
+ end
255
+
256
+ def transition_from_crypto_providers
257
+ self.class.transition_from_crypto_providers
258
+ end
259
+ end
227
260
  end
228
261
  end
229
262
  end
@@ -29,7 +29,7 @@ module Authlogic
29
29
  module Methods
30
30
  def self.included(klass)
31
31
  return if !klass.column_names.include?("single_access_token")
32
-
32
+
33
33
  klass.class_eval do
34
34
  include InstanceMethods
35
35
  validates_uniqueness_of :single_access_token, :if => :single_access_token_changed?
@@ -4,7 +4,7 @@ module Authlogic
4
4
  # provides. Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite, etc.
5
5
  class RailsAdapter < AbstractAdapter
6
6
  def authenticate_with_http_basic(&block)
7
- yield [nil, nil]
7
+ controller.authenticate_with_http_basic(&block)
8
8
  end
9
9
 
10
10
  def cookies
@@ -13,7 +13,7 @@ module Authlogic
13
13
 
14
14
  def cookie_domain
15
15
  @cookie_domain_key ||= (Rails::VERSION::MAJOR >= 2 && Rails::VERSION::MINOR >= 3) ? :domain : :session_domain
16
- controller.class.session_options[@cookie_domain_key]
16
+ ActionController::Base.session_options[@cookie_domain_key]
17
17
  end
18
18
 
19
19
  def request_content_type
@@ -5,7 +5,7 @@ module Authlogic
5
5
  # This encryption method is reversible if you have the supplied key. So in order to use this encryption method you must supply it with a key first.
6
6
  # In an initializer, or before your application initializes, you should do the following:
7
7
  #
8
- # Authlogic::CryptoProviders::AES256.key = "my really long and unique key, preferrable a bunch of random characters"
8
+ # Authlogic::CryptoProviders::AES256.key = "my really long and unique key, preferrably a bunch of random characters"
9
9
  #
10
10
  # My final comment is that this is a strong encryption method, but its main weakness is that its reversible. If you do not need to reverse the hash
11
11
  # then you should consider Sha512 or BCrypt instead.
@@ -34,7 +34,7 @@ module Authlogic
34
34
 
35
35
  private
36
36
  def aes
37
- raise ArgumentError.new("You provide a key like #{name}.key = my_key before using the #{name}") if @key.blank?
37
+ raise ArgumentError.new("You must provide a key like #{name}.key = my_key before using the #{name}") if @key.blank?
38
38
  @aes ||= OpenSSL::Cipher::Cipher.new("AES-256-ECB")
39
39
  end
40
40
  end
@@ -35,7 +35,9 @@ module Authlogic
35
35
  #
36
36
  # Tell acts_as_authentic to use it:
37
37
  #
38
- # acts_as_authentic :crypto_provider => Authlogic::CryptoProviders::BCrypt
38
+ # acts_as_authentic do |c|
39
+ # c.crypto_provider = Authlogic::CryptoProviders::BCrypt
40
+ # end
39
41
  #
40
42
  # You are good to go!
41
43
  class BCrypt
@@ -2,8 +2,8 @@ require "digest/sha1"
2
2
 
3
3
  module Authlogic
4
4
  module CryptoProviders
5
- # This class was made for the users transitioning from restful_authentication. I highly discourage using this crypto provider as it inferior to your other options.
6
- # Please use any other provider offered by Authlogic.
5
+ # This class was made for the users transitioning from restful_authentication. I highly discourage using this
6
+ # crypto provider as it inferior to your other options. Please use any other provider offered by Authlogic.
7
7
  class Sha1
8
8
  class << self
9
9
  def join_token
@@ -17,7 +17,7 @@ module Authlogic
17
17
  # authlogic.attributes.user_session.login
18
18
  def human_attribute_name(attribute_key_name, options = {})
19
19
  options[:count] ||= 1
20
- options[:default] ||= attribute_key_name.humanize
20
+ options[:default] ||= attribute_key_name.to_s.humanize
21
21
  I18n.t("attributes.#{name.underscore}.#{attribute_key_name}", options)
22
22
  end
23
23
 
@@ -98,13 +98,15 @@ module Authlogic
98
98
  end
99
99
 
100
100
  def cookie_credentials
101
- controller.cookies[cookie_key]
101
+ controller.cookies[cookie_key] && controller.cookies[cookie_key].split("::")
102
102
  end
103
103
 
104
104
  # Tries to validate the session from information in the cookie
105
105
  def persist_by_cookie
106
- if cookie_credentials
107
- self.unauthorized_record = search_for_record("find_by_persistence_token", cookie_credentials)
106
+ persistence_token, record_id = cookie_credentials
107
+ if !persistence_token.nil?
108
+ record = record_id.nil? ? search_for_record("find_by_persistence_token", persistence_token) : search_for_record("find_by_#{klass.primary_key}", record_id)
109
+ self.unauthorized_record = record if record && record.persistence_token == persistence_token
108
110
  valid?
109
111
  else
110
112
  false
@@ -113,7 +115,7 @@ module Authlogic
113
115
 
114
116
  def save_cookie
115
117
  controller.cookies[cookie_key] = {
116
- :value => record.persistence_token,
118
+ :value => "#{record.persistence_token}::#{record.send(record.class.primary_key)}",
117
119
  :expires => remember_me_until,
118
120
  :domain => controller.cookie_domain
119
121
  }
@@ -29,10 +29,13 @@ module Authlogic
29
29
  # end
30
30
  # end
31
31
  #
32
- # * <tt>Default:</tt> "find_by_#{login_field}"
32
+ # Now just specifcy the name of this method for this configuration option and you are all set. You can do anything you want here. Maybe you allow users to have multiple logins
33
+ # and you want to search a has_many relationship, etc. The sky is the limit.
34
+ #
35
+ # * <tt>Default:</tt> "find_by_case_insensitive_#{login_field}"
33
36
  # * <tt>Accepts:</tt> Symbol or String
34
37
  def find_by_login_method(value = nil)
35
- config(:find_by_login_method, value, "find_by_#{login_field}")
38
+ config(:find_by_login_method, value, "find_with_#{login_field}")
36
39
  end
37
40
  alias_method :find_by_login_method=, :find_by_login_method
38
41
 
@@ -41,19 +44,19 @@ module Authlogic
41
44
  # login with a field called "login" and then find users by email this is compeltely doable. See the find_by_login_method configuration
42
45
  # option for more details.
43
46
  #
44
- # * <tt>Default:</tt> Uses the configuration option in your model: User.login_field
47
+ # * <tt>Default:</tt> klass.login_field || klass.email_field
45
48
  # * <tt>Accepts:</tt> Symbol or String
46
49
  def login_field(value = nil)
47
50
  config(:login_field, value, klass.login_field || klass.email_field)
48
51
  end
49
52
  alias_method :login_field=, :login_field
50
53
 
51
- # Works exactly like login_field, but for the password instead.
54
+ # Works exactly like login_field, but for the password instead. Returns :password if a login_field exists.
52
55
  #
53
56
  # * <tt>Default:</tt> :password
54
57
  # * <tt>Accepts:</tt> Symbol or String
55
58
  def password_field(value = nil)
56
- config(:password_field, value, :password)
59
+ config(:password_field, value, login_field && :password)
57
60
  end
58
61
  alias_method :password_field=, :password_field
59
62
 
@@ -71,18 +74,23 @@ module Authlogic
71
74
  module InstanceMethods
72
75
  def initialize(*args)
73
76
  if !self.class.configured_password_methods
74
- self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
75
- self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
76
- self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
77
- self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
77
+ if login_field
78
+ self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
79
+ self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
80
+ end
81
+
82
+ if password_field
83
+ self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
84
+ self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
78
85
 
79
- self.class.class_eval <<-"end_eval", __FILE__, __LINE__
80
- private
81
- # 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.
82
- def protected_#{password_field}
83
- @#{password_field}
84
- end
85
- end_eval
86
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
87
+ private
88
+ # 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.
89
+ def protected_#{password_field}
90
+ @#{password_field}
91
+ end
92
+ end_eval
93
+ end
86
94
 
87
95
  self.class.configured_password_methods = true
88
96
  end
@@ -114,7 +122,7 @@ module Authlogic
114
122
 
115
123
  private
116
124
  def authenticating_with_password?
117
- !send(login_field).nil? || !send("protected_#{password_field}").nil?
125
+ login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
118
126
  end
119
127
 
120
128
  def validate_by_password
@@ -16,7 +16,7 @@ module Authlogic
16
16
  def credentials=(value)
17
17
  super
18
18
  values = value.is_a?(Array) ? value : [value]
19
- self.priority_record = values.second if values.second.class < ::ActiveRecord::Base
19
+ self.priority_record = values[1] if values[1].class < ::ActiveRecord::Base
20
20
  end
21
21
 
22
22
  private
@@ -49,7 +49,7 @@ module Authlogic
49
49
  def session_key
50
50
  build_key(self.class.session_key)
51
51
  end
52
-
52
+
53
53
  def update_session
54
54
  controller.session[session_key] = record && record.persistence_token
55
55
  controller.session["#{session_key}_#{klass.primary_key}"] = record && record.send(record.class.primary_key)
@@ -4,11 +4,10 @@ module Authlogic
4
4
  # a request is made, ultimately letting you log in users in functional tests.
5
5
  class ControllerAdapter < ControllerAdapters::AbstractAdapter
6
6
  def authenticate_with_http_basic(&block)
7
- controller.authenticate_with_http_basic(&block)
8
7
  end
9
8
 
10
9
  def cookies
11
- new_cookies = {}
10
+ new_cookies = MockCookieJar.new
12
11
  super.each do |key, value|
13
12
  new_cookies[key] = value[:value]
14
13
  end
@@ -20,7 +19,7 @@ module Authlogic
20
19
  end
21
20
 
22
21
  def request
23
- @request ||= MockRequest.new
22
+ @request ||= MockRequest.new(controller)
24
23
  end
25
24
 
26
25
  def request_content_type
@@ -2,7 +2,7 @@ module Authlogic
2
2
  module TestCase
3
3
  # Basically acts like a controller but doesn't do anything. Authlogic can interact with this, do it's thing and then you
4
4
  # can look at the controller object to see if anything changed.
5
- class MockController < ControllerAdapters::AbstractAdapter
5
+ class MockController < ControllerAdapter
6
6
  attr_accessor :http_user, :http_password
7
7
  attr_writer :request_content_type
8
8
 
@@ -29,10 +29,6 @@ module Authlogic
29
29
  @params ||= {}
30
30
  end
31
31
 
32
- def request
33
- @request ||= MockRequest.new
34
- end
35
-
36
32
  def request_content_type
37
33
  @request_content_type ||= "text/html"
38
34
  end
@@ -1,12 +1,24 @@
1
1
  module Authlogic
2
2
  module TestCase
3
3
  class MockRequest # :nodoc:
4
+ attr_accessor :controller
5
+
6
+ def initialize(controller)
7
+ self.controller = controller
8
+ end
9
+
4
10
  def request_method
5
11
  nil
6
12
  end
7
13
 
14
+ def referer
15
+ end
16
+
8
17
  def remote_ip
9
- "1.1.1.1"
18
+ (controller && controller.respond_to?(:env) && controller.env.is_a?(Hahs) && controller.env['REMOTE_ADDR']) || "1.1.1.1"
19
+ end
20
+
21
+ def user_agent
10
22
  end
11
23
  end
12
24
  end
@@ -41,7 +41,7 @@ module Authlogic # :nodoc:
41
41
 
42
42
  MAJOR = 2
43
43
  MINOR = 0
44
- TINY = 5
44
+ TINY = 6
45
45
 
46
46
  # The current version as a Version instance
47
47
  CURRENT = new(MAJOR, MINOR, TINY)
@@ -4,7 +4,8 @@ module Authlogic
4
4
  def self.should_be_authentic
5
5
  klass = model_class
6
6
  should "acts as authentic" do
7
- assert klass.respond_to?(:acts_as_authentic_config)
7
+ assert klass.respond_to?(:password=)
8
+ assert klass.respond_to?(:valid_password?)
8
9
  end
9
10
  end
10
11
  end
@@ -8,5 +8,11 @@ module ActsAsAuthenticTest
8
8
  end
9
9
  end
10
10
  end
11
+
12
+ def test_acts_as_authentic_with_old_config
13
+ assert_raise(ArgumentError) do
14
+ User.acts_as_authentic({})
15
+ end
16
+ end
11
17
  end
12
18
  end
@@ -44,7 +44,7 @@ module ActsAsAuthenticTest
44
44
  end
45
45
 
46
46
  def test_validates_uniqueness_of_email_field_options_config
47
- default = {:scope => Employee.validations_scope, :if => "#{Employee.email_field}_changed?".to_sym}
47
+ default = {:case_sensitive => false, :scope => Employee.validations_scope, :if => "#{Employee.email_field}_changed?".to_sym}
48
48
  assert_equal default, Employee.validates_uniqueness_of_email_field_options
49
49
 
50
50
  Employee.validates_uniqueness_of_email_field_options = {:yes => "no"}
@@ -81,9 +81,20 @@ module ActsAsAuthenticTest
81
81
  assert !u.valid?
82
82
  assert u.errors.on(:email)
83
83
 
84
+ u.email = "BJOHNSON@binarylogic.com"
85
+ assert !u.valid?
86
+ assert u.errors.on(:email)
87
+
84
88
  u.email = "a@a.com"
85
89
  assert !u.valid?
86
90
  assert !u.errors.on(:email)
87
91
  end
92
+
93
+ def test_find_with_email
94
+ ben = users(:ben)
95
+ assert_equal ben, User.find_with_email("bjohnson@binarylogic.com")
96
+ assert_equal ben, User.find_with_email("bJohnson@binarylogic.com")
97
+ assert_equal ben, User.find_with_email("BJOHNSON@BINARYLOGIC.COM")
98
+ end
88
99
  end
89
100
  end
@@ -44,7 +44,7 @@ module ActsAsAuthenticTest
44
44
  end
45
45
 
46
46
  def test_validates_uniqueness_of_login_field_options_config
47
- default = {:scope => User.validations_scope, :if => "#{User.login_field}_changed?".to_sym}
47
+ default = {:case_sensitive => false, :scope => User.validations_scope, :if => "#{User.login_field}_changed?".to_sym}
48
48
  assert_equal default, User.validates_uniqueness_of_login_field_options
49
49
 
50
50
  User.validates_uniqueness_of_login_field_options = {:yes => "no"}
@@ -81,9 +81,20 @@ module ActsAsAuthenticTest
81
81
  assert !u.valid?
82
82
  assert u.errors.on(:login)
83
83
 
84
+ u.login = "BJOHNSON"
85
+ assert !u.valid?
86
+ assert u.errors.on(:login)
87
+
84
88
  u.login = "fdsfdsf"
85
89
  assert !u.valid?
86
90
  assert !u.errors.on(:login)
87
91
  end
92
+
93
+ def test_find_with_login
94
+ ben = users(:ben)
95
+ assert_equal ben, User.find_with_login("bjohnson")
96
+ assert_equal ben, User.find_with_login("BJOHNSON")
97
+ assert_equal ben, User.find_with_login("Bjohnson")
98
+ end
88
99
  end
89
100
  end
@@ -22,6 +22,16 @@ module ActsAsAuthenticTest
22
22
  assert_equal :password_salt, User.password_salt_field
23
23
  end
24
24
 
25
+ def test_ignore_blank_passwords_config
26
+ assert User.ignore_blank_passwords
27
+ assert Employee.ignore_blank_passwords
28
+
29
+ User.ignore_blank_passwords = false
30
+ assert !User.ignore_blank_passwords
31
+ User.ignore_blank_passwords true
32
+ assert User.ignore_blank_passwords
33
+ end
34
+
25
35
  def test_validate_password_field_config
26
36
  assert User.validate_password_field
27
37
  assert Employee.validate_password_field
@@ -5,6 +5,7 @@ module SessionTest
5
5
  class ClassMethodsTest < ActiveSupport::TestCase
6
6
  def test_human_attribute_name
7
7
  assert_equal "Some attribute", UserSession.human_attribute_name("some_attribute")
8
+ assert_equal "Some attribute", UserSession.human_attribute_name(:some_attribute)
8
9
  end
9
10
 
10
11
  def test_human_name
@@ -91,7 +91,7 @@ module SessionTest
91
91
  ben = users(:ben)
92
92
  session = UserSession.new(ben)
93
93
  assert session.save
94
- assert_equal ben.persistence_token, controller.cookies["user_credentials"]
94
+ assert_equal "#{ben.persistence_token}::#{ben.id}", controller.cookies["user_credentials"]
95
95
  end
96
96
 
97
97
  def test_after_destroy_destroy_cookie
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authlogic
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Johnson of Binary Logic
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-30 00:00:00 -04:00
12
+ date: 2009-04-09 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 1.11.0
33
+ version: 1.12.1
34
34
  version:
35
35
  description: A clean, simple, and unobtrusive ruby authentication solution.
36
36
  email: bjohnson@binarylogic.com