Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Release v2.0 RC1

  • Loading branch information...
commit 605162d30b284d4ec8ed540cced4c5735f1d2ad9 1 parent dbf37ed
@binarylogic binarylogic authored
Showing with 3,197 additions and 2,760 deletions.
  1. +10 −0 CHANGELOG.rdoc
  2. +0 −76 Manifest
  3. +108 −0 Manifest.txt
  4. +104 −389 README.rdoc
  5. +14 −7 Rakefile
  6. +23 −33 lib/authlogic.rb
  7. +42 −0 lib/authlogic/acts_as_authentic/base.rb
  8. +37 −0 lib/authlogic/acts_as_authentic/config.rb
  9. +72 −0 lib/authlogic/acts_as_authentic/email.rb
  10. +42 −0 lib/authlogic/acts_as_authentic/logged_in_status.rb
  11. +60 −0 lib/authlogic/acts_as_authentic/login.rb
  12. +18 −0 lib/authlogic/acts_as_authentic/magic_columns.rb
  13. +189 −0 lib/authlogic/acts_as_authentic/password.rb
  14. +91 −0 lib/authlogic/acts_as_authentic/perishable_token.rb
  15. +60 −0 lib/authlogic/acts_as_authentic/persistence_token.rb
  16. +42 −0 lib/authlogic/acts_as_authentic/restful_authentication.rb
  17. +24 −0 lib/authlogic/acts_as_authentic/scope.rb
  18. +113 −0 lib/authlogic/acts_as_authentic/session_maintenance.rb
  19. +44 −0 lib/authlogic/acts_as_authentic/single_access_token.rb
  20. +10 −6 lib/authlogic/{session/authenticates_many_association.rb → authenticates_many/association.rb}
  21. +54 −0 lib/authlogic/authenticates_many/base.rb
  22. +2 −3 lib/authlogic/controller_adapters/abstract_adapter.rb
  23. +0 −4 lib/authlogic/controller_adapters/merb_adapter.rb
  24. +0 −4 lib/authlogic/controller_adapters/rails_adapter.rb
  25. +0 −2  lib/authlogic/crypto_providers/aes256.rb
  26. +0 −2  lib/authlogic/crypto_providers/bcrypt.rb
  27. +0 −2  lib/authlogic/crypto_providers/sha1.rb
  28. +1 −3 lib/authlogic/crypto_providers/sha512.rb
  29. +1 −3 lib/authlogic/i18n.rb
  30. +0 −22 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/base.rb
  31. +0 −238 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb
  32. +0 −152 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb
  33. +0 −51 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/logged_in.rb
  34. +0 −71 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/perishability.rb
  35. +0 −94 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb
  36. +0 −86 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance.rb
  37. +0 −61 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/single_access.rb
  38. +0 −58 lib/authlogic/orm_adapters/active_record_adapter/authenticates_many.rb
  39. +2 −0  lib/authlogic/random.rb
  40. +8 −8 lib/authlogic/session/active_record_trickery.rb
  41. +128 −252 lib/authlogic/session/base.rb
  42. +36 −27 lib/authlogic/session/brute_force_protection.rb
  43. +23 −15 lib/authlogic/session/callbacks.rb
  44. +0 −421 lib/authlogic/session/config.rb
  45. +97 −23 lib/authlogic/session/cookies.rb
  46. +12 −1 lib/authlogic/session/errors.rb
  47. +23 −0 lib/authlogic/session/http_auth.rb
  48. +75 −0 lib/authlogic/session/magic_columns.rb
  49. +58 −0 lib/authlogic/session/magic_states.rb
  50. +82 −19 lib/authlogic/session/params.rb
  51. +159 −0 lib/authlogic/session/password.rb
  52. +4 −4 lib/authlogic/session/{perishability.rb → perishable_token.rb}
  53. +0 −24 lib/authlogic/session/record_info.rb
  54. +60 −56 lib/authlogic/session/scopes.rb
  55. +44 −39 lib/authlogic/session/session.rb
  56. +63 −31 lib/authlogic/session/timeout.rb
  57. +42 −0 lib/authlogic/session/unauthorized_record.rb
  58. +3 −3 lib/authlogic/testing/test_unit_helpers.rb
  59. +3 −3 lib/authlogic/version.rb
  60. +15 −0 test/acts_as_authentic_tests/base_test.rb
  61. +10 −0 test/acts_as_authentic_tests/config_test.rb
  62. +79 −0 test/acts_as_authentic_tests/email_test.rb
  63. +36 −0 test/acts_as_authentic_tests/logged_in_status_test.rb
  64. +79 −0 test/acts_as_authentic_tests/login_test.rb
  65. +27 −0 test/acts_as_authentic_tests/magic_columns_test.rb
  66. +212 −0 test/acts_as_authentic_tests/password_test.rb
  67. +56 −0 test/acts_as_authentic_tests/perishable_token_test.rb
  68. +55 −0 test/acts_as_authentic_tests/persistence_token_test.rb
  69. +68 −0 test/acts_as_authentic_tests/session_maintenance_test.rb
  70. +39 −0 test/acts_as_authentic_tests/single_access_test.rb
  71. +16 −0 test/authenticates_many_test.rb
  72. +4 −4 test/fixtures/employees.yml
  73. +5 −5 test/fixtures/users.yml
  74. +6 −0 test/libs/company.rb
  75. +7 −0 test/libs/employee.rb
  76. +2 −0  test/libs/employee_session.rb
  77. +3 −0  test/libs/project.rb
  78. +2 −0  test/libs/user_session.rb
  79. +8 −0 test/session_tests/active_record_trickery_test.rb
  80. +19 −142 test/session_tests/base_test.rb
  81. +9 −1 test/session_tests/brute_force_protection_test.rb
  82. +6 −0 test/session_tests/callbacks_test.rb
  83. +0 −184 test/session_tests/config_test.rb
  84. +72 −7 test/session_tests/cookies_test.rb
  85. +16 −0 test/session_tests/http_auth_test.rb
  86. +55 −0 test/session_tests/magic_columns_test.rb
  87. +56 −0 test/session_tests/magic_states_test.rb
  88. +28 −11 test/session_tests/params_test.rb
  89. +72 −0 test/session_tests/password_test.rb
  90. +1 −2  test/session_tests/scopes_test.rb
  91. +14 −31 test/session_tests/session_test.rb
  92. +15 −55 test/session_tests/timeout_test.rb
  93. +12 −0 test/session_tests/unauthorized_record_test.rb
  94. +10 −25 test/test_helper.rb
  95. 0  {test/session_tests → test_old}/authenticates_many_association_test.rb
  96. 0  {test → test_old}/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb
  97. 0  ...→ test_old}/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/credentials_test.rb
  98. 0  ...t → test_old}/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/logged_in_test.rb
  99. 0  ...arr; test_old}/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/perishability_test.rb
  100. 0  ...→ test_old}/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/persistence_test.rb
  101. 0  ...est_old}/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/session_maintenance_test.rb
  102. 0  ...arr; test_old}/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/single_access_test.rb
  103. 0  {test → test_old}/orm_adapters_tests/active_record_adapter_tests/authenticates_many_test.rb
View
10 CHANGELOG.rdoc
@@ -1,3 +1,13 @@
+== 2.0.0 RC 1
+
+* Refactored nearly all code and tests, especially acts_as_authentic. Got rid of the meta programming and rewrote to use modules and hooks.
+* Changed configuration method for acts_as_authentic to accept a block instead of a hash.
+* Added more hooks / callbacks to Session::Base.
+* Reorganized all modules to contain related configuration options and any other relevant code.
+* Split out authentication by password and unauthorized record into their own modules, to make way for alternate authentication add ons (OpenId, etc)
+* The record attribute will NEVER be set until after validation passes, similar to how ActiveRecord executes UPDATEs and CREATEs.
+* Fixed bug with session maintenance where user would log in as new user when creating another user account, typically an admin function.
+
== 1.4.4
* Moved session maintenance to a before_save, to save on queries executed and to skip an unexpected / additional save on the user object.
View
76 Manifest
@@ -1,76 +0,0 @@
-CHANGELOG.rdoc
-generators/session/session_generator.rb
-generators/session/templates/session.rb
-init.rb
-lib/authlogic/controller_adapters/abstract_adapter.rb
-lib/authlogic/controller_adapters/merb_adapter.rb
-lib/authlogic/controller_adapters/rails_adapter.rb
-lib/authlogic/crypto_providers/aes256.rb
-lib/authlogic/crypto_providers/bcrypt.rb
-lib/authlogic/crypto_providers/sha1.rb
-lib/authlogic/crypto_providers/sha512.rb
-lib/authlogic/i18n.rb
-lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/base.rb
-lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb
-lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb
-lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/logged_in.rb
-lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/perishability.rb
-lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/persistence.rb
-lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/session_maintenance.rb
-lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/single_access.rb
-lib/authlogic/orm_adapters/active_record_adapter/authenticates_many.rb
-lib/authlogic/session/active_record_trickery.rb
-lib/authlogic/session/authenticates_many_association.rb
-lib/authlogic/session/base.rb
-lib/authlogic/session/brute_force_protection.rb
-lib/authlogic/session/callbacks.rb
-lib/authlogic/session/config.rb
-lib/authlogic/session/cookies.rb
-lib/authlogic/session/errors.rb
-lib/authlogic/session/params.rb
-lib/authlogic/session/perishability.rb
-lib/authlogic/session/record_info.rb
-lib/authlogic/session/scopes.rb
-lib/authlogic/session/session.rb
-lib/authlogic/session/timeout.rb
-lib/authlogic/testing/test_unit_helpers.rb
-lib/authlogic/version.rb
-lib/authlogic.rb
-Manifest
-MIT-LICENSE
-Rakefile
-README.rdoc
-shoulda_macros/authlogic.rb
-test/crypto_provider_tests/aes256_test.rb
-test/crypto_provider_tests/bcrypt_test.rb
-test/crypto_provider_tests/sha1_test.rb
-test/crypto_provider_tests/sha512_test.rb
-test/fixtures/companies.yml
-test/fixtures/employees.yml
-test/fixtures/projects.yml
-test/fixtures/users.yml
-test/libs/mock_controller.rb
-test/libs/mock_cookie_jar.rb
-test/libs/mock_request.rb
-test/libs/ordered_hash.rb
-test/libs/user.rb
-test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb
-test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/credentials_test.rb
-test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/logged_in_test.rb
-test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/perishability_test.rb
-test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/persistence_test.rb
-test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/session_maintenance_test.rb
-test/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/single_access_test.rb
-test/orm_adapters_tests/active_record_adapter_tests/authenticates_many_test.rb
-test/session_tests/active_record_trickery_test.rb
-test/session_tests/authenticates_many_association_test.rb
-test/session_tests/base_test.rb
-test/session_tests/brute_force_protection_test.rb
-test/session_tests/config_test.rb
-test/session_tests/cookies_test.rb
-test/session_tests/params_test.rb
-test/session_tests/perishability_test.rb
-test/session_tests/scopes_test.rb
-test/session_tests/session_test.rb
-test/session_tests/timeout_test.rb
-test/test_helper.rb
View
108 Manifest.txt
@@ -0,0 +1,108 @@
+CHANGELOG.rdoc
+MIT-LICENSE
+Manifest.txt
+README.rdoc
+Rakefile
+generators/session/session_generator.rb
+generators/session/templates/session.rb
+init.rb
+lib/authlogic.rb
+lib/authlogic/acts_as_authentic/base.rb
+lib/authlogic/acts_as_authentic/config.rb
+lib/authlogic/acts_as_authentic/email.rb
+lib/authlogic/acts_as_authentic/logged_in_status.rb
+lib/authlogic/acts_as_authentic/login.rb
+lib/authlogic/acts_as_authentic/magic_columns.rb
+lib/authlogic/acts_as_authentic/password.rb
+lib/authlogic/acts_as_authentic/perishable_token.rb
+lib/authlogic/acts_as_authentic/persistence_token.rb
+lib/authlogic/acts_as_authentic/restful_authentication.rb
+lib/authlogic/acts_as_authentic/scope.rb
+lib/authlogic/acts_as_authentic/session_maintenance.rb
+lib/authlogic/acts_as_authentic/single_access_token.rb
+lib/authlogic/authenticates_many/association.rb
+lib/authlogic/authenticates_many/base.rb
+lib/authlogic/controller_adapters/abstract_adapter.rb
+lib/authlogic/controller_adapters/merb_adapter.rb
+lib/authlogic/controller_adapters/rails_adapter.rb
+lib/authlogic/crypto_providers/aes256.rb
+lib/authlogic/crypto_providers/bcrypt.rb
+lib/authlogic/crypto_providers/sha1.rb
+lib/authlogic/crypto_providers/sha512.rb
+lib/authlogic/i18n.rb
+lib/authlogic/random.rb
+lib/authlogic/session/active_record_trickery.rb
+lib/authlogic/session/base.rb
+lib/authlogic/session/brute_force_protection.rb
+lib/authlogic/session/callbacks.rb
+lib/authlogic/session/cookies.rb
+lib/authlogic/session/errors.rb
+lib/authlogic/session/http_auth.rb
+lib/authlogic/session/magic_columns.rb
+lib/authlogic/session/magic_states.rb
+lib/authlogic/session/params.rb
+lib/authlogic/session/password.rb
+lib/authlogic/session/perishable_token.rb
+lib/authlogic/session/scopes.rb
+lib/authlogic/session/session.rb
+lib/authlogic/session/timeout.rb
+lib/authlogic/session/unauthorized_record.rb
+lib/authlogic/testing/test_unit_helpers.rb
+lib/authlogic/version.rb
+shoulda_macros/authlogic.rb
+test/acts_as_authentic_tests/base_test.rb
+test/acts_as_authentic_tests/config_test.rb
+test/acts_as_authentic_tests/email_test.rb
+test/acts_as_authentic_tests/logged_in_status_test.rb
+test/acts_as_authentic_tests/login_test.rb
+test/acts_as_authentic_tests/magic_columns_test.rb
+test/acts_as_authentic_tests/password_test.rb
+test/acts_as_authentic_tests/perishable_token_test.rb
+test/acts_as_authentic_tests/persistence_token_test.rb
+test/acts_as_authentic_tests/session_maintenance_test.rb
+test/acts_as_authentic_tests/single_access_test.rb
+test/authenticates_many_test.rb
+test/crypto_provider_tests/aes256_test.rb
+test/crypto_provider_tests/bcrypt_test.rb
+test/crypto_provider_tests/sha1_test.rb
+test/crypto_provider_tests/sha512_test.rb
+test/fixtures/companies.yml
+test/fixtures/employees.yml
+test/fixtures/projects.yml
+test/fixtures/users.yml
+test/libs/company.rb
+test/libs/employee.rb
+test/libs/employee_session.rb
+test/libs/mock_controller.rb
+test/libs/mock_cookie_jar.rb
+test/libs/mock_request.rb
+test/libs/ordered_hash.rb
+test/libs/project.rb
+test/libs/user.rb
+test/libs/user_session.rb
+test/random_tests/random_test.rb
+test/session_tests/active_record_trickery_test.rb
+test/session_tests/base_test.rb
+test/session_tests/brute_force_protection_test.rb
+test/session_tests/callbacks_test.rb
+test/session_tests/cookies_test.rb
+test/session_tests/http_auth_test.rb
+test/session_tests/magic_columns_test.rb
+test/session_tests/magic_states_test.rb
+test/session_tests/params_test.rb
+test/session_tests/password_test.rb
+test/session_tests/perishability_test.rb
+test/session_tests/scopes_test.rb
+test/session_tests/session_test.rb
+test/session_tests/timeout_test.rb
+test/session_tests/unauthorized_record_test.rb
+test/test_helper.rb
+test_old/authenticates_many_association_test.rb
+test_old/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/config_test.rb
+test_old/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/credentials_test.rb
+test_old/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/logged_in_test.rb
+test_old/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/perishability_test.rb
+test_old/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/persistence_test.rb
+test_old/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/session_maintenance_test.rb
+test_old/orm_adapters_tests/active_record_adapter_tests/acts_as_authentic_tests/single_access_test.rb
+test_old/orm_adapters_tests/active_record_adapter_tests/authenticates_many_test.rb
View
493 README.rdoc
@@ -1,8 +1,8 @@
= Authlogic
-Authlogic is a clean, simple, and unobtrusive ruby authentication solution. Put simply, its the Chuck Norris of authentication solutions for your framework of choice.
+Authlogic is a clean, simple, and unobtrusive ruby authentication solution.
-So what is Authlogic, and why would I create a solution to a problem that already has plenty of solutions? Because none of the current solutions feel right. The feel wrong because logic is not organized properly in the MVC structure. 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, because the logic is not in the right spot. Authlogic solves this by placing the session maintenance logic into its own domain (aka "model"), where it belongs. Moving session maintenance into its own domain has its benefits:
+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:
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.
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.
@@ -13,11 +13,84 @@ So what is Authlogic, and why would I create a solution to a problem that alread
Authlogic can do all of this and much more, keep reading to see...
-== Quick example
+== Helpful links
+
+* <b>Documentation:</b> http://authlogic.rubyforge.org
+* <b>Tutorial: Authlogic basic setup:</b> http://www.binarylogic.com/2008/11/3/tutorial-authlogic-basic-setup
+* <b>Tutorial: Reset passwords with Authlogic the RESTful way:</b> http://www.binarylogic.com/2008/11/16/tutorial-reset-passwords-with-authlogic
+* <b>Tutorial: Using OpenID with Authlogic:</b> http://www.binarylogic.com/2008/11/21/tutorial-using-openid-with-authlogic
+* <b>Live example of the tutorials above (with source):</b> http://authlogicexample.binarylogic.com
+* <b>Tutorial: Easily migrate from restful_authentication:</b> http://www.binarylogic.com/2008/11/23/tutorial-easily-migrate-from-restful_authentication-to-authlogic
+* <b>Tutorial: Upgrade passwords easily with Authlogic:</b> http://www.binarylogic.com/2008/11/23/tutorial-upgrade-passwords-easily-with-authlogic
+* <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/18752-authlogic
+* <b>Google group:</b> http://groups.google.com/group/authlogic
+
+**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.
+
+== Documentation
+
+You can find anything you want about Authlogic in the documentation, all that you need to do is understand the basic design behind it.
+
+That being said, Authlogic is split into 2 main parts:
+
+ 1. Authlogic::Session, which manages sessions.
+ 2. Authlogic::ActsAsAuthentic, which adds in functionality to your ActiveRecord model.
+
+Each of the above has its various sub modules that contain common logic. The modules is responsible for including everything related to it, including configuration.
+
+For example, if you want to timeout users after a certain period of inactivity, you would look in Authlogic::Session::Timeout. To help you out, I listed the following modules with short descriptions:
+
+=== Authlogic::ActsAsAuthentic module
+
+This module is for the acts_as_authentic method you call in your model. It contains all code for the model side of the authentication.
+
+* Authlogic::ActsAsAuthentic::Base - Provides the acts_as_authentic class method and includes all of the submodules.
+* Authlogic::ActsAsAuthentic::Email - Handles everything related to the email field.
+* Authlogic::ActsAsAuthentic::LoggedInStatus - Provides handy named scopes and methods for determining if the user is logged in or out.
+* Authlogic::ActsAsAuthentic::Login - Handles everything related to the login field.
+* Authlogic::ActsAsAuthentic::MagicColumns - Handles everything related to the "magic" fields: login_count, failed_login_count, etc.
+* Authlogic::ActsAsAuthentic::Password - This one is important. It handles encrypting your password, salting it, etc. It also has support for migrating password algorithms and transitioning from restful_authentication.
+* Authlogic::ActsAsAuthentic::PerishableToken - Handles maintaining the perishable token field, also provides a class level method for finding record using the token.
+* Authlogic::ActsAsAuthentic::PersistenceToken - Handles maintaining the persistence token. This is the token stored in cookies and sessions to persist the users session.
+* Authlogic::ActsAsAuthentic::RestfulAuthentication - Provides configuration options to easily migrate from the restful_authentication plugin.
+* Authlogic::ActsAsAuthentic::Scope - Allows you to scope validations, etc. Just like the :scope option for validates_uniqueness_of
+* Authlogic::ActsAsAuthentic::SessionMaintenance - Handles automatically logging the user in. EX: a new user registers, automatically log them in.
+* Authlogic::ActsAsAuthentic::SingleAccessToken - Handles maintaining the single access token.
+
+=== Authlogic::Session module
+
+This module is for the session side of authentication. It interfaces with the model and ultimately maintains the cookie and session value among other things.
+
+* Authlogic::Session::BruteForceProtection - Disables accounts after a certain number of consecutive failed login attempted.
+* Authlogic::Session::Callbacks - Your tools to extend Authlogic, lets you hook in and add/modify behavior.
+* Authlogic::Session::Cookies - Handles authentication via cookies.
+* Authlogic::Session::Errors - Stores validation errors, extends ActiveRecord::Errors.
+* Authlogic::Session::HttpAuth - Handles authentication via basic HTTP authentication.
+* Authlogic::Session::MagicColumns - Maintains "magic" database columns, similar to created_at and updated_at for ActiveRecord.
+* Authlogic::Session::MagicStates - Automatically validates based on the records states: active, approved, and confirmed.
+* Authlogic::Session::Params - Handles authentication via params, aka single access token.
+* Authlogic::Session::Password - Handles authentication via a traditional username and password.
+* Authlogic::Session::PerishableToken - Maintains the perishable token, used for resetting passwords, etc.
+* Authlogic::Session::Scopes - Implements scoping, just like ActiveRecord's with_scope, but for Authlogic instead.
+* Authlogic::Session::Session - Handles authentication via the session.
+* Authlogic::Session::Timeout - Implements automatically logging out after a certain period of inactivity.
+* Authlogic::Session::UnauthorizedRecord - Handles authentication by passing an ActiveRecord object.
-Let's take a rails application...
+=== Miscellaneous modules
-What if creating a user session could be as simple as...
+* Authlogic::AuthenticatesMany - Responsible for allowing you to scope sessions to a parent record. Similar to a has_many and belongs_to relationship. This lets you do the same thing with sessions.
+* Authlogic::ControllerAdapters - Contains adapters for the various framework controllers: Rails, Merb, etc.
+* Authlogic::CryptoProviders - Contains various encryption algorithms that Authlogic uses, allowing you to choose your encryption method.
+* Authlogic::I18n - Acts JUST LIKE the rails I18n library, and provides internationalization to Authlogic.
+* Authlogic::Random - Responsible for generating random strings.
+* Authlogic::Testing - Various helper methods for testing frameworks to help you test your code.
+* Authlogic::Version - A handy class for determine the version of Authlogic in a number of ways.
+
+
+== Quick example
+
+What if creating sessions worked like an ORM library on the surface...
UserSession.create(params[:user_session])
@@ -43,7 +116,7 @@ What if your user sessions controller could look just like your other controller
end
end
-Look familiar? If you didn't know any better, you would think UserSession was an ActiveRecord model. I think that's pretty cool, because it fits nicely into the RESTful development pattern, a style we all know and love. What about the view...
+As you can see, this fits nicely into the RESTful development pattern. What about the view...
<% form_for @user_session do |f| %>
<%= f.error_messages %>
@@ -73,20 +146,6 @@ Or how about persisting the session...
end
end
-== Helpful links
-
-* <b>Documentation:</b> http://authlogic.rubyforge.org
-* <b>Tutorial: Authlogic basic setup:</b> http://www.binarylogic.com/2008/11/3/tutorial-authlogic-basic-setup
-* <b>Tutorial: Reset passwords with Authlogic the RESTful way:</b> http://www.binarylogic.com/2008/11/16/tutorial-reset-passwords-with-authlogic
-* <b>Tutorial: Using OpenID with Authlogic:</b> http://www.binarylogic.com/2008/11/21/tutorial-using-openid-with-authlogic
-* <b>Live example of the tutorials above (with source):</b> http://authlogicexample.binarylogic.com
-* <b>Tutorial: Easily migrate from restful_authentication:</b> http://www.binarylogic.com/2008/11/23/tutorial-easily-migrate-from-restful_authentication-to-authlogic
-* <b>Tutorial: Upgrade passwords easily with Authlogic:</b> http://www.binarylogic.com/2008/11/23/tutorial-upgrade-passwords-easily-with-authlogic
-* <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/18752-authlogic
-* <b>Google group:</b> http://groups.google.com/group/authlogic
-
-If you find a bug or a problem please post it on lighthouse. If you need help with something or it is specific to your app, please use google groups. I check both regularly and get emails when anything happens, so that is the best place to get help.
-
== Install and use
Install the gem / plugin (recommended)
@@ -122,388 +181,44 @@ This will create a file that looks similar to:
The user model needs to 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 Authlogic::Session::Config::ClassMethods 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
- t.string :crypted_password, :null => false
- t.string :password_salt, :null => false
- t.string :persistence_token, :null => false
- t.string :single_access_token, :null => false # optional, see the tokens section below.
- t.string :perishable_token, :null => false # optional, see the tokens section below.
- t.integer :login_count, :null => false, :default => 0 # optional, this is a "magic" column, see the magic columns section below
- t.integer :failed_login_count, :null => false, :default => 0 # optional, this is a "magic" column, see the magic columns section below
+ t.string :login, :null => false # optional, you can use email instead, or both
+ t.string :crypted_password, :null => false
+ t.string :password_salt, :null => false
+ t.string :persistence_token, :null => false
+ t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params
+ t.string :perishable_token, :null => false # optional, see Authlogic::Session::Perishability
+ t.integer :login_count, :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
+ t.integer :failed_login_count, :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
+ t.datetime :last_request_at # optional, see Authlogic::Session::MagicColumns
+ t.datetime :current_login_at # optional, see Authlogic::Session::MagicColumns
+ t.datetime :last_login_at # optional, see Authlogic::Session::MagicColumns
+ t.string :current_login_ip # optional, see Authlogic::Session::MagicColumns
+ t.string :last_login_ip # optional, see Authlogic::Session::MagicColumns
=== Set up your model
Make sure you have a model that you will be authenticating with. For this example let's say you have a User model:
class User < ActiveRecord::Base
- acts_as_authentic # for options see documentation: Authlogic::ORMAdapters::ActiveRecordAdapter::ActsAsAuthentic::Config
- end
-
-One thing to keep in mind here is that the default :crypto_provider for Authlogic is Sha512. You are *NOT* forced to use this. See the encryption methods section below for more information.
-
-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 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:
-
- # app/models/user.rb
- class User < ActiveRecord::Base
- acts_as_authentic :act_like_restful_authentication => true
- end
-
-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
-
-Just like ActiveRecord has "magic" columns, such as: created_at and updated_at. Authlogic has its own "magic" columns too:
-
- Column name Description
- login_count Increased every time an explicit login is made. This will *NOT* increase if logging in by a session, cookie, or basic http auth
- failed_login_count This increases for each consecutive failed login. See Authlogic::Session::BruteForceProtection and the consecutive_failed_logins_limit config option for more details.
- last_request_at Updates every time the user logs in, either by explicitly logging in, or logging in by cookie, session, or http auth
- current_login_at Updates with the current time when an explicit login is made.
- last_login_at Updates with the value of current_login_at before it is reset.
- current_login_ip Updates with the request remote_ip when an explicit login is made.
- last_login_ip Updates with the value of current_login_ip before it is reset.
-
-== Magic States
-
-Authlogic tries to check the state of the record before creating the session. If your record responds to the following methods and any of them return false, validation will fail:
-
- Method name Description
- active? Is the record marked as active?
- approved? Has the record been approved?
- confirmed? Has the record been conirmed?
-
-Authlogic does nothing to define these methods for you, its up to you to define what they mean. If your object responds to these methods Authlogic will use them, otherwise they are ignored.
-
-What's neat about this is that these are checked upon any type of login. When logging in explicitly, by cookie, session, or basic http auth. So if you mark a user inactive in the middle of their session they wont be logged back in next time they refresh the page. Giving you complete control.
-
-Need Authlogic to check your own "state"? No problem, check out the hooks section below. Add in a before_validation to do your own checking. The sky is the limit.
-
-== Hooks / Callbacks
-
-Just like ActiveRecord you can create your own hooks / callbacks so that you can do whatever you want when certain actions are performed. Such as before_save, after_save, etc.
-
-See Authlogic::Session::Callbacks for more information.
-
-== Multiple Sessions / Session Identifiers
-
-You're asking: "why would I want multiple sessions?". Take this example:
-
-You have an app where users login and then need to re-login to view / change their billing information. Similar to how Apple's me.com works. What you could do is have the user login with their normal session, then have an entirely new session that represents their "secure" session. But wait, this is 2 users sessions. No problem:
-
- # regular user session
- @user_session = UserSession.new
- @user_session.id
- # => nil
-
- # secure user session
- @secure_user_session = UserSession.new(:secure)
- @secure_user_session.id
- # => :secure
-
-This will keep everything separate. The :secure session will store its info in a separate cookie, separate session, etc. Just set the id and you are good to go. Need to retrieve the session?
-
- @user_session = UserSession.find
- @secure_user_session = UserSession.find(:secure)
-
-For more information on ids checkout Authlogic::Session::Base#id
-
-== Encryption methods
-
-Authlogic is designed so you can use *any* encryption method you want. It delegates this task to a class of your choice. Authlogic comes preloaded with some common algorithms:
-
-1. Authlogic::CryptoProviders::Sha1 (used mainly for migrating from restful_authentication)
-2. Authlogic::CryptoProviders::Sha512 (default)
-3. Authlogic::CryptoProviders::BCrypt (requires bcrypt-ruby gem)
-4. Authlogic::CryptoProviders::AES256 (requires you to supply a key, see the AES256 class in the docs for more info)
-
-By default Authlogic uses salted Sha512 with 20 stretches, but you can easily change this. For example, if you wanted to use the BCrypt algorithm just do the following:
-
- acts_as_authentic :crypto_provider => Authlogic::CryptoProviders::BCrypt
-
-For more information on BCrypt checkout my blog post on it: http://www.binarylogic.com/2008/11/22/storing-nuclear-launch-codes-in-your-app-enter-bcrypt-for-authlogic
-
-Also, check out the Authlogic::CryptoProviders module and subclasses to get an idea of how to write your own crypto provider. You don't have to use the provided classes, you can easily write your own. All that you have to do is make a class with a class level encrypt and matches? method. That's it, all of the encryption and decryption logic is left to you.
-
-== Switching to a new encryption method
-
-Switching to a new encryption method used to be a pain in the ass. Authlogic has an option that makes this dead simple. Let's say you want to migrate to the BCrypt encryption method from Sha512:
-
- acts_as_authentic :crypto_provider => Authlogic::CryptoProviders::BCrypt,
- :transition_from_crypto_provider => Authlogic::CryptoProviders::Sha512
-
-That's it. When a user successfully logs in and is using the old method their password will be updated with the new method and all new registrations will use the new method as well. Your users won't know anything changed.
-
-But wait, what if a couple of years later CCrypt comes out and its better than BCrypt and you're still in the middle of transitioning all of your users to BCrypt. Oh no!
-
-Not to worry, because Authlogic can transition your users from more than one algorithm. Just pass an array to :transition_from_crypto_provider
-
- acts_as_authentic :crypto_provider => CCrypt,
- :transition_from_crypto_provider => [Authlogic::CryptoProviders::Sha512, Authlogic::CryptoProviders::BCrypt]
-
-That's it, specify as many as you want. One thing to keep in mind here is that if you are using BCrypt you should never have to do this. All that you need to do is increase the cost to make the algorithm stronger, no need to jump to entirely new algorithm. I did this for example purposes only.
-
-== Tokens (persistence, resetting passwords, private feed access, etc.)
-
-To start, let me define tokens as Authlogic sees it. A token is a form of credentials that grants some type of access to their account. Depending on the type of access, a different type of token may be needed. Put simply, it's a way for the user to say "I am this person, let me proceed". What types of different access you ask? Here are just a few:
-
-1. Regular account access
-2. Access to reset their password
-3. Access to a private feed
-4. Access to confirm their account
-
-There could be many more depending on your application. What's great about Authlogic is that it doesn't care what you do or how you want to grant access to accounts. That's up to you and your application. Authlogic just cares about the type of tokens you need. Instead of giving you a token for each specific task, it gives you all of the necessary *types* of tokens, and you get to use them how you wish. It maintains the tokens and gives you all of the tools you need to use them. Just add the fields to your database and you are good to go.
-
-Here are the 3 tokens in more detail:
-
-=== 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 (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" token (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.
-
-For even more flexibility Authlogic looks for a method in your controller called single_access_allowed?. If that method exists and returns true Authlogic will try to log in the user with this method. Here is a quick example:
-
-class UsersController < ApplicationController
- private
- def single_access_allowed?
- action_name == "index"
- end
-
-The above will only allow logging in via the single access toke with the index method only.
-
-This field is optional, if you want to use it just add the field to your database:
-
- t.string :single_access_token, :null => false
- # or call it feeds_token, feed_token, or whatever you want with configuration
-
-This is great for private feed access. So your URL to that user's private feed could look something like:
-
- http://www.mydomain.com/account/feed.rss?user_credentials=4LiXF7FiGUppIPubBPey
-
-The user_credentials parameter name is configurable (see Authlogic::Session::Config), but if that parameter exists Authlogic will automatically use it to try and grant that user access. You don't have to do a thing: UserSession.find will take care of it just like it does for everything else.
-
-For more information see: Authlogic::ORMAdapters::ActiveRecordAdapter::ActsAsAuthentic::SingleAccess
-
-=== 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...
-
-1. In a before_validation in your model, so basically every time the record is saved
-2. Any time a new session is successfully saved (aka logged in)
-
-This is perfect for <b>resetting passwords</b> or <b>confirming accounts</b>. You email them a url with this token in it, and then use this token to find the record and perform your action.
-
-This field is optional, if you want to use it just add the field to your database:
-
- t.string :perishable_token, :null => false
- # or call it password_reset_token, pw_reset_token, activation_token, or whatever you want with configuration
-
-Finding the record with this token couldn't be easier, Authlogic provides a special finder method that you can use. I highly recommend using it as it adds extra security:
-
- User.find_using_perishable_token(token)
- User.find_using_perishable_token(token, 20.minutes)
-
-That's all you need to do to locate the record. Here is what it does for extra security:
-
-1. Ignores blank tokens all together. If a blank token is passed nil will be returned.
-2. It checks the age of the token, by default the threshold is 10 minutes, meaning if the token is older than 10 minutes, it is not valid and no record will be returned. You can change the default or just override it by passing the threshold as the second parameter. If you don't want a threshold at all, pass 0.
-
-Just like the single access token this uses a friendly token, so it is easier to email / copy and paste.
-
-For a detailed tutorial on how to reset password using this token see the helpful links section above.
-
-For more information see: Authlogic::ORMAdapters::ActiveRecordAdapter::ActsAsAuthentic::Perishability
-
-== Scoping
-
-Scoping with authentication is a little tricky because it can come in many different flavors:
-
-1. Accounts have many users, meaning users can only belong to one account at a time.
-2. Accounts have and belong to many users, meaning a user can belong to more than one account.
-3. Users access their accounts via subdomains.
-4. Users access their accounts by selecting their account and storing their selection, *NOT* using subdomains. Maybe you store their selection in a session, cookie, or the database. It doesn't matter.
-
-Now mix and match the above, it can get pretty hairy. Fear not, because Authlogic is designed in a manner where it doesn't care how you do it, all that you have to do is break it down. When scoping a session there are 3 parts you might want to scope:
-
-1. The model (the validations, etc)
-2. The session (finding the record)
-3. The cookies (the names of the session key and cookie)
-
-I will describe each below, in order.
-
-=== 1. Scoping your model
-
-This scopes your login field validation, so that users are allowed to have the same login, just not in the same account.
-
- # app/models/user.rb
- class User < ActiveRecord::Base
- acts_as_authentic :scope => :account_id
+ acts_as_authentic # for options see documentation in: Authlogic::ActsAsAuthentic
end
-=== 2. Scoping your session
-
-When the session tries to validate it searches for a record. You want to scope that search. No problem...
-
-The goal of Authlogic was to not try and introduce anything new. As a result I came up with:
-
- @account.user_sessions.find
- @account.user_sessions.create
- @account.user_sessions.build
- # ... etc
-
-This works just like ActiveRecord, so it should come natural. Here is how you get this functionality:
-
- class Account < ActiveRecord::Base
- authenticates_many :user_sessions
- end
-
-=== 3. Scoping cookies
-
-What's neat about cookies is that if you use sub domains they automatically scope their self. Meaning if you create a cookie in whatever.yourdomain.com it will not exist in another.yourdomain.com. So if you are using subdomains to scope your users, you don't have to do anything.
-
-But what if you *don't* want to separate your cookies by subdomains? You can accomplish this by doing:
-
- ActionController::Base.session_options[:session_domain] = '.mydomain.com'
-
-or for Rails 2.3.0 or higher:
-
- ActionController::Base.session_options[:domain] = '.mydomain.com'
-
-
-Notice the above is configuration for your session, not your cookies. Authlogic notices this and assume this is how you want to treat your cookies as well. As a result, it applies this domain to the cookies it sets. Now your session and all cookies act the same and are scoped under the same domain under Authlogic.
-
-Now let's look at this from the other angle. What if you are *NOT* using subdomains, but still want to separate cookies for each account. Simple, set the :scope_cookies option for authenticate_many:
-
- class Account < ActiveRecord::Base
- authenticates_many :user_sessions, :scope_cookies => true
- end
-
-Done, Authlogic will give each cookie a unique name depending on the account.
-
-With the above information you should be able to scope your sessions any way you want. Just mix and match the tools above to accomplish this. Also check out the documentation on Authlogic::ActiveRecord::AuthenticatesMany.
-
-== Errors
-
-The errors in Authlogic work JUST LIKE ActiveRecord. In fact, it uses the exact same ActiveRecord errors class. Use it the same way:
-
- class UserSession
- validate :check_if_awesome
-
- private
- def check_if_awesome
- errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
- errors.add_to_base("You must be awesome to log in") unless record.awesome?
- end
- end
-
-== Timing Out Sessions (Logging out after inactivity)
-
-Think about financial websites, if you are inactive for a certain period of time you will be asked to log back in on your next request. You can do this with Authlogic easily, there are 2 parts to this:
-
-1. Define the timeout threshold:
-
- acts_as_authentic :logged_in_timeout => 10.minutes # default is 10.minutes
-
-2. Enable logging out on timeouts
-
- class UserSession < Authlogic::Session::Base
- logout_on_timeout true # default if false
- end
-
-This will require a user to log back in if they are inactive for more than 10 minutes. In order for this feature to be used you must have a last_request_at datetime column in your table for whatever model you are authenticating with.
-
-== Automatic Session Updating
-
-This is one of my favorite features that I think is pretty cool. It's things like this that make a library great and let you know you are on the right track.
-
-Just to clear up any confusion, Authlogic stores both the record id and the persistence token in the session. Why? So stale sessions can not be persisted. It stores the id so it can quickly find the record, and the persistence token to ensure no sessions are stale. The persistence token changes with the password, if someone is logged in and their password is changed, they should be logged out, unless they made the change. That being said, the person making the change needs their session to be updated with the new persistence token, so they stay logged in, which is what this section is all about.
-
-So what if a user changes their password? You have to re-log them in with the new password, recreate the session, etc. This is messy and leads to redundant code. Or what if a user creates a new user account? You have to do the same thing. Here's an even better one: what if a user is in the admin area and changes their own password? There might even be another place passwords can change. It shouldn't matter, your code should be written in a way where you don't have to remember to do this.
-
-Instead of updating sessions all over the place, doesn't it make sense to do this at a lower level? Like the User model? You're saying "but Ben, models can't mess around with sessions and cookies". True...but Authlogic can, and you can access Authlogic just like a model. I know in most situations it's not good practice to do this, but I view this in the same class as sweepers, and feel like it actually is good practice here. User sessions are directly tied to users, they should be connected on the model level.
-
-Fear not, because the acts_as_authentic method you call in your model takes care of this for you, by adding an after_save callback to automatically keep the session up to date. You don't have to worry about it anymore. Don't even think about it. Let your UsersController deal with users, not users *AND* sessions. *ANYTIME* the user changes his password in *ANY* way, his session will be updated.
-
-Here is basically how this is done....
-
- class User < ActiveRecord::Base
- after_save :maintain_sessions!
-
- private
- def maintain_sessions!
- # If we aren't logged in and a user is created, log them in as that user
- # If we aren't logged in and a user's password changes, log them in as that user
- # If we are logged in and they change their password, update the session so they remain logged in
- end
- end
-
-Obviously there is a little more to it than this, but hopefully this clarifies any confusion. Basically if you are *logged out* and you are changing passwords, Authlogic will log you in, since you already know the password for that account. Lastly, this can be altered / disabled via a configuration option. Just set :session_ids => nil when calling acts_as_authentic.
-
-When things come together like this I think its a sign that you are doing something right. Put that in your pipe and smoke it!
-
-== Internationalization (I18n) / Changing Messages
-
-Please see Authlogic::I18n for more information. Internationalization is very easy to implement, in fact if you are using the default rails I18n library then you don't need to do anything other than defining the messages in your localization configuration files. See Authlogic::I18n for a complete list of keys you need to define.
-
-== Testing
-
-Testing with authlogic is easy, there is a helper file that will add some convenient test helpers for you. In your test_helper.rb file do the following:
-
- # test/test_helper.rb
- require 'authlogic/testing/test_unit_helpers'
-
-You get the following methods:
-
- set_session_for(record_object)
- set_cookie_for(record_object)
- set_http_auth_for(username, password)
-
-In your test, before you execute a request, just call one of those methods and it will set the proper values so that it will seem as if that record is logged in.
-
-You can also checkout the authlogic_example application (see helpful links above), the tests there use this.
-
-== Deployment
-
-If you are having problems with your sessions, right now there is a bug with Rails sessions using cookie store. Checkout the comments on this commit: http://github.com/binarylogic/authlogic/commit/495e95e5a2f724e6f1e339c4871168c6c5e6a7ca
-
-As a temporary fix, you should use Rails' ActiveRecord session store. Authlogic just leverages the Rails' session system, it doesn't do anything tricky, or anything you wouldn't do in your controller.
-
-Regardless, once a fix is released this will be a non issue.
+One thing to keep in mind here is that the default crypto_provider for Authlogic is Sha512. You are *NOT* forced to use this. You can easily choose your own encryption method, or even write your own: see the Authlogic::ActsAsAuthentic::Password module for more info.
-== Framework agnostic (Rails, Merb, etc.)
+You are all set. You might also want to check out the tutorials (see above in "helpful links") for a more detailed walk through.
-I designed Authlogic to be framework agnostic, meaning it doesn't care what framework you use it in. Right out of the box it supports rails and merb. I have not had the opportunity to use other frameworks, but the only thing stopping Authlogic from being used in other frameworks is a simple adapter. Check out controller_adapters/rails_adapter, or controller_adapters/merb_adapter.
+=== Next Steps
-Since pretty much all of the frameworks in ruby follow the Rack standards, the code should be very similar across adapters. In fact that abstract adapter assumes you are following the Rack standards. If your framework is following the rack standards, there really isn't any code you should have to write. Check out the merb_adapter to see for yourself, the merb adapter is basically blank. You're saying "but Ben, why not just hook into Rack and avoid the need for controller adapters all together?". It's not that simple, because rails doesn't inherit from the Rack::Request class, plus there are small differences between how rack is implemented in each framework. Authlogic has to hook into your controller with a before_filter anyways, so it can "activate" itself. Why not just use the controller object? Also when we have access to the controller object we can do other nifty things. Checkout the OpenID tutorial in the helpful links section above to see what I mean.
+Some of the following next steps might apply to you. If you, check out the documentation for the module.
-The point in all of this rambling is that implementing Authlogic is as simple as creating an adapter. I created both the rails and merb adapters in under 10 minutes. If you have an adapter you created and would like to add please let me know and I will add it into the source.
+1. Migrating from restful_authentication? Checkout Authlogic::ActsAsAuthentic::RestfulAuthentication
+2. Need multiple session types in your app? Check out Authlogic::Session::Base#id
+3. Need to reset passwords or activate accounts? Use the perishable token. See Authlogic::ActsAsAuthentic::PerishableToken
+4. Need to give API access or access to a private feed? Use basic HTTP auth or authentication by params. See Authlogic::Session::HttpAuth or Authlogic::Session::Params
+5. Need to internationalize your app? See Authlogic::I18n
+6. Need help testing? Checkout the Authlogic::
-== How it works
+== Interesting in how it works?
Interested in how all of this all works? Basically a before filter is automatically set in your controller which lets Authlogic know about the current controller object. This "activates" Authlogic and allows Authlogic to set sessions, cookies, login via basic http auth, etc. If you are using your framework in a multiple thread environment, don't worry. I kept that in mind and made this thread safe.
View
21 Rakefile
@@ -1,13 +1,20 @@
-require 'rubygems'
+ENV['RDOCOPT'] = "-S -f html -T hanna"
+
+require "rubygems"
+require "hoe"
require File.dirname(__FILE__) << "/lib/authlogic/version"
-require 'echoe'
-
-Echoe.new 'authlogic' do |p|
- p.version = Authlogic::Version::STRING
+
+Hoe.new("Authlogic", Authlogic::Version::STRING) do |p|
+ p.name = "authlogic"
p.author = "Ben Johnson of Binary Logic"
p.email = 'bjohnson@binarylogic.com'
- p.project = 'authlogic'
p.summary = "A clean, simple, and unobtrusive ruby authentication solution."
+ p.description = "A clean, simple, and unobtrusive ruby authentication solution."
p.url = "http://github.com/binarylogic/authlogic"
- p.dependencies = %w(activesupport echoe)
+ p.history_file = "CHANGELOG.rdoc"
+ p.readme_file = "README.rdoc"
+ p.extra_rdoc_files = ["CHANGELOG.rdoc", "README.rdoc"]
+ p.test_globs = ["test/*/test_*.rb", "test/*/*_test.rb"]
+ p.extra_deps = %w(activesupport)
+ p.post_install_message = "Version 2.0 introduces a lot of changes and refactoring. The big change is how acts_as_authentic accepts configuration options. Instead of a hash, it now accepts a block: acts_as_authentic { |c| c.my_config_option = my_value}. See the docs for more details."
end
View
56 lib/authlogic.rb
@@ -13,46 +13,36 @@
require File.dirname(__FILE__) + "/authlogic/crypto_providers/bcrypt"
require File.dirname(__FILE__) + "/authlogic/crypto_providers/aes256"
-if defined?(ActiveRecord)
- require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/base"
- 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/perishability"
- 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/acts_as_authentic/single_access"
- require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config" # call this last so the configuration options are passed down the chain
- require File.dirname(__FILE__) + "/authlogic/orm_adapters/active_record_adapter/authenticates_many"
-end
+require File.dirname(__FILE__) + "/authlogic/authenticates_many/base"
+require File.dirname(__FILE__) + "/authlogic/authenticates_many/association"
+
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/email"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/logged_in_status"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/login"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/magic_columns"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/password"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/perishable_token"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/persistence_token"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/restful_authentication"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/scope"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/session_maintenance"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/single_access_token"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/base"
+require File.dirname(__FILE__) + "/authlogic/acts_as_authentic/config"
-require File.dirname(__FILE__) + "/authlogic/session/authenticates_many_association"
require File.dirname(__FILE__) + "/authlogic/session/active_record_trickery"
require File.dirname(__FILE__) + "/authlogic/session/brute_force_protection"
require File.dirname(__FILE__) + "/authlogic/session/callbacks"
-require File.dirname(__FILE__) + "/authlogic/session/config"
require File.dirname(__FILE__) + "/authlogic/session/cookies"
require File.dirname(__FILE__) + "/authlogic/session/errors"
+require File.dirname(__FILE__) + "/authlogic/session/http_auth"
+require File.dirname(__FILE__) + "/authlogic/session/magic_columns"
+require File.dirname(__FILE__) + "/authlogic/session/magic_states"
require File.dirname(__FILE__) + "/authlogic/session/params"
-require File.dirname(__FILE__) + "/authlogic/session/perishability"
-require File.dirname(__FILE__) + "/authlogic/session/record_info"
-require File.dirname(__FILE__) + "/authlogic/session/session"
+require File.dirname(__FILE__) + "/authlogic/session/password"
+require File.dirname(__FILE__) + "/authlogic/session/perishable_token"
require File.dirname(__FILE__) + "/authlogic/session/scopes"
+require File.dirname(__FILE__) + "/authlogic/session/session"
require File.dirname(__FILE__) + "/authlogic/session/timeout"
+require File.dirname(__FILE__) + "/authlogic/session/unauthorized_record"
require File.dirname(__FILE__) + "/authlogic/session/base"
-
-module Authlogic
- module Session
- class Base
- include ActiveRecordTrickery
- include Callbacks
- include BruteForceProtection
- include Cookies
- include Params
- include Perishability
- include RecordInfo
- include Session
- include Scopes
- include Timeout
- end
- end
-end
View
42 lib/authlogic/acts_as_authentic/base.rb
@@ -0,0 +1,42 @@
+module Authlogic
+ module ActsAsAuthentic
+ # Adds in the acts_as_authentic method to ActiveRecord.
+ module Base
+ # This includes a lot of helpful methods for authenticating records which The Authlogic::Session module relies on.
+ # To use it just do:
+ #
+ # class User < ActiveRecord::Base
+ # acts_as_authentic
+ # end
+ #
+ # Configuration is easy:
+ #
+ # acts_as_authentic do |c|
+ # c.my_configuration_option = my_value
+ # end
+ #
+ # See the various sub modules for the configuration they provide.
+ def acts_as_authentic(&block)
+ cattr_accessor :aaa_config
+ c = Config.new(self)
+ yield c if block_given?
+ self.aaa_config = c
+
+ # We need to include these after configuration is set, because some of these module
+ # use the configuration when included.
+ include Email::Methods
+ include LoggedInStatus::Methods
+ include Login::Methods
+ include MagicColumns::Methods
+ include Password::Callbacks
+ include Password::Methods
+ include PerishableToken::Methods if column_names.include?("perishable_token")
+ include PersistenceToken::Methods
+ include SessionMaintenance::Methods
+ include SingleAccessToken::Methods if column_names.include?("single_access_token")
+ end
+ end
+
+ ::ActiveRecord::Base.extend(Base) if defined?(::ActiveRecord)
+ end
+end
View
37 lib/authlogic/acts_as_authentic/config.rb
@@ -0,0 +1,37 @@
+module Authlogic
+ module ActsAsAuthentic
+ class Config # :nodoc:
+ include Email::Config
+ include LoggedInStatus::Config
+ include Login::Config
+ include Password::Config
+ include PerishableToken::Config
+ include RestfulAuthentication::Config
+ include Scope::Config
+ include SessionMaintenance::Config
+ include SingleAccessToken::Config
+
+ attr_accessor :klass
+
+ def initialize(klass)
+ self.klass = klass
+ end
+
+ private
+ def config(key, value, default_value = nil, read_value = nil)
+ if value == read_value
+ v = instance_variable_defined?("@#{key}") ? instance_variable_get("@#{key}") : nil
+ return v if !v.nil?
+ instance_variable_set("@#{key}", default_value)
+ else
+ instance_variable_set("@#{key}", value)
+ end
+ end
+
+ def first_column_to_exist(*columns_to_check) # :nodoc:
+ columns_to_check.each { |column_name| return column_name.to_sym if klass.column_names.include?(column_name.to_s) }
+ columns_to_check.first ? columns_to_check.first.to_sym : nil
+ end
+ end
+ end
+end
View
72 lib/authlogic/acts_as_authentic/email.rb
@@ -0,0 +1,72 @@
+module Authlogic
+ module ActsAsAuthentic
+ # Sometimes models won't have an explicit "login" or "username" field. Instead they want to use the email field.
+ # In this case, authlogic provides validations to make sure the email submited is actually a valid email. Don't worry,
+ # if you do have a login or username field, Authlogic will still validate your email field. One less thing you have to
+ # worry about.
+ module Email
+ # Configuration to modify how Authlogic handles the email field.
+ module Config
+ # The name of the field that stores email addresses.
+ #
+ # * <tt>Default:</tt> :email, if it exists
+ # * <tt>Accepts:</tt> Symbol
+ def email_field(value = nil)
+ config(:email_field, value, first_column_to_exist(nil, :email))
+ end
+ alias_method :email_field=, :email_field
+
+ # Toggles validating the email field or not.
+ #
+ # * <tt>Default:</tt> true
+ # * <tt>Accepts:</tt> Boolean
+ def validate_email_field(value = nil)
+ config(:validate_email_field, value, true)
+ end
+ alias_method :validate_email_field=, :validate_email_field
+
+ # A hash of options for the validates_length_of call for the email field. Allows you to change this however you want.
+ #
+ # * <tt>Default:</tt> {:within => 6..100}
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
+ def validates_length_of_email_field_options(value = nil)
+ config(:validates_length_of_email_field_options, value, {:within => 6..100})
+ end
+ alias_method :validates_length_of_email_field_options=, :validates_length_of_email_field_options
+
+ # A hash of options for the validates_format_of call for the email field. Allows you to change this however you want.
+ #
+ # * <tt>Default:</tt> {:with => email_regex, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")}
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_format_of
+ def validates_format_of_email_field_options(value = nil)
+ config(:validates_format_of_email_field_options, value, {:with => email_regex, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")})
+ end
+ alias_method :validates_format_of_email_field_options=, :validates_format_of_email_field_options
+
+ private
+ def email_regex
+ return @email_regex if @email_regex
+ email_name_regex = '[\w\.%\+\-]+'
+ domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
+ 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)'
+ @email_regex = /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
+ end
+ end
+
+ # All methods relating to the email field
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ if aaa_config.validate_email_field
+ if aaa_config.email_field
+ validates_length_of aaa_config.email_field, aaa_config.validates_length_of_email_field_options
+ validates_format_of aaa_config.email_field, aaa_config.validates_format_of_email_field_options
+ validates_uniqueness_of aaa_config.email_field, :scope => aaa_config.scope, :if => "#{aaa_config.email_field}_changed?".to_sym
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
View
42 lib/authlogic/acts_as_authentic/logged_in_status.rb
@@ -0,0 +1,42 @@
+module Authlogic
+ module ActsAsAuthentic
+ # Since web applications are stateless there is not sure fire way to tell if a user is logged in or not,
+ # from the database perspective. The best way to do this is to provide a "timeout" based on inactivity.
+ # So if that user is inactive for a certain amount of time we assume they are logged out. That's what this
+ # module is all about.
+ module LoggedInStatus
+ # All configuration for the logged in status feature set.
+ module Config
+ # The timeout to determine when a user is logged in or not.
+ #
+ # * <tt>Default:</tt> 10.minutes
+ # * <tt>Accepts:</tt> Fixnum
+ def logged_in_timeout(value = nil)
+ config(:logged_in_timeout, (!value.nil? && value.to_i) || value, 10.minutes.to_i)
+ end
+ alias_method :logged_in_timeout=, :logged_in_timeout
+ end
+
+ # All methods for the logged in status feature seat.
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ named_scope :logged_in, lambda { {:conditions => ["last_request_at > ?", aaa_config.logged_in_timeout.seconds.ago]} }
+ named_scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", aaa_config.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 > aaa_config.logged_in_timeout.seconds.ago
+ end
+
+ # Opposite of logged_in?
+ def logged_out?
+ !logged_in?
+ end
+ end
+ end
+ end
+end
View
60 lib/authlogic/acts_as_authentic/login.rb
@@ -0,0 +1,60 @@
+module Authlogic
+ module ActsAsAuthentic
+ # Handles everything related to the login field.
+ module Login
+ # Confguration for the login field.
+ module Config
+ # The name of the login field in the database.
+ #
+ # * <tt>Default:</tt> :login or :username, if they exist
+ # * <tt>Accepts:</tt> Symbol
+ def login_field(value = nil)
+ config(:login_field, value, first_column_to_exist(nil, :login, :username))
+ end
+ alias_method :login_field=, :login_field
+
+ # Whether or not the validate the login field
+ #
+ # * <tt>Default:</tt> true
+ # * <tt>Accepts:</tt> Boolean
+ def validate_login_field(value = nil)
+ config(:validate_login_field, value, true)
+ end
+ alias_method :validate_login_field=, :validate_login_field
+
+ # A hash of options for the validates_length_of call for the login field. Allows you to change this however you want.
+ #
+ # * <tt>Default:</tt> {:within => 6..100}
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
+ def validates_length_of_login_field_options(value = nil)
+ config(:validates_length_of_login_field_options, value, {:within => 6..100})
+ end
+ alias_method :validates_length_of_login_field_options=, :validates_length_of_login_field_options
+
+ # A hash of options for the validates_format_of call for the email field. Allows you to change this however you want.
+ #
+ # * <tt>Default:</tt> {:with => /\A\w[\w\.\-_@ ]+\z/, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")}
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_format_of
+ def validates_format_of_login_field_options(value = nil)
+ config(:validates_format_of_login_field_options, value, {:with => /\A\w[\w\.\-_@ ]+\z/, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")})
+ end
+ alias_method :validates_format_of_login_field_options=, :validates_format_of_login_field_options
+ end
+
+ # All methods relating to the login field
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ if aaa_config.validate_login_field
+ if aaa_config.login_field
+ validates_length_of aaa_config.login_field, aaa_config.validates_length_of_login_field_options
+ validates_format_of aaa_config.login_field, aaa_config.validates_format_of_login_field_options
+ validates_uniqueness_of aaa_config.login_field, :scope => aaa_config.scope, :if => "#{aaa_config.login_field}_changed?".to_sym
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
View
18 lib/authlogic/acts_as_authentic/magic_columns.rb
@@ -0,0 +1,18 @@
+module Authlogic
+ module ActsAsAuthentic
+ # Magic columns are like ActiveRecord's created_at and updated_at columns. They are "magically" maintained for
+ # you. Authlogic has the same thing, but these are maintained on the session side. Please see Authlogic::Session::MagicColumns
+ # for more details. This module merely adds validations for the magic columns if they exist.
+ module MagicColumns
+ # Methods relating to the magic columns
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ validates_numericality_of :login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
+ validates_numericality_of :failed_login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("failed_login_count")
+ end
+ end
+ end
+ end
+ end
+end
View
189 lib/authlogic/acts_as_authentic/password.rb
@@ -0,0 +1,189 @@
+module Authlogic
+ module ActsAsAuthentic
+ # This module has a lot of neat functionality. It is responsible for encrypting your password, salting it, and verifying it.
+ # It can also help you transition to a new encryption algorithm. See the Config sub module for configuration options.
+ module Password
+ module Config
+ # The name of the crypted_password field in the database.
+ #
+ # * <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))
+ end
+ alias_method :crypted_password_field=, :crypted_password_field
+
+ # The name of the password_salt field in the database.
+ #
+ # * <tt>Default:</tt> :password_salt, :pw_salt, :salt
+ # * <tt>Accepts:</tt> Symbol
+ def password_salt_field(value = nil)
+ config(:password_salt_field, value, first_column_to_exist(:password_salt, :pw_salt, :salt))
+ end
+ alias_method :password_salt_field=, :password_salt_field
+
+ # Whether or not to validate the password field.
+ #
+ # * <tt>Default:</tt> true
+ # * <tt>Accepts:</tt> Boolean
+ def validate_password_field(value = nil)
+ config(:validate_password_field, value, true)
+ end
+ alias_method :validate_password_field=, :validate_password_field
+
+ # A hash of options for the validates_confirmation_of call for the password field. Allows you to change this however you want.
+ #
+ # * <tt>Default:</tt> {:minimum => 4, :if => "#{password_salt_field}_changed?".to_sym}
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_confirmation_of
+ def validates_confirmation_of_password_field_options(value = nil)
+ config(:validates_confirmation_of_password_field_options, value, {:minimum => 4, :if => "#{password_salt_field}_changed?".to_sym})
+ end
+ alias_method :validates_confirmation_of_password_field_options=, :validates_confirmation_of_password_field_options
+
+ # A hash of options for the validates_length_of call for the password_confirmation field. Allows you to change this however you want.
+ #
+ # * <tt>Default:</tt> {:minimum => 4, :if => :require_password_confirmation?}
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
+ def validates_length_of_password_confirmation_field_options(value = nil)
+ config(:validates_length_of_password_confirmation_field_options, value, {:minimum => 4, :if => :require_password_confirmation?})
+ end
+ alias_method :validates_length_of_password_confirmation_field_options=, :validates_length_of_password_confirmation_field_options
+
+ # The class you want to use to encrypt and verify your encrypted passwords. See the Authlogic::CryptoProviders module for more info
+ # on the available methods and how to create your own.
+ #
+ # * <tt>Default:</tt> CryptoProviders::Sha512
+ # * <tt>Accepts:</tt> Class
+ def crypto_provider(value = nil)
+ config(:crypto_provider, value, CryptoProviders::Sha512)
+ end
+ alias_method :crypto_provider=, :crypto_provider
+
+ # Let's say you originally encrypted your passwords with Sha1. Sha1 is starting to join the party with MD5 and you want to switch
+ # to something stronger. No problem, just specify your new and improved algorithm with the crypt_provider option and then let
+ # Authlogic know you are transitioning from Sha1 using this option. Authlogic will take care of everything, including transitioning
+ # your users to the new algorithm. The next time a user logs in, they will be granted access using the old algorithm and their
+ # password will be resaved with the new algorithm. All new users will obviously use the new algorithm as well.
+ #
+ # Lastly, if you want to transition again, you can pass an array of crypto providers. So you can transition from as many algorithms
+ # as you want.
+ #
+ # * <tt>Default:</tt> nil
+ # * <tt>Accepts:</tt> Class or Array
+ def transition_from_crypto_providers(value = nil)
+ config(:transition_from_crypto_providers, (!value.nil? && [value].flatten.compact) || value, [])
+ end
+ alias_method :transition_from_crypto_providers=, :transition_from_crypto_providers
+ end
+
+ # Callbacks / hooks to allow other modules to modify the behavior of this module.
+ module Callbacks
+ METHODS = [
+ "before_password_set", "after_password_set",
+ "before_password_verification", "after_password_verification"
+ ]
+
+ def self.included(klass)
+ klass.define_callbacks *METHODS
+ end
+
+ private
+ METHODS.each do |method|
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def #{method}
+ run_callbacks(:#{method}) { |result, object| result == false }
+ end
+ end_eval
+ end
+ end
+
+ # The methods related to the password field.
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ if aaa_config.validate_password_field
+ validates_confirmation_of :password, aaa_config.validates_confirmation_of_password_field_options
+ validates_length_of :password_confirmation, aaa_config.validates_length_of_password_confirmation_field_options
+ end
+ end
+ end
+
+ # 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 pass.blank?
+ before_password_set
+ @password = pass
+ send("#{aaa_config.password_salt_field}=", Authlogic::Random.friendly_token)
+ send("#{aaa_config.crypted_password_field}=", aaa_config.crypto_provider.encrypt(*encrypt_arguments(@password, aaa_config.act_like_restful_authentication ? :restful_authentication : nil)))
+ 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(aaa_config.crypted_password_field).blank? || send(aaa_config.password_salt_field).blank?
+
+ before_password_verification
+
+ crypto_providers = [aaa_config.crypto_provider] + aaa_config.transition_from_crypto_providers
+ crypto_providers.each_with_index do |encryptor, index|
+ # The arguments_type of for the transitioning from restful_authentication
+ arguments_type = (aaa_config.act_like_restful_authentication && index == 0) ||
+ (aaa_config.transition_from_restful_authentication && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
+ :restful_authentication : nil
+
+ if encryptor.matches?(send(aaa_config.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(aaa_config.crypted_password_field)))
+ self.password = attempted_password
+ save(false)
+ end
+
+ after_password_verification
+
+ return true
+ end
+ 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 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)
+ case arguments_type
+ when :restful_authentication
+ [REST_AUTH_SITE_KEY, send(aaa_config.password_salt_field), raw_password, REST_AUTH_SITE_KEY]
+ else
+ [raw_password, send(aaa_config.password_salt_field)]
+ end
+ end
+
+ def require_password_confirmation?
+ new_record? || send("#{aaa_config.password_salt_field}_changed?") || send(aaa_config.crypted_password_field).blank?
+ end
+ end
+ end
+ end
+end
View
91 lib/authlogic/acts_as_authentic/perishable_token.rb
@@ -0,0 +1,91 @@
+module Authlogic
+ module ActsAsAuthentic
+ # This provides a handy token that is "perishable". Meaning the token is only good for a certain amount of time. This is perfect for
+ # resetting password, confirming accounts, etc. Typically during these actions you send them this token in via their email. Once they
+ # use the token and do what they need to do, that token should expire. Don't worry about maintaining this, changing it, or expiring it
+ # yourself. Authlogic does all of this for you. See the sub modules for all of the tools Authlogic provides to you.
+ module PerishableToken
+ # Change how the perishable token works.
+ module Config
+ # When using the find_using_perishable_token method the token can expire. If the token is expired, no
+ # record will be returned. Use this option to specify how long the token is valid for.
+ #
+ # * <tt>Default:</tt> 10.minutes
+ # * <tt>Accepts:</tt> Fixnum
+ def perishable_token_valid_for(value = nil)
+ config(:perishable_token_valid_for, (!value.nil? && value.to_i) || value, 10.minutes.to_i)
+ end
+ alias_method :perishable_token_valid_for=, :perishable_token_valid_for
+
+ # Authlogic tries to expire and change the perishable token as much as possible, without comprising
+ # it's purpose. This is for security reasons. If you want to manage it yourself, you can stop
+ # Authlogic from getting your in way by setting this to true.
+ #
+ # * <tt>Default:</tt> false
+ # * <tt>Accepts:</tt> Boolean
+ def disable_perishable_token_maintenance(value = nil)
+ config(:disable_perishable_token_maintenance, value, false)
+ end
+ alias_method :disable_perishable_token_maintenance=, :disable_perishable_token_maintenance
+ end
+
+ # All methods relating to the perishable token.
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ extend ClassMethods
+ include InstanceMethods
+
+ validates_uniqueness_of :perishable_token, :if => :perishable_token_changed?
+ before_save :reset_perishable_token, :unless => :disable_perishable_token_maintenance?
+ end
+ end
+
+ # Class level methods for the perishable token
+ module ClassMethods
+ # Use this methdo to find a record with a perishable token. This method does 2 things for you:
+ #
+ # 1. It ignores blank tokens
+ # 2. It enforces the perishable_token_valid_for configuration option.
+ #
+ # If you want to use a different timeout value, just pass it as the second parameter:
+ #
+ # User.find_using_perishable_token(token, 1.hour)
+ def find_using_perishable_token(token, age = aaa_config.perishable_token_valid_for)
+ return if token.blank?
+ age = age.to_i
+
+ conditions_sql = "perishable_token = ?"
+ conditions_subs = [token]
+
+ if column_names.include?("updated_at") && age > 0
+ conditions_sql += " and updated_at > ?"
+ conditions_subs << age.seconds.ago
+ end
+
+ find(:first, :conditions => [conditions_sql, *conditions_subs])
+ end
+ end
+
+ # Instance level methods for the perishable token.
+ module InstanceMethods
+ # Resets the perishable token to a random friendly token.
+ def reset_perishable_token
+ self.perishable_token = Random.friendly_token
+ end
+
+ # Same as reset_perishable_token, but then saves the record afterwards.
+ def reset_perishable_token!
+ reset_perishable_token
+ save_without_session_maintenance(false)
+ end
+
+ # A convenience method based on the disable_perishable_token_maintenance configuration option.
+ def disable_perishable_token_maintenance?
+ aaa_config.disable_perishable_token_maintenance == true
+ end
+ end
+ end
+ end
+ end
+end
View
60 lib/authlogic/acts_as_authentic/persistence_token.rb
@@ -0,0 +1,60 @@
+module Authlogic
+ module ActsAsAuthentic
+ # Maintains the persistence token, the token responsible for persisting sessions. This token
+ # gets stores in the session and the cookie.
+ module PersistenceToken
+ # Methods for the persistence token.
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ extend ClassMethods
+ include InstanceMethods
+
+ after_password_set :reset_persistence_token
+ after_password_verification :reset_persistence_token!, :if => :reset_persistence_token?
+
+ validates_presence_of :persistence_token
+ validates_uniqueness_of :persistence_token, :if => :persistence_token_changed?
+
+ before_validation :reset_persistence_token, :if => :reset_persistence_token?
+ end
+ end
+
+ # Class level methods for the persistence token.
+ module ClassMethods
+ # Resets ALL persistence tokens in the database, which will require all users to reauthenticate.
+ def 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
+
+ # Instance level methods for the persistence token.
+ module InstanceMethods
+ # Resets the persistence_token field to a random hex value.
+ def reset_persistence_token
+ self.persistence_token = Authlogic::Random.hex_token
+ end
+
+ # Same as reset_persistence_token, but then saves the record.
+ def reset_persistence_token!
+ reset_persistence_token
+ save_without_session_maintenance(false)
+ end
+ alias_method :forget!, :reset_persistence_token!
+
+ private
+ def reset_persistence_token?
+ persistence_token.blank?
+ end
+ end
+ end
+ end
+ end
+end
View
42 lib/authlogic/acts_as_authentic/restful_authentication.rb
@@ -0,0 +1,42 @@
+module Authlogic
+ module ActsAsAuthentic
+ # This module is responsible for transitioning existing applications from the restful_authentication plugin.
+ module RestfulAuthentication
+ module Config
+ # Switching an existing app to Authlogic from restful_authentication? No problem, just set this true and your users won't know
+ # anything changed. From your database perspective nothing will change at all. Authlogic will continue to encrypt passwords
+ # just like restful_authentication, so your app won't skip a beat. Although, might consider transitioning your users to a newer
+ # and stronger algorithm. Checkout the transition_from_restful_authentication option.
+ #
+ # * <tt>Default:</tt> false
+ # * <tt>Accepts:</tt> Boolean
+ def act_like_restful_authentication(value = nil)
+ r = config(:act_like_restful_authentication, value, false)
+ set_restful_authentication_config if value
+ r
+ end
+ alias_method :act_like_restful_authentication=, :act_like_restful_authentication
+
+ # This works just like act_like_restful_authentication except that it will start transitioning your users to the algorithm you
+ # specify with the crypto provider option. The next time they log in it will resave their password with the new algorithm
+ # and any new record will use the new algorithm as well.
+ def transition_from_restful_authentication(value = nil)
+ r = config(:transition_from_restful_authentication, value, false)
+ set_restful_authentication_config if value
+ r
+ end
+ alias_method :transition_from_restful_authentication=, :transition_from_restful_authentication
+
+ private
+ def set_restful_authentication_config
+ crypto_provider_key = act_like_restful_authentication ? :crypto_provider : :transition_from_crypto_providers
+ self.send("#{crypto_provider_key}=", CryptoProviders::Sha1)
+ if !defined?(::REST_AUTH_SITE_KEY) || ::REST_AUTH_SITE_KEY.nil?
+ class_eval("::REST_AUTH_SITE_KEY = nil") if !defined?(::REST_AUTH_SITE_KEY)
+ CryptoProviders::Sha1.stretches = 1
+ end
+ end
+ end
+ end
+ end
+end
View
24 lib/authlogic/acts_as_authentic/scope.rb
@@ -0,0 +1,24 @@
+module Authlogic
+ module ActsAsAuthentic
+ # Allows you to scope everything to specific fields.
+ module Scope
+ # All configuration for the scope feature.
+ module Config
+ # Allows you to scope everything to specific field(s). Works just like validates_uniqueness_of.
+ # For example, let's say a user belongs to a company, and you want to scope everything to the
+ # company:
+ #
+ # acts_as_authentic do |c|
+ # c.scope = :company_id
+ # end
+ #
+ # * <tt>Default:</tt> nil
+ # * <tt>Accepts:</tt> Symbol or Array of symbols
+ def scope(value = nil)
+ config(:scope, value)
+ end
+ alias_method :scope=, :scope
+ end
+ end
+ end
+end
View
113 lib/authlogic/acts_as_authentic/session_maintenance.rb
@@ -0,0 +1,113 @@
+module Authlogic
+ module ActsAsAuthentic
+ # This is one of my favorite features that I think is pretty cool. It's things like this that make a library great
+ # and let you know you are on the right track.
+ #
+ # Just to clear up any confusion, Authlogic stores both the record id and the persistence token in the session.
+ # Why? So stale sessions can not be persisted. It stores the id so it can quickly find the record, and the
+ # persistence token to ensure no sessions are stale. So if the persistence token changes, the user muct log
+ # back in.
+ #
+ # Well, the persistence token changes with the password. What happens if the user changes his own password?
+ # He shouldn't have to log back in, he's the one that made the change.
+ #
+ # That being said, wouldn't it be nice if their session and cookie information was automatically updated?
+ # Instead of cluttering up your controller with redundant session code. The same thing goes for new
+ # registrations.
+ #
+ # That's what this module is all about. This will automatically maintain the cookie and session values as
+ # records are saved.
+ module SessionMaintenance
+ module Config
+ # As you may know, authlogic sessions can be separate by id (See Authlogic::Session::Base#id). You can
+ # specify here what session ids you want auto maintained. By default it is the main session, which has
+ # an id of nil.
+ #
+ # * <tt>Default:</tt> [nil]
+ # * <tt>Accepts:</tt> Array
+ def session_ids(value = nil)
+ config(:session_ids, value, [nil])
+ end
+ alias_method :session_ids=, :session_ids
+
+ # The name of the associated session class. This is inferred by the name of the model.
+ #
+ # * <tt>Default:</tt> "#{klass.name}Session".constantize
+ # * <tt>Accepts:</tt> Class
+ def session_class(value = nil)
+ config(:session_class, value, "#{klass.name}Session".constantize)
+ end
+ alias_method :session_class=, :session_class
+ end
+
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ before_save :get_session_information, :if => :update_sessions?
+ before_save :maintain_sessions, :if => :update_sessions?
+ attr_accessor
+ end
+ end
+
+ # Save the record and skip session maintenance all together.
+ def save_without_session_maintenance(*args)
+ self.skip_session_maintenance = true
+ result = save(*args)
+ self.skip_session_maintenance = false
+ result
+ end
+
+ private
+ def skip_session_maintenance=(value)
+ @skip_session_maintenance = value
+ end
+
+ def skip_session_maintenance
+ @skip_session_maintenance ||= false
+ end
+
+ def update_sessions?
+ !skip_session_maintenance && aaa_config.session_class.activated? && !aaa_config.session_ids.blank? && persistence_token_changed?
+ end
+
+ def get_session_information
+ # Need to determine if we are completely logged out, or logged in as another user
+ @_sessions = []
+
+ aaa_config.session_ids.each do |session_id|
+ session = aaa_config.session_class.find(session_id, self)
+ @_sessions << session if session && session.record
+ end
+ end
+
+ def maintain_sessions
+ if @_sessions.empty?
+ create_session
+ else
+ 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 = aaa_config.session_ids.first
+ aaa_config.session_class.create(*[self, self, session_id].compact)
+
+ return true
+ end
+
+ def update_sessions
+ # We found sessions above, let's update them with the new info
+ @_sessions.each do |stale_session|
+ next if stale_session.record != self
+ stale_session.unauthorized_record = self
+ stale_session.save
+ end
+
+ return true
+ end
+ end
+ end
+ end
+end
View
44 lib/authlogic/acts_as_authentic/single_access_token.rb
@@ -0,0 +1,44 @@
+module Authlogic
+ module ActsAsAuthentic
+ # This module is responsible for maintaining the single_access token. For more information the single access token and how to use it,
+ # see the Authlogic::Session::Params module.
+ module SingleAccessToken
+ module Config
+ def change_single_access_token_with_password(value = nil)
+ config(:change_single_access_token_with_password, value, false)
+ end
+ alias_method :change_single_access_token_with_password=, :change_single_access_token_with_password
+ end
+
+ module Methods
+ def self.included(klass)
+ klass.class_eval do
+ validates_uniqueness_of :single_access_token, :if => :single_access_token_changed?
+ before_validation :reset_single_access_token, :if => :reset_single_access_token?
+ after_password_set :reset_single_access_token, :if => :change_single_access_token_with_password?
+ end
+ end
+
+ # Resets the single_access_token to a random friendly token.
+ def reset_single_access_token
+ self.single_access_token = Authlogic::Random.friendly_token
+ end
+
+ # same as reset_single_access_token, but then saves the record.
+ def reset_single_access_token!
+ reset_single_access_token
+ save_without_session_maintenance
+ end
+
+ protected
+ def reset_single_access_token?
+ single_access_token.blank?
+ end
+
+ def change_single_access_token_with_password?
+ aaa_config.change_single_access_token_with_password
+ end
+ end
+ end
+ end
+end
View
16 ...thlogic/session/authenticates_many_association.rb → lib/authlogic/authenticates_many/association.rb
@@ -1,15 +1,19 @@
module Authlogic
- module Session
- # = Authenticates Many Association
- #
- # An object of this class is used as a proxy for the authenticates_many relationship. It basically allows you to "save" scope details and call them on an object, which allows you to do the following:
+ module AuthenticatesMany
+ # An object of this class is used as a proxy for the authenticates_many relationship. It basically allows you to "save" scope details
+ # and call them on an object, which allows you to do the following:
#
# @account.user_sessions.new
# @account.user_sessions.find
# # ... etc
#
- # You can call all of the class level methods off of an object with a saved scope, so that calling the above methods scopes the user sessions down to that specific account.
- class AuthenticatesManyAssociation # :nodoc:
+ # You can call all of the class level methods off of an object with a saved scope, so that calling the above methods scopes the user
+ # sessions down to that specific account. To implement this via ActiveRecord do something like:
+ #
+ # class User < ActiveRecord::Base
+ # authenticates_many :user_sessions
+ # end
+ class Association
attr_accessor :klass, :find_options, :id
def initialize(klass, find_options, id)
View
54 lib/authlogic/authenticates_many/base.rb
@@ -0,0 +1,54 @@
+module Authlogic
+ # This allows you to scope your authentication. For example, let's say all users belong to an account, you want to make sure only users
+ # that belong to that account can actually login into that account. Simple, just do:
+ #
+ # class Account < ActiveRecord::Base
+ # authenticates_many :user_sessions
+ # end
+ #
+ # Now you can scope sessions just like everything else in ActiveRecord:
+ #
+ # @account.user_sessions.new(*args)
+ # @account.user_sessions.create(*args)
+ # @account.user_sessions.find(*args)
+ # # ... etc
+ #
+ # For more information checkout the authenticates_many method in the Base sub module.
+ module AuthenticatesMany
+ module Base
+ # Allows you set essentially set up a relationship with your sessions. See module definition above for more details.
+ #
+ # === Options
+ #
+ # * <tt>session_class:</tt> default: "#{name}Session",
+ # This is the related session class.
+ #
+ # * <tt>relationship_name:</tt> default: options[:session_class].klass_name.underscore.pluralize,
+ # This is the name of the relationship you want to use to scope everything. For example an Account has many Users. There should be a relationship
+ # called :users that you defined with a has_many. The reason we use the relationship is so you don't have to repeat yourself. The relatonship
+ # could have all kinds of custom options. So instead of repeating yourself we essentially use the scope that the relationship creates.
+ #
+ # * <tt>find_options:</tt> default: nil,
+ # By default the find options are created from the relationship you specify with :relationship_name. But if you want to override this and
+ # manually specify find_options you can do it here. Specify options just as you would in ActiveRecord::Base.find.
+ #
+ # * <tt>scope_cookies:</tt> default: false
+ # By the nature of cookies they scope theirself if you are using subdomains to access accounts. If you aren't using subdomains you need to have
+ # separate cookies for each account, assuming a user is logging into mroe than one account. Authlogic can take care of this for you by
+ # prefixing the name of the cookie and sessin with the model id. You just need to tell Authlogic to do this by passing this option.
+ def authenticates_many(name, options = {})
+ options[:session_class] ||= name.to_s.classify.constantize
+ options[:relationship_name] ||= options[:session_class].klass_name.underscore.pluralize
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def #{name}
+ find_options = #{options[:find_options].inspect} || #{options[:relationship_name]}.scope(:find)
+ find_options.delete_if { |key, value| ![:conditions, :include, :joins].include?(key.to_sym) || value.nil? }
+ @#{name} ||= Authlogic::AuthenticatesMany::Association.new(#{options[:session_class]}, find_options, #{options[:scope_cookies] ? "self.class.model_name.underscore + '_' + self.send(self.class.primary_key).to_s" : "nil"})
+ end
+ end_eval
+ end
+ end
+
+ ::ActiveRecord::Base.extend(Base) if defined?(::ActiveRecord)
+ end
+end
View
5 lib/authlogic/controller_adapters/abstract_adapter.rb
@@ -1,8 +1,7 @@
module Authlogic
module ControllerAdapters # :nodoc:
- # = Abstract Adapter
- #
- # Allows you to use Authlogic in any framework you want, not just rails. See the RailsAdapter or MerbAdapter for an example of how to adapt Authlogic to work with your framework.
+ # Allows you to use Authlogic in any framework you want, not just rails. See the RailsAdapter or MerbAdapter
+ # for an example of how to adapt Authlogic to work with your framework.
class AbstractAdapter
attr_accessor :controller
View
4 lib/authlogic/controller_adapters/merb_adapter.rb
@@ -1,12 +1,8 @@
module Authlogic
module ControllerAdapters
- # = Merb Adapter
- #
# Adapts authlogic to work with merb. The point is to close the gap between what authlogic expects and what the merb controller object
# provides. Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite, etc.
class MerbAdapter < AbstractAdapter
- # = Merb Implementation
- #
# Lets Authlogic know about the controller object via a before filter, AKA "activates" authlogic.
module MerbImplementation
def self.included(klass) # :nodoc:
View
4 lib/authlogic/controller_adapters/rails_adapter.rb
@@ -1,7 +1,5 @@
module Authlogic
module ControllerAdapters
- # = Rails Adapter
- #
# Adapts authlogic to work with rails. The point is to close the gap between what authlogic expects and what the rails controller object
# provides. Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite, etc.
class RailsAdapter < AbstractAdapter
@@ -22,8 +20,6 @@ def request_content_type
request.format.to_s
end
- # = Rails Implementation
- #
# Lets Authlogic know about the controller object via a before filter, AKA "activates" authlogic.
module RailsImplementation
def self.included(klass) # :nodoc:
View
2  lib/authlogic/crypto_providers/aes256.rb
@@ -2,8 +2,6 @@
module Authlogic
module CryptoProviders
- # = AES256
- #
# 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.
# In an initializer, or before your application initializes, you should do the following:
#
View
2  lib/authlogic/crypto_providers/bcrypt.rb
@@ -5,8 +5,6 @@
module Authlogic
module CryptoProviders
- # = Bcrypt
- #
# For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear launch codes you might want to consier BCrypt. This is an extremely
# secure hashing algorithm, mainly because it is slow. A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
# password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this, generating a password takes exponentially longer than any
View
2  lib/authlogic/crypto_providers/sha1.rb
@@ -2,8 +2,6 @@
module Authlogic
module CryptoProviders
- # = Sha1
- #
# 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.
# Please use any other provider offered by Authlogic.
class Sha1
View
4 lib/authlogic/crypto_providers/sha512.rb
@@ -1,9 +1,7 @@
require "digest/sha2"
module Authlogic
- # = Crypto Providers
- #
- # The acts_as_authentic method allows you to pass a :crypto_provider option. This allows you to use any type of encryption you like.
+ # The acts_as_authentic method has a crypto_provider option. This allows you to use any type of encryption you like.
# Just create a class with a class level encrypt and matches? method. See example below.
#
# === Example
View
4 lib/authlogic/i18n.rb
@@ -1,6 +1,4 @@
module Authlogic
- # I18n
- #
# This class allows any message in Authlogic to use internationalization. In earlier versions of Authlogic each message was translated via configuration.
# This cluttered up the configuration and cluttered up Authlogic. So all translation has been extracted out into this class. Now all messages pass through
# this class, making it much easier to implement in I18n library / plugin you want. Use this as a layer that sits between Authlogic and whatever I18n
@@ -40,7 +38,7 @@ module Authlogic
# not_active: Your account is not active
# not_confirmed: Your account is not confirmed
# not_approved: Your account is not approved
- # blank_record: You can not login with a blank record
+ # no_authentication_attempted: No authentication method was attempted
class I18n
class << self
# All message translation is passed to this method. The first argument is the key for the message. The second is options, see the rails I18n library for a list of options used.
View
22 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/base.rb
@@ -1,22 +0,0 @@
-module Authlogic
- module ORMAdapters # :nodoc:
- module ActiveRecordAdapter # :nodoc:
- # = Acts As Authentic
- #
- # Provides the acts_as_authentic method to include in your models to help with authentication. You can include it as follows:
- #
- # class User < ActiveRecord::Base
- # acts_as_authentic :option => "value"
- # end
- #
- # For a list of configuration options see the ActsAsAuthentic::Config module.
- module ActsAsAuthentic
- # All logic for this method is split up into sub modules. See sub modules for more details.
- def acts_as_authentic(options = {})
- end
- end
- end
- end
-end
-
-ActiveRecord::Base.extend Authlogic::ORMAdapters::ActiveRecordAdapter::ActsAsAuthentic
View
238 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/config.rb
@@ -1,238 +0,0 @@
-module Authlogic
- module ORMAdapters
- module ActiveRecordAdapter
- module ActsAsAuthentic
- # = Config
- #
- # Allows you to set various configuration when calling acts_as_authentic. Pass your configuration like the following:
- #
- # class User < ActiveRecord::Base
- # acts_as_authentic :my_option => "my value"
- # end
- #
- # === Class Methods
- #
- # * <tt>acts_as_authentic_config</tt> - returns a hash of the acts_as_authentic configuration, including the defaults
- #
- # === General Options
- #
- # * <tt>session_class</tt> - default: "#{name}Session",
- # This is the related session class. A lot of the configuration will be based off of the configuration values of this class.
- #
- # * <tt>crypto_provider</tt> - default: Authlogic::CryptoProviders::Sha512,
- # This is the class that provides your encryption. By default Authlogic provides its own crypto provider that uses Sha512 encrypton.
- #
- # * <tt>transition_from_crypto_provider</tt> - default: nil,
- # This will transition your users to a new encryption algorithm. Let's say you are using Sha1 and you want to transition to Sha512. Just set the
- # :crypto_provider option to Authlogic::CryptoProviders::Sha512 and then set this option to Authlogic::CryptoProviders::Sha1. Every time a user
- # logs in their password will be resaved with the new algorithm and all new registrations will use the new algorithm as well.
- #
- # * <tt>act_like_restful_authentication</tt> - default: false,
- # If you are migrating from restful_authentication you will want to set this to true, this way your users will still be able to log in and it will seems as
- # if nothing has changed. If you don't do this none of your users will be able to log in. If you are starting a new project I do not recommend enabling this
- # as the password encryption algorithm used in restful_authentication (Sha1) is not as secure as the one used in authlogic (Sha512). IF you REALLY want to be secure
- # checkout Authlogic::CryptoProviders::BCrypt.
- #
- # * <tt>transition_from_restful_authentication</tt> - default: false,
- # This works just like :transition_from_crypto_provider, but it makes some special exceptions so that your users will transition from restful_authentication, since
- # restful_authentication does things a little different than Authlogic.
- #
- # * <tt>login_field</tt> - default: :login, :username, or :email, depending on which column is present, if none are present defaults to :login
- # The name of the field used for logging in. Only specify if you aren't using any of the defaults.
- #
- # * <tt>login_field_type</tt> - default: options[:login_field] == :email ? :email : :login,
- # Tells authlogic how to validation the field, what regex to use, etc. If the field name is email it will automatically use :email,
- # otherwise it uses :login.
- #
- # * <tt>password_field</tt> - default: :password,
- # This is the name of the field to set the password, *NOT* the field the encrypted password is stored. Defaults the what the configuration
- #
- # * <tt>crypted_password_field</tt> - default: :crypted_password, :encrypted_password, :password_hash, :pw_hash, depends on which columns are present, if none are present defaults to nil
- # The name of the database field where your encrypted password is stored.
- #
- # * <tt>password_salt_field</tt> - default: :password_salt, :pw_salt, or :salt, depending on which column is present, defaults to :password_salt if none are present,
- # This is the name of the field in your database that stores your password salt.
- #
- # * <tt>email_field</tt> - default: :email, depending on if it is present, if :email is not present defaults to nil
- # The name of the field used to store the email address. Only specify this if you arent using this as your :login_field.
- #
- # * <tt>single_access_token_field</tt> - default: :single_access_token, :feed_token, or :feeds_token, depending on which column is present, if none are present defaults to nil
- # This is the name of the field to login with single access, mainly used for private feed access. Only specify if the name of the field is different
- # then the defaults. See the "Single Access" section in the README for more details on how single access works.
- #
- # * <tt>change_single_access_token_with_password</tt> - default: false,
- # When a user changes their password do you want the single access token to change as well? That's what this configuration option is all about.
- #
- # * <tt>perishable_token_field</tt> - default: :perishable_token, :password_reset_token, :pw_reset_token, :reset_password_token, or :reset_pw_token, depending on which column is present, if none are present defaults to nil
- # This is the name of the field in your database that stores your perishable token. The token you should use to confirm your users or allow a password reset. Authlogic takes care
- # of maintaining this for you and making sure it changes when needed. Use this token for whatever you want, but keep in mind it is temporary, hence the term "perishable".
- #
- # * <tt>perishable_token_valid_for</tt> - default: 10.minutes,
- # Authlogic gives you a sepcial method for finding records by the perishable token (see Authlogic::ORMAdapters::ActiveRecordAdapter::ActcsAsAuthentic::Perishability). In this method
- # it checks for the age of the token. If the token is older than whatever you specify here, a record will NOT be returned. This way the tokens are perishable, thus making this system much
- # more secure.
- #
- # * <tt>disable_perishble_token_maintenance</tt> - default: false,
- # Authlogic automatically maintains when to reset the perishable_token. This token should reset frequently because it is "perishable", but how frequent depends on your app.
- # By default it tries to reset this token as much as possible, which is done via a before_validation callback. If for some reason you want to maintain this yourself just
- # set this to true and use the reset_perishable_token and reset_perishable_token! methods to maintain it yourself.
- #
- # * <tt>persistence_token_field</tt> - default: :persistence_token, :remember_token, or :cookie_tokien, depending on which column is present,
- # defaults to :persistence_token if none are present,
- # This is the name of the field your persistence token is stored. The persistence token is a unique token that is stored in the users cookie and
- # session. This way you have complete control of when sessions expire and you don't have to change passwords to expire sessions. This also
- # ensures that stale sessions can not be persisted. By stale, I mean sessions that are logged in using an outdated password.
- #
- # * <tt>logged_in_timeout</tt> - default: 10.minutes,
- # This is 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 passed here they are assumed "logged out". This uses the last_request_at field, this field must be present for this option to take effect.
- #
- # * <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.
- #
- # === Validation Options
- #
- # * <tt>validate_fields</tt> - default: true,
- # Tells Authlogic if it should validate ANY of the fields: login_field, email_field, and password_field. If set to false, no validations will be set for any of these fields.
- #
- # * <tt>validate_login_field</tt> - default: true,
- # Tells authlogic if it should validate the :login_field. If set to false, no validations will be set for this field at all.
- #
- # * <tt>validate_email_field</tt> - default: true,
- # Tells Authlogic if it should validate the email field. If set to false, no validations will be set for this field at all.
- #
- # * <tt>validate_password_field</tt> - default: :password,
- # Tells authlogic if it should validate the :password_field. If set to false, no validations will be set for this field at all.
- #
- # * <tt>scope</tt> - default: nil,
- # This scopes validations. If all of your users belong to an account you might want to scope everything to the account. Just pass :account_id
- #
- # * <tt>validation_options</tt> - default: {},
- # Options to pass to ALL validations. These are the options ActiveRecord supplies with their validation methods, see the ActiveRecord documentation for more details.
- #
- # * <tt>login_field_validation_options</tt> - default: {},
- # The same as :validation_options but these are only applied to validations that pertain to the :login_field
- #
- # * <tt>login_field_validates_length_of_options</tt> - default: :login_field_type == :email ? {:within => 6..100} : {:within => 2..100},
- # These options are applied to the validates_length_of call for the :login_field
- #
- # * <tt>login_field_validates_format_of_options</tt> - default: :login_field_type == :email ? {:with => standard_email_regex, :message => "should look like an email address."} : {:with => standard_login_regex, :message => "should use only letters, numbers, spaces, and .-_@ please."},
- # These options are applied to the validates_format_of call for the :login_field
- #
- # * <tt>login_field_validates_uniqueness_of_options</tt> - default: {:allow_blank => true},
- # These options are applied to the validates_uniqueness_of call for the :login_field, the :allow_blank => true just prevents the error message when you have options login fields
- # such as an OpenID field. The other validations will make sure the field is not actaully blank.
- #
- # * <tt>password_field_validation_options</tt> - default: {},
- # The same as :validation_options but these are only applied to validations that pertain to the :password_field
- #
- # * <tt>password_field_validates_length_of_options</tt> - default: {:minimum => 4},
- # These options are applied to the validates_length_of call for the :password_field
- #
- # * <tt>password_field_validates_confirmation_of_options</tt> - default: {},
- # These options are applied to the validates_confirmation_of call for the :password_field
- #
- # * <tt>password_confirmation_field_validates_presence_of_options</tt> - default: {},
- # These options are applied to the validates_presence_of call for the :password_confirmation_field.
- #
- # * <tt>email_field_validation_options</tt> - default: {},
- # The same as :validation_options but these are only applied to validations that pertain to the :email_field
- #
- # * <tt>email_field_validates_length_of_options</tt> - default: same as :login_field if :login_field_type == :email,
- # These options are applied to the validates_length_of call for the :email_field
- #
- # * <tt>email_field_validates_format_of_options</tt> - default: same as :login_field if :login_field_type == :email,
- # These options are applied to the validates_format_of call for the :email_field
- #
- # * <tt>email_field_validates_uniqueness_of_options</tt> - default: same as :login_field if :login_field_type == :email,
- # These options are applied to the validates_uniqueness_of call for the :email_field
- module Config
- def acts_as_authentic_with_config(options = {})
- # Stop all configuration if the DB is not set up
- begin
- column_names
- rescue Exception
- return
- end
-
- # Base configuration
- options[:session_class] ||= "#{name}Session"
- options[:crypto_provider] ||= CryptoProviders::Sha512
- options[:login_field] ||= first_column_to_exist(:login, :username, :email)
- options[:login_field_type] ||= options[:login_field] == :email ? :email : :login
- options[:password_field] ||= :password
- options[:crypted_password_field] ||= first_column_to_exist(:crypted_password, :encrypted_password, :password_hash, :pw_hash)
- options[:password_salt_field] ||= first_column_to_exist(:password_salt, :pw_salt, :salt)
-
- options[:email_field] = first_column_to_exist(nil, :email) unless options.key?(:email_field)
- options[:email_field] = nil if options[:email_field] == options[:login_field]
- options[:persistence_token_field] ||= options[:remember_token_field] || first_column_to_exist(:persistence_token, :remember_token, :cookie_token)
- options[:single_access_token_field] ||= first_column_to_exist(nil, :single_access_token, :feed_token, :feeds_token)
- options[:perishable_token_field] ||= options[:password_reset_token_field] || first_column_to_exist(nil, :perishable_token, :password_reset_token, :pw_reset_token, :reset_password_token, :reset_pw_token, :activation_token)
- options[:perishable_token_valid_for] ||= 10.minutes
- options[:perishable_token_valid_for] = options[:perishable_token_valid_for].to_i
- options[:logged_in_timeout] ||= 10.minutes
- options[:logged_in_timeout] = options[:logged_in_timeout].to_i
- options[:session_ids] ||= [nil]
-
- # Validation configuration
- options[:validate_fields] = true unless options.key?(:validate_fields)
- options[:validate_login_field] = true unless options.key?(:validate_login_field)
- options[:validate_password_field] = true unless options.key?(:validate_password_field)
- options[:validate_email_field] = true unless options.key?(:validate_email_field)
-
- options[:validation_options] ||= {}
-
- [:login, :password, :email].each do |field_name|
- field_key = "#{field_name}_field_validation_options".to_sym
- options[field_key] = options[:validation_options].merge(options[field_key] || {})
-
- 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] || {})
- end
- end
-
- options[:password_confirmation_field_validates_presence_of_options] ||= {}
-
- if options[:scope]
- options[:login_field_validates_uniqueness_of_options][:scope] ||= options[:scope]
- options[:email_field_validates_uniqueness_of_options][:scope] ||= options[:scope]
- end
-
- if options[:act_like_restful_authentication] || options[:transition_from_restful_authentication]
- crypto_provider_key = options[:act_like_restful_authentication] ? :crypto_provider : :transition_from_crypto_provider
- options[crypto_provider_key] = CryptoProviders::Sha1
- if !defined?(REST_AUTH_SITE_KEY) || REST_AUTH_SITE_KEY.nil?
- class_eval("::REST_AUTH_SITE_KEY = nil") unless defined?(REST_AUTH_SITE_KEY)
- options[crypto_provider_key].stretches = 1
- end
- end
-
- options[:transition_from_crypto_provider] = [options[:transition_from_crypto_provider]].compact unless options[:transition_from_crypto_provider].is_a?(Array)
-
- cattr_accessor :acts_as_authentic_config
- self.acts_as_authentic_config = options
- acts_as_authentic_without_config(options)
- end
-
- def first_column_to_exist(*columns_to_check) # :nodoc:
- 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
- end
- end
- end
- end
- end
-end
-
-ActiveRecord::Base.class_eval do
- class << self
- include Authlogic::ORMAdapters::ActiveRecordAdapter::ActsAsAuthentic::Config
- alias_method_chain :acts_as_authentic, :config
- end
-end
View
152 lib/authlogic/orm_adapters/active_record_adapter/acts_as_authentic/credentials.rb
@@ -1,152 +0,0 @@
-module Authlogic
- module ORMAdapters
- module ActiveRecordAdapter
- module ActsAsAuthentic
- # = Credentials
- #
- # Handles any credential specific code, such as validating the login, encrpyting the password, etc.
- #
- # === Class Methods
- #
- # * <tt>friendly_unique_token</tt> - returns a random string of 20 alphanumeric characters. Used when resetting the password. This is a more user friendly token then a long Sha512 hash.
- #
- # === Instance Methods
- #
- # * <tt>{options[:password_field]}=(value)</tt> - encrypts a raw password and sets it to your crypted_password_field. Also sets the password_salt to a random token.
- # * <tt>valid_{options[:password_field]}?(password_to_check)</tt> - checks is the password is valid. The password passed must be the raw password, not encrypted.
- # * <tt>reset_{options[:password_field]}</tt> - resets the password using the friendly_unique_token class method
- # * <tt>reset_{options[:password_field]}!</tt> - calls reset_password and then saves the record
- module Credentials
- def acts_as_authentic_with_credentials(options = {})
- acts_as_authentic_without_credentials(options)
-
- if options[:validate_fields]
- email_name_regex = '[\w\.%\+\-]+'
- domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
- 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)'
- email_field_regex ||= /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
-
- if options[:validate_login_field]
- case options[:login_field_type]
- when :email
- validates_length_of options[:login_field], sanitize_validation_length_options({:within => 6..100}, options[:login_field_validates_length_of_options])
- validates_format_of options[:login_field], {:with => email_field_regex, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")}.merge(options[:login_field_validates_format_of_options])
- else
- validates_length_of options[:login_field], sanitize_validation_length_options({:within => 2..100}, options[:login_field_validates_length_of_options])
- validates_format_of options[:login_field], {:with => /\A\w[\w\.\-_@ ]+\z/, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")}.merge(options[:login_field_validates_format_of_options])
- end
-
- validates_uniqueness_of options[:login_field], {:allow_blank => true}.merge(options[:login_field_validates_uniqueness_of_options].merge(:if => "#{options[:login_field]}_changed?".to_sym))
- end
-
- if options[:validate_password_field]
- validates_length_of options[:password_field], sanitize_validation_length_options({:minimum => 4}, 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 => "#{options[:password_salt_field]}_changed?".to_sym)
- validates_presence_of "#{options[:password_field]}_confirmation", options[:password_confirmation_field_validates_presence_of_options].merge(:if => "#{options[:password_salt_field]}_changed?".to_sym)
- end
-
- if options[:validate_email_field] && options[:email_field]
- validates_length_of options[:email_field], sanitize_validation_length_options({:within => 6..100}, options[:email_field_validates_length_of_options])
- validates_format_of options[:email_field], {:with => email_field_regex, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")}.merge(options[:email_field_validates_format_of_options])
- validates_uniqueness_of options[:email_field], options[:email_field_validates_uniqueness_of_options].merge(:if => "#{options[:email_field]}_changed?".to_sym)
- end
- end
-
- attr_accessor "validate_#{options[:password_field]}".to_sym
- attr_reader options[:password_field]
-
- class_eval <<-"end_eval", __FILE__, __LINE__
- def self.friendly_unique_token
- Authlogic::Random.friendly_token
- end
-
- def #{options[:password_field]}=(pass)
- return if pass.blank?
- @#{options[:password_field]} = pass
- self.#{options[:password_salt_field]} = self.class.unique_token
- self.#{options[:crypted_password_field]} = #{options[:crypto_provider]}.encrypt(*encrypt_arguments(@#{options[:password_field]}, #{options[:act_like_restful_authentication].inspect} ? :restful_authentication : nil))
- end
-
- def valid_#{options[:password_field]}?(attempted_password)
- return false if attempted_password.blank? || #{options[:crypted_password_field]}.blank? || #{options[:password_salt_field]}.blank?
-
- ([#{options[:crypto_provider]}] + #{options[:transition_from_crypto_provider].inspect}).each_with_index do |encryptor, index|
- # The arguments_type of for the transitioning from restful_authentication
- arguments_type = (#{options[:act_like_restful_authentication].inspect} && index == 0) ||
- (#{options[:transition_from_restful_authentication].inspect} && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
- :restful_authentication : nil
-
- if encryptor.matches?(#{options[: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?(#{options[:crypted_password_field]}))
- self.password = attempted_password
- save(false)
- end
-
- return true
- end
- end
-
- false
- end
-
- def reset_#{options[:password_field]}