lib/authlogic/session/password.rb in authlogic-2.0.11 vs lib/authlogic/session/password.rb in authlogic-2.0.12
- old
+ new
@@ -27,47 +27,76 @@
# def self.find_by_username_or_email(login)
# find_by_username(login) || find_by_email(login)
# end
# end
#
- # 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
+ # Now just specify 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
# and you want to search a has_many relationship, etc. The sky is the limit.
#
# * <tt>Default:</tt> "find_by_smart_case_login_field"
# * <tt>Accepts:</tt> Symbol or String
def find_by_login_method(value = nil)
- config(:find_by_login_method, value, "find_by_smart_case_login_field")
+ rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
end
alias_method :find_by_login_method=, :find_by_login_method
+ # The text used to identify credentials (username/password) combination when a bad login attempt occurs.
+ # When you show error messages for a bad login, it's considered good security practice to hide which field
+ # the user has entered incorrectly (the login field or the password field). For a full explanation, see
+ # http://www.gnucitizen.org/blog/username-enumeration-vulnerabilities/
+ #
+ # Example of use:
+ #
+ # class UserSession < Authlogic::Session::Base
+ # generalize_credentials_error_messages true
+ # end
+ #
+ # This would make the error message for bad logins and bad passwords look identical:
+ #
+ # Login/Password combination is not valid
+ #
+ # The downside to enabling this is that is can be too vague for a user that has a hard time remembering
+ # their username and password combinations. It also disables the ability to to highlight the field
+ # with the error when you use form_for.
+ #
+ # If you are developing an app where security is an extreme priority (such as a financial application),
+ # then you should enable this. Otherwise, leaving this off is fine.
+ #
+ # * <tt>Default</tt> false
+ # * <tt>Accepts:</tt> Boolean
+ def generalize_credentials_error_messages(value = nil)
+ rw_config(:generalize_credentials_error_messages, value, false)
+ end
+ alias_method :generalize_credentials_error_messages=, :generalize_credentials_error_messages
+
# 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 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 more details.
#
# * <tt>Default:</tt> klass.login_field || klass.email_field
# * <tt>Accepts:</tt> Symbol or String
def login_field(value = nil)
- config(:login_field, value, klass.login_field || klass.email_field)
+ rw_config(:login_field, value, klass.login_field || klass.email_field)
end
alias_method :login_field=, :login_field
# Works exactly like login_field, but for the password instead. Returns :password if a login_field exists.
#
# * <tt>Default:</tt> :password
# * <tt>Accepts:</tt> Symbol or String
def password_field(value = nil)
- config(:password_field, value, login_field && :password)
+ rw_config(:password_field, value, login_field && :password)
end
alias_method :password_field=, :password_field
# The name of the method in your model used to verify the password. This should be an instance method. It should also be prepared to accept a raw password and a crytped password.
#
# * <tt>Default:</tt> "valid_#{password_field}?"
# * <tt>Accepts:</tt> Symbol or String
def verify_password_method(value = nil)
- config(:verify_password_method, value, "valid_#{password_field}?")
+ rw_config(:verify_password_method, value, "valid_#{password_field}?")
end
alias_method :verify_password_method=, :verify_password_method
end
# Password related instance methods
@@ -83,11 +112,11 @@
self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
private
- # 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.
+ # The password should not be accessible publicly. This way forms using form_for don't fill the password with the attempted password. To prevent this we just create this method that is private.
def protected_#{password_field}
@#{password_field}
end
end_eval
end
@@ -96,10 +125,11 @@
end
super
end
+ # Returns the login_field / password_field credentials combination in hash form.
def credentials
if authenticating_with_password?
details = {}
details[login_field.to_sym] = send(login_field)
details[password_field.to_sym] = "<protected>"
@@ -107,10 +137,11 @@
else
super
end
end
+ # Accepts the login_field / password_field credentials combination in hash form.
def credentials=(value)
super
values = value.is_a?(Array) ? value : [value]
if values.first.is_a?(Hash)
values.first.with_indifferent_access.slice(login_field, password_field).each do |field, value|
@@ -124,32 +155,40 @@
def authenticating_with_password?
login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
end
def validate_by_password
- errors.add(login_field, I18n.t('error_messages.login_blank', :default => "can not be blank")) if send(login_field).blank?
- errors.add(password_field, I18n.t('error_messages.password_blank', :default => "can not be blank")) if send("protected_#{password_field}").blank?
+ errors.add(login_field, I18n.t('error_messages.login_blank', :default => "cannot be blank")) if send(login_field).blank?
+ errors.add(password_field, I18n.t('error_messages.password_blank', :default => "cannot be blank")) if send("protected_#{password_field}").blank?
return if errors.count > 0
self.attempted_record = search_for_record(find_by_login_method, send(login_field))
if attempted_record.blank?
- errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "does not exist"))
+ generalize_credentials_error_messages? ? add_general_credentials_error : errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "is not valid"))
return
end
if !attempted_record.send(verify_password_method, send("protected_#{password_field}"))
- errors.add(password_field, I18n.t('error_messages.password_invalid', :default => "is not valid"))
+ generalize_credentials_error_messages? ? add_general_credentials_error : errors.add(password_field, I18n.t('error_messages.password_invalid', :default => "is not valid"))
return
end
end
def find_by_login_method
self.class.find_by_login_method
end
def login_field
self.class.login_field
+ end
+
+ def add_general_credentials_error
+ errors.add_to_base(I18n.t('error_messages.general_credentials_error', :default => "#{login_field.to_s.humanize}/Password combination is not valid"))
+ end
+
+ def generalize_credentials_error_messages?
+ self.class.generalize_credentials_error_messages == true
end
def password_field
self.class.password_field
end
\ No newline at end of file