Permalink
Browse files

Reorganized ORM code and tests

  • Loading branch information...
binarylogic committed Nov 9, 2008
1 parent f606857 commit 9bca67d7d9fe8d507685c35dbe3564ad3d4c6a9f
View
@@ -2,7 +2,11 @@
* Moved Rack standards into abstract_adapter for the controllers.
* Added logging_in_with_credentials?, logging_in_with_unauthorized_record?
-* Fixed typo in abstract_adapter, black to block
+* Fixed typo in abstract_adapter, black to block.
+* Cleaned up / reorganized tests.
+* Moved ActiveRecord additions to ORM Adapters name space to make way for Data Mapper.
+* Reorganized and modified acts_as_authentic to be free standing and not get info from the related session.
+* The session now gets its configuration from the model, since determining which fields are present is ORM specific.
== 1.0.0 released 2008-11-05
View
@@ -4,7 +4,9 @@ Authlogic is a clean and simple ruby authentication solution. Put simply, its th
The last thing we need is another authentication solution, right? That's what I thought until I tried out some of the current solutions in both rails and merb. None of them felt right. They were either too complicated, bloated, littered my application with tons of code, or were just confusing. This is not the simple / elegant ruby we all fell in love with. We need a "ruby like" authentication solution. Authlogic is my attempt to satisfy that need...
-Let's take a rails application, wouldn't it be nice to keep your app up to date with the latest and greatest security techniques with a simple update of a plugin?
+Let's take a rails application...
+
+Wouldn't it be nice to keep your app up to date with the latest and greatest security techniques with a simple update of a plugin?
What if you could have authentication up and running in minutes without having to run a generator? All because it's simple, like everything else.
View
@@ -9,7 +9,11 @@
require File.dirname(__FILE__) + "/authlogic/sha512_crypto_provider"
if defined?(ActiveRecord)
- require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/acts_as_authentic"
+ require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/acts_as_authentic"
+ require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials"
+ require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/logged_in"
+ require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence"
+ require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance"
require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/authenticates_many"
end
@@ -71,225 +71,14 @@ module ActsAsAuthentic
# * <tt>logged_in_timeout:</tt> default: 10.minutes,
# This is really just a nifty feature to tell if a user is logged in or not. It's based on activity. So if the user in inactive longer than
# the value you pass here they are assumed "logged out".
- #
+ #
# * <tt>session_ids:</tt> default: [nil],
# The sessions that we want to automatically reset when a user is created or updated so you don't have to worry about this. Set to [] to disable.
# Should be an array of ids. See the Authlogic::Session documentation for information on ids. The order is important.
# The first id should be your main session, the session they need to log into first. This is generally nil. When you don't specify an id
# in your session you are really just inexplicitly saying you want to use the id of nil.
def acts_as_authentic(options = {})
- # If we don't have a database, skip all of this, solves initial setup errors
- begin
- column_names
- rescue Exception
- return
- end
-
- # Setup default options
- begin
- options[:session_class] ||= "#{name}Session".constantize
- rescue NameError
- raise NameError.new("You must create a #{name}Session class before a model can act_as_authentic. If that is not the name of the class pass the class constant via the :session_class option.")
- end
-
- options[:crypto_provider] ||= Sha512CryptoProvider
- options[:crypto_provider_type] ||= options[:crypto_provider].respond_to?(:decrypt) ? :encryption : :hash
- options[:login_field] ||= options[:session_class].login_field
- options[:login_field_type] ||= options[:login_field] == :email ? :email : :login
- options[:password_field] ||= options[:session_class].password_field
- options[:crypted_password_field] ||=
- (column_names.include?("crypted_password") && :crypted_password) ||
- (column_names.include?("encrypted_password") && :encrypted_password) ||
- (column_names.include?("password_hash") && :password_hash) ||
- (column_names.include?("pw_hash") && :pw_hash) ||
- :crypted_password
- options[:password_salt_field] ||=
- (column_names.include?("password_salt") && :password_salt) ||
- (column_names.include?("pw_salt") && :pw_salt) ||
- (column_names.include?("salt") && :salt) ||
- :password_salt
- options[:remember_token_field] ||= options[:session_class].remember_token_field
- options[:logged_in_timeout] ||= 10.minutes
- options[:session_ids] ||= [nil]
-
- # Validations
- case options[:login_field_type]
- when :email
- validates_length_of options[:login_field], :within => 6..100
- email_name_regex = '[\w\.%\+\-]+'
- domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
- domain_tld_regex = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'
- options[:login_field_regex] ||= /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
- options[:login_field_regex_message] ||= "should look like an email address."
- validates_format_of options[:login_field], :with => options[:login_field_regex], :message => options[:login_field_regex_message]
- else
- validates_length_of options[:login_field], :within => 2..100
- options[:login_field_regex] ||= /\A\w[\w\.\-_@ ]+\z/
- options[:login_field_regex_message] ||= "use only letters, numbers, spaces, and .-_@ please."
- validates_format_of options[:login_field], :with => options[:login_field_regex], :message => options[:login_field_regex_message]
- end
-
- validates_uniqueness_of options[:login_field], :scope => options[:scope]
- validates_uniqueness_of options[:remember_token_field]
- validate :validate_password
- validates_numericality_of :login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
-
- if column_names.include?("last_request_at")
- named_scope :logged_in, lambda { {:conditions => ["last_request_at > ?", options[:logged_in_timeout].ago]} }
- named_scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", options[:logged_in_timeout].ago]} }
- end
-
- before_save :get_session_information, :if => :update_sessions?
- after_save :maintain_sessions!, :if => :update_sessions?
-
- # Attributes
- attr_writer "confirm_#{options[:password_field]}"
- attr_accessor "tried_to_set_#{options[:password_field]}"
-
- # Class methods
- class_eval <<-"end_eval", __FILE__, __LINE__
- def self.unique_token
- # Force using the Sha512 because all that we are doing is creating a unique token, a hash is perfect for this
- Authlogic::Sha512CryptoProvider.encrypt(Time.now.to_s + (1..10).collect{ rand.to_s }.join)
- end
-
- def self.crypto_provider
- #{options[:crypto_provider]}
- end
-
- def self.forget_all!
- # Paginate these to save on memory
- records = nil
- i = 0
- begin
- records = find(:all, :limit => 50, :offset => i)
- records.each { |record| record.forget! }
- i += 50
- end while !records.blank?
- end
- end_eval
-
- # Instance methods
- if column_names.include?("last_request_at")
- class_eval <<-"end_eval", __FILE__, __LINE__
- def logged_in?
- !last_request_at.nil? && last_request_at > #{options[:logged_in_timeout].to_i}.seconds.ago
- end
- end_eval
- end
-
- class_eval <<-"end_eval", __FILE__, __LINE__
- def #{options[:password_field]}=(pass)
- return if pass.blank?
- self.tried_to_set_#{options[:password_field]} = true
- @#{options[:password_field]} = pass
- self.#{options[:remember_token_field]} = self.class.unique_token
- self.#{options[:password_salt_field]} = self.class.unique_token
- self.#{options[:crypted_password_field]} = crypto_provider.encrypt(@#{options[:password_field]} + #{options[:password_salt_field]})
- end
-
- def valid_#{options[:password_field]}?(attempted_password)
- return false if attempted_password.blank? || #{options[:crypted_password_field]}.blank? || #{options[:password_salt_field]}.blank?
- attempted_password == #{options[:crypted_password_field]} ||
- (crypto_provider.respond_to?(:decrypt) && crypto_provider.decrypt(#{options[:crypted_password_field]}) == attempted_password + #{options[:password_salt_field]}) ||
- (!crypto_provider.respond_to?(:decrypt) && crypto_provider.encrypt(attempted_password + #{options[:password_salt_field]}) == #{options[:crypted_password_field]})
- end
- end_eval
-
- class_eval <<-"end_eval", __FILE__, __LINE__
- def #{options[:password_field]}; end
- def confirm_#{options[:password_field]}; end
-
- def crypto_provider
- self.class.crypto_provider
- end
-
- def forget!
- self.#{options[:remember_token_field]} = self.class.unique_token
- save_without_session_maintenance(false)
- end
-
- def reset_#{options[:password_field]}!
- chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
- newpass = ""
- 1.upto(10) { |i| newpass << chars[rand(chars.size-1)] }
- self.#{options[:password_field]} = newpass
- self.confirm_#{options[:password_field]} = newpass
- save_without_session_maintenance(false)
- end
- alias_method :randomize_password!, :reset_password!
-
- def save_without_session_maintenance(*args)
- @skip_session_maintenance = true
- result = save(*args)
- @skip_session_maintenance = false
- result
- end
-
- protected
- def update_sessions?
- !@skip_session_maintenance && #{options[:session_class]}.activated? && !#{options[:session_ids].inspect}.blank? && #{options[:remember_token_field]}_changed?
- end
-
- def get_session_information
- # Need to determine if we are completely logged out, or logged in as another user
- @_sessions = []
- @_logged_out = true
-
- #{options[:session_ids].inspect}.each do |session_id|
- session = #{options[:session_class]}.find(*[session_id].compact)
- if session
- if !session.record.blank?
- @_logged_out = false
- @_sessions << session if session.record == self
- end
- end
- end
- end
-
- def maintain_sessions!
- if @_logged_out
- create_session!
- elsif !@_sessions.blank?
- update_sessions!
- end
- end
-
- def create_session!
- # We only want to automatically login into the first session, since this is the main session. The other sessions are sessions
- # that need to be created after logging into the main session.
- session_id = #{options[:session_ids].inspect}.first
-
- # If we are already logged in, ignore this completely. All that we care about is updating ourself.
- next if #{options[:session_class]}.find(*[session_id].compact)
-
- # Log me in
- args = [self, session_id].compact
- #{options[:session_class]}.create(*args)
- end
-
- def update_sessions!
- # We found sessions above, let's update them with the new info
- @_sessions.each do |stale_session|
- stale_session.unauthorized_record = self
- stale_session.save
- end
- end
-
- def tried_to_set_password?
- tried_to_set_password == true
- end
-
- def validate_password
- if new_record? || tried_to_set_#{options[:password_field]}?
- if @#{options[:password_field]}.blank?
- errors.add(:#{options[:password_field]}, "can not be blank")
- else
- errors.add(:confirm_#{options[:password_field]}, "did not match") if @confirm_#{options[:password_field]} != @#{options[:password_field]}
- end
- end
- end
- end_eval
+ # All logic for this method is split up into sub modules. This a stub to create a method chain off of and provide documentation.
end
end
end
@@ -0,0 +1,131 @@
+module Authlogic
+ module ORMAdapters
+ module ActiveRecordAdapter
+ module Credentials # :nodoc:
+ def acts_as_authentic_with_credentials(options = {})
+ acts_as_authentic_without_credentials(options)
+
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def self.login_field
+ @login_field ||= #{options[:login_field].inspect} ||
+ (column_names.include?("login") && :login) ||
+ (column_names.include?("username") && :username) ||
+ (column_names.include?("email") && :email) ||
+ :login
+ end
+
+ def self.password_field
+ @password_field ||= #{options[:password_field].inspect} ||
+ (column_names.include?("password") && :password) ||
+ (column_names.include?("pass") && :pass) ||
+ :password
+ end
+
+ def self.crypted_password_field
+ @crypted_password_field ||= #{options[:crypted_password_field].inspect} ||
+ (column_names.include?("crypted_password") && :crypted_password) ||
+ (column_names.include?("encrypted_password") && :encrypted_password) ||
+ (column_names.include?("password_hash") && :password_hash) ||
+ (column_names.include?("pw_hash") && :pw_hash) ||
+ :crypted_password
+ end
+
+ def self.password_salt_field
+ @password_salt_field ||= #{options[:password_salt_field].inspect} ||
+ (column_names.include?("password_salt") && :password_salt) ||
+ (column_names.include?("pw_salt") && :pw_salt) ||
+ (column_names.include?("salt") && :salt) ||
+ :password_salt
+ end
+ end_eval
+
+ options[:crypto_provider] ||= Sha512CryptoProvider
+ options[:login_field_type] ||= login_field == :email ? :email : :login
+
+ # Validations
+ case options[:login_field_type]
+ when :email
+ validates_length_of login_field, :within => 6..100
+ email_name_regex = '[\w\.%\+\-]+'
+ domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
+ domain_tld_regex = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'
+ options[:login_field_regex] ||= /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
+ options[:login_field_regex_message] ||= "should look like an email address."
+ validates_format_of login_field, :with => options[:login_field_regex], :message => options[:login_field_regex_message]
+ else
+ validates_length_of login_field, :within => 2..100
+ options[:login_field_regex] ||= /\A\w[\w\.\-_@ ]+\z/
+ options[:login_field_regex_message] ||= "use only letters, numbers, spaces, and .-_@ please."
+ validates_format_of login_field, :with => options[:login_field_regex], :message => options[:login_field_regex_message]
+ end
+
+ validates_uniqueness_of login_field, :scope => options[:scope]
+ validate :validate_password
+
+ attr_writer "confirm_#{password_field}"
+ attr_accessor "tried_to_set_#{password_field}"
+
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def self.crypto_provider
+ #{options[:crypto_provider]}
+ end
+
+ def crypto_provider
+ self.class.crypto_provider
+ end
+
+ def #{password_field}=(pass)
+ return if pass.blank?
+ self.tried_to_set_#{password_field} = true
+ @#{password_field} = pass
+ self.#{password_salt_field} = self.class.unique_token
+ self.#{crypted_password_field} = crypto_provider.encrypt(@#{password_field} + #{password_salt_field})
+ end
+
+ def valid_#{password_field}?(attempted_password)
+ return false if attempted_password.blank? || #{crypted_password_field}.blank? || #{password_salt_field}.blank?
+ attempted_password == #{crypted_password_field} ||
+ (crypto_provider.respond_to?(:decrypt) && crypto_provider.decrypt(#{crypted_password_field}) == attempted_password + #{password_salt_field}) ||
+ (!crypto_provider.respond_to?(:decrypt) && crypto_provider.encrypt(attempted_password + #{password_salt_field}) == #{crypted_password_field})
+ end
+
+ def #{password_field}; end
+ def confirm_#{password_field}; end
+
+ def reset_#{password_field}!
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ newpass = ""
+ 1.upto(10) { |i| newpass << chars[rand(chars.size-1)] }
+ self.#{password_field} = newpass
+ self.confirm_#{password_field} = newpass
+ save_without_session_maintenance(false)
+ end
+ alias_method :randomize_password!, :reset_password!
+
+ protected
+ def tried_to_set_password?
+ tried_to_set_password == true
+ end
+
+ def validate_password
+ if new_record? || tried_to_set_#{password_field}?
+ if @#{password_field}.blank?
+ errors.add(:#{password_field}, "can not be blank")
+ else
+ errors.add(:confirm_#{password_field}, "did not match") if @confirm_#{password_field} != @#{password_field}
+ end
+ end
+ end
+ end_eval
+ end
+ end
+ end
+ end
+end
+
+ActiveRecord::Base.class_eval do
+ class << self
+ include Authlogic::ORMAdapters::ActiveRecordAdapter::Credentials
+ alias_method_chain :acts_as_authentic, :credentials
+ end
+end
Oops, something went wrong.

0 comments on commit 9bca67d

Please sign in to comment.