authlogic 2.0.1 → 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 +110 -1
- data/{MIT-LICENSE → LICENSE} +1 -1
- data/README.rdoc +138 -161
- data/Rakefile +40 -19
- data/VERSION.yml +4 -0
- data/authlogic.gemspec +212 -0
- data/init.rb +1 -1
- data/lib/authlogic/acts_as_authentic/base.rb +44 -35
- data/lib/authlogic/acts_as_authentic/email.rb +47 -14
- data/lib/authlogic/acts_as_authentic/logged_in_status.rb +20 -14
- data/lib/authlogic/acts_as_authentic/login.rb +84 -8
- data/lib/authlogic/acts_as_authentic/password.rb +207 -88
- data/lib/authlogic/acts_as_authentic/perishable_token.rb +9 -4
- data/lib/authlogic/acts_as_authentic/persistence_token.rb +4 -2
- data/lib/authlogic/acts_as_authentic/restful_authentication.rb +5 -4
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +16 -4
- data/lib/authlogic/acts_as_authentic/single_access_token.rb +10 -3
- data/lib/authlogic/acts_as_authentic/validations_scope.rb +1 -1
- data/lib/authlogic/controller_adapters/abstract_adapter.rb +13 -1
- data/lib/authlogic/controller_adapters/rails_adapter.rb +14 -4
- data/lib/authlogic/controller_adapters/sinatra_adapter.rb +61 -0
- data/lib/authlogic/crypto_providers/aes256.rb +2 -2
- data/lib/authlogic/crypto_providers/bcrypt.rb +3 -1
- data/lib/authlogic/crypto_providers/sha1.rb +2 -2
- data/lib/authlogic/i18n/translator.rb +15 -0
- data/lib/authlogic/i18n.rb +43 -14
- data/lib/authlogic/regex.rb +25 -0
- data/lib/authlogic/session/activation.rb +2 -0
- data/lib/authlogic/session/active_record_trickery.rb +26 -5
- data/lib/authlogic/session/brute_force_protection.rb +30 -11
- data/lib/authlogic/session/callbacks.rb +18 -7
- data/lib/authlogic/session/cookies.rb +21 -17
- data/lib/authlogic/session/existence.rb +6 -2
- data/lib/authlogic/session/foundation.rb +1 -1
- data/lib/authlogic/session/http_auth.rb +46 -11
- data/lib/authlogic/session/magic_columns.rb +29 -9
- data/lib/authlogic/session/magic_states.rb +6 -5
- data/lib/authlogic/session/params.rb +16 -10
- data/lib/authlogic/session/password.rb +115 -31
- data/lib/authlogic/session/priority_record.rb +1 -1
- data/lib/authlogic/session/session.rb +5 -3
- data/lib/authlogic/session/timeout.rb +1 -1
- data/lib/authlogic/session/validation.rb +13 -7
- 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/lib/authlogic/test_case.rb +114 -0
- data/lib/authlogic.rb +4 -3
- data/rails/init.rb +1 -0
- data/shoulda_macros/authlogic.rb +3 -2
- data/test/acts_as_authentic_test/base_test.rb +6 -0
- data/test/acts_as_authentic_test/email_test.rb +25 -7
- data/test/acts_as_authentic_test/login_test.rb +37 -7
- data/test/acts_as_authentic_test/magic_columns_test.rb +4 -4
- data/test/acts_as_authentic_test/password_test.rb +46 -44
- data/test/acts_as_authentic_test/perishable_token_test.rb +36 -2
- data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
- data/test/acts_as_authentic_test/session_maintenance_test.rb +26 -18
- data/test/acts_as_authentic_test/single_access_test.rb +6 -1
- data/test/i18n_test.rb +33 -0
- data/test/libs/affiliate.rb +7 -0
- data/test/libs/ldaper.rb +3 -0
- data/test/random_test.rb +2 -2
- data/test/session_test/activation_test.rb +1 -1
- data/test/session_test/active_record_trickery_test.rb +12 -2
- data/test/session_test/brute_force_protection_test.rb +37 -12
- data/test/session_test/cookies_test.rb +3 -3
- data/test/session_test/existence_test.rb +3 -3
- data/test/session_test/http_auth_test.rb +20 -8
- data/test/session_test/magic_columns_test.rb +9 -6
- data/test/session_test/magic_states_test.rb +3 -3
- data/test/session_test/params_test.rb +4 -4
- data/test/session_test/password_test.rb +23 -1
- data/test/session_test/session_test.rb +9 -9
- data/test/session_test/timeout_test.rb +10 -1
- data/test/test_helper.rb +53 -23
- metadata +42 -27
- data/Manifest.txt +0 -111
- data/lib/authlogic/testing/test_unit_helpers.rb +0 -39
- data/lib/authlogic/version.rb +0 -56
- data/test/libs/mock_controller.rb +0 -35
- data/test/libs/mock_cookie_jar.rb +0 -10
- data/test/libs/mock_request.rb +0 -5
|
@@ -1,23 +1,58 @@
|
|
|
1
1
|
module Authlogic
|
|
2
2
|
module Session
|
|
3
|
-
# Handles all authentication that deals with basic HTTP auth.
|
|
3
|
+
# Handles all authentication that deals with basic HTTP auth. Which is authentication built into the HTTP protocol:
|
|
4
|
+
#
|
|
5
|
+
# http://username:password@whatever.com
|
|
6
|
+
#
|
|
7
|
+
# Also, if you are not comfortable letting users pass their raw username and password you can always use the single
|
|
8
|
+
# access token. See Authlogic::Session::Params for more info.
|
|
4
9
|
module HttpAuth
|
|
5
10
|
def self.included(klass)
|
|
6
|
-
klass.
|
|
11
|
+
klass.class_eval do
|
|
12
|
+
extend Config
|
|
13
|
+
include InstanceMethods
|
|
14
|
+
persist :persist_by_http_auth, :if => :persist_by_http_auth?
|
|
15
|
+
end
|
|
7
16
|
end
|
|
8
17
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
# Configuration for the HTTP basic auth feature of Authlogic.
|
|
19
|
+
module Config
|
|
20
|
+
# Do you want to allow your users to log in via HTTP basic auth?
|
|
21
|
+
#
|
|
22
|
+
# I recommend keeping this enabled. The only time I feel this should be disabled is if you are not comfortable
|
|
23
|
+
# having your users provide their raw username and password. Whatever the reason, you can disable it here.
|
|
24
|
+
#
|
|
25
|
+
# * <tt>Default:</tt> true
|
|
26
|
+
# * <tt>Accepts:</tt> Boolean
|
|
27
|
+
def allow_http_basic_auth(value = nil)
|
|
28
|
+
rw_config(:allow_http_basic_auth, value, true)
|
|
29
|
+
end
|
|
30
|
+
alias_method :allow_http_basic_auth=, :allow_http_basic_auth
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Instance methods for the HTTP basic auth feature of authlogic.
|
|
34
|
+
module InstanceMethods
|
|
35
|
+
private
|
|
36
|
+
def persist_by_http_auth?
|
|
37
|
+
allow_http_basic_auth? && login_field && password_field
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def persist_by_http_auth
|
|
41
|
+
controller.authenticate_with_http_basic do |login, password|
|
|
42
|
+
if !login.blank? && !password.blank?
|
|
43
|
+
send("#{login_field}=", login)
|
|
44
|
+
send("#{password_field}=", password)
|
|
45
|
+
return valid?
|
|
46
|
+
end
|
|
16
47
|
end
|
|
48
|
+
|
|
49
|
+
false
|
|
17
50
|
end
|
|
18
51
|
|
|
19
|
-
|
|
20
|
-
|
|
52
|
+
def allow_http_basic_auth?
|
|
53
|
+
self.class.allow_http_basic_auth == true
|
|
54
|
+
end
|
|
55
|
+
end
|
|
21
56
|
end
|
|
22
57
|
end
|
|
23
58
|
end
|
|
@@ -15,22 +15,24 @@ module Authlogic
|
|
|
15
15
|
klass.class_eval do
|
|
16
16
|
extend Config
|
|
17
17
|
include InstanceMethods
|
|
18
|
-
after_persisting :set_last_request_at
|
|
18
|
+
after_persisting :set_last_request_at, :if => :set_last_request_at?
|
|
19
19
|
validate :increase_failed_login_count
|
|
20
20
|
before_save :update_info
|
|
21
|
+
before_save :set_last_request_at, :if => :set_last_request_at?
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
# Configuration for the magic columns feature.
|
|
25
26
|
module Config
|
|
26
|
-
# Every time a session is found the last_request_at field for that record is updatd with the current time, if that field exists.
|
|
27
|
-
#
|
|
28
|
-
#
|
|
27
|
+
# Every time a session is found the last_request_at field for that record is updatd with the current time, if that field exists.
|
|
28
|
+
# If you want to limit how frequent that field is updated specify the threshold here. For example, if your user is making a
|
|
29
|
+
# request every 5 seconds, and you feel this is too frequent, and feel a minute is a good threashold. Set this to 1.minute.
|
|
30
|
+
# Once a minute has passed in between requests the field will be updated.
|
|
29
31
|
#
|
|
30
32
|
# * <tt>Default:</tt> 0
|
|
31
33
|
# * <tt>Accepts:</tt> integer representing time in seconds
|
|
32
34
|
def last_request_at_threshold(value = nil)
|
|
33
|
-
|
|
35
|
+
rw_config(:last_request_at_threshold, value, 0)
|
|
34
36
|
end
|
|
35
37
|
alias_method :last_request_at_threshold=, :last_request_at_threshold
|
|
36
38
|
end
|
|
@@ -39,7 +41,7 @@ module Authlogic
|
|
|
39
41
|
module InstanceMethods
|
|
40
42
|
private
|
|
41
43
|
def increase_failed_login_count
|
|
42
|
-
if
|
|
44
|
+
if invalid_password? && attempted_record.respond_to?(:failed_login_count)
|
|
43
45
|
attempted_record.failed_login_count ||= 0
|
|
44
46
|
attempted_record.failed_login_count += 1
|
|
45
47
|
end
|
|
@@ -59,11 +61,29 @@ module Authlogic
|
|
|
59
61
|
record.current_login_ip = controller.request.remote_ip
|
|
60
62
|
end
|
|
61
63
|
end
|
|
64
|
+
|
|
65
|
+
# This method lets authlogic know whether it should allow the last_request_at field to be updated
|
|
66
|
+
# with the current time (Time.now). One thing to note here is that it also checks for the existence of a
|
|
67
|
+
# last_request_update_allowed? method in your controller. This allows you to control this method pragmatically
|
|
68
|
+
# in your controller.
|
|
69
|
+
#
|
|
70
|
+
# For example, what if you had a javascript function that polled the server updating how much time is left in their
|
|
71
|
+
# session before it times out. Obviously you would want to ignore this request, because then the user would never time out.
|
|
72
|
+
# So you can do something like this in your controller:
|
|
73
|
+
#
|
|
74
|
+
# def last_request_update_allowed?
|
|
75
|
+
# action_name =! "update_session_time_left"
|
|
76
|
+
# end
|
|
77
|
+
#
|
|
78
|
+
# You can do whatever you want with that method.
|
|
79
|
+
def set_last_request_at? # :doc:
|
|
80
|
+
return false if !record || !klass.column_names.include?("last_request_at")
|
|
81
|
+
return controller.last_request_update_allowed? if controller.responds_to_last_request_update_allowed?
|
|
82
|
+
record.last_request_at.blank? || last_request_at_threshold.to_i.seconds.ago >= record.last_request_at
|
|
83
|
+
end
|
|
62
84
|
|
|
63
85
|
def set_last_request_at
|
|
64
|
-
|
|
65
|
-
record.last_request_at = klass.default_timezone == :utc ? Time.now.utc : Time.now
|
|
66
|
-
end
|
|
86
|
+
record.last_request_at = klass.default_timezone == :utc ? Time.now.utc : Time.now
|
|
67
87
|
end
|
|
68
88
|
|
|
69
89
|
def last_request_at_threshold
|
|
@@ -9,7 +9,8 @@ module Authlogic
|
|
|
9
9
|
#
|
|
10
10
|
# Authlogic does nothing to define these methods for you, its up to you to define what they mean. If your object responds to these methods Authlogic will use them, otherwise they are ignored.
|
|
11
11
|
#
|
|
12
|
-
# What's neat about this is that these are checked upon any type of login. When logging in explicitly, by cookie, session, or basic http auth.
|
|
12
|
+
# What's neat about this is that these are checked upon any type of login. When logging in explicitly, by cookie, session, or basic http auth.
|
|
13
|
+
# So if you mark a user inactive in the middle of their session they wont be logged back in next time they refresh the page. Giving you complete control.
|
|
13
14
|
#
|
|
14
15
|
# Need Authlogic to check your own "state"? No problem, check out the hooks section below. Add in a before_validation to do your own checking. The sky is the limit.
|
|
15
16
|
module MagicStates
|
|
@@ -17,7 +18,7 @@ module Authlogic
|
|
|
17
18
|
klass.class_eval do
|
|
18
19
|
extend Config
|
|
19
20
|
include InstanceMethods
|
|
20
|
-
validate :validate_magic_states
|
|
21
|
+
validate :validate_magic_states, :unless => :disable_magic_states?
|
|
21
22
|
end
|
|
22
23
|
end
|
|
23
24
|
|
|
@@ -30,7 +31,7 @@ module Authlogic
|
|
|
30
31
|
# * <tt>Default:</tt> false
|
|
31
32
|
# * <tt>Accepts:</tt> Boolean
|
|
32
33
|
def disable_magic_states(value = nil)
|
|
33
|
-
|
|
34
|
+
rw_config(:disable_magic_states, value, false)
|
|
34
35
|
end
|
|
35
36
|
alias_method :disable_magic_states=, :disable_magic_states
|
|
36
37
|
end
|
|
@@ -43,10 +44,10 @@ module Authlogic
|
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
def validate_magic_states
|
|
46
|
-
return true if
|
|
47
|
+
return true if attempted_record.nil?
|
|
47
48
|
[:active, :approved, :confirmed].each do |required_status|
|
|
48
49
|
if attempted_record.respond_to?("#{required_status}?") && !attempted_record.send("#{required_status}?")
|
|
49
|
-
errors.
|
|
50
|
+
errors.add(:base, I18n.t("error_messages.not_#{required_status}", :default => "Your account is not #{required_status}"))
|
|
50
51
|
return false
|
|
51
52
|
end
|
|
52
53
|
end
|
|
@@ -43,17 +43,18 @@ module Authlogic
|
|
|
43
43
|
# * <tt>Default:</tt> cookie_key
|
|
44
44
|
# * <tt>Accepts:</tt> String
|
|
45
45
|
def params_key(value = nil)
|
|
46
|
-
|
|
46
|
+
rw_config(:params_key, value, cookie_key)
|
|
47
47
|
end
|
|
48
48
|
alias_method :params_key=, :params_key
|
|
49
49
|
|
|
50
|
-
# Authentication is allowed via a single access token, but maybe this is something you don't want for your application as a whole. Maybe this is
|
|
51
|
-
# Specify a list of allowed request types and single access authentication will only be
|
|
50
|
+
# Authentication is allowed via a single access token, but maybe this is something you don't want for your application as a whole. Maybe this is
|
|
51
|
+
# something you only want for specific request types. Specify a list of allowed request types and single access authentication will only be
|
|
52
|
+
# allowed for the ones you specify.
|
|
52
53
|
#
|
|
53
|
-
# * <tt>Default:</tt> "application/rss+xml", "application/atom+xml"
|
|
54
|
-
# * <tt>Accepts:</tt> String of request type, or :all to allow single access authentication for any and all request types
|
|
54
|
+
# * <tt>Default:</tt> ["application/rss+xml", "application/atom+xml"]
|
|
55
|
+
# * <tt>Accepts:</tt> String of a request type, or :all or :any to allow single access authentication for any and all request types
|
|
55
56
|
def single_access_allowed_request_types(value = nil)
|
|
56
|
-
|
|
57
|
+
rw_config(:single_access_allowed_request_types, value, ["application/rss+xml", "application/atom+xml"])
|
|
57
58
|
end
|
|
58
59
|
alias_method :single_access_allowed_request_types=, :single_access_allowed_request_types
|
|
59
60
|
end
|
|
@@ -68,10 +69,15 @@ module Authlogic
|
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
def params_enabled?
|
|
71
|
-
params_credentials
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
return false if !params_credentials || !klass.column_names.include?("single_access_token")
|
|
73
|
+
return controller.single_access_allowed? if controller.responds_to_single_access_allowed?
|
|
74
|
+
|
|
75
|
+
case single_access_allowed_request_types
|
|
76
|
+
when Array
|
|
77
|
+
single_access_allowed_request_types.include?(controller.request_content_type) || single_access_allowed_request_types.include?(:all)
|
|
78
|
+
else
|
|
79
|
+
[:all, :any].include?(single_access_allowed_request_types)
|
|
80
|
+
end
|
|
75
81
|
end
|
|
76
82
|
|
|
77
83
|
def params_key
|
|
@@ -16,12 +16,15 @@ module Authlogic
|
|
|
16
16
|
|
|
17
17
|
# Password configuration
|
|
18
18
|
module Config
|
|
19
|
-
# Authlogic tries to validate the credentials passed to it. One part of validation is actually finding the user and
|
|
19
|
+
# Authlogic tries to validate the credentials passed to it. One part of validation is actually finding the user and
|
|
20
|
+
# making sure it exists. What method it uses the do this is up to you.
|
|
20
21
|
#
|
|
21
|
-
# Let's say you have a UserSession that is authenticating a User. By default UserSession will call User.find_by_login(login).
|
|
22
|
-
#
|
|
22
|
+
# Let's say you have a UserSession that is authenticating a User. By default UserSession will call User.find_by_login(login).
|
|
23
|
+
# You can change what method UserSession calls by specifying it here. Then in your User model you can make that method do
|
|
24
|
+
# anything you want, giving you complete control of how users are found by the UserSession.
|
|
23
25
|
#
|
|
24
|
-
# Let's take an example: You want to allow users to login by username or email. Set this to the name of the class method
|
|
26
|
+
# Let's take an example: You want to allow users to login by username or email. Set this to the name of the class method
|
|
27
|
+
# that does this in the User model. Let's call it "find_by_username_or_email"
|
|
25
28
|
#
|
|
26
29
|
# class User < ActiveRecord::Base
|
|
27
30
|
# def self.find_by_username_or_email(login)
|
|
@@ -29,40 +32,81 @@ module Authlogic
|
|
|
29
32
|
# end
|
|
30
33
|
# end
|
|
31
34
|
#
|
|
32
|
-
#
|
|
35
|
+
# Now just specify the name of this method for this configuration option and you are all set. You can do anything you
|
|
36
|
+
# want here. Maybe you allow users to have multiple logins and you want to search a has_many relationship, etc. The sky is the limit.
|
|
37
|
+
#
|
|
38
|
+
# * <tt>Default:</tt> "find_by_smart_case_login_field"
|
|
33
39
|
# * <tt>Accepts:</tt> Symbol or String
|
|
34
40
|
def find_by_login_method(value = nil)
|
|
35
|
-
|
|
41
|
+
rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
|
|
36
42
|
end
|
|
37
43
|
alias_method :find_by_login_method=, :find_by_login_method
|
|
38
44
|
|
|
45
|
+
# The text used to identify credentials (username/password) combination when a bad login attempt occurs.
|
|
46
|
+
# When you show error messages for a bad login, it's considered good security practice to hide which field
|
|
47
|
+
# the user has entered incorrectly (the login field or the password field). For a full explanation, see
|
|
48
|
+
# http://www.gnucitizen.org/blog/username-enumeration-vulnerabilities/
|
|
49
|
+
#
|
|
50
|
+
# Example of use:
|
|
51
|
+
#
|
|
52
|
+
# class UserSession < Authlogic::Session::Base
|
|
53
|
+
# generalize_credentials_error_messages true
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# This would make the error message for bad logins and bad passwords look identical:
|
|
57
|
+
#
|
|
58
|
+
# Login/Password combination is not valid
|
|
59
|
+
#
|
|
60
|
+
# Alternatively you may use a custom message:
|
|
61
|
+
#
|
|
62
|
+
# class UserSession < AuthLogic::Session::Base
|
|
63
|
+
# generalize_credentials_error_messages "Your login information is invalid"
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# This will instead show your custom error message when the UserSession is invalid.
|
|
67
|
+
#
|
|
68
|
+
# The downside to enabling this is that is can be too vague for a user that has a hard time remembering
|
|
69
|
+
# their username and password combinations. It also disables the ability to to highlight the field
|
|
70
|
+
# with the error when you use form_for.
|
|
71
|
+
#
|
|
72
|
+
# If you are developing an app where security is an extreme priority (such as a financial application),
|
|
73
|
+
# then you should enable this. Otherwise, leaving this off is fine.
|
|
74
|
+
#
|
|
75
|
+
# * <tt>Default</tt> false
|
|
76
|
+
# * <tt>Accepts:</tt> Boolean
|
|
77
|
+
def generalize_credentials_error_messages(value = nil)
|
|
78
|
+
rw_config(:generalize_credentials_error_messages, value, false)
|
|
79
|
+
end
|
|
80
|
+
alias_method :generalize_credentials_error_messages=, :generalize_credentials_error_messages
|
|
81
|
+
|
|
39
82
|
# The name of the method you want Authlogic to create for storing the login / username. Keep in mind this is just for your
|
|
40
83
|
# Authlogic::Session, if you want it can be something completely different than the field in your model. So if you wanted people to
|
|
41
84
|
# login with a field called "login" and then find users by email this is compeltely doable. See the find_by_login_method configuration
|
|
42
85
|
# option for more details.
|
|
43
86
|
#
|
|
44
|
-
# * <tt>Default:</tt>
|
|
87
|
+
# * <tt>Default:</tt> klass.login_field || klass.email_field
|
|
45
88
|
# * <tt>Accepts:</tt> Symbol or String
|
|
46
89
|
def login_field(value = nil)
|
|
47
|
-
|
|
90
|
+
rw_config(:login_field, value, klass.login_field || klass.email_field)
|
|
48
91
|
end
|
|
49
92
|
alias_method :login_field=, :login_field
|
|
50
93
|
|
|
51
|
-
# Works exactly like login_field, but for the password instead.
|
|
94
|
+
# Works exactly like login_field, but for the password instead. Returns :password if a login_field exists.
|
|
52
95
|
#
|
|
53
96
|
# * <tt>Default:</tt> :password
|
|
54
97
|
# * <tt>Accepts:</tt> Symbol or String
|
|
55
98
|
def password_field(value = nil)
|
|
56
|
-
|
|
99
|
+
rw_config(:password_field, value, login_field && :password)
|
|
57
100
|
end
|
|
58
101
|
alias_method :password_field=, :password_field
|
|
59
102
|
|
|
60
|
-
# The name of the method in your model used to verify the password. This should be an instance method. It should also
|
|
103
|
+
# The name of the method in your model used to verify the password. This should be an instance method. It should also
|
|
104
|
+
# be prepared to accept a raw password and a crytped password.
|
|
61
105
|
#
|
|
62
|
-
# * <tt>Default:</tt> "
|
|
106
|
+
# * <tt>Default:</tt> "valid_password?"
|
|
63
107
|
# * <tt>Accepts:</tt> Symbol or String
|
|
64
108
|
def verify_password_method(value = nil)
|
|
65
|
-
|
|
109
|
+
rw_config(:verify_password_method, value, "valid_password?")
|
|
66
110
|
end
|
|
67
111
|
alias_method :verify_password_method=, :verify_password_method
|
|
68
112
|
end
|
|
@@ -71,18 +115,24 @@ module Authlogic
|
|
|
71
115
|
module InstanceMethods
|
|
72
116
|
def initialize(*args)
|
|
73
117
|
if !self.class.configured_password_methods
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
118
|
+
if login_field
|
|
119
|
+
self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
|
|
120
|
+
self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
if password_field
|
|
124
|
+
self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
|
|
125
|
+
self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
|
|
78
126
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
127
|
+
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
|
|
128
|
+
private
|
|
129
|
+
# The password should not be accessible publicly. This way forms using form_for don't fill the password with the
|
|
130
|
+
# attempted password. To prevent this we just create this method that is private.
|
|
131
|
+
def protected_#{password_field}
|
|
132
|
+
@#{password_field}
|
|
133
|
+
end
|
|
134
|
+
end_eval
|
|
135
|
+
end
|
|
86
136
|
|
|
87
137
|
self.class.configured_password_methods = true
|
|
88
138
|
end
|
|
@@ -90,6 +140,7 @@ module Authlogic
|
|
|
90
140
|
super
|
|
91
141
|
end
|
|
92
142
|
|
|
143
|
+
# Returns the login_field / password_field credentials combination in hash form.
|
|
93
144
|
def credentials
|
|
94
145
|
if authenticating_with_password?
|
|
95
146
|
details = {}
|
|
@@ -101,6 +152,7 @@ module Authlogic
|
|
|
101
152
|
end
|
|
102
153
|
end
|
|
103
154
|
|
|
155
|
+
# Accepts the login_field / password_field credentials combination in hash form.
|
|
104
156
|
def credentials=(value)
|
|
105
157
|
super
|
|
106
158
|
values = value.is_a?(Array) ? value : [value]
|
|
@@ -112,29 +164,47 @@ module Authlogic
|
|
|
112
164
|
end
|
|
113
165
|
end
|
|
114
166
|
|
|
167
|
+
def invalid_password?
|
|
168
|
+
invalid_password == true
|
|
169
|
+
end
|
|
170
|
+
|
|
115
171
|
private
|
|
116
172
|
def authenticating_with_password?
|
|
117
|
-
!send(login_field).nil? || !send("protected_#{password_field}").nil?
|
|
173
|
+
login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
|
|
118
174
|
end
|
|
119
175
|
|
|
120
176
|
def validate_by_password
|
|
121
|
-
|
|
122
|
-
|
|
177
|
+
self.invalid_password = false
|
|
178
|
+
|
|
179
|
+
errors.add(login_field, I18n.t('error_messages.login_blank', :default => "cannot be blank")) if send(login_field).blank?
|
|
180
|
+
errors.add(password_field, I18n.t('error_messages.password_blank', :default => "cannot be blank")) if send("protected_#{password_field}").blank?
|
|
123
181
|
return if errors.count > 0
|
|
124
182
|
|
|
125
183
|
self.attempted_record = search_for_record(find_by_login_method, send(login_field))
|
|
126
|
-
|
|
127
184
|
if attempted_record.blank?
|
|
128
|
-
|
|
185
|
+
generalize_credentials_error_messages? ?
|
|
186
|
+
add_general_credentials_error :
|
|
187
|
+
errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "is not valid"))
|
|
129
188
|
return
|
|
130
189
|
end
|
|
131
190
|
|
|
132
191
|
if !attempted_record.send(verify_password_method, send("protected_#{password_field}"))
|
|
133
|
-
|
|
192
|
+
self.invalid_password = true
|
|
193
|
+
generalize_credentials_error_messages? ?
|
|
194
|
+
add_general_credentials_error :
|
|
195
|
+
errors.add(password_field, I18n.t('error_messages.password_invalid', :default => "is not valid"))
|
|
134
196
|
return
|
|
135
197
|
end
|
|
136
198
|
end
|
|
137
199
|
|
|
200
|
+
def invalid_password
|
|
201
|
+
@invalid_password
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def invalid_password=(value)
|
|
205
|
+
@invalid_password = value
|
|
206
|
+
end
|
|
207
|
+
|
|
138
208
|
def find_by_login_method
|
|
139
209
|
self.class.find_by_login_method
|
|
140
210
|
end
|
|
@@ -143,6 +213,20 @@ module Authlogic
|
|
|
143
213
|
self.class.login_field
|
|
144
214
|
end
|
|
145
215
|
|
|
216
|
+
def add_general_credentials_error
|
|
217
|
+
error_message =
|
|
218
|
+
if self.class.generalize_credentials_error_messages.is_a? String
|
|
219
|
+
self.class.generalize_credentials_error_messages
|
|
220
|
+
else
|
|
221
|
+
"#{login_field.to_s.humanize}/Password combination is not valid"
|
|
222
|
+
end
|
|
223
|
+
errors.add(:base, I18n.t('error_messages.general_credentials_error', :default => error_message))
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def generalize_credentials_error_messages?
|
|
227
|
+
self.class.generalize_credentials_error_messages
|
|
228
|
+
end
|
|
229
|
+
|
|
146
230
|
def password_field
|
|
147
231
|
self.class.password_field
|
|
148
232
|
end
|
|
@@ -153,4 +237,4 @@ module Authlogic
|
|
|
153
237
|
end
|
|
154
238
|
end
|
|
155
239
|
end
|
|
156
|
-
end
|
|
240
|
+
end
|
|
@@ -16,7 +16,7 @@ module Authlogic
|
|
|
16
16
|
def credentials=(value)
|
|
17
17
|
super
|
|
18
18
|
values = value.is_a?(Array) ? value : [value]
|
|
19
|
-
self.priority_record = values
|
|
19
|
+
self.priority_record = values[1] if values[1].class < ::ActiveRecord::Base
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
@@ -20,7 +20,7 @@ module Authlogic
|
|
|
20
20
|
# * <tt>Default:</tt> cookie_key
|
|
21
21
|
# * <tt>Accepts:</tt> Symbol or String
|
|
22
22
|
def session_key(value = nil)
|
|
23
|
-
|
|
23
|
+
rw_config(:session_key, value, cookie_key)
|
|
24
24
|
end
|
|
25
25
|
alias_method :session_key=, :session_key
|
|
26
26
|
end
|
|
@@ -34,7 +34,9 @@ module Authlogic
|
|
|
34
34
|
if !persistence_token.nil?
|
|
35
35
|
# Allow finding by persistence token, because when records are created the session is maintained in a before_save, when there is no id.
|
|
36
36
|
# This is done for performance reasons and to save on queries.
|
|
37
|
-
record = record_id.nil? ?
|
|
37
|
+
record = record_id.nil? ?
|
|
38
|
+
search_for_record("find_by_persistence_token", persistence_token) :
|
|
39
|
+
search_for_record("find_by_#{klass.primary_key}", record_id)
|
|
38
40
|
self.unauthorized_record = record if record && record.persistence_token == persistence_token
|
|
39
41
|
valid?
|
|
40
42
|
else
|
|
@@ -49,7 +51,7 @@ module Authlogic
|
|
|
49
51
|
def session_key
|
|
50
52
|
build_key(self.class.session_key)
|
|
51
53
|
end
|
|
52
|
-
|
|
54
|
+
|
|
53
55
|
def update_session
|
|
54
56
|
controller.session[session_key] = record && record.persistence_token
|
|
55
57
|
controller.session["#{session_key}_#{klass.primary_key}"] = record && record.send(record.class.primary_key)
|
|
@@ -48,7 +48,7 @@ module Authlogic
|
|
|
48
48
|
# * <tt>Default:</tt> false
|
|
49
49
|
# * <tt>Accepts:</tt> Boolean
|
|
50
50
|
def logout_on_timeout(value = nil)
|
|
51
|
-
|
|
51
|
+
rw_config(:logout_on_timeout, value, false)
|
|
52
52
|
end
|
|
53
53
|
alias_method :logout_on_timeout=, :logout_on_timeout
|
|
54
54
|
end
|
|
@@ -10,10 +10,16 @@ module Authlogic
|
|
|
10
10
|
# private
|
|
11
11
|
# def check_if_awesome
|
|
12
12
|
# errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
|
|
13
|
-
# errors.
|
|
13
|
+
# errors.add(:base, "You must be awesome to log in") unless attempted_record.awesome?
|
|
14
14
|
# end
|
|
15
15
|
# end
|
|
16
|
-
class Errors < ::ActiveRecord::Errors
|
|
16
|
+
class Errors < (defined?(::ActiveModel) ? ::ActiveModel::Errors : ::ActiveRecord::Errors)
|
|
17
|
+
unless defined?(::ActiveModel)
|
|
18
|
+
def [](key)
|
|
19
|
+
value = super
|
|
20
|
+
value.is_a?(Array) ? value : [value].compact
|
|
21
|
+
end
|
|
22
|
+
end
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
# You should use this as a place holder for any records that you find during validation. The main reason for this is to
|
|
@@ -39,7 +45,7 @@ module Authlogic
|
|
|
39
45
|
# private
|
|
40
46
|
# def check_if_awesome
|
|
41
47
|
# errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
|
|
42
|
-
# errors.
|
|
48
|
+
# errors.add(:base, "You must be awesome to log in") unless attempted_record.awesome?
|
|
43
49
|
# end
|
|
44
50
|
# end
|
|
45
51
|
def errors
|
|
@@ -58,19 +64,19 @@ module Authlogic
|
|
|
58
64
|
validate
|
|
59
65
|
ensure_authentication_attempted
|
|
60
66
|
|
|
61
|
-
if errors.
|
|
67
|
+
if errors.size == 0
|
|
62
68
|
new_session? ? after_validation_on_create : after_validation_on_update
|
|
63
69
|
after_validation
|
|
64
70
|
end
|
|
65
71
|
|
|
66
72
|
save_record(attempted_record)
|
|
67
|
-
errors.
|
|
73
|
+
errors.size == 0
|
|
68
74
|
end
|
|
69
75
|
|
|
70
76
|
private
|
|
71
77
|
def ensure_authentication_attempted
|
|
72
|
-
errors.
|
|
78
|
+
errors.add(:base, I18n.t('error_messages.no_authentication_details', :default => "You did not provide any details for authentication.")) if errors.empty? && attempted_record.nil?
|
|
73
79
|
end
|
|
74
80
|
end
|
|
75
81
|
end
|
|
76
|
-
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Authlogic
|
|
2
|
+
module TestCase
|
|
3
|
+
# Basically acts like a controller but doesn't do anything. Authlogic can interact with this, do it's thing and then you
|
|
4
|
+
# can look at the controller object to see if anything changed.
|
|
5
|
+
class MockController < ControllerAdapters::AbstractAdapter
|
|
6
|
+
attr_accessor :http_user, :http_password
|
|
7
|
+
attr_writer :request_content_type
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def authenticate_with_http_basic(&block)
|
|
13
|
+
yield http_user, http_password
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def cookies
|
|
17
|
+
@cookies ||= MockCookieJar.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def cookie_domain
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def logger
|
|
25
|
+
@logger ||= MockLogger.new
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def params
|
|
29
|
+
@params ||= {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def request
|
|
33
|
+
@request ||= MockRequest.new(controller)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def request_content_type
|
|
37
|
+
@request_content_type ||= "text/html"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def session
|
|
41
|
+
@session ||= {}
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|