expertiza-authlogic 2.1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/CHANGELOG.rdoc +345 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +246 -0
  4. data/Rakefile +41 -0
  5. data/VERSION.yml +5 -0
  6. data/expertiza-authlogic.gemspec +161 -0
  7. data/generators/session/session_generator.rb +9 -0
  8. data/generators/session/templates/session.rb +2 -0
  9. data/init.rb +1 -0
  10. data/lib/authlogic.rb +64 -0
  11. data/lib/authlogic/acts_as_authentic/base.rb +107 -0
  12. data/lib/authlogic/acts_as_authentic/email.rb +110 -0
  13. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +60 -0
  14. data/lib/authlogic/acts_as_authentic/login.rb +141 -0
  15. data/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
  16. data/lib/authlogic/acts_as_authentic/password.rb +391 -0
  17. data/lib/authlogic/acts_as_authentic/perishable_token.rb +105 -0
  18. data/lib/authlogic/acts_as_authentic/persistence_token.rb +68 -0
  19. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +76 -0
  20. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +139 -0
  21. data/lib/authlogic/acts_as_authentic/single_access_token.rb +65 -0
  22. data/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
  23. data/lib/authlogic/authenticates_many/association.rb +42 -0
  24. data/lib/authlogic/authenticates_many/base.rb +55 -0
  25. data/lib/authlogic/controller_adapters/abstract_adapter.rb +67 -0
  26. data/lib/authlogic/controller_adapters/merb_adapter.rb +30 -0
  27. data/lib/authlogic/controller_adapters/rails_adapter.rb +48 -0
  28. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +61 -0
  29. data/lib/authlogic/crypto_providers/aes256.rb +43 -0
  30. data/lib/authlogic/crypto_providers/bcrypt.rb +90 -0
  31. data/lib/authlogic/crypto_providers/md5.rb +34 -0
  32. data/lib/authlogic/crypto_providers/sha1.rb +35 -0
  33. data/lib/authlogic/crypto_providers/sha256.rb +50 -0
  34. data/lib/authlogic/crypto_providers/sha512.rb +50 -0
  35. data/lib/authlogic/crypto_providers/wordpress.rb +43 -0
  36. data/lib/authlogic/i18n.rb +83 -0
  37. data/lib/authlogic/i18n/translator.rb +15 -0
  38. data/lib/authlogic/random.rb +33 -0
  39. data/lib/authlogic/regex.rb +25 -0
  40. data/lib/authlogic/session/activation.rb +58 -0
  41. data/lib/authlogic/session/active_record_trickery.rb +64 -0
  42. data/lib/authlogic/session/base.rb +37 -0
  43. data/lib/authlogic/session/brute_force_protection.rb +96 -0
  44. data/lib/authlogic/session/callbacks.rb +99 -0
  45. data/lib/authlogic/session/cookies.rb +130 -0
  46. data/lib/authlogic/session/existence.rb +93 -0
  47. data/lib/authlogic/session/foundation.rb +63 -0
  48. data/lib/authlogic/session/http_auth.rb +58 -0
  49. data/lib/authlogic/session/id.rb +41 -0
  50. data/lib/authlogic/session/klass.rb +78 -0
  51. data/lib/authlogic/session/magic_columns.rb +95 -0
  52. data/lib/authlogic/session/magic_states.rb +59 -0
  53. data/lib/authlogic/session/params.rb +101 -0
  54. data/lib/authlogic/session/password.rb +240 -0
  55. data/lib/authlogic/session/perishable_token.rb +18 -0
  56. data/lib/authlogic/session/persistence.rb +70 -0
  57. data/lib/authlogic/session/priority_record.rb +34 -0
  58. data/lib/authlogic/session/scopes.rb +101 -0
  59. data/lib/authlogic/session/session.rb +62 -0
  60. data/lib/authlogic/session/timeout.rb +82 -0
  61. data/lib/authlogic/session/unauthorized_record.rb +50 -0
  62. data/lib/authlogic/session/validation.rb +82 -0
  63. data/lib/authlogic/test_case.rb +120 -0
  64. data/lib/authlogic/test_case/mock_controller.rb +45 -0
  65. data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
  66. data/lib/authlogic/test_case/mock_logger.rb +10 -0
  67. data/lib/authlogic/test_case/mock_request.rb +19 -0
  68. data/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
  69. data/rails/init.rb +1 -0
  70. data/shoulda_macros/authlogic.rb +69 -0
  71. data/test/acts_as_authentic_test/base_test.rb +18 -0
  72. data/test/acts_as_authentic_test/email_test.rb +101 -0
  73. data/test/acts_as_authentic_test/logged_in_status_test.rb +36 -0
  74. data/test/acts_as_authentic_test/login_test.rb +109 -0
  75. data/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
  76. data/test/acts_as_authentic_test/password_test.rb +245 -0
  77. data/test/acts_as_authentic_test/perishable_token_test.rb +90 -0
  78. data/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
  79. data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
  80. data/test/acts_as_authentic_test/session_maintenance_test.rb +84 -0
  81. data/test/acts_as_authentic_test/single_access_test.rb +44 -0
  82. data/test/authenticates_many_test.rb +16 -0
  83. data/test/crypto_provider_test/aes256_test.rb +14 -0
  84. data/test/crypto_provider_test/bcrypt_test.rb +14 -0
  85. data/test/crypto_provider_test/sha1_test.rb +23 -0
  86. data/test/crypto_provider_test/sha256_test.rb +14 -0
  87. data/test/crypto_provider_test/sha512_test.rb +14 -0
  88. data/test/fixtures/companies.yml +5 -0
  89. data/test/fixtures/employees.yml +17 -0
  90. data/test/fixtures/projects.yml +3 -0
  91. data/test/fixtures/users.yml +24 -0
  92. data/test/i18n_test.rb +33 -0
  93. data/test/libs/affiliate.rb +7 -0
  94. data/test/libs/company.rb +6 -0
  95. data/test/libs/employee.rb +7 -0
  96. data/test/libs/employee_session.rb +2 -0
  97. data/test/libs/ldaper.rb +3 -0
  98. data/test/libs/ordered_hash.rb +9 -0
  99. data/test/libs/project.rb +3 -0
  100. data/test/libs/user.rb +5 -0
  101. data/test/libs/user_session.rb +6 -0
  102. data/test/random_test.rb +42 -0
  103. data/test/session_test/activation_test.rb +43 -0
  104. data/test/session_test/active_record_trickery_test.rb +36 -0
  105. data/test/session_test/brute_force_protection_test.rb +101 -0
  106. data/test/session_test/callbacks_test.rb +6 -0
  107. data/test/session_test/cookies_test.rb +112 -0
  108. data/test/session_test/credentials_test.rb +0 -0
  109. data/test/session_test/existence_test.rb +64 -0
  110. data/test/session_test/http_auth_test.rb +28 -0
  111. data/test/session_test/id_test.rb +17 -0
  112. data/test/session_test/klass_test.rb +40 -0
  113. data/test/session_test/magic_columns_test.rb +62 -0
  114. data/test/session_test/magic_states_test.rb +60 -0
  115. data/test/session_test/params_test.rb +53 -0
  116. data/test/session_test/password_test.rb +106 -0
  117. data/test/session_test/perishability_test.rb +15 -0
  118. data/test/session_test/persistence_test.rb +21 -0
  119. data/test/session_test/scopes_test.rb +60 -0
  120. data/test/session_test/session_test.rb +59 -0
  121. data/test/session_test/timeout_test.rb +52 -0
  122. data/test/session_test/unauthorized_record_test.rb +13 -0
  123. data/test/session_test/validation_test.rb +23 -0
  124. data/test/test_helper.rb +182 -0
  125. metadata +205 -0
@@ -0,0 +1,60 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # Since web applications are stateless there is not sure fire way to tell if a user is logged in or not,
4
+ # from the database perspective. The best way to do this is to provide a "timeout" based on inactivity.
5
+ # So if that user is inactive for a certain amount of time we assume they are logged out. That's what this
6
+ # module is all about.
7
+ module LoggedInStatus
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ extend Config
11
+ add_acts_as_authentic_module(Methods)
12
+ end
13
+ end
14
+
15
+ # All configuration for the logged in status feature set.
16
+ module Config
17
+ # The timeout to determine when a user is logged in or not.
18
+ #
19
+ # * <tt>Default:</tt> 10.minutes
20
+ # * <tt>Accepts:</tt> Fixnum
21
+ def logged_in_timeout(value = nil)
22
+ rw_config(:logged_in_timeout, (!value.nil? && value.to_i) || value, 10.minutes.to_i)
23
+ end
24
+ alias_method :logged_in_timeout=, :logged_in_timeout
25
+ end
26
+
27
+ # All methods for the logged in status feature seat.
28
+ module Methods
29
+ def self.included(klass)
30
+ return if !klass.column_names.include?("last_request_at")
31
+
32
+ klass.class_eval do
33
+ include InstanceMethods
34
+
35
+ named_scope :logged_in, lambda { {:conditions => ["last_request_at > ?", logged_in_timeout.seconds.ago]} }
36
+ named_scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", logged_in_timeout.seconds.ago]} }
37
+ end
38
+ end
39
+
40
+ module InstanceMethods
41
+ # Returns true if the last_request_at > logged_in_timeout.
42
+ def logged_in?
43
+ raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at)
44
+ !last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
45
+ end
46
+
47
+ # Opposite of logged_in?
48
+ def logged_out?
49
+ !logged_in?
50
+ end
51
+
52
+ private
53
+ def logged_in_timeout
54
+ self.class.logged_in_timeout
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,141 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # Handles everything related to the login field.
4
+ module Login
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ extend Config
8
+ add_acts_as_authentic_module(Methods)
9
+ end
10
+ end
11
+
12
+ # Confguration for the login field.
13
+ module Config
14
+ # The name of the login field in the database.
15
+ #
16
+ # * <tt>Default:</tt> :login or :username, if they exist
17
+ # * <tt>Accepts:</tt> Symbol
18
+ def login_field(value = nil)
19
+ rw_config(:login_field, value, first_column_to_exist(nil, :login, :username))
20
+ end
21
+ alias_method :login_field=, :login_field
22
+
23
+ # Whether or not the validate the login field
24
+ #
25
+ # * <tt>Default:</tt> true
26
+ # * <tt>Accepts:</tt> Boolean
27
+ def validate_login_field(value = nil)
28
+ rw_config(:validate_login_field, value, true)
29
+ end
30
+ alias_method :validate_login_field=, :validate_login_field
31
+
32
+ # A hash of options for the validates_length_of call for the login field. Allows you to change this however you want.
33
+ #
34
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
35
+ # merge options into it. Checkout the convenience function merge_validates_length_of_login_field_options to merge
36
+ # options.</b>
37
+ #
38
+ # * <tt>Default:</tt> {:within => 3..100}
39
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
40
+ def validates_length_of_login_field_options(value = nil)
41
+ rw_config(:validates_length_of_login_field_options, value, {:within => 3..100})
42
+ end
43
+ alias_method :validates_length_of_login_field_options=, :validates_length_of_login_field_options
44
+
45
+ # A convenience function to merge options into the validates_length_of_login_field_options. So intead of:
46
+ #
47
+ # self.validates_length_of_login_field_options = validates_length_of_login_field_options.merge(:my_option => my_value)
48
+ #
49
+ # You can do this:
50
+ #
51
+ # merge_validates_length_of_login_field_options :my_option => my_value
52
+ def merge_validates_length_of_login_field_options(options = {})
53
+ self.validates_length_of_login_field_options = validates_length_of_login_field_options.merge(options)
54
+ end
55
+
56
+ # A hash of options for the validates_format_of call for the login field. Allows you to change this however you want.
57
+ #
58
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
59
+ # merge options into it. Checkout the convenience function merge_validates_format_of_login_field_options to merge
60
+ # options.</b>
61
+ #
62
+ # * <tt>Default:</tt> {:with => Authlogic::Regex.login, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")}
63
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_format_of
64
+ def validates_format_of_login_field_options(value = nil)
65
+ rw_config(:validates_format_of_login_field_options, value, {:with => Authlogic::Regex.login, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")})
66
+ end
67
+ alias_method :validates_format_of_login_field_options=, :validates_format_of_login_field_options
68
+
69
+ # See merge_validates_length_of_login_field_options. The same thing, except for validates_format_of_login_field_options
70
+ def merge_validates_format_of_login_field_options(options = {})
71
+ self.validates_format_of_login_field_options = validates_format_of_login_field_options.merge(options)
72
+ end
73
+
74
+ # A hash of options for the validates_uniqueness_of call for the login field. Allows you to change this however you want.
75
+ #
76
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
77
+ # merge options into it. Checkout the convenience function merge_validates_format_of_login_field_options to merge
78
+ # options.</b>
79
+ #
80
+ # * <tt>Default:</tt> {:case_sensitive => false, :scope => validations_scope, :if => "#{login_field}_changed?".to_sym}
81
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_uniqueness_of
82
+ def validates_uniqueness_of_login_field_options(value = nil)
83
+ rw_config(:validates_uniqueness_of_login_field_options, value, {:case_sensitive => false, :scope => validations_scope, :if => "#{login_field}_changed?".to_sym})
84
+ end
85
+ alias_method :validates_uniqueness_of_login_field_options=, :validates_uniqueness_of_login_field_options
86
+
87
+ # See merge_validates_length_of_login_field_options. The same thing, except for validates_uniqueness_of_login_field_options
88
+ def merge_validates_uniqueness_of_login_field_options(options = {})
89
+ self.validates_uniqueness_of_login_field_options = validates_uniqueness_of_login_field_options.merge(options)
90
+ end
91
+
92
+ # This method allows you to find a record with the given login. If you notice, with ActiveRecord you have the
93
+ # validates_uniqueness_of validation function. They give you a :case_sensitive option. I handle this in the same
94
+ # manner that they handle that. If you are using the login field and set false for the :case_sensitive option in
95
+ # validates_uniqueness_of_login_field_options this method will modify the query to look something like:
96
+ #
97
+ # first(:conditions => ["LOWER(#{quoted_table_name}.#{login_field}) = ?", login.downcase])
98
+ #
99
+ # If you don't specify this it calls the good old find_by_* method:
100
+ #
101
+ # find_by_login(login)
102
+ #
103
+ # The above also applies for using email as your login, except that you need to set the :case_sensitive in
104
+ # validates_uniqueness_of_email_field_options to false.
105
+ #
106
+ # The only reason I need to do the above is for Postgres and SQLite since they perform case sensitive searches with the
107
+ # find_by_* methods.
108
+ def find_by_smart_case_login_field(login)
109
+ if login_field
110
+ find_with_case(login_field, login, validates_uniqueness_of_login_field_options[:case_sensitive] != false)
111
+ else
112
+ find_with_case(email_field, login, validates_uniqueness_of_email_field_options[:case_sensitive] != false)
113
+ end
114
+ end
115
+
116
+ private
117
+ def find_with_case(field, value, sensitivity = true)
118
+ if sensitivity
119
+ send("find_by_#{field}", value)
120
+ else
121
+ first(:conditions => ["LOWER(#{quoted_table_name}.#{field}) = ?", value.mb_chars.downcase])
122
+ end
123
+ end
124
+ end
125
+
126
+ # All methods relating to the login field
127
+ module Methods
128
+ # Adds in various validations, modules, etc.
129
+ def self.included(klass)
130
+ klass.class_eval do
131
+ if validate_login_field && login_field
132
+ validates_length_of login_field, validates_length_of_login_field_options
133
+ validates_format_of login_field, validates_format_of_login_field_options
134
+ validates_uniqueness_of login_field, validates_uniqueness_of_login_field_options
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,24 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # Magic columns are like ActiveRecord's created_at and updated_at columns. They are "magically" maintained for
4
+ # you. Authlogic has the same thing, but these are maintained on the session side. Please see Authlogic::Session::MagicColumns
5
+ # for more details. This module merely adds validations for the magic columns if they exist.
6
+ module MagicColumns
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ add_acts_as_authentic_module(Methods)
10
+ end
11
+ end
12
+
13
+ # Methods relating to the magic columns
14
+ module Methods
15
+ def self.included(klass)
16
+ klass.class_eval do
17
+ validates_numericality_of :login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
18
+ 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")
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,391 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # This module has a lot of neat functionality. It is responsible for encrypting your password, salting it, and verifying it.
4
+ # It can also help you transition to a new encryption algorithm. See the Config sub module for configuration options.
5
+ module Password
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ extend Config
9
+ add_acts_as_authentic_module(Callbacks)
10
+ add_acts_as_authentic_module(Methods)
11
+ end
12
+ end
13
+
14
+ # All configuration for the password aspect of acts_as_authentic.
15
+ module Config
16
+ # The name of the virtual password field, used for setting the password.
17
+ #
18
+ # * <tt>Default:</tt> :password
19
+ # * <tt>Accepts:</tt> Symbol
20
+ def password_field(value = nil)
21
+ rw_config(:password_field, value, :password)
22
+ end
23
+ alias_method :password_field=, :password_field
24
+
25
+ # The name of the virtual password confirmation field, used for setting the password.
26
+ # The value of this field is "#{password_field}_confirmation", and is not configurable.
27
+ #
28
+ # * <tt>Default:</tt> :password_confirmation
29
+ def password_confirmation_field()
30
+ rw_config(:password_confirmation_field, nil, "#{password_field}_confirmation".to_sym)
31
+ end
32
+
33
+ # The name of the crypted_password field in the database.
34
+ #
35
+ # * <tt>Default:</tt> :crypted_password, :encrypted_password, :password_hash, or :pw_hash
36
+ # * <tt>Accepts:</tt> Symbol
37
+ def crypted_password_field(value = nil)
38
+ rw_config(:crypted_password_field, value, first_column_to_exist(nil, :crypted_password, :encrypted_password, :password_hash, :pw_hash))
39
+ end
40
+ alias_method :crypted_password_field=, :crypted_password_field
41
+
42
+ # The name of the password_salt field in the database.
43
+ #
44
+ # * <tt>Default:</tt> :password_salt, :pw_salt, :salt, nil if none exist
45
+ # * <tt>Accepts:</tt> Symbol
46
+ def password_salt_field(value = nil)
47
+ rw_config(:password_salt_field, value, first_column_to_exist(nil, :password_salt, :pw_salt, :salt))
48
+ end
49
+ alias_method :password_salt_field=, :password_salt_field
50
+
51
+ # Whether or not to require a password confirmation. If you don't want your users to confirm their password
52
+ # just set this to false.
53
+ #
54
+ # * <tt>Default:</tt> true
55
+ # * <tt>Accepts:</tt> Boolean
56
+ def require_password_confirmation(value = nil)
57
+ rw_config(:require_password_confirmation, value, true)
58
+ end
59
+ alias_method :require_password_confirmation=, :require_password_confirmation
60
+
61
+ # By default passwords are required when a record is new or the crypted_password is blank, but if both of these things
62
+ # are met a password is not required. In this case, blank passwords are ignored.
63
+ #
64
+ # Think about a profile page, where the user can edit all of their information, including changing their password.
65
+ # If they do not want to change their password they just leave the fields blank. This will try to set the password to
66
+ # a blank value, in which case is incorrect behavior. As such, Authlogic ignores this. But let's say you have a completely
67
+ # separate page for resetting passwords, you might not want to ignore blank passwords. If this is the case for you, then
68
+ # just set this value to false.
69
+ #
70
+ # * <tt>Default:</tt> true
71
+ # * <tt>Accepts:</tt> Boolean
72
+ def ignore_blank_passwords(value = nil)
73
+ rw_config(:ignore_blank_passwords, value, true)
74
+ end
75
+ alias_method :ignore_blank_passwords=, :ignore_blank_passwords
76
+
77
+ # When calling valid_password?("some pass") do you want to check that password against what's in that object or whats in
78
+ # the datbase. Take this example:
79
+ #
80
+ # u = User.first
81
+ # u.password = "new pass"
82
+ # u.valid_password?("old pass")
83
+ #
84
+ # Should the last line above return true or false? The record hasn't been saved yet, so most would assume true.
85
+ # Other would assume false. So I let you decide by giving you this option.
86
+ #
87
+ # * <tt>Default:</tt> true
88
+ # * <tt>Accepts:</tt> Boolean
89
+ def check_passwords_against_database(value = nil)
90
+ rw_config(:check_passwords_against_database, value, true)
91
+ end
92
+ alias_method :check_passwords_against_database=, :check_passwords_against_database
93
+
94
+ # Whether or not to validate the password field.
95
+ #
96
+ # * <tt>Default:</tt> true
97
+ # * <tt>Accepts:</tt> Boolean
98
+ def validate_password_field(value = nil)
99
+ rw_config(:validate_password_field, value, true)
100
+ end
101
+ alias_method :validate_password_field=, :validate_password_field
102
+
103
+ # A hash of options for the validates_length_of call for the password field. Allows you to change this however you want.
104
+ #
105
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
106
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
107
+ # options.</b>
108
+ #
109
+ # * <tt>Default:</tt> {:minimum => 4, :if => :require_password?}
110
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
111
+ def validates_length_of_password_field_options(value = nil)
112
+ rw_config(:validates_length_of_password_field_options, value, {:minimum => 4, :if => :require_password?})
113
+ end
114
+ alias_method :validates_length_of_password_field_options=, :validates_length_of_password_field_options
115
+
116
+ # A convenience function to merge options into the validates_length_of_login_field_options. So intead of:
117
+ #
118
+ # self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(:my_option => my_value)
119
+ #
120
+ # You can do this:
121
+ #
122
+ # merge_validates_length_of_password_field_options :my_option => my_value
123
+ def merge_validates_length_of_password_field_options(options = {})
124
+ self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(options)
125
+ end
126
+
127
+ # A hash of options for the validates_confirmation_of call for the password field. Allows you to change this however you want.
128
+ #
129
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
130
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
131
+ # options.</b>
132
+ #
133
+ # * <tt>Default:</tt> {:if => :require_password?}
134
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_confirmation_of
135
+ def validates_confirmation_of_password_field_options(value = nil)
136
+ rw_config(:validates_confirmation_of_password_field_options, value, {:if => :require_password?})
137
+ end
138
+ alias_method :validates_confirmation_of_password_field_options=, :validates_confirmation_of_password_field_options
139
+
140
+ # See merge_validates_length_of_password_field_options. The same thing, except for validates_confirmation_of_password_field_options
141
+ def merge_validates_confirmation_of_password_field_options(options = {})
142
+ self.validates_confirmation_of_password_field_options = validates_confirmation_of_password_field_options.merge(options)
143
+ end
144
+
145
+ # A hash of options for the validates_length_of call for the password_confirmation field. Allows you to change this however you want.
146
+ #
147
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
148
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
149
+ # options.</b>
150
+ #
151
+ # * <tt>Default:</tt> validates_length_of_password_field_options
152
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
153
+ def validates_length_of_password_confirmation_field_options(value = nil)
154
+ rw_config(:validates_length_of_password_confirmation_field_options, value, validates_length_of_password_field_options)
155
+ end
156
+ alias_method :validates_length_of_password_confirmation_field_options=, :validates_length_of_password_confirmation_field_options
157
+
158
+ # See merge_validates_length_of_password_field_options. The same thing, except for validates_length_of_password_confirmation_field_options
159
+ def merge_validates_length_of_password_confirmation_field_options(options = {})
160
+ self.validates_length_of_password_confirmation_field_options = validates_length_of_password_confirmation_field_options.merge(options)
161
+ end
162
+
163
+ # The class you want to use to encrypt and verify your encrypted passwords. See the Authlogic::CryptoProviders module for more info
164
+ # on the available methods and how to create your own.
165
+ #
166
+ # * <tt>Default:</tt> CryptoProviders::Sha512
167
+ # * <tt>Accepts:</tt> Class
168
+ def crypto_provider(value = nil)
169
+ rw_config(:crypto_provider, value, CryptoProviders::Sha512)
170
+ end
171
+ alias_method :crypto_provider=, :crypto_provider
172
+
173
+ # Let's say you originally encrypted your passwords with Sha1. Sha1 is starting to join the party with MD5 and you want to switch
174
+ # to something stronger. No problem, just specify your new and improved algorithm with the crypt_provider option and then let
175
+ # Authlogic know you are transitioning from Sha1 using this option. Authlogic will take care of everything, including transitioning
176
+ # your users to the new algorithm. The next time a user logs in, they will be granted access using the old algorithm and their
177
+ # password will be resaved with the new algorithm. All new users will obviously use the new algorithm as well.
178
+ #
179
+ # Lastly, if you want to transition again, you can pass an array of crypto providers. So you can transition from as many algorithms
180
+ # as you want.
181
+ #
182
+ # * <tt>Default:</tt> nil
183
+ # * <tt>Accepts:</tt> Class or Array
184
+ def transition_from_crypto_providers(value = nil)
185
+ rw_config(:transition_from_crypto_providers, (!value.nil? && [value].flatten.compact) || value, [])
186
+ end
187
+ alias_method :transition_from_crypto_providers=, :transition_from_crypto_providers
188
+ end
189
+
190
+ # Callbacks / hooks to allow other modules to modify the behavior of this module.
191
+ module Callbacks
192
+ METHODS = [
193
+ "before_password_set", "after_password_set",
194
+ "before_password_verification", "after_password_verification"
195
+ ]
196
+
197
+ def self.included(klass)
198
+ return if klass.crypted_password_field.nil?
199
+ klass.define_callbacks *METHODS
200
+
201
+ # If Rails 3, support the new callback syntax
202
+ if klass.send(klass.respond_to?(:singleton_class) ? :singleton_class : :metaclass).method_defined?(:set_callback)
203
+ METHODS.each do |method|
204
+ klass.class_eval <<-"end_eval", __FILE__, __LINE__
205
+ def self.#{method}(*methods, &block)
206
+ set_callback :#{method}, *methods, &block
207
+ end
208
+ end_eval
209
+ end
210
+ end
211
+ end
212
+
213
+ private
214
+ METHODS.each do |method|
215
+ class_eval <<-"end_eval", __FILE__, __LINE__
216
+ def #{method}
217
+ run_callbacks(:#{method}) { |result, object| result == false }
218
+ end
219
+ end_eval
220
+ end
221
+ end
222
+
223
+ # The methods related to the password field.
224
+ module Methods
225
+ def self.included(klass)
226
+ return if klass.crypted_password_field.nil?
227
+
228
+ klass.class_eval do
229
+ include InstanceMethods
230
+
231
+ if validate_password_field
232
+ validates_length_of password_field, validates_length_of_password_field_options
233
+
234
+ if require_password_confirmation
235
+ validates_confirmation_of password_field, validates_confirmation_of_password_field_options
236
+ validates_length_of "#{password_confirmation_field}", validates_length_of_password_confirmation_field_options
237
+ end
238
+ end
239
+
240
+ after_save :reset_password_changed
241
+
242
+ # The password
243
+ define_method password_field do
244
+ @password
245
+ end
246
+
247
+ # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
248
+ # the password.
249
+ define_method "#{password_field}=" do |pass|
250
+ return if ignore_blank_passwords? && pass.blank?
251
+ before_password_set
252
+ @password = pass
253
+
254
+ arguments_type = nil
255
+ if act_like_restful_authentication?
256
+ arguments_type = :restful_authentication
257
+ elsif salt_first?
258
+ arguments_type = :salt_first
259
+ end
260
+
261
+ send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
262
+ send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, false, arguments_type)))
263
+ @password_changed = true
264
+ after_password_set
265
+ end
266
+ end
267
+ end
268
+
269
+ module InstanceMethods
270
+ # Accepts a raw password to determine if it is the correct password or not. Notice the second argument. That defaults to the value of
271
+ # check_passwords_against_database. See that method for mor information, but basically it just tells Authlogic to check the password
272
+ # against the value in the database or the value in the object.
273
+ def valid_password?(attempted_password, check_against_database = check_passwords_against_database?)
274
+ crypted = check_against_database && send("#{crypted_password_field}_changed?") ? send("#{crypted_password_field}_was") : send(crypted_password_field)
275
+ return false if attempted_password.blank? || crypted.blank?
276
+ before_password_verification
277
+
278
+ crypto_providers.each_with_index do |encryptor, index|
279
+ # The arguments_type of for the transitioning from restful_authentication
280
+ arguments_type = (act_like_restful_authentication? && index == 0) ||
281
+ (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
282
+ :restful_authentication : nil
283
+ arguments_type = :salt_first if salt_first?
284
+
285
+ if encryptor.matches?(crypted, *encrypt_arguments(attempted_password, check_against_database, arguments_type))
286
+ transition_password(attempted_password) if transition_password?(index, encryptor, crypted, check_against_database)
287
+ after_password_verification
288
+ return true
289
+ end
290
+ end
291
+
292
+ false
293
+ end
294
+
295
+ # Resets the password to a random friendly token.
296
+ def reset_password
297
+ friendly_token = Authlogic::Random.friendly_token
298
+ self.send("#{password_field}=", friendly_token)
299
+ self.send("#{password_confirmation_field}=", friendly_token)
300
+ end
301
+ alias_method :randomize_password, :reset_password
302
+
303
+ # Resets the password to a random friendly token and then saves the record.
304
+ def reset_password!
305
+ reset_password
306
+ save_without_session_maintenance(false)
307
+ end
308
+ alias_method :randomize_password!, :reset_password!
309
+
310
+ private
311
+ def check_passwords_against_database?
312
+ self.class.check_passwords_against_database == true
313
+ end
314
+
315
+ def crypto_providers
316
+ [crypto_provider] + transition_from_crypto_providers
317
+ end
318
+
319
+ def encrypt_arguments(raw_password, check_against_database, arguments_type = nil)
320
+ salt = nil
321
+ salt = (check_against_database && send("#{password_salt_field}_changed?") ? send("#{password_salt_field}_was") : send(password_salt_field)) if password_salt_field
322
+
323
+ case arguments_type
324
+ when :restful_authentication
325
+ [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
326
+ when :salt_first
327
+ [salt, raw_password]
328
+ else
329
+ [raw_password, salt].compact
330
+ end
331
+ end
332
+
333
+ # Determines if we need to tranisiton the password.
334
+ # If the index > 0 then we are using an "transition from" crypto provider.
335
+ # If the encryptor has a cost and the cost it outdated.
336
+ # If we aren't using database values
337
+ # If we are using database values, only if the password hasnt change so we don't overwrite any changes
338
+ def transition_password?(index, encryptor, crypted, check_against_database)
339
+ (index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))) &&
340
+ (!check_against_database || !send("#{crypted_password_field}_changed?"))
341
+ end
342
+
343
+ def transition_password(attempted_password)
344
+ self.send("#{password_field}=", attempted_password)
345
+ save(false)
346
+ end
347
+
348
+ def require_password?
349
+ new_record? || password_changed? || send(crypted_password_field).blank?
350
+ end
351
+
352
+ def ignore_blank_passwords?
353
+ self.class.ignore_blank_passwords == true
354
+ end
355
+
356
+ def password_changed?
357
+ @password_changed == true
358
+ end
359
+
360
+ def reset_password_changed
361
+ @password_changed = nil
362
+ end
363
+
364
+ def password_field
365
+ self.class.password_field
366
+ end
367
+
368
+ def password_confirmation_field
369
+ self.class.password_confirmation_field
370
+ end
371
+
372
+ def crypted_password_field
373
+ self.class.crypted_password_field
374
+ end
375
+
376
+ def password_salt_field
377
+ self.class.password_salt_field
378
+ end
379
+
380
+ def crypto_provider
381
+ self.class.crypto_provider
382
+ end
383
+
384
+ def transition_from_crypto_providers
385
+ self.class.transition_from_crypto_providers
386
+ end
387
+ end
388
+ end
389
+ end
390
+ end
391
+ end