Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

* 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.
  • Loading branch information...
commit 5c0ac4ff05ceefa52533e0903811bcdc15c767fe 1 parent 1bb82d1
@binarylogic authored
View
1  CHANGELOG.rdoc
@@ -8,6 +8,7 @@
* 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+
* Add configuration option for Authlogic::ActsAsAuthentic: ignore_blank_passwords
* Fix cookie_domain in rails adapter
+* 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.
== 2.0.5 released 2009-3-30
View
6 README.rdoc
@@ -27,7 +27,7 @@ Authlogic can do all of this and much more, keep reading to see...
**Before contacting me, please read:**
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.
-== Authlogic "add on" directory
+== Authlogic "add ons"
* <b>Authlogic OpenID addon:</b> http://github.com/binarylogic/authlogic_openid
@@ -189,7 +189,7 @@ This will create a file that looks similar to:
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.
t.string :login, :null => false # optional, you can use email instead, or both
- t.string :crypted_password, :null => false # required
+ t.string :crypted_password, :null => false # optional, see below
t.string :password_salt, :null => false # optional, but highly recommended
t.string :persistence_token, :null => false # required
t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params
@@ -202,6 +202,8 @@ The user model should have the following columns. The names of these columns can
t.string :current_login_ip # optional, see Authlogic::Session::MagicColumns
t.string :last_login_ip # optional, see Authlogic::Session::MagicColumns
+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.
+
=== 4. Set up your model
Make sure you have a model that you will be authenticating with. Since we are using the User model it should look something like:
View
4 lib/authlogic/acts_as_authentic/base.rb
@@ -73,9 +73,9 @@ def config(key, value, default_value = nil, read_value = nil)
end
end
- def first_column_to_exist(*columns_to_check) # :nodoc:
+ def first_column_to_exist(*columns_to_check)
columns_to_check.each { |column_name| return column_name.to_sym if column_names.include?(column_name.to_s) }
- columns_to_check.first ? columns_to_check.first.to_sym : nil
+ columns_to_check.first && columns_to_check.first.to_sym
end
end
View
32 lib/authlogic/acts_as_authentic/logged_in_status.rb
@@ -27,27 +27,33 @@ def logged_in_timeout(value = nil)
# All methods for the logged in status feature seat.
module Methods
def self.included(klass)
+ return if !klass.column_names.include?("last_request_at")
+
klass.class_eval do
+ include InstanceMethods
+
named_scope :logged_in, lambda { {:conditions => ["last_request_at > ?", logged_in_timeout.seconds.ago]} }
named_scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", logged_in_timeout.seconds.ago]} }
end
end
- # Returns true if the last_request_at > logged_in_timeout.
- def logged_in?
- raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at)
- !last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
- end
-
- # Opposite of logged_in?
- def logged_out?
- !logged_in?
- end
+ module InstanceMethods
+ # Returns true if the last_request_at > logged_in_timeout.
+ def logged_in?
+ raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at)
+ !last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
+ end
- private
- def logged_in_timeout
- self.class.logged_in_timeout
+ # Opposite of logged_in?
+ def logged_out?
+ !logged_in?
end
+
+ private
+ def logged_in_timeout
+ self.class.logged_in_timeout
+ end
+ end
end
end
end
View
171 lib/authlogic/acts_as_authentic/password.rb
@@ -18,7 +18,7 @@ module Config
# * <tt>Default:</tt> :crypted_password, :encrypted_password, :password_hash, or :pw_hash
# * <tt>Accepts:</tt> Symbol
def crypted_password_field(value = nil)
- config(:crypted_password_field, value, first_column_to_exist(:crypted_password, :encrypted_password, :password_hash, :pw_hash))
+ config(:crypted_password_field, value, first_column_to_exist(nil, :crypted_password, :encrypted_password, :password_hash, :pw_hash))
end
alias_method :crypted_password_field=, :crypted_password_field
@@ -118,6 +118,7 @@ module Callbacks
]
def self.included(klass)
+ return if !klass.column_names.include?(klass.crypted_password_field.to_s)
klass.define_callbacks *METHODS
end
@@ -134,7 +135,11 @@ def #{method}
# The methods related to the password field.
module Methods
def self.included(klass)
+ return if !klass.column_names.include?(klass.crypted_password_field.to_s)
+
klass.class_eval do
+ include InstanceMethods
+
if validate_password_field
validates_length_of :password, validates_length_of_password_field_options
validates_confirmation_of :password, validates_confirmation_of_password_field_options
@@ -143,107 +148,109 @@ def self.included(klass)
end
end
- # The password
- def password
- @password
- end
+ module InstanceMethods
+ # The password
+ def password
+ @password
+ end
- # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
- # the password.
- def password=(pass)
- return if ignore_blank_passwords? && pass.blank?
- before_password_set
- @password = pass
- send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
- send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, act_like_restful_authentication? ? :restful_authentication : nil)))
- @password_changed = true
- after_password_set
- end
+ # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
+ # the password.
+ def password=(pass)
+ return if ignore_blank_passwords? && pass.blank?
+ before_password_set
+ @password = pass
+ send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
+ send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, act_like_restful_authentication? ? :restful_authentication : nil)))
+ @password_changed = true
+ after_password_set
+ end
- # Accepts a raw password to determine if it is the correct password or not.
- def valid_password?(attempted_password)
- return false if attempted_password.blank? || send(crypted_password_field).blank?
+ # Accepts a raw password to determine if it is the correct password or not.
+ def valid_password?(attempted_password)
+ return false if attempted_password.blank? || send(crypted_password_field).blank?
- before_password_verification
+ before_password_verification
- crypto_providers = [crypto_provider] + transition_from_crypto_providers
- crypto_providers.each_with_index do |encryptor, index|
- # The arguments_type of for the transitioning from restful_authentication
- arguments_type = (act_like_restful_authentication? && index == 0) ||
- (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
- :restful_authentication : nil
+ crypto_providers = [crypto_provider] + transition_from_crypto_providers
+ crypto_providers.each_with_index do |encryptor, index|
+ # The arguments_type of for the transitioning from restful_authentication
+ arguments_type = (act_like_restful_authentication? && index == 0) ||
+ (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
+ :restful_authentication : nil
- if encryptor.matches?(send(crypted_password_field), *encrypt_arguments(attempted_password, arguments_type))
- # If we are transitioning from an older encryption algorithm and the password is still using the old algorithm
- # 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
- # the new cost.
- if index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))
- self.password = attempted_password
- save(false)
- end
+ if encryptor.matches?(send(crypted_password_field), *encrypt_arguments(attempted_password, arguments_type))
+ # If we are transitioning from an older encryption algorithm and the password is still using the old algorithm
+ # 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
+ # the new cost.
+ if index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))
+ self.password = attempted_password
+ save(false)
+ end
- after_password_verification
+ after_password_verification
- return true
+ return true
+ end
end
- end
- false
- end
+ false
+ end
- # Resets the password to a random friendly token.
- def reset_password
- friendly_token = Authlogic::Random.friendly_token
- self.password = friendly_token
- self.password_confirmation = friendly_token
- end
- alias_method :randomize_password, :reset_password
+ # Resets the password to a random friendly token.
+ def reset_password
+ friendly_token = Authlogic::Random.friendly_token
+ self.password = friendly_token
+ self.password_confirmation = friendly_token
+ end
+ alias_method :randomize_password, :reset_password
- # Resets the password to a random friendly token and then saves the record.
- def reset_password!
- reset_password
- save_without_session_maintenance(false)
- end
- alias_method :randomize_password!, :reset_password!
+ # Resets the password to a random friendly token and then saves the record.
+ def reset_password!
+ reset_password
+ save_without_session_maintenance(false)
+ end
+ alias_method :randomize_password!, :reset_password!
- private
- def encrypt_arguments(raw_password, arguments_type = nil)
- salt = password_salt_field ? send(password_salt_field) : nil
- case arguments_type
- when :restful_authentication
- [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
- else
- [raw_password, salt].compact
+ private
+ def encrypt_arguments(raw_password, arguments_type = nil)
+ salt = password_salt_field ? send(password_salt_field) : nil
+ case arguments_type
+ when :restful_authentication
+ [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
+ else
+ [raw_password, salt].compact
+ end
end
- end
- def require_password?
- new_record? || password_changed? || send(crypted_password_field).blank?
- end
+ def require_password?
+ new_record? || password_changed? || send(crypted_password_field).blank?
+ end
- def ignore_blank_passwords?
- self.class.ignore_blank_passwords == true
- end
+ def ignore_blank_passwords?
+ self.class.ignore_blank_passwords == true
+ end
- def password_changed?
- @password_changed == true
- end
+ def password_changed?
+ @password_changed == true
+ end
- def crypted_password_field
- self.class.crypted_password_field
- end
+ def crypted_password_field
+ self.class.crypted_password_field
+ end
- def password_salt_field
- self.class.password_salt_field
- end
+ def password_salt_field
+ self.class.password_salt_field
+ end
- def crypto_provider
- self.class.crypto_provider
- end
+ def crypto_provider
+ self.class.crypto_provider
+ end
- def transition_from_crypto_providers
- self.class.transition_from_crypto_providers
- end
+ def transition_from_crypto_providers
+ self.class.transition_from_crypto_providers
+ end
+ end
end
end
end
View
39 lib/authlogic/session/password.rb
@@ -5,7 +5,7 @@ module Password
def self.included(klass)
klass.class_eval do
extend Config
- include InstanceMethods
+ include Methods
validate :validate_by_password, :if => :authenticating_with_password?
class << self
@@ -41,19 +41,19 @@ def find_by_login_method(value = nil)
# 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> Uses the configuration option in your model: User.login_field
+ # * <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)
end
alias_method :login_field=, :login_field
- # Works exactly like login_field, but for the password instead.
+ # 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, :password)
+ config(:password_field, value, login_field && :password)
end
alias_method :password_field=, :password_field
@@ -68,21 +68,26 @@ def verify_password_method(value = nil)
end
# Password related instance methods
- module InstanceMethods
+ module Methods
def initialize(*args)
if !self.class.configured_password_methods
- self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
- self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
- self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
- self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
+ if login_field
+ self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
+ self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
+ end
+
+ if password_field
+ 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.
- def protected_#{password_field}
- @#{password_field}
- end
- end_eval
+ 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.
+ def protected_#{password_field}
+ @#{password_field}
+ end
+ end_eval
+ end
self.class.configured_password_methods = true
end
@@ -114,7 +119,7 @@ def credentials=(value)
private
def authenticating_with_password?
- !send(login_field).nil? || !send("protected_#{password_field}").nil?
+ login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
end
def validate_by_password
Please sign in to comment.
Something went wrong with that request. Please try again.