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.
Files changed (86) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG.rdoc +110 -1
  3. data/{MIT-LICENSE → LICENSE} +1 -1
  4. data/README.rdoc +138 -161
  5. data/Rakefile +40 -19
  6. data/VERSION.yml +4 -0
  7. data/authlogic.gemspec +212 -0
  8. data/init.rb +1 -1
  9. data/lib/authlogic/acts_as_authentic/base.rb +44 -35
  10. data/lib/authlogic/acts_as_authentic/email.rb +47 -14
  11. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +20 -14
  12. data/lib/authlogic/acts_as_authentic/login.rb +84 -8
  13. data/lib/authlogic/acts_as_authentic/password.rb +207 -88
  14. data/lib/authlogic/acts_as_authentic/perishable_token.rb +9 -4
  15. data/lib/authlogic/acts_as_authentic/persistence_token.rb +4 -2
  16. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +5 -4
  17. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +16 -4
  18. data/lib/authlogic/acts_as_authentic/single_access_token.rb +10 -3
  19. data/lib/authlogic/acts_as_authentic/validations_scope.rb +1 -1
  20. data/lib/authlogic/controller_adapters/abstract_adapter.rb +13 -1
  21. data/lib/authlogic/controller_adapters/rails_adapter.rb +14 -4
  22. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +61 -0
  23. data/lib/authlogic/crypto_providers/aes256.rb +2 -2
  24. data/lib/authlogic/crypto_providers/bcrypt.rb +3 -1
  25. data/lib/authlogic/crypto_providers/sha1.rb +2 -2
  26. data/lib/authlogic/i18n/translator.rb +15 -0
  27. data/lib/authlogic/i18n.rb +43 -14
  28. data/lib/authlogic/regex.rb +25 -0
  29. data/lib/authlogic/session/activation.rb +2 -0
  30. data/lib/authlogic/session/active_record_trickery.rb +26 -5
  31. data/lib/authlogic/session/brute_force_protection.rb +30 -11
  32. data/lib/authlogic/session/callbacks.rb +18 -7
  33. data/lib/authlogic/session/cookies.rb +21 -17
  34. data/lib/authlogic/session/existence.rb +6 -2
  35. data/lib/authlogic/session/foundation.rb +1 -1
  36. data/lib/authlogic/session/http_auth.rb +46 -11
  37. data/lib/authlogic/session/magic_columns.rb +29 -9
  38. data/lib/authlogic/session/magic_states.rb +6 -5
  39. data/lib/authlogic/session/params.rb +16 -10
  40. data/lib/authlogic/session/password.rb +115 -31
  41. data/lib/authlogic/session/priority_record.rb +1 -1
  42. data/lib/authlogic/session/session.rb +5 -3
  43. data/lib/authlogic/session/timeout.rb +1 -1
  44. data/lib/authlogic/session/validation.rb +13 -7
  45. data/lib/authlogic/test_case/mock_controller.rb +45 -0
  46. data/lib/authlogic/test_case/mock_cookie_jar.rb +14 -0
  47. data/lib/authlogic/test_case/mock_logger.rb +10 -0
  48. data/lib/authlogic/test_case/mock_request.rb +19 -0
  49. data/lib/authlogic/test_case/rails_request_adapter.rb +30 -0
  50. data/lib/authlogic/test_case.rb +114 -0
  51. data/lib/authlogic.rb +4 -3
  52. data/rails/init.rb +1 -0
  53. data/shoulda_macros/authlogic.rb +3 -2
  54. data/test/acts_as_authentic_test/base_test.rb +6 -0
  55. data/test/acts_as_authentic_test/email_test.rb +25 -7
  56. data/test/acts_as_authentic_test/login_test.rb +37 -7
  57. data/test/acts_as_authentic_test/magic_columns_test.rb +4 -4
  58. data/test/acts_as_authentic_test/password_test.rb +46 -44
  59. data/test/acts_as_authentic_test/perishable_token_test.rb +36 -2
  60. data/test/acts_as_authentic_test/restful_authentication_test.rb +40 -0
  61. data/test/acts_as_authentic_test/session_maintenance_test.rb +26 -18
  62. data/test/acts_as_authentic_test/single_access_test.rb +6 -1
  63. data/test/i18n_test.rb +33 -0
  64. data/test/libs/affiliate.rb +7 -0
  65. data/test/libs/ldaper.rb +3 -0
  66. data/test/random_test.rb +2 -2
  67. data/test/session_test/activation_test.rb +1 -1
  68. data/test/session_test/active_record_trickery_test.rb +12 -2
  69. data/test/session_test/brute_force_protection_test.rb +37 -12
  70. data/test/session_test/cookies_test.rb +3 -3
  71. data/test/session_test/existence_test.rb +3 -3
  72. data/test/session_test/http_auth_test.rb +20 -8
  73. data/test/session_test/magic_columns_test.rb +9 -6
  74. data/test/session_test/magic_states_test.rb +3 -3
  75. data/test/session_test/params_test.rb +4 -4
  76. data/test/session_test/password_test.rb +23 -1
  77. data/test/session_test/session_test.rb +9 -9
  78. data/test/session_test/timeout_test.rb +10 -1
  79. data/test/test_helper.rb +53 -23
  80. metadata +42 -27
  81. data/Manifest.txt +0 -111
  82. data/lib/authlogic/testing/test_unit_helpers.rb +0 -39
  83. data/lib/authlogic/version.rb +0 -56
  84. data/test/libs/mock_controller.rb +0 -35
  85. data/test/libs/mock_cookie_jar.rb +0 -10
  86. 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.persist :persist_by_http_auth
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
- private
10
- def persist_by_http_auth
11
- controller.authenticate_with_http_basic do |login, password|
12
- if !login.blank? && !password.blank?
13
- send("#{login_field}=", login)
14
- send("#{password_field}=", password)
15
- return valid?
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
- false
20
- end
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. If you want to limit how frequent that field is updated specify the threshold
27
- # here. For example, if your user is making a request every 5 seconds, and you feel this is too frequent, and feel a minute is a good threashold. Set this to 1.minute. Once a minute has passed in between
28
- # requests the field will be updated.
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
- config(:last_request_at_threshold, value, 0)
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 errors.on(password_field) && attempted_record.respond_to?(:failed_login_count)
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
- if record && record.class.column_names.include?("last_request_at") && (record.last_request_at.blank? || last_request_at_threshold.to_i.seconds.ago >= record.last_request_at)
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. 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.
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
- config(:disable_magic_states, value, false)
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 disable_magic_states? || attempted_record.nil?
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.add_to_base(I18n.t("error_messages.not_#{required_status}", :default => "Your account is not #{required_status}"))
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
- config(:params_key, value, cookie_key)
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 something you only want for specific request types.
51
- # Specify a list of allowed request types and single access authentication will only be allowed for the ones you specify. Checkout the "Single Access / Private Feeds Access" section in the README.
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
- config(:single_access_allowed_request_types, value, ["application/rss+xml", "application/atom+xml"])
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 && klass.column_names.include?("single_access_token") &&
72
- (single_access_allowed_request_types.include?(controller.request_content_type) ||
73
- single_access_allowed_request_types.include?(:all) ||
74
- controller.single_access_allowed?)
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 making sure it exists. What method it uses the do this is up to you.
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). You can change what method UserSession calls by specifying it here. Then
22
- # in your User model you can make that method do anything you want, giving you complete control of how users are found by the UserSession.
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 that does this in the User model. Let's call it "find_by_username_or_email"
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
- # * <tt>Default:</tt> "find_by_#{login_field}"
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
- config(:find_by_login_method, value, "find_by_#{login_field}")
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> Uses the configuration option in your model: User.login_field
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
- config(:login_field, value, klass.login_field || klass.email_field)
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
- config(:password_field, value, :password)
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 be prepared to accept a raw password and a crytped password.
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> "valid_#{password_field}?"
106
+ # * <tt>Default:</tt> "valid_password?"
63
107
  # * <tt>Accepts:</tt> Symbol or String
64
108
  def verify_password_method(value = nil)
65
- config(:verify_password_method, value, "valid_#{password_field}?")
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
- self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
75
- self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
76
- self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
77
- self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
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
- self.class.class_eval <<-"end_eval", __FILE__, __LINE__
80
- private
81
- # The password should not be accessible publicly. This way forms using form_for don't fill the password with the attempted password. The prevent this we just create this method that is private.
82
- def protected_#{password_field}
83
- @#{password_field}
84
- end
85
- end_eval
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
- errors.add(login_field, I18n.t('error_messages.login_blank', :default => "can not be blank")) if send(login_field).blank?
122
- errors.add(password_field, I18n.t('error_messages.password_blank', :default => "can not be blank")) if send("protected_#{password_field}").blank?
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
- errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "does not exist"))
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
- errors.add(password_field, I18n.t('error_messages.password_invalid', :default => "is not valid"))
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.second if values.second.class < ::ActiveRecord::Base
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
- config(:session_key, value, cookie_key)
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? ? search_for_record("find_by_persistence_token", persistence_token) : search_for_record("find_by_#{klass.primary_key}", record_id)
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
- config(:logout_on_timeout, value, false)
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.add_to_base("You must be awesome to log in") unless record.awesome?
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.add_to_base("You must be awesome to log in") unless record.awesome?
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.empty?
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.empty?
73
+ errors.size == 0
68
74
  end
69
75
 
70
76
  private
71
77
  def ensure_authentication_attempted
72
- errors.add_to_base(I18n.t('error_messages.no_authentication_details', :default => "You did not provide any details for authentication.")) if errors.empty? && attempted_record.nil?
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
@@ -0,0 +1,14 @@
1
+ module Authlogic
2
+ module TestCase
3
+ class MockCookieJar < Hash # :nodoc:
4
+ def [](key)
5
+ hash = super
6
+ hash && hash[:value]
7
+ end
8
+
9
+ def delete(key, options = {})
10
+ super(key)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module Authlogic
2
+ module TestCase
3
+ # Simple class to replace real loggers, so that we can raise any errors being logged.
4
+ class MockLogger
5
+ def error(message)
6
+ raise message
7
+ end
8
+ end
9
+ end
10
+ end