diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index a168547a..0c073195 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -1,7 +1,11 @@ -== 1.3.5 released 2008-11-24 +== 1.3.5 released 2008-11-30 * :transition_from_crypto_provider for acts_as_authentic now accepts an array to transition from multiple providers. Which solves the problem of a double transition. * Added AES256 as a crypto_provider option, for those that want to use a reversible encryption method by supplying a key. +* Fixed typo for using validates_format_of_options instead of validates_length_of_options +* Fixed bug when accessing the dynamic method for accessing the session record in a namespace, since it uses class_name.underscore which replaces :: with a / +* Added minimum length requirement of 4 for the password, and removed validates_presence_of for password since validates_length_of enforces this +* Set before_validation to reset the persistence token if it is blank, since a password is not required for open id authentication == 1.3.4 released 2008-11-24 diff --git a/README.rdoc b/README.rdoc index cb2a1612..7574275e 100644 --- a/README.rdoc +++ b/README.rdoc @@ -2,7 +2,7 @@ Authlogic is a clean, simple, and unobtrusive ruby authentication solution. Put simply, its the Chuck Norris of authentication solutions for your framework of choice. -So what is Authlogic, and why would I create a solution to a problem that already has plenty of solutions? Because none of the solutions felt right to me, RESTful development and authentication just didn't seem to go well together. It was like trying to fit a square peg in a round hole. All of the current solutions, for both rails and merb, just seemed to force that square peg in the round hole for me. Just because they did it for me doesn't make it 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... +So what is Authlogic, and why would I create a solution to a problem that already has plenty of solutions? Because none of the solutions felt right to me, RESTful development and authentication just didn't seem to go well together. It was like trying to fit a square peg in a round hole. All of the current solutions, for both rails and merb, just seemed to force that square peg in the round hole for me. Just because they did it for me doesn't make it right. They were either too complicated, bloated, littered my application with tons of code, had no platform for reasonable updating, used an inferior encryption algorithm, 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... @@ -130,9 +130,7 @@ One thing to keep in mind here is that the default :crypto_provider for Authlogi You are all set, now go use it just like you would with any other ActiveRecord model. Either glance at the code at the beginning of this README or check out the tutorials (see above in "helpful links") for a more detailed walk through. -== Migrating from restful_authentication - -This is for migrating an existing application from restful_authentication to Authlogic. If you are starting a new application, please ignore this section. +== Migrating an existing app from restful_authentication and upgrading your encryption For those that are switching existing apps over, I made an option especially for you. Just do the following and everything will be taken care of, your users won't even know anything changed: @@ -141,13 +139,25 @@ For those that are switching existing apps over, I made an option especially for acts_as_authentic :act_like_restful_authentication => true end -Or you can transition your users to the Authlogic password system: +The above will not change a thing, from your database's perspective it will be as if you are still using restful_authentication. + +Or you can upgrade from Sha1 and transition your users to a much more secure encryption algorithm: # app/models/user.rb class User < ActiveRecord::Base acts_as_authentic :transition_from_restful_authentication => true end +By default this will switch your users to Authlogic's Sha512 implementation. You do *NOT* have to use this. Check out the encryption methods section below for a list of encryption methods Authlogic provides you. If you want to use something besides Sha512 just specify it by doing: + + # app/models/user.rb + class User < ActiveRecord::Base + acts_as_authentic :transition_from_restful_authentication => true, + :crypto_provider => Authlogic::CryptoProviders::BCrypt + end + +Every time a user logs in their password will be upgraded and every time a new account is created it will use the new algorithm all while allowing users to login with the old algorithm. + For more information checkout my blog post on this: http://www.binarylogic.com/2008/11/23/tutorial-easily-migrate-from-restful_authentication-to-authlogic == Magic Columns @@ -272,11 +282,11 @@ There could be many more depending on your application. What's great about Authl Here are the 3 tokens in more detail: -=== 1. Persistence token +=== 1. Persistence token (stored in cookie / session) This token is used to persist the user's session. This is the token that is stored in the session and the cookie, so that during each request the user stays logged in. What's unique about this token is that the first time it is used the value is stored in the session, thus persisting the session. This field is required and must be in your database. -=== 2. Single access token +=== 2. Single access token (private feed access, etc.) This token is used for single access only, it is not persisted. Meaning the user provides it, Authlogic grants them access, and that's it. If they want access again they need to provide the token again. Authlogic will *NEVER* store this value in the session or a cookie. For added security, by default this token is *ONLY* allowed for RSS and ATOM requests. Also, this token does *NOT* change with the password. Meaning if the user changes their password, this token will remain the same. Lastly, this token uses a "friendly" toke (see the URL example below) so that it is easier to email / copy and paste. You can change all of this with configuration (see Authlogic::Session::config), so if you don't like how this works by default, just set some simple configuration in your session. @@ -293,7 +303,7 @@ The single_access_token parameter name is configurable (see Authlogic::Session:: For more information see: Authlogic::ORMAdapters::ActiveRecordAdapter::ActsAsAuthentic::SingleAccess -=== 3. Perishable token +=== 3. Perishable token (resetting passwords, confirming accounts, etc) This token is used for temporary account access, hence the term "perishable". This token is constantly changing, it changes... @@ -445,7 +455,7 @@ From there it is pretty simple. When you try to create a new session the record == What's wrong with the current solutions? -You probably don't care, but I think releasing the millionth authentication solution for a framework that has been around for over 4 years requires a little explanation. +You probably don't care, but I think releasing the millionth ruby authentication solution requires a little explanation. I don't necessarily think the current solutions are "wrong", nor am I saying Authlogic is the answer to your prayers. But, to me, the current solutions were lacking something. Here's what I came up with... @@ -459,6 +469,10 @@ Using a library that hundreds of other people use has it advantages. Probably on Lastly, there is a pattern here, why clutter up all of your applications with the same code over and over? +=== Security gets outdated + +Just as I stated in the above section, you can't stay up to date with your security since the code is generated and updating the plugin does nothing. If there is one thing you should stay up to date with, it's security. But it's not just the fact that there is no reasonable method for receiving updates. It's the fact that they tie you down to an encryption algorithm *AND* they use a bad one at that. Every single solution I've seen uses Sha1, which is joining the party with MD5. Sha1 is not as secure as it used to be. But that's the nature of algorithms, they eventually get phased out, which is fine. Everyone knows this, why not accommodate for this? Authlogic does this with the :transition_from_crypto_provider option. It takes care of transitioning all of your users to a new algorithm. Even better, it provides BCrypt as an option which should, in theory, never require you to switch since you can adjust the cost and make the encryption stronger. At the same time, still compatible with older passwords using the lower cost. + === Why test the same code over and over? I've noticed my apps get cluttered with authentication tests, and they are the same exact tests! This irritates me. When you have identical tests across your apps thats a red flag that code can be extracted into a library. What's great about Authlogic is that I tested it for you. You don't write tests that test the internals of ActiveRecord do you? The same applies for Authlogic. Only test code that you've written. Essentially testing authentication is similar to testing any another RESTful controller. This makes your tests focused and easier to understand. diff --git a/lib/authlogic/crypto_providers/aes256.rb b/lib/authlogic/crypto_providers/aes256.rb index a3a3536b..8613fa27 100644 --- a/lib/authlogic/crypto_providers/aes256.rb +++ b/lib/authlogic/crypto_providers/aes256.rb @@ -30,6 +30,8 @@ def matches?(crypted, *tokens) aes.decrypt aes.key = @key (aes.update(crypted.unpack("m").first) + aes.final) == tokens.join + rescue OpenSSL::CipherError + false end private diff --git a/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb b/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb index 0eb19996..c92ca1b8 100644 --- a/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb +++ b/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb @@ -124,8 +124,8 @@ module ActsAsAuthentic # * password_field_validation_options - default: {}, # The same as :validation_options but these are only applied to validations that pertain to the :password_field # - # * password_field_validates_presence_of_options - default: {:on => :create}, - # These options are applied to the validates_presence_of call for the :password_field + # * password_field_validates_length_of_options - default: {:minimum => 4}, + # These options are applied to the validates_length_of call for the :password_field # # * login_field_validates_confirmation_of_options - default: {}, # These options are applied to the validates_confirmation_of call for the :password_field @@ -187,7 +187,7 @@ def acts_as_authentic_with_config(options = {}) field_key = "#{field_name}_field_validation_options".to_sym options[field_key] = options[:validation_options].merge(options[field_key] || {}) - validation_types = field_name == :password ? [:presence, :confirmation] : [:length, :format, :uniqueness] + validation_types = field_name == :password ? [:length, :confirmation] : [:length, :format, :uniqueness] validation_types.each do |validation_type| validation_key = "#{field_name}_field_validates_#{validation_type}_of_options".to_sym options[validation_key] = options[field_key].merge(options[validation_key] || {}) diff --git a/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb b/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb index aeecf3d0..319ac3ee 100644 --- a/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb +++ b/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb @@ -30,7 +30,7 @@ def acts_as_authentic_with_credentials(options = {}) case options[:login_field_type] when :email validates_length_of options[:login_field], {:within => 6..100}.merge(options[:login_field_validates_length_of_options]) - validates_format_of options[:login_field], {:with => email_field_regex, :message => "should look like an email address."}.merge(options[:login_field_validates_length_of_options]) + validates_format_of options[:login_field], {:with => email_field_regex, :message => "should look like an email address."}.merge(options[:login_field_validates_format_of_options]) else validates_length_of options[:login_field], {:within => 2..100}.merge(options[:login_field_validates_length_of_options]) validates_format_of options[:login_field], {:with => /\A\w[\w\.\-_@ ]+\z/, :message => "should use only letters, numbers, spaces, and .-_@ please."}.merge(options[:login_field_validates_format_of_options]) @@ -40,9 +40,9 @@ def acts_as_authentic_with_credentials(options = {}) end if options[:validate_password_field] - validates_presence_of options[:password_field], {:on => :create}.merge(options[:password_field_validates_presence_of_options]) - validates_confirmation_of options[:password_field], options[:password_field_validates_confirmation_of_options].merge(:if => "#{options[:crypted_password_field]}_changed?".to_sym) - validates_presence_of "#{options[:password_field]}_confirmation", :if => "#{options[:crypted_password_field]}_changed?" + validates_length_of options[:password_field], {:minimum => 4}.merge(options[:password_field_validates_length_of_options].merge(:if => "validate_#{options[:password_field]}?".to_sym)) + validates_confirmation_of options[:password_field], options[:password_field_validates_confirmation_of_options].merge(:if => "validate_#{options[:password_field]}?".to_sym) + validates_presence_of "#{options[:password_field]}_confirmation", :if => "validate_#{options[:password_field]}?".to_sym end if options[:validate_email_field] && options[:email_field] @@ -113,6 +113,10 @@ def reset_#{options[:password_field]}! end alias_method :randomize_password!, :reset_password! + def validate_#{options[:password_field]}? + new_record? || #{options[:crypted_password_field]}_changed? + end + private def encrypt_arguments(raw_password, arguments_type = nil) case arguments_type diff --git a/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb b/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb index d778fcef..5ffea191 100644 --- a/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb +++ b/lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb @@ -22,7 +22,10 @@ module Persistence def acts_as_authentic_with_persistence(options = {}) acts_as_authentic_without_persistence(options) + validates_presence_of options[:persistence_token_field] validates_uniqueness_of options[:persistence_token_field], :if => "#{options[:persistence_token_field]}_changed?".to_sym + + before_validation "reset_#{options[:persistence_token_field]}".to_sym, :if => "reset_#{options[:persistence_token_field]}?".to_sym def forget_all! # Paginate these to save on memory @@ -46,10 +49,23 @@ def forget! end def #{options[:password_field]}_with_persistence=(value) - self.#{options[:persistence_token_field]} = self.class.unique_token + reset_#{options[:persistence_token_field]} self.#{options[:password_field]}_without_persistence = value end alias_method_chain :#{options[:password_field]}=, :persistence + + def reset_#{options[:persistence_token_field]} + self.#{options[:persistence_token_field]} = self.class.unique_token + end + + def reset_#{options[:persistence_token_field]}! + reset_#{options[:persistence_token_field]} + save_without_session_maintenance(false) + end + + def reset_#{options[:persistence_token_field]}? + #{options[:persistence_token_field]}.blank? + end end_eval end end diff --git a/lib/authlogic/session/base.rb b/lib/authlogic/session/base.rb index 69c46548..cbd53fa9 100644 --- a/lib/authlogic/session/base.rb +++ b/lib/authlogic/session/base.rb @@ -203,7 +203,7 @@ def find_record end # Allows you to set a unique identifier for your session, so that you can have more than 1 session at a time. A good example when this might be needed is when you want to have a normal user session - # and a "secure" user session. The secure user session would be created only when they want to modify their billing information, or other sensative information. Similar to me.com. This requires 2 + # and a "secure" user session. The secure user session would be created only when they want to modify their billing information, or other sensitive information. Similar to me.com. This requires 2 # user sessions. Just use an id for the "secure" session and you should be good. # # You can set the id during initialization (see initialize for more information), or as an attribute: @@ -357,7 +357,7 @@ def create_configurable_methods! return if respond_to?(login_field) # already created these methods self.class.class_eval <<-"end_eval", __FILE__, __LINE__ - alias_method :#{klass_name.underscore}, :record + alias_method :#{klass_name.underscore.split("/").last}, :record attr_reader :#{login_field} diff --git a/lib/authlogic/version.rb b/lib/authlogic/version.rb index 336e106a..c3497e2b 100644 --- a/lib/authlogic/version.rb +++ b/lib/authlogic/version.rb @@ -44,7 +44,7 @@ def to_a MAJOR = 1 MINOR = 3 - TINY = 4 + TINY = 5 # The current version as a Version instance CURRENT = new(MAJOR, MINOR, TINY) diff --git a/test/libs/aes128_crypto_provider.rb b/test/libs/aes128_crypto_provider.rb deleted file mode 100644 index e13e5122..00000000 --- a/test/libs/aes128_crypto_provider.rb +++ /dev/null @@ -1,17 +0,0 @@ -require "ezcrypto" - -class AES128CryptoProvider - class << self - def encrypt(*tokens) - [key.encrypt(tokens.join)].pack("m").chomp - end - - def matches?(crypted, *tokens) - key.decrypt(crypted.unpack("m").first) == tokens.join - end - - def key - EzCrypto::Key.with_password "master_key", "some_salt" - end - end -end \ No newline at end of file diff --git a/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb b/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb index f524e5f0..0e978547 100644 --- a/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb +++ b/test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb @@ -36,7 +36,7 @@ def test_acts_as_authentic_config :validate_fields => true, :login_field => :login, :perishable_token_valid_for => 600, - :password_field_validates_presence_of_options => {}, + :password_field_validates_length_of_options => {}, :password_field => :password, :validate_login_field => true, :email_field => :email, diff --git a/test/test_helper.rb b/test/test_helper.rb index c7cfdbdb..4404cb81 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -4,7 +4,6 @@ require "active_record" require 'active_record/fixtures' require File.dirname(__FILE__) + '/../lib/authlogic' unless defined?(Authlogic) -require File.dirname(__FILE__) + '/libs/aes128_crypto_provider' require File.dirname(__FILE__) + '/libs/mock_request' require File.dirname(__FILE__) + '/libs/mock_cookie_jar' require File.dirname(__FILE__) + '/libs/mock_controller'