kschrader-authlogic 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +9 -0
- data/CHANGELOG.rdoc +346 -0
- data/LICENSE +20 -0
- data/README.rdoc +245 -0
- data/Rakefile +49 -0
- data/VERSION.yml +4 -0
- data/authlogic.gemspec +205 -0
- data/generators/session/session_generator.rb +9 -0
- data/generators/session/templates/session.rb +2 -0
- data/init.rb +1 -0
- data/lib/authlogic.rb +55 -0
- data/lib/authlogic/acts_as_authentic/base.rb +112 -0
- data/lib/authlogic/acts_as_authentic/email.rb +110 -0
- data/lib/authlogic/acts_as_authentic/logged_in_status.rb +60 -0
- data/lib/authlogic/acts_as_authentic/login.rb +141 -0
- data/lib/authlogic/acts_as_authentic/magic_columns.rb +24 -0
- data/lib/authlogic/acts_as_authentic/password.rb +344 -0
- data/lib/authlogic/acts_as_authentic/perishable_token.rb +105 -0
- data/lib/authlogic/acts_as_authentic/persistence_token.rb +68 -0
- data/lib/authlogic/acts_as_authentic/restful_authentication.rb +61 -0
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +139 -0
- data/lib/authlogic/acts_as_authentic/single_access_token.rb +65 -0
- data/lib/authlogic/acts_as_authentic/validations_scope.rb +32 -0
- data/lib/authlogic/authenticates_many/association.rb +42 -0
- data/lib/authlogic/authenticates_many/base.rb +55 -0
- data/lib/authlogic/controller_adapters/abstract_adapter.rb +67 -0
- data/lib/authlogic/controller_adapters/merb_adapter.rb +30 -0
- data/lib/authlogic/controller_adapters/rails_adapter.rb +48 -0
- data/lib/authlogic/crypto_providers/aes256.rb +43 -0
- data/lib/authlogic/crypto_providers/bcrypt.rb +89 -0
- data/lib/authlogic/crypto_providers/md5.rb +34 -0
- data/lib/authlogic/crypto_providers/sha1.rb +35 -0
- data/lib/authlogic/crypto_providers/sha512.rb +50 -0
- data/lib/authlogic/i18n.rb +63 -0
- data/lib/authlogic/random.rb +33 -0
- data/lib/authlogic/regex.rb +25 -0
- data/lib/authlogic/session/activation.rb +58 -0
- data/lib/authlogic/session/active_record_trickery.rb +55 -0
- data/lib/authlogic/session/base.rb +37 -0
- data/lib/authlogic/session/brute_force_protection.rb +92 -0
- data/lib/authlogic/session/callbacks.rb +87 -0
- data/lib/authlogic/session/cookies.rb +130 -0
- data/lib/authlogic/session/existence.rb +93 -0
- data/lib/authlogic/session/foundation.rb +63 -0
- data/lib/authlogic/session/http_auth.rb +58 -0
- data/lib/authlogic/session/id.rb +41 -0
- data/lib/authlogic/session/klass.rb +75 -0
- data/lib/authlogic/session/magic_columns.rb +94 -0
- data/lib/authlogic/session/magic_states.rb +58 -0
- data/lib/authlogic/session/params.rb +100 -0
- data/lib/authlogic/session/password.rb +231 -0
- data/lib/authlogic/session/perishable_token.rb +18 -0
- data/lib/authlogic/session/persistence.rb +70 -0
- data/lib/authlogic/session/priority_record.rb +34 -0
- data/lib/authlogic/session/scopes.rb +101 -0
- data/lib/authlogic/session/session.rb +60 -0
- data/lib/authlogic/session/timeout.rb +82 -0
- data/lib/authlogic/session/unauthorized_record.rb +50 -0
- data/lib/authlogic/session/validation.rb +80 -0
- data/lib/authlogic/test_case.rb +114 -0
- data/lib/authlogic/test_case/mock_controller.rb +45 -0
- data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
- data/lib/authlogic/test_case/mock_logger.rb +10 -0
- data/lib/authlogic/test_case/mock_request.rb +19 -0
- data/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
- data/rails/init.rb +1 -0
- data/shoulda_macros/authlogic.rb +13 -0
- data/test/acts_as_authentic_test/base_test.rb +18 -0
- data/test/acts_as_authentic_test/email_test.rb +97 -0
- data/test/acts_as_authentic_test/logged_in_status_test.rb +36 -0
- data/test/acts_as_authentic_test/login_test.rb +109 -0
- data/test/acts_as_authentic_test/magic_columns_test.rb +27 -0
- data/test/acts_as_authentic_test/password_test.rb +236 -0
- data/test/acts_as_authentic_test/perishable_token_test.rb +90 -0
- data/test/acts_as_authentic_test/persistence_token_test.rb +55 -0
- data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
- data/test/acts_as_authentic_test/session_maintenance_test.rb +84 -0
- data/test/acts_as_authentic_test/single_access_test.rb +44 -0
- data/test/authenticates_many_test.rb +16 -0
- data/test/crypto_provider_test/aes256_test.rb +14 -0
- data/test/crypto_provider_test/bcrypt_test.rb +14 -0
- data/test/crypto_provider_test/sha1_test.rb +23 -0
- data/test/crypto_provider_test/sha512_test.rb +14 -0
- data/test/fixtures/companies.yml +5 -0
- data/test/fixtures/employees.yml +17 -0
- data/test/fixtures/projects.yml +3 -0
- data/test/fixtures/users.yml +24 -0
- data/test/libs/affiliate.rb +7 -0
- data/test/libs/company.rb +6 -0
- data/test/libs/employee.rb +7 -0
- data/test/libs/employee_session.rb +2 -0
- data/test/libs/ldaper.rb +3 -0
- data/test/libs/ordered_hash.rb +9 -0
- data/test/libs/project.rb +3 -0
- data/test/libs/user.rb +5 -0
- data/test/libs/user_session.rb +2 -0
- data/test/random_test.rb +49 -0
- data/test/session_test/activation_test.rb +43 -0
- data/test/session_test/active_record_trickery_test.rb +27 -0
- data/test/session_test/brute_force_protection_test.rb +101 -0
- data/test/session_test/callbacks_test.rb +6 -0
- data/test/session_test/cookies_test.rb +107 -0
- data/test/session_test/credentials_test.rb +0 -0
- data/test/session_test/existence_test.rb +64 -0
- data/test/session_test/http_auth_test.rb +28 -0
- data/test/session_test/id_test.rb +17 -0
- data/test/session_test/klass_test.rb +35 -0
- data/test/session_test/magic_columns_test.rb +62 -0
- data/test/session_test/magic_states_test.rb +60 -0
- data/test/session_test/params_test.rb +53 -0
- data/test/session_test/password_test.rb +106 -0
- data/test/session_test/perishability_test.rb +15 -0
- data/test/session_test/persistence_test.rb +21 -0
- data/test/session_test/scopes_test.rb +60 -0
- data/test/session_test/session_test.rb +59 -0
- data/test/session_test/timeout_test.rb +52 -0
- data/test/session_test/unauthorized_record_test.rb +13 -0
- data/test/session_test/validation_test.rb +23 -0
- data/test/test_helper.rb +174 -0
- metadata +229 -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,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,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
|