a_b 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. data/MIT-LICENSE +18 -0
  2. data/README.markdown +4 -0
  3. data/Rakefile +74 -0
  4. data/config.ru +3 -0
  5. data/config/database.example.yml +6 -0
  6. data/config/externals.yml +6 -0
  7. data/config/mail.example.yml +32 -0
  8. data/db/migrate/001_a_b_tests.rb +14 -0
  9. data/db/migrate/002_a_b_variants.rb +19 -0
  10. data/db/migrate/003_a_b_users.rb +25 -0
  11. data/db/migrate/004_a_b_tokens.rb +12 -0
  12. data/features/example.feature +10 -0
  13. data/features/support/env.rb +35 -0
  14. data/features/support/rspec.rb +11 -0
  15. data/features/support/webrat.rb +3 -0
  16. data/gemspec.rb +41 -0
  17. data/lib/a_b.rb +31 -0
  18. data/lib/a_b/boot.rb +30 -0
  19. data/lib/a_b/controller/api.rb +32 -0
  20. data/lib/a_b/controller/application.rb +6 -0
  21. data/lib/a_b/controller/index.rb +16 -0
  22. data/lib/a_b/controller/sessions.rb +28 -0
  23. data/lib/a_b/controller/tests.rb +37 -0
  24. data/lib/a_b/controller/variants.rb +14 -0
  25. data/lib/a_b/helper/api.rb +8 -0
  26. data/lib/a_b/helper/application.rb +56 -0
  27. data/lib/a_b/helper/index.rb +12 -0
  28. data/lib/a_b/model/a_b_test.rb +65 -0
  29. data/lib/a_b/model/a_b_variant.rb +131 -0
  30. data/lib/a_b/model/token.rb +22 -0
  31. data/lib/a_b/model/user.rb +5 -0
  32. data/lib/a_b/model/user_session.rb +4 -0
  33. data/lib/a_b/view/index.haml +129 -0
  34. data/lib/a_b/view/index.sass +55 -0
  35. data/lib/a_b/view/layout.haml +15 -0
  36. data/lib/a_b/view/layout.sass +34 -0
  37. data/lib/a_b/view/log_in.haml +15 -0
  38. data/lib/a_b/view/log_in.sass +11 -0
  39. data/public/css/blueprint/ie.css +35 -0
  40. data/public/css/blueprint/print.css +29 -0
  41. data/public/css/blueprint/screen.css +257 -0
  42. data/public/js/index.js +126 -0
  43. data/public/js/jquery.js +19 -0
  44. data/public/js/visit.js +59 -0
  45. data/script/console +2 -0
  46. data/script/env.rb +30 -0
  47. data/spec/spec.opts +1 -0
  48. data/spec/spec_helper.rb +16 -0
  49. data/vendor/authlogic/CHANGELOG.rdoc +345 -0
  50. data/vendor/authlogic/LICENSE +20 -0
  51. data/vendor/authlogic/README.rdoc +246 -0
  52. data/vendor/authlogic/Rakefile +42 -0
  53. data/vendor/authlogic/VERSION.yml +5 -0
  54. data/vendor/authlogic/authlogic.gemspec +217 -0
  55. data/vendor/authlogic/generators/session/session_generator.rb +9 -0
  56. data/vendor/authlogic/generators/session/templates/session.rb +2 -0
  57. data/vendor/authlogic/init.rb +1 -0
  58. data/vendor/authlogic/lib/authlogic.rb +57 -0
  59. data/vendor/authlogic/lib/authlogic/acts_as_authentic/base.rb +107 -0
  60. data/vendor/authlogic/lib/authlogic/acts_as_authentic/email.rb +110 -0
  61. data/vendor/authlogic/lib/authlogic/acts_as_authentic/logged_in_status.rb +60 -0
  62. data/vendor/authlogic/lib/authlogic/acts_as_authentic/login.rb +141 -0
  63. data/vendor/authlogic/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
  64. data/vendor/authlogic/lib/authlogic/acts_as_authentic/password.rb +344 -0
  65. data/vendor/authlogic/lib/authlogic/acts_as_authentic/perishable_token.rb +105 -0
  66. data/vendor/authlogic/lib/authlogic/acts_as_authentic/persistence_token.rb +68 -0
  67. data/vendor/authlogic/lib/authlogic/acts_as_authentic/restful_authentication.rb +61 -0
  68. data/vendor/authlogic/lib/authlogic/acts_as_authentic/session_maintenance.rb +139 -0
  69. data/vendor/authlogic/lib/authlogic/acts_as_authentic/single_access_token.rb +65 -0
  70. data/vendor/authlogic/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
  71. data/vendor/authlogic/lib/authlogic/authenticates_many/association.rb +42 -0
  72. data/vendor/authlogic/lib/authlogic/authenticates_many/base.rb +55 -0
  73. data/vendor/authlogic/lib/authlogic/controller_adapters/abstract_adapter.rb +67 -0
  74. data/vendor/authlogic/lib/authlogic/controller_adapters/merb_adapter.rb +30 -0
  75. data/vendor/authlogic/lib/authlogic/controller_adapters/rails_adapter.rb +48 -0
  76. data/vendor/authlogic/lib/authlogic/controller_adapters/sinatra_adapter.rb +61 -0
  77. data/vendor/authlogic/lib/authlogic/crypto_providers/aes256.rb +43 -0
  78. data/vendor/authlogic/lib/authlogic/crypto_providers/bcrypt.rb +90 -0
  79. data/vendor/authlogic/lib/authlogic/crypto_providers/md5.rb +34 -0
  80. data/vendor/authlogic/lib/authlogic/crypto_providers/sha1.rb +35 -0
  81. data/vendor/authlogic/lib/authlogic/crypto_providers/sha256.rb +50 -0
  82. data/vendor/authlogic/lib/authlogic/crypto_providers/sha512.rb +50 -0
  83. data/vendor/authlogic/lib/authlogic/crypto_providers/wordpress.rb +43 -0
  84. data/vendor/authlogic/lib/authlogic/i18n.rb +83 -0
  85. data/vendor/authlogic/lib/authlogic/i18n/translator.rb +15 -0
  86. data/vendor/authlogic/lib/authlogic/random.rb +33 -0
  87. data/vendor/authlogic/lib/authlogic/regex.rb +25 -0
  88. data/vendor/authlogic/lib/authlogic/session/activation.rb +58 -0
  89. data/vendor/authlogic/lib/authlogic/session/active_record_trickery.rb +61 -0
  90. data/vendor/authlogic/lib/authlogic/session/base.rb +37 -0
  91. data/vendor/authlogic/lib/authlogic/session/brute_force_protection.rb +96 -0
  92. data/vendor/authlogic/lib/authlogic/session/callbacks.rb +88 -0
  93. data/vendor/authlogic/lib/authlogic/session/cookies.rb +130 -0
  94. data/vendor/authlogic/lib/authlogic/session/existence.rb +93 -0
  95. data/vendor/authlogic/lib/authlogic/session/foundation.rb +63 -0
  96. data/vendor/authlogic/lib/authlogic/session/http_auth.rb +58 -0
  97. data/vendor/authlogic/lib/authlogic/session/id.rb +41 -0
  98. data/vendor/authlogic/lib/authlogic/session/klass.rb +78 -0
  99. data/vendor/authlogic/lib/authlogic/session/magic_columns.rb +95 -0
  100. data/vendor/authlogic/lib/authlogic/session/magic_states.rb +59 -0
  101. data/vendor/authlogic/lib/authlogic/session/params.rb +101 -0
  102. data/vendor/authlogic/lib/authlogic/session/password.rb +240 -0
  103. data/vendor/authlogic/lib/authlogic/session/perishable_token.rb +18 -0
  104. data/vendor/authlogic/lib/authlogic/session/persistence.rb +70 -0
  105. data/vendor/authlogic/lib/authlogic/session/priority_record.rb +34 -0
  106. data/vendor/authlogic/lib/authlogic/session/scopes.rb +101 -0
  107. data/vendor/authlogic/lib/authlogic/session/session.rb +62 -0
  108. data/vendor/authlogic/lib/authlogic/session/timeout.rb +82 -0
  109. data/vendor/authlogic/lib/authlogic/session/unauthorized_record.rb +50 -0
  110. data/vendor/authlogic/lib/authlogic/session/validation.rb +82 -0
  111. data/vendor/authlogic/lib/authlogic/test_case.rb +120 -0
  112. data/vendor/authlogic/lib/authlogic/test_case/mock_controller.rb +45 -0
  113. data/vendor/authlogic/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
  114. data/vendor/authlogic/lib/authlogic/test_case/mock_logger.rb +10 -0
  115. data/vendor/authlogic/lib/authlogic/test_case/mock_request.rb +19 -0
  116. data/vendor/authlogic/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
  117. data/vendor/authlogic/rails/init.rb +1 -0
  118. data/vendor/authlogic/shoulda_macros/authlogic.rb +69 -0
  119. data/vendor/authlogic/test/acts_as_authentic_test/base_test.rb +18 -0
  120. data/vendor/authlogic/test/acts_as_authentic_test/email_test.rb +97 -0
  121. data/vendor/authlogic/test/acts_as_authentic_test/logged_in_status_test.rb +36 -0
  122. data/vendor/authlogic/test/acts_as_authentic_test/login_test.rb +109 -0
  123. data/vendor/authlogic/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
  124. data/vendor/authlogic/test/acts_as_authentic_test/password_test.rb +236 -0
  125. data/vendor/authlogic/test/acts_as_authentic_test/perishable_token_test.rb +90 -0
  126. data/vendor/authlogic/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
  127. data/vendor/authlogic/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
  128. data/vendor/authlogic/test/acts_as_authentic_test/session_maintenance_test.rb +84 -0
  129. data/vendor/authlogic/test/acts_as_authentic_test/single_access_test.rb +44 -0
  130. data/vendor/authlogic/test/authenticates_many_test.rb +16 -0
  131. data/vendor/authlogic/test/crypto_provider_test/aes256_test.rb +14 -0
  132. data/vendor/authlogic/test/crypto_provider_test/bcrypt_test.rb +14 -0
  133. data/vendor/authlogic/test/crypto_provider_test/sha1_test.rb +23 -0
  134. data/vendor/authlogic/test/crypto_provider_test/sha256_test.rb +14 -0
  135. data/vendor/authlogic/test/crypto_provider_test/sha512_test.rb +14 -0
  136. data/vendor/authlogic/test/fixtures/companies.yml +5 -0
  137. data/vendor/authlogic/test/fixtures/employees.yml +17 -0
  138. data/vendor/authlogic/test/fixtures/projects.yml +3 -0
  139. data/vendor/authlogic/test/fixtures/users.yml +24 -0
  140. data/vendor/authlogic/test/i18n_test.rb +33 -0
  141. data/vendor/authlogic/test/libs/affiliate.rb +7 -0
  142. data/vendor/authlogic/test/libs/company.rb +6 -0
  143. data/vendor/authlogic/test/libs/employee.rb +7 -0
  144. data/vendor/authlogic/test/libs/employee_session.rb +2 -0
  145. data/vendor/authlogic/test/libs/ldaper.rb +3 -0
  146. data/vendor/authlogic/test/libs/ordered_hash.rb +9 -0
  147. data/vendor/authlogic/test/libs/project.rb +3 -0
  148. data/vendor/authlogic/test/libs/user.rb +5 -0
  149. data/vendor/authlogic/test/libs/user_session.rb +6 -0
  150. data/vendor/authlogic/test/random_test.rb +49 -0
  151. data/vendor/authlogic/test/session_test/activation_test.rb +43 -0
  152. data/vendor/authlogic/test/session_test/active_record_trickery_test.rb +36 -0
  153. data/vendor/authlogic/test/session_test/brute_force_protection_test.rb +101 -0
  154. data/vendor/authlogic/test/session_test/callbacks_test.rb +6 -0
  155. data/vendor/authlogic/test/session_test/cookies_test.rb +112 -0
  156. data/vendor/authlogic/test/session_test/credentials_test.rb +0 -0
  157. data/vendor/authlogic/test/session_test/existence_test.rb +64 -0
  158. data/vendor/authlogic/test/session_test/http_auth_test.rb +28 -0
  159. data/vendor/authlogic/test/session_test/id_test.rb +17 -0
  160. data/vendor/authlogic/test/session_test/klass_test.rb +40 -0
  161. data/vendor/authlogic/test/session_test/magic_columns_test.rb +62 -0
  162. data/vendor/authlogic/test/session_test/magic_states_test.rb +60 -0
  163. data/vendor/authlogic/test/session_test/params_test.rb +53 -0
  164. data/vendor/authlogic/test/session_test/password_test.rb +106 -0
  165. data/vendor/authlogic/test/session_test/perishability_test.rb +15 -0
  166. data/vendor/authlogic/test/session_test/persistence_test.rb +21 -0
  167. data/vendor/authlogic/test/session_test/scopes_test.rb +60 -0
  168. data/vendor/authlogic/test/session_test/session_test.rb +59 -0
  169. data/vendor/authlogic/test/session_test/timeout_test.rb +52 -0
  170. data/vendor/authlogic/test/session_test/unauthorized_record_test.rb +13 -0
  171. data/vendor/authlogic/test/session_test/validation_test.rb +23 -0
  172. data/vendor/authlogic/test/test_helper.rb +182 -0
  173. metadata +325 -0
@@ -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,344 @@
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 crypted_password field in the database.
17
+ #
18
+ # * <tt>Default:</tt> :crypted_password, :encrypted_password, :password_hash, or :pw_hash
19
+ # * <tt>Accepts:</tt> Symbol
20
+ def crypted_password_field(value = nil)
21
+ rw_config(:crypted_password_field, value, first_column_to_exist(nil, :crypted_password, :encrypted_password, :password_hash, :pw_hash))
22
+ end
23
+ alias_method :crypted_password_field=, :crypted_password_field
24
+
25
+ # The name of the password_salt field in the database.
26
+ #
27
+ # * <tt>Default:</tt> :password_salt, :pw_salt, :salt, nil if none exist
28
+ # * <tt>Accepts:</tt> Symbol
29
+ def password_salt_field(value = nil)
30
+ rw_config(:password_salt_field, value, first_column_to_exist(nil, :password_salt, :pw_salt, :salt))
31
+ end
32
+ alias_method :password_salt_field=, :password_salt_field
33
+
34
+ # Whether or not to require a password confirmation. If you don't want your users to confirm their password
35
+ # just set this to false.
36
+ #
37
+ # * <tt>Default:</tt> true
38
+ # * <tt>Accepts:</tt> Boolean
39
+ def require_password_confirmation(value = nil)
40
+ rw_config(:require_password_confirmation, value, true)
41
+ end
42
+ alias_method :require_password_confirmation=, :require_password_confirmation
43
+
44
+ # By default passwords are required when a record is new or the crypted_password is blank, but if both of these things
45
+ # are met a password is not required. In this case, blank passwords are ignored.
46
+ #
47
+ # Think about a profile page, where the user can edit all of their information, including changing their password.
48
+ # If they do not want to change their password they just leave the fields blank. This will try to set the password to
49
+ # a blank value, in which case is incorrect behavior. As such, Authlogic ignores this. But let's say you have a completely
50
+ # separate page for resetting passwords, you might not want to ignore blank passwords. If this is the case for you, then
51
+ # just set this value to false.
52
+ #
53
+ # * <tt>Default:</tt> true
54
+ # * <tt>Accepts:</tt> Boolean
55
+ def ignore_blank_passwords(value = nil)
56
+ rw_config(:ignore_blank_passwords, value, true)
57
+ end
58
+ alias_method :ignore_blank_passwords=, :ignore_blank_passwords
59
+
60
+ # When calling valid_password?("some pass") do you want to check that password against what's in that object or whats in
61
+ # the datbase. Take this example:
62
+ #
63
+ # u = User.first
64
+ # u.password = "new pass"
65
+ # u.valid_password?("old pass")
66
+ #
67
+ # Should the last line above return true or false? The record hasn't been saved yet, so most would assume yes.
68
+ # Other would assume no. So I let you decide by giving you this option.
69
+ #
70
+ # * <tt>Default:</tt> true
71
+ # * <tt>Accepts:</tt> Boolean
72
+ def check_passwords_against_database(value = nil)
73
+ rw_config(:check_passwords_against_database, value, true)
74
+ end
75
+ alias_method :check_passwords_against_database=, :check_passwords_against_database
76
+
77
+ # Whether or not to validate the password field.
78
+ #
79
+ # * <tt>Default:</tt> true
80
+ # * <tt>Accepts:</tt> Boolean
81
+ def validate_password_field(value = nil)
82
+ rw_config(:validate_password_field, value, true)
83
+ end
84
+ alias_method :validate_password_field=, :validate_password_field
85
+
86
+ # A hash of options for the validates_length_of call for the password field. Allows you to change this however you want.
87
+ #
88
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
89
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
90
+ # options.</b>
91
+ #
92
+ # * <tt>Default:</tt> {:minimum => 4, :if => :require_password?}
93
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
94
+ def validates_length_of_password_field_options(value = nil)
95
+ rw_config(:validates_length_of_password_field_options, value, {:minimum => 4, :if => :require_password?})
96
+ end
97
+ alias_method :validates_length_of_password_field_options=, :validates_length_of_password_field_options
98
+
99
+ # A convenience function to merge options into the validates_length_of_login_field_options. So intead of:
100
+ #
101
+ # self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(:my_option => my_value)
102
+ #
103
+ # You can do this:
104
+ #
105
+ # merge_validates_length_of_password_field_options :my_option => my_value
106
+ def merge_validates_length_of_password_field_options(options = {})
107
+ self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(options)
108
+ end
109
+
110
+ # A hash of options for the validates_confirmation_of call for the password field. Allows you to change this however you want.
111
+ #
112
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
113
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
114
+ # options.</b>
115
+ #
116
+ # * <tt>Default:</tt> {:if => :require_password?}
117
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_confirmation_of
118
+ def validates_confirmation_of_password_field_options(value = nil)
119
+ rw_config(:validates_confirmation_of_password_field_options, value, {:if => :require_password?})
120
+ end
121
+ alias_method :validates_confirmation_of_password_field_options=, :validates_confirmation_of_password_field_options
122
+
123
+ # See merge_validates_length_of_password_field_options. The same thing, except for validates_confirmation_of_password_field_options
124
+ def merge_validates_confirmation_of_password_field_options(options = {})
125
+ self.validates_confirmation_of_password_field_options = validates_confirmation_of_password_field_options.merge(options)
126
+ end
127
+
128
+ # A hash of options for the validates_length_of call for the password_confirmation field. Allows you to change this however you want.
129
+ #
130
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
131
+ # merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
132
+ # options.</b>
133
+ #
134
+ # * <tt>Default:</tt> validates_length_of_password_field_options
135
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
136
+ def validates_length_of_password_confirmation_field_options(value = nil)
137
+ rw_config(:validates_length_of_password_confirmation_field_options, value, validates_length_of_password_field_options)
138
+ end
139
+ alias_method :validates_length_of_password_confirmation_field_options=, :validates_length_of_password_confirmation_field_options
140
+
141
+ # See merge_validates_length_of_password_field_options. The same thing, except for validates_length_of_password_confirmation_field_options
142
+ def merge_validates_length_of_password_confirmation_field_options(options = {})
143
+ self.validates_length_of_password_confirmation_field_options = validates_length_of_password_confirmation_field_options.merge(options)
144
+ end
145
+
146
+ # The class you want to use to encrypt and verify your encrypted passwords. See the Authlogic::CryptoProviders module for more info
147
+ # on the available methods and how to create your own.
148
+ #
149
+ # * <tt>Default:</tt> CryptoProviders::Sha512
150
+ # * <tt>Accepts:</tt> Class
151
+ def crypto_provider(value = nil)
152
+ rw_config(:crypto_provider, value, CryptoProviders::Sha512)
153
+ end
154
+ alias_method :crypto_provider=, :crypto_provider
155
+
156
+ # Let's say you originally encrypted your passwords with Sha1. Sha1 is starting to join the party with MD5 and you want to switch
157
+ # to something stronger. No problem, just specify your new and improved algorithm with the crypt_provider option and then let
158
+ # Authlogic know you are transitioning from Sha1 using this option. Authlogic will take care of everything, including transitioning
159
+ # your users to the new algorithm. The next time a user logs in, they will be granted access using the old algorithm and their
160
+ # password will be resaved with the new algorithm. All new users will obviously use the new algorithm as well.
161
+ #
162
+ # Lastly, if you want to transition again, you can pass an array of crypto providers. So you can transition from as many algorithms
163
+ # as you want.
164
+ #
165
+ # * <tt>Default:</tt> nil
166
+ # * <tt>Accepts:</tt> Class or Array
167
+ def transition_from_crypto_providers(value = nil)
168
+ rw_config(:transition_from_crypto_providers, (!value.nil? && [value].flatten.compact) || value, [])
169
+ end
170
+ alias_method :transition_from_crypto_providers=, :transition_from_crypto_providers
171
+ end
172
+
173
+ # Callbacks / hooks to allow other modules to modify the behavior of this module.
174
+ module Callbacks
175
+ METHODS = [
176
+ "before_password_set", "after_password_set",
177
+ "before_password_verification", "after_password_verification"
178
+ ]
179
+
180
+ def self.included(klass)
181
+ return if klass.crypted_password_field.nil?
182
+ klass.define_callbacks *METHODS
183
+ end
184
+
185
+ private
186
+ METHODS.each do |method|
187
+ class_eval <<-"end_eval", __FILE__, __LINE__
188
+ def #{method}
189
+ run_callbacks(:#{method}) { |result, object| result == false }
190
+ end
191
+ end_eval
192
+ end
193
+ end
194
+
195
+ # The methods related to the password field.
196
+ module Methods
197
+ def self.included(klass)
198
+ return if klass.crypted_password_field.nil?
199
+
200
+ klass.class_eval do
201
+ include InstanceMethods
202
+
203
+ if validate_password_field
204
+ validates_length_of :password, validates_length_of_password_field_options
205
+
206
+ if require_password_confirmation
207
+ validates_confirmation_of :password, validates_confirmation_of_password_field_options
208
+ validates_length_of :password_confirmation, validates_length_of_password_confirmation_field_options
209
+ end
210
+ end
211
+
212
+ after_save :reset_password_changed
213
+ end
214
+ end
215
+
216
+ module InstanceMethods
217
+ # The password
218
+ def password
219
+ @password
220
+ end
221
+
222
+ # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
223
+ # the password.
224
+ def password=(pass)
225
+ return if ignore_blank_passwords? && pass.blank?
226
+ before_password_set
227
+ @password = pass
228
+ send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
229
+ send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, false, act_like_restful_authentication? ? :restful_authentication : nil)))
230
+ @password_changed = true
231
+ after_password_set
232
+ end
233
+
234
+ # Accepts a raw password to determine if it is the correct password or not. Notice the second argument. That defaults to the value of
235
+ # check_passwords_against_database. See that method for mor information, but basically it just tells Authlogic to check the password
236
+ # against the value in the database or the value in the object.
237
+ def valid_password?(attempted_password, check_against_database = check_passwords_against_database?)
238
+ crypted = check_against_database && send("#{crypted_password_field}_changed?") ? send("#{crypted_password_field}_was") : send(crypted_password_field)
239
+ return false if attempted_password.blank? || crypted.blank?
240
+ before_password_verification
241
+
242
+ crypto_providers.each_with_index do |encryptor, index|
243
+ # The arguments_type of for the transitioning from restful_authentication
244
+ arguments_type = (act_like_restful_authentication? && index == 0) ||
245
+ (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
246
+ :restful_authentication : nil
247
+
248
+ if encryptor.matches?(crypted, *encrypt_arguments(attempted_password, check_against_database, arguments_type))
249
+ transition_password(attempted_password) if transition_password?(index, encryptor, crypted, check_against_database)
250
+ after_password_verification
251
+ return true
252
+ end
253
+ end
254
+
255
+ false
256
+ end
257
+
258
+ # Resets the password to a random friendly token.
259
+ def reset_password
260
+ friendly_token = Authlogic::Random.friendly_token
261
+ self.password = friendly_token
262
+ self.password_confirmation = friendly_token
263
+ end
264
+ alias_method :randomize_password, :reset_password
265
+
266
+ # Resets the password to a random friendly token and then saves the record.
267
+ def reset_password!
268
+ reset_password
269
+ save_without_session_maintenance(false)
270
+ end
271
+ alias_method :randomize_password!, :reset_password!
272
+
273
+ private
274
+ def check_passwords_against_database?
275
+ self.class.check_passwords_against_database == true
276
+ end
277
+
278
+ def crypto_providers
279
+ [crypto_provider] + transition_from_crypto_providers
280
+ end
281
+
282
+ def encrypt_arguments(raw_password, check_against_database, arguments_type = nil)
283
+ salt = nil
284
+ salt = (check_against_database && send("#{password_salt_field}_changed?") ? send("#{password_salt_field}_was") : send(password_salt_field)) if password_salt_field
285
+
286
+ case arguments_type
287
+ when :restful_authentication
288
+ [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
289
+ else
290
+ [raw_password, salt].compact
291
+ end
292
+ end
293
+
294
+ # Determines if we need to tranisiton the password.
295
+ # If the index > 0 then we are using an "transition from" crypto provider.
296
+ # If the encryptor has a cost and the cost it outdated.
297
+ # If we aren't using database values
298
+ # If we are using database values, only if the password hasnt change so we don't overwrite any changes
299
+ def transition_password?(index, encryptor, crypted, check_against_database)
300
+ (index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))) &&
301
+ (!check_against_database || !send("#{crypted_password_field}_changed?"))
302
+ end
303
+
304
+ def transition_password(attempted_password)
305
+ self.password = attempted_password
306
+ save(false)
307
+ end
308
+
309
+ def require_password?
310
+ new_record? || password_changed? || send(crypted_password_field).blank?
311
+ end
312
+
313
+ def ignore_blank_passwords?
314
+ self.class.ignore_blank_passwords == true
315
+ end
316
+
317
+ def password_changed?
318
+ @password_changed == true
319
+ end
320
+
321
+ def reset_password_changed
322
+ @password_changed = nil
323
+ end
324
+
325
+ def crypted_password_field
326
+ self.class.crypted_password_field
327
+ end
328
+
329
+ def password_salt_field
330
+ self.class.password_salt_field
331
+ end
332
+
333
+ def crypto_provider
334
+ self.class.crypto_provider
335
+ end
336
+
337
+ def transition_from_crypto_providers
338
+ self.class.transition_from_crypto_providers
339
+ end
340
+ end
341
+ end
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,105 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # This provides a handy token that is "perishable". Meaning the token is only good for a certain amount of time. This is perfect for
4
+ # resetting password, confirming accounts, etc. Typically during these actions you send them this token in via their email. Once they
5
+ # 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
6
+ # yourself. Authlogic does all of this for you. See the sub modules for all of the tools Authlogic provides to you.
7
+ module PerishableToken
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
+ # Change how the perishable token works.
16
+ module Config
17
+ # When using the find_using_perishable_token method the token can expire. If the token is expired, no
18
+ # record will be returned. Use this option to specify how long the token is valid for.
19
+ #
20
+ # * <tt>Default:</tt> 10.minutes
21
+ # * <tt>Accepts:</tt> Fixnum
22
+ def perishable_token_valid_for(value = nil)
23
+ rw_config(:perishable_token_valid_for, (!value.nil? && value.to_i) || value, 10.minutes.to_i)
24
+ end
25
+ alias_method :perishable_token_valid_for=, :perishable_token_valid_for
26
+
27
+ # Authlogic tries to expire and change the perishable token as much as possible, without comprising
28
+ # it's purpose. This is for security reasons. If you want to manage it yourself, you can stop
29
+ # Authlogic from getting your in way by setting this to true.
30
+ #
31
+ # * <tt>Default:</tt> false
32
+ # * <tt>Accepts:</tt> Boolean
33
+ def disable_perishable_token_maintenance(value = nil)
34
+ rw_config(:disable_perishable_token_maintenance, value, false)
35
+ end
36
+ alias_method :disable_perishable_token_maintenance=, :disable_perishable_token_maintenance
37
+ end
38
+
39
+ # All methods relating to the perishable token.
40
+ module Methods
41
+ def self.included(klass)
42
+ return if !klass.column_names.include?("perishable_token")
43
+
44
+ klass.class_eval do
45
+ extend ClassMethods
46
+ include InstanceMethods
47
+
48
+ validates_uniqueness_of :perishable_token, :if => :perishable_token_changed?
49
+ before_save :reset_perishable_token, :unless => :disable_perishable_token_maintenance?
50
+ end
51
+ end
52
+
53
+ # Class level methods for the perishable token
54
+ module ClassMethods
55
+ # Use this methdo to find a record with a perishable token. This method does 2 things for you:
56
+ #
57
+ # 1. It ignores blank tokens
58
+ # 2. It enforces the perishable_token_valid_for configuration option.
59
+ #
60
+ # If you want to use a different timeout value, just pass it as the second parameter:
61
+ #
62
+ # User.find_using_perishable_token(token, 1.hour)
63
+ def find_using_perishable_token(token, age = self.perishable_token_valid_for)
64
+ return if token.blank?
65
+ age = age.to_i
66
+
67
+ conditions_sql = "perishable_token = ?"
68
+ conditions_subs = [token]
69
+
70
+ if column_names.include?("updated_at") && age > 0
71
+ conditions_sql += " and updated_at > ?"
72
+ conditions_subs << age.seconds.ago
73
+ end
74
+
75
+ find(:first, :conditions => [conditions_sql, *conditions_subs])
76
+ end
77
+
78
+ # This method will raise ActiveRecord::NotFound if no record is found.
79
+ def find_using_perishable_token!(token, age = perishable_token_valid_for)
80
+ find_using_perishable_token(token, age) || raise(ActiveRecord::RecordNotFound)
81
+ end
82
+ end
83
+
84
+ # Instance level methods for the perishable token.
85
+ module InstanceMethods
86
+ # Resets the perishable token to a random friendly token.
87
+ def reset_perishable_token
88
+ self.perishable_token = Random.friendly_token
89
+ end
90
+
91
+ # Same as reset_perishable_token, but then saves the record afterwards.
92
+ def reset_perishable_token!
93
+ reset_perishable_token
94
+ save_without_session_maintenance(false)
95
+ end
96
+
97
+ # A convenience method based on the disable_perishable_token_maintenance configuration option.
98
+ def disable_perishable_token_maintenance?
99
+ self.class.disable_perishable_token_maintenance == true
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end