railsware-authlogic 2.1.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG.rdoc +345 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +246 -0
  5. data/Rakefile +41 -0
  6. data/VERSION.yml +5 -0
  7. data/authlogic.gemspec +216 -0
  8. data/generators/session/session_generator.rb +9 -0
  9. data/generators/session/templates/session.rb +2 -0
  10. data/init.rb +1 -0
  11. data/lib/authlogic.rb +64 -0
  12. data/lib/authlogic/acts_as_authentic/base.rb +107 -0
  13. data/lib/authlogic/acts_as_authentic/email.rb +110 -0
  14. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +65 -0
  15. data/lib/authlogic/acts_as_authentic/login.rb +141 -0
  16. data/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
  17. data/lib/authlogic/acts_as_authentic/password.rb +355 -0
  18. data/lib/authlogic/acts_as_authentic/perishable_token.rb +105 -0
  19. data/lib/authlogic/acts_as_authentic/persistence_token.rb +68 -0
  20. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +61 -0
  21. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +139 -0
  22. data/lib/authlogic/acts_as_authentic/single_access_token.rb +65 -0
  23. data/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
  24. data/lib/authlogic/authenticates_many/association.rb +42 -0
  25. data/lib/authlogic/authenticates_many/base.rb +55 -0
  26. data/lib/authlogic/controller_adapters/abstract_adapter.rb +67 -0
  27. data/lib/authlogic/controller_adapters/merb_adapter.rb +30 -0
  28. data/lib/authlogic/controller_adapters/rails_adapter.rb +48 -0
  29. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +61 -0
  30. data/lib/authlogic/crypto_providers/aes256.rb +43 -0
  31. data/lib/authlogic/crypto_providers/bcrypt.rb +90 -0
  32. data/lib/authlogic/crypto_providers/md5.rb +34 -0
  33. data/lib/authlogic/crypto_providers/sha1.rb +35 -0
  34. data/lib/authlogic/crypto_providers/sha256.rb +50 -0
  35. data/lib/authlogic/crypto_providers/sha512.rb +50 -0
  36. data/lib/authlogic/crypto_providers/wordpress.rb +43 -0
  37. data/lib/authlogic/i18n.rb +83 -0
  38. data/lib/authlogic/i18n/translator.rb +15 -0
  39. data/lib/authlogic/random.rb +33 -0
  40. data/lib/authlogic/regex.rb +25 -0
  41. data/lib/authlogic/session/activation.rb +58 -0
  42. data/lib/authlogic/session/active_record_trickery.rb +64 -0
  43. data/lib/authlogic/session/base.rb +37 -0
  44. data/lib/authlogic/session/brute_force_protection.rb +96 -0
  45. data/lib/authlogic/session/callbacks.rb +99 -0
  46. data/lib/authlogic/session/cookies.rb +130 -0
  47. data/lib/authlogic/session/existence.rb +93 -0
  48. data/lib/authlogic/session/foundation.rb +71 -0
  49. data/lib/authlogic/session/http_auth.rb +58 -0
  50. data/lib/authlogic/session/id.rb +41 -0
  51. data/lib/authlogic/session/klass.rb +78 -0
  52. data/lib/authlogic/session/magic_columns.rb +95 -0
  53. data/lib/authlogic/session/magic_states.rb +59 -0
  54. data/lib/authlogic/session/params.rb +101 -0
  55. data/lib/authlogic/session/password.rb +240 -0
  56. data/lib/authlogic/session/perishable_token.rb +18 -0
  57. data/lib/authlogic/session/persistence.rb +70 -0
  58. data/lib/authlogic/session/priority_record.rb +34 -0
  59. data/lib/authlogic/session/scopes.rb +101 -0
  60. data/lib/authlogic/session/session.rb +62 -0
  61. data/lib/authlogic/session/timeout.rb +82 -0
  62. data/lib/authlogic/session/unauthorized_record.rb +50 -0
  63. data/lib/authlogic/session/validation.rb +82 -0
  64. data/lib/authlogic/test_case.rb +120 -0
  65. data/lib/authlogic/test_case/mock_controller.rb +45 -0
  66. data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
  67. data/lib/authlogic/test_case/mock_logger.rb +10 -0
  68. data/lib/authlogic/test_case/mock_request.rb +19 -0
  69. data/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
  70. data/rails/init.rb +1 -0
  71. data/shoulda_macros/authlogic.rb +69 -0
  72. data/test/acts_as_authentic_test/base_test.rb +18 -0
  73. data/test/acts_as_authentic_test/email_test.rb +101 -0
  74. data/test/acts_as_authentic_test/logged_in_status_test.rb +36 -0
  75. data/test/acts_as_authentic_test/login_test.rb +109 -0
  76. data/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
  77. data/test/acts_as_authentic_test/password_test.rb +236 -0
  78. data/test/acts_as_authentic_test/perishable_token_test.rb +90 -0
  79. data/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
  80. data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
  81. data/test/acts_as_authentic_test/session_maintenance_test.rb +84 -0
  82. data/test/acts_as_authentic_test/single_access_test.rb +44 -0
  83. data/test/authenticates_many_test.rb +16 -0
  84. data/test/crypto_provider_test/aes256_test.rb +14 -0
  85. data/test/crypto_provider_test/bcrypt_test.rb +14 -0
  86. data/test/crypto_provider_test/sha1_test.rb +23 -0
  87. data/test/crypto_provider_test/sha256_test.rb +14 -0
  88. data/test/crypto_provider_test/sha512_test.rb +14 -0
  89. data/test/fixtures/companies.yml +5 -0
  90. data/test/fixtures/employees.yml +17 -0
  91. data/test/fixtures/projects.yml +3 -0
  92. data/test/fixtures/users.yml +24 -0
  93. data/test/i18n_test.rb +33 -0
  94. data/test/libs/affiliate.rb +7 -0
  95. data/test/libs/company.rb +6 -0
  96. data/test/libs/employee.rb +7 -0
  97. data/test/libs/employee_session.rb +2 -0
  98. data/test/libs/ldaper.rb +3 -0
  99. data/test/libs/ordered_hash.rb +9 -0
  100. data/test/libs/project.rb +3 -0
  101. data/test/libs/user.rb +5 -0
  102. data/test/libs/user_session.rb +6 -0
  103. data/test/random_test.rb +42 -0
  104. data/test/session_test/activation_test.rb +43 -0
  105. data/test/session_test/active_record_trickery_test.rb +36 -0
  106. data/test/session_test/brute_force_protection_test.rb +101 -0
  107. data/test/session_test/callbacks_test.rb +6 -0
  108. data/test/session_test/cookies_test.rb +112 -0
  109. data/test/session_test/credentials_test.rb +0 -0
  110. data/test/session_test/existence_test.rb +64 -0
  111. data/test/session_test/http_auth_test.rb +28 -0
  112. data/test/session_test/id_test.rb +17 -0
  113. data/test/session_test/klass_test.rb +40 -0
  114. data/test/session_test/magic_columns_test.rb +62 -0
  115. data/test/session_test/magic_states_test.rb +60 -0
  116. data/test/session_test/params_test.rb +53 -0
  117. data/test/session_test/password_test.rb +106 -0
  118. data/test/session_test/perishability_test.rb +15 -0
  119. data/test/session_test/persistence_test.rb +21 -0
  120. data/test/session_test/scopes_test.rb +60 -0
  121. data/test/session_test/session_test.rb +59 -0
  122. data/test/session_test/timeout_test.rb +52 -0
  123. data/test/session_test/unauthorized_record_test.rb +13 -0
  124. data/test/session_test/validation_test.rb +23 -0
  125. data/test/test_helper.rb +182 -0
  126. metadata +255 -0
@@ -0,0 +1,110 @@
1
+ module Authlogic
2
+ module ActsAsAuthentic
3
+ # Sometimes models won't have an explicit "login" or "username" field. Instead they want to use the email field.
4
+ # In this case, authlogic provides validations to make sure the email submited is actually a valid email. Don't worry,
5
+ # if you do have a login or username field, Authlogic will still validate your email field. One less thing you have to
6
+ # worry about.
7
+ module Email
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
+ # Configuration to modify how Authlogic handles the email field.
16
+ module Config
17
+ # The name of the field that stores email addresses.
18
+ #
19
+ # * <tt>Default:</tt> :email, if it exists
20
+ # * <tt>Accepts:</tt> Symbol
21
+ def email_field(value = nil)
22
+ rw_config(:email_field, value, first_column_to_exist(nil, :email, :email_address))
23
+ end
24
+ alias_method :email_field=, :email_field
25
+
26
+ # Toggles validating the email field or not.
27
+ #
28
+ # * <tt>Default:</tt> true
29
+ # * <tt>Accepts:</tt> Boolean
30
+ def validate_email_field(value = nil)
31
+ rw_config(:validate_email_field, value, true)
32
+ end
33
+ alias_method :validate_email_field=, :validate_email_field
34
+
35
+ # A hash of options for the validates_length_of call for the email field. Allows you to change this however you want.
36
+ #
37
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
38
+ # merge options into it. Checkout the convenience function merge_validates_length_of_email_field_options to merge
39
+ # options.</b>
40
+ #
41
+ # * <tt>Default:</tt> {:within => 6..100}
42
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
43
+ def validates_length_of_email_field_options(value = nil)
44
+ rw_config(:validates_length_of_email_field_options, value, {:within => 6..100})
45
+ end
46
+ alias_method :validates_length_of_email_field_options=, :validates_length_of_email_field_options
47
+
48
+ # A convenience function to merge options into the validates_length_of_email_field_options. So intead of:
49
+ #
50
+ # self.validates_length_of_email_field_options = validates_length_of_email_field_options.merge(:my_option => my_value)
51
+ #
52
+ # You can do this:
53
+ #
54
+ # merge_validates_length_of_email_field_options :my_option => my_value
55
+ def merge_validates_length_of_email_field_options(options = {})
56
+ self.validates_length_of_email_field_options = validates_length_of_email_field_options.merge(options)
57
+ end
58
+
59
+ # A hash of options for the validates_format_of call for the email field. Allows you to change this however you want.
60
+ #
61
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
62
+ # merge options into it. Checkout the convenience function merge_validates_format_of_email_field_options to merge
63
+ # options.</b>
64
+ #
65
+ # * <tt>Default:</tt> {:with => Authlogic::Regex.email, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")}
66
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_format_of
67
+ def validates_format_of_email_field_options(value = nil)
68
+ rw_config(:validates_format_of_email_field_options, value, {:with => Authlogic::Regex.email, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")})
69
+ end
70
+ alias_method :validates_format_of_email_field_options=, :validates_format_of_email_field_options
71
+
72
+ # See merge_validates_length_of_email_field_options. The same thing except for validates_format_of_email_field_options.
73
+ def merge_validates_format_of_email_field_options(options = {})
74
+ self.validates_format_of_email_field_options = validates_format_of_email_field_options.merge(options)
75
+ end
76
+
77
+ # A hash of options for the validates_uniqueness_of call for the email field. Allows you to change this however you want.
78
+ #
79
+ # <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
80
+ # merge options into it. Checkout the convenience function merge_validates_uniqueness_of_email_field_options to merge
81
+ # options.</b>
82
+ #
83
+ # * <tt>Default:</tt> {:case_sensitive => false, :scope => validations_scope, :if => "#{email_field}_changed?".to_sym}
84
+ # * <tt>Accepts:</tt> Hash of options accepted by validates_uniqueness_of
85
+ def validates_uniqueness_of_email_field_options(value = nil)
86
+ rw_config(:validates_uniqueness_of_email_field_options, value, {:case_sensitive => false, :scope => validations_scope, :if => "#{email_field}_changed?".to_sym})
87
+ end
88
+ alias_method :validates_uniqueness_of_email_field_options=, :validates_uniqueness_of_email_field_options
89
+
90
+ # See merge_validates_length_of_email_field_options. The same thing except for validates_uniqueness_of_email_field_options.
91
+ def merge_validates_uniqueness_of_email_field_options(options = {})
92
+ self.validates_uniqueness_of_email_field_options = validates_uniqueness_of_email_field_options.merge(options)
93
+ end
94
+ end
95
+
96
+ # All methods relating to the email field
97
+ module Methods
98
+ def self.included(klass)
99
+ klass.class_eval do
100
+ if validate_email_field && email_field
101
+ validates_length_of email_field, validates_length_of_email_field_options
102
+ validates_format_of email_field, validates_format_of_email_field_options
103
+ validates_uniqueness_of email_field, validates_uniqueness_of_email_field_options
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,65 @@
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
+ if ActiveRecord::VERSION::MAJOR < 3
36
+ named_scope :logged_in, lambda { {:conditions => ["last_request_at > ?", logged_in_timeout.seconds.ago]} }
37
+ named_scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", logged_in_timeout.seconds.ago]} }
38
+ else
39
+ scope :logged_in, lambda { {:conditions => ["last_request_at > ?", logged_in_timeout.seconds.ago]} }
40
+ scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", logged_in_timeout.seconds.ago]} }
41
+ end
42
+ end
43
+ end
44
+
45
+ module InstanceMethods
46
+ # Returns true if the last_request_at > logged_in_timeout.
47
+ def logged_in?
48
+ raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at)
49
+ !last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
50
+ end
51
+
52
+ # Opposite of logged_in?
53
+ def logged_out?
54
+ !logged_in?
55
+ end
56
+
57
+ private
58
+ def logged_in_timeout
59
+ self.class.logged_in_timeout
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ 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,355 @@
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 true.
68
+ # Other would assume false. 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
+
184
+ # If Rails 3, support the new callback syntax
185
+ if klass.send(klass.respond_to?(:singleton_class) ? :singleton_class : :metaclass).method_defined?(:set_callback)
186
+ METHODS.each do |method|
187
+ klass.class_eval <<-"end_eval", __FILE__, __LINE__
188
+ def self.#{method}(*methods, &block)
189
+ set_callback :#{method}, *methods, &block
190
+ end
191
+ end_eval
192
+ end
193
+ end
194
+ end
195
+
196
+ private
197
+ METHODS.each do |method|
198
+ class_eval <<-"end_eval", __FILE__, __LINE__
199
+ def #{method}
200
+ run_callbacks(:#{method}) { |result, object| result == false }
201
+ end
202
+ end_eval
203
+ end
204
+ end
205
+
206
+ # The methods related to the password field.
207
+ module Methods
208
+ def self.included(klass)
209
+ return if klass.crypted_password_field.nil?
210
+
211
+ klass.class_eval do
212
+ include InstanceMethods
213
+
214
+ if validate_password_field
215
+ validates_length_of :password, validates_length_of_password_field_options
216
+
217
+ if require_password_confirmation
218
+ validates_confirmation_of :password, validates_confirmation_of_password_field_options
219
+ validates_length_of :password_confirmation, validates_length_of_password_confirmation_field_options
220
+ end
221
+ end
222
+
223
+ after_save :reset_password_changed
224
+ end
225
+ end
226
+
227
+ module InstanceMethods
228
+ # The password
229
+ def password
230
+ @password
231
+ end
232
+
233
+ # This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
234
+ # the password.
235
+ def password=(pass)
236
+ return if ignore_blank_passwords? && pass.blank?
237
+ before_password_set
238
+ @password = pass
239
+ send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
240
+ send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, false, act_like_restful_authentication? ? :restful_authentication : nil)))
241
+ @password_changed = true
242
+ after_password_set
243
+ end
244
+
245
+ # Accepts a raw password to determine if it is the correct password or not. Notice the second argument. That defaults to the value of
246
+ # check_passwords_against_database. See that method for mor information, but basically it just tells Authlogic to check the password
247
+ # against the value in the database or the value in the object.
248
+ def valid_password?(attempted_password, check_against_database = check_passwords_against_database?)
249
+ crypted = check_against_database && send("#{crypted_password_field}_changed?") ? send("#{crypted_password_field}_was") : send(crypted_password_field)
250
+ return false if attempted_password.blank? || crypted.blank?
251
+ before_password_verification
252
+
253
+ crypto_providers.each_with_index do |encryptor, index|
254
+ # The arguments_type of for the transitioning from restful_authentication
255
+ arguments_type = (act_like_restful_authentication? && index == 0) ||
256
+ (transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
257
+ :restful_authentication : nil
258
+
259
+ if encryptor.matches?(crypted, *encrypt_arguments(attempted_password, check_against_database, arguments_type))
260
+ transition_password(attempted_password) if transition_password?(index, encryptor, crypted, check_against_database)
261
+ after_password_verification
262
+ return true
263
+ end
264
+ end
265
+
266
+ false
267
+ end
268
+
269
+ # Resets the password to a random friendly token.
270
+ def reset_password
271
+ friendly_token = Authlogic::Random.friendly_token
272
+ self.password = friendly_token
273
+ self.password_confirmation = friendly_token
274
+ end
275
+ alias_method :randomize_password, :reset_password
276
+
277
+ # Resets the password to a random friendly token and then saves the record.
278
+ def reset_password!
279
+ reset_password
280
+ save_without_session_maintenance(false)
281
+ end
282
+ alias_method :randomize_password!, :reset_password!
283
+
284
+ private
285
+ def check_passwords_against_database?
286
+ self.class.check_passwords_against_database == true
287
+ end
288
+
289
+ def crypto_providers
290
+ [crypto_provider] + transition_from_crypto_providers
291
+ end
292
+
293
+ def encrypt_arguments(raw_password, check_against_database, arguments_type = nil)
294
+ salt = nil
295
+ salt = (check_against_database && send("#{password_salt_field}_changed?") ? send("#{password_salt_field}_was") : send(password_salt_field)) if password_salt_field
296
+
297
+ case arguments_type
298
+ when :restful_authentication
299
+ [REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
300
+ else
301
+ [raw_password, salt].compact
302
+ end
303
+ end
304
+
305
+ # Determines if we need to tranisiton the password.
306
+ # If the index > 0 then we are using an "transition from" crypto provider.
307
+ # If the encryptor has a cost and the cost it outdated.
308
+ # If we aren't using database values
309
+ # If we are using database values, only if the password hasnt change so we don't overwrite any changes
310
+ def transition_password?(index, encryptor, crypted, check_against_database)
311
+ (index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))) &&
312
+ (!check_against_database || !send("#{crypted_password_field}_changed?"))
313
+ end
314
+
315
+ def transition_password(attempted_password)
316
+ self.password = attempted_password
317
+ save(ActiveRecord::VERSION::MAJOR < 3 ? false : { :validate => false })
318
+ end
319
+
320
+ def require_password?
321
+ new_record? || password_changed? || send(crypted_password_field).blank?
322
+ end
323
+
324
+ def ignore_blank_passwords?
325
+ self.class.ignore_blank_passwords == true
326
+ end
327
+
328
+ def password_changed?
329
+ @password_changed == true
330
+ end
331
+
332
+ def reset_password_changed
333
+ @password_changed = nil
334
+ end
335
+
336
+ def crypted_password_field
337
+ self.class.crypted_password_field
338
+ end
339
+
340
+ def password_salt_field
341
+ self.class.password_salt_field
342
+ end
343
+
344
+ def crypto_provider
345
+ self.class.crypto_provider
346
+ end
347
+
348
+ def transition_from_crypto_providers
349
+ self.class.transition_from_crypto_providers
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end
355
+ end