authlogic 3.8.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/.github/triage.md +87 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +62 -6
  5. data/.rubocop_todo.yml +51 -267
  6. data/.travis.yml +4 -26
  7. data/CHANGELOG.md +226 -2
  8. data/CONTRIBUTING.md +15 -5
  9. data/Gemfile +2 -2
  10. data/README.md +183 -91
  11. data/Rakefile +1 -1
  12. data/UPGRADING.md +20 -0
  13. data/authlogic.gemspec +25 -16
  14. data/lib/authlogic.rb +45 -45
  15. data/lib/authlogic/acts_as_authentic/base.rb +18 -11
  16. data/lib/authlogic/acts_as_authentic/email.rb +32 -28
  17. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +1 -1
  18. data/lib/authlogic/acts_as_authentic/login.rb +32 -42
  19. data/lib/authlogic/acts_as_authentic/magic_columns.rb +6 -6
  20. data/lib/authlogic/acts_as_authentic/password.rb +53 -31
  21. data/lib/authlogic/acts_as_authentic/perishable_token.rb +18 -17
  22. data/lib/authlogic/acts_as_authentic/persistence_token.rb +7 -12
  23. data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +64 -0
  24. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +11 -3
  25. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +30 -10
  26. data/lib/authlogic/acts_as_authentic/single_access_token.rb +4 -4
  27. data/lib/authlogic/authenticates_many/association.rb +3 -3
  28. data/lib/authlogic/authenticates_many/base.rb +2 -2
  29. data/lib/authlogic/config.rb +0 -1
  30. data/lib/authlogic/controller_adapters/abstract_adapter.rb +11 -4
  31. data/lib/authlogic/controller_adapters/rack_adapter.rb +7 -3
  32. data/lib/authlogic/controller_adapters/rails_adapter.rb +2 -0
  33. data/lib/authlogic/crypto_providers/aes256.rb +1 -1
  34. data/lib/authlogic/crypto_providers/bcrypt.rb +1 -1
  35. data/lib/authlogic/crypto_providers/scrypt.rb +6 -6
  36. data/lib/authlogic/crypto_providers/sha1.rb +10 -5
  37. data/lib/authlogic/crypto_providers/sha256.rb +11 -8
  38. data/lib/authlogic/crypto_providers/wordpress.rb +2 -2
  39. data/lib/authlogic/i18n.rb +4 -2
  40. data/lib/authlogic/random.rb +10 -28
  41. data/lib/authlogic/regex.rb +11 -8
  42. data/lib/authlogic/session/activation.rb +6 -3
  43. data/lib/authlogic/session/active_record_trickery.rb +13 -9
  44. data/lib/authlogic/session/base.rb +15 -4
  45. data/lib/authlogic/session/brute_force_protection.rb +14 -7
  46. data/lib/authlogic/session/callbacks.rb +53 -30
  47. data/lib/authlogic/session/cookies.rb +57 -16
  48. data/lib/authlogic/session/existence.rb +21 -11
  49. data/lib/authlogic/session/foundation.rb +56 -10
  50. data/lib/authlogic/session/http_auth.rb +15 -8
  51. data/lib/authlogic/session/klass.rb +7 -5
  52. data/lib/authlogic/session/magic_columns.rb +24 -11
  53. data/lib/authlogic/session/magic_states.rb +11 -4
  54. data/lib/authlogic/session/params.rb +6 -2
  55. data/lib/authlogic/session/password.rb +46 -73
  56. data/lib/authlogic/session/persistence.rb +11 -7
  57. data/lib/authlogic/session/priority_record.rb +7 -4
  58. data/lib/authlogic/session/scopes.rb +15 -6
  59. data/lib/authlogic/session/session.rb +20 -10
  60. data/lib/authlogic/session/timeout.rb +2 -2
  61. data/lib/authlogic/session/unauthorized_record.rb +1 -1
  62. data/lib/authlogic/session/validation.rb +1 -1
  63. data/lib/authlogic/test_case.rb +65 -2
  64. data/lib/authlogic/test_case/mock_controller.rb +5 -4
  65. data/lib/authlogic/test_case/mock_cookie_jar.rb +11 -2
  66. data/lib/authlogic/test_case/mock_request.rb +5 -1
  67. data/lib/authlogic/test_case/rails_request_adapter.rb +3 -2
  68. data/lib/authlogic/version.rb +16 -0
  69. data/test/acts_as_authentic_test/email_test.rb +33 -34
  70. data/test/acts_as_authentic_test/logged_in_status_test.rb +1 -1
  71. data/test/acts_as_authentic_test/login_test.rb +73 -78
  72. data/test/acts_as_authentic_test/password_test.rb +30 -18
  73. data/test/acts_as_authentic_test/perishable_token_test.rb +9 -3
  74. data/test/acts_as_authentic_test/persistence_token_test.rb +4 -0
  75. data/test/acts_as_authentic_test/session_maintenance_test.rb +66 -14
  76. data/test/adapter_test.rb +21 -0
  77. data/test/gemfiles/Gemfile.rails-4.2.x +2 -2
  78. data/test/gemfiles/Gemfile.rails-5.0.x +2 -2
  79. data/test/gemfiles/Gemfile.rails-master +6 -0
  80. data/test/i18n_test.rb +1 -1
  81. data/test/libs/company.rb +2 -2
  82. data/test/random_test.rb +7 -37
  83. data/test/session_test/active_record_trickery_test.rb +4 -3
  84. data/test/session_test/brute_force_protection_test.rb +8 -8
  85. data/test/session_test/callbacks_test.rb +1 -1
  86. data/test/session_test/cookies_test.rb +27 -4
  87. data/test/session_test/existence_test.rb +15 -4
  88. data/test/session_test/foundation_test.rb +16 -0
  89. data/test/session_test/http_auth_test.rb +3 -1
  90. data/test/session_test/magic_columns_test.rb +10 -12
  91. data/test/session_test/params_test.rb +4 -1
  92. data/test/session_test/password_test.rb +7 -7
  93. data/test/session_test/persistence_test.rb +1 -0
  94. data/test/session_test/scopes_test.rb +7 -7
  95. data/test/session_test/session_test.rb +2 -2
  96. data/test/session_test/timeout_test.rb +1 -1
  97. data/test/session_test/unauthorized_record_test.rb +1 -1
  98. data/test/test_helper.rb +111 -103
  99. metadata +68 -64
  100. data/test/gemfiles/Gemfile.rails-3.2.x +0 -7
  101. data/test/gemfiles/Gemfile.rails-4.0.x +0 -7
  102. data/test/gemfiles/Gemfile.rails-4.1.x +0 -7
@@ -1,7 +1,18 @@
1
1
  module Authlogic
2
2
  module Session # :nodoc:
3
- # This is the base class Authlogic, where all modules are included. For information on functionality see the various
4
- # sub modules.
3
+ # This is the most important class in Authlogic. You will inherit this class
4
+ # for your own eg. `UserSession`.
5
+ #
6
+ # Code is organized topically. Each topic is represented by a module. So, to
7
+ # learn about password-based authentication, read the `Password` module.
8
+ #
9
+ # It is common for methods (.initialize and #credentials=, for example) to
10
+ # be implemented in multiple mixins. Those methods will call `super`, so the
11
+ # order of `include`s here is important.
12
+ #
13
+ # Also, to fully understand such a method (like #credentials=) you will need
14
+ # to mentally combine all of its definitions. This is perhaps the primary
15
+ # disadvantage of topical organization using modules.
5
16
  class Base
6
17
  include Foundation
7
18
  include Callbacks
@@ -15,8 +26,8 @@ module Authlogic
15
26
  include Session
16
27
  include HttpAuth
17
28
 
18
- # Included in a specific order so magic states gets ran after a record is found
19
- # TODO: What does "magic states gets ran" mean? Be specific.
29
+ # Included in a specific order so magic states gets run after a record is found
30
+ # TODO: What does "magic states gets run" mean? Be specific.
20
31
  include Password
21
32
  include UnauthorizedRecord
22
33
  include MagicStates
@@ -25,8 +25,8 @@ module Authlogic
25
25
  klass.class_eval do
26
26
  extend Config
27
27
  include InstanceMethods
28
- validate :reset_failed_login_count, :if => :reset_failed_login_count?
29
- validate :validate_failed_logins, :if => :being_brute_force_protected?
28
+ validate :reset_failed_login_count, if: :reset_failed_login_count?
29
+ validate :validate_failed_logins, if: :being_brute_force_protected?
30
30
  end
31
31
  end
32
32
 
@@ -73,15 +73,22 @@ module Authlogic
73
73
  # with configuration. By default they will be banned for 2 hours. During
74
74
  # that 2 hour period this method will return true.
75
75
  def being_brute_force_protected?
76
- exceeded_failed_logins_limit? && (failed_login_ban_for <= 0 ||
77
- (attempted_record.respond_to?(:updated_at) && attempted_record.updated_at >= failed_login_ban_for.seconds.ago))
76
+ exceeded_failed_logins_limit? &&
77
+ (
78
+ failed_login_ban_for <= 0 ||
79
+ attempted_record.respond_to?(:updated_at) &&
80
+ attempted_record.updated_at >= failed_login_ban_for.seconds.ago
81
+ )
78
82
  end
79
83
 
80
84
  private
81
85
 
82
86
  def exceeded_failed_logins_limit?
83
- !attempted_record.nil? && attempted_record.respond_to?(:failed_login_count) && consecutive_failed_logins_limit > 0 &&
84
- attempted_record.failed_login_count && attempted_record.failed_login_count >= consecutive_failed_logins_limit
87
+ !attempted_record.nil? &&
88
+ attempted_record.respond_to?(:failed_login_count) &&
89
+ consecutive_failed_logins_limit > 0 &&
90
+ attempted_record.failed_login_count &&
91
+ attempted_record.failed_login_count >= consecutive_failed_logins_limit
85
92
  end
86
93
 
87
94
  def reset_failed_login_count?
@@ -100,7 +107,7 @@ module Authlogic
100
107
  :base,
101
108
  I18n.t(
102
109
  'error_messages.consecutive_failed_logins_limit_exceeded',
103
- :default => "Consecutive failed logins limit exceeded, account has been" +
110
+ default: "Consecutive failed logins limit exceeded, account has been" +
104
111
  (failed_login_ban_for == 0 ? "" : " temporarily") +
105
112
  " disabled."
106
113
  )
@@ -1,15 +1,18 @@
1
1
  module Authlogic
2
2
  module Session
3
- # Between these callbacks and the configuration, this is the contract between me and you to safely
4
- # modify Authlogic's behavior. I will do everything I can to make sure these do not change.
3
+ # Between these callbacks and the configuration, this is the contract between me and
4
+ # you to safely modify Authlogic's behavior. I will do everything I can to make sure
5
+ # these do not change.
5
6
  #
6
- # Check out the sub modules of Authlogic::Session. They are very concise, clear, and to the point. More
7
- # importantly they use the same API that you would use to extend Authlogic. That being said, they are great
8
- # examples of how to extend Authlogic and add / modify behavior to Authlogic. These modules could easily be pulled out
9
- # into their own plugin and become an "add on" without any change.
7
+ # Check out the sub modules of Authlogic::Session. They are very concise, clear, and
8
+ # to the point. More importantly they use the same API that you would use to extend
9
+ # Authlogic. That being said, they are great examples of how to extend Authlogic and
10
+ # add / modify behavior to Authlogic. These modules could easily be pulled out into
11
+ # their own plugin and become an "add on" without any change.
10
12
  #
11
- # Now to the point of this module. Just like in ActiveRecord you have before_save, before_validation, etc.
12
- # You have similar callbacks with Authlogic, see the METHODS constant below. The order of execution is as follows:
13
+ # Now to the point of this module. Just like in ActiveRecord you have before_save,
14
+ # before_validation, etc. You have similar callbacks with Authlogic, see the METHODS
15
+ # constant below. The order of execution is as follows:
13
16
  #
14
17
  # before_persisting
15
18
  # persist
@@ -38,9 +41,10 @@ module Authlogic
38
41
  # destroy
39
42
  # after_destroy
40
43
  #
41
- # Notice the "save record if changed?" lines above. This helps with performance. If you need to make
42
- # changes to the associated record, there is no need to save the record, Authlogic will do it for you.
43
- # This allows multiple modules to modify the record and execute as few queries as possible.
44
+ # Notice the "save record if changed?" lines above. This helps with performance. If
45
+ # you need to make changes to the associated record, there is no need to save the
46
+ # record, Authlogic will do it for you. This allows multiple modules to modify the
47
+ # record and execute as few queries as possible.
44
48
  #
45
49
  # **WARNING**: unlike ActiveRecord, these callbacks must be set up on the class level:
46
50
  #
@@ -50,38 +54,57 @@ module Authlogic
50
54
  # # ..etc
51
55
  # end
52
56
  #
53
- # You can NOT define a "before_validation" method, this is bad practice and does not allow Authlogic
54
- # to extend properly with multiple extensions. Please ONLY use the method above.
57
+ # You can NOT define a "before_validation" method, this is bad practice and does not
58
+ # allow Authlogic to extend properly with multiple extensions. Please ONLY use the
59
+ # method above.
55
60
  module Callbacks
56
61
  METHODS = [
57
- "before_persisting", "persist", "after_persisting",
58
- "before_validation", "before_validation_on_create", "before_validation_on_update", "validate",
59
- "after_validation_on_update", "after_validation_on_create", "after_validation",
60
- "before_save", "before_create", "before_update", "after_update", "after_create", "after_save",
61
- "before_destroy", "after_destroy"
62
- ]
62
+ "before_persisting",
63
+ "persist",
64
+ "after_persisting",
65
+ "before_validation",
66
+ "before_validation_on_create",
67
+ "before_validation_on_update",
68
+ "validate",
69
+ "after_validation_on_update",
70
+ "after_validation_on_create",
71
+ "after_validation",
72
+ "before_save",
73
+ "before_create",
74
+ "before_update",
75
+ "after_update",
76
+ "after_create",
77
+ "after_save",
78
+ "before_destroy",
79
+ "after_destroy"
80
+ ].freeze
63
81
 
64
82
  def self.included(base) #:nodoc:
65
83
  base.send :include, ActiveSupport::Callbacks
66
84
  if Gem::Version.new(ActiveSupport::VERSION::STRING) >= Gem::Version.new('5')
67
- base.define_callbacks *METHODS + [{ :terminator => ->(target, result_lambda) { result_lambda.call == false } }]
68
- base.define_callbacks *['persist', { :terminator => ->(target, result_lambda) { result_lambda.call == true } }]
85
+ base.define_callbacks(
86
+ *METHODS + [{ terminator: ->(_target, result_lambda) { result_lambda.call == false } }]
87
+ )
88
+ base.define_callbacks(
89
+ 'persist',
90
+ terminator: ->(_target, result_lambda) { result_lambda.call == true }
91
+ )
69
92
  elsif Gem::Version.new(ActiveSupport::VERSION::STRING) >= Gem::Version.new('4.1')
70
- base.define_callbacks *METHODS + [{ :terminator => ->(target, result) { result == false } }]
71
- base.define_callbacks *['persist', { :terminator => ->(target, result) { result == true } }]
93
+ base.define_callbacks(*METHODS + [{ terminator: ->(_target, result) { result == false } }])
94
+ base.define_callbacks('persist', terminator: ->(_target, result) { result == true })
72
95
  else
73
- base.define_callbacks *METHODS + [{ :terminator => 'result == false' }]
74
- base.define_callbacks *['persist', { :terminator => 'result == true' }]
96
+ base.define_callbacks(*METHODS + [{ terminator: 'result == false' }])
97
+ base.define_callbacks('persist', terminator: 'result == true')
75
98
  end
76
99
 
77
100
  # If Rails 3, support the new callback syntax
78
101
  if base.singleton_class.method_defined?(:set_callback)
79
102
  METHODS.each do |method|
80
- base.class_eval <<-"end_eval", __FILE__, __LINE__
103
+ base.class_eval <<-EOS, __FILE__, __LINE__
81
104
  def self.#{method}(*methods, &block)
82
105
  set_callback :#{method}, *methods, &block
83
106
  end
84
- end_eval
107
+ EOS
85
108
  end
86
109
  end
87
110
  end
@@ -89,16 +112,16 @@ module Authlogic
89
112
  private
90
113
 
91
114
  METHODS.each do |method|
92
- class_eval <<-"end_eval", __FILE__, __LINE__
115
+ class_eval <<-EOS, __FILE__, __LINE__
93
116
  def #{method}
94
117
  run_callbacks(:#{method})
95
118
  end
96
- end_eval
119
+ EOS
97
120
  end
98
121
 
99
122
  def save_record(alternate_record = nil)
100
123
  r = alternate_record || record
101
- r.save_without_session_maintenance(:validate => false) if r && r.changed? && !r.readonly?
124
+ r.save_without_session_maintenance(validate: false) if r && r.changed? && !r.readonly?
102
125
  end
103
126
  end
104
127
  end
@@ -3,6 +3,8 @@ module Authlogic
3
3
  # Handles all authentication that deals with cookies, such as persisting,
4
4
  # saving, and destroying.
5
5
  module Cookies
6
+ VALID_SAME_SITE_VALUES = [nil, 'Lax', 'Strict'].freeze
7
+
6
8
  def self.included(klass)
7
9
  klass.class_eval do
8
10
  extend Config
@@ -54,23 +56,37 @@ module Authlogic
54
56
  # Should the cookie be set as secure? If true, the cookie will only be sent over
55
57
  # SSL connections
56
58
  #
57
- # * <tt>Default:</tt> false
59
+ # * <tt>Default:</tt> true
58
60
  # * <tt>Accepts:</tt> Boolean
59
61
  def secure(value = nil)
60
- rw_config(:secure, value, false)
62
+ rw_config(:secure, value, true)
61
63
  end
62
64
  alias_method :secure=, :secure
63
65
 
64
66
  # Should the cookie be set as httponly? If true, the cookie will not be
65
67
  # accessible from javascript
66
68
  #
67
- # * <tt>Default:</tt> false
69
+ # * <tt>Default:</tt> true
68
70
  # * <tt>Accepts:</tt> Boolean
69
71
  def httponly(value = nil)
70
- rw_config(:httponly, value, false)
72
+ rw_config(:httponly, value, true)
71
73
  end
72
74
  alias_method :httponly=, :httponly
73
75
 
76
+ # Should the cookie be prevented from being send along with cross-site
77
+ # requests?
78
+ #
79
+ # * <tt>Default:</tt> nil
80
+ # * <tt>Accepts:</tt> String, one of nil, 'Lax' or 'Strict'
81
+ def same_site(value = nil)
82
+ unless VALID_SAME_SITE_VALUES.include?(value)
83
+ msg = "Invalid same_site value: #{value}. Valid: #{VALID_SAME_SITE_VALUES.inspect}"
84
+ raise ArgumentError.new(msg)
85
+ end
86
+ rw_config(:same_site, value)
87
+ end
88
+ alias_method :same_site=, :same_site
89
+
74
90
  # Should the cookie be signed? If the controller adapter supports it, this is a
75
91
  # measure against cookie tampering.
76
92
  def sign_cookie(value = nil)
@@ -95,8 +111,8 @@ module Authlogic
95
111
  self.remember_me = values.first.with_indifferent_access[:remember_me]
96
112
  end
97
113
  else
98
- r = values.find { |value| value.is_a?(TrueClass) || value.is_a?(FalseClass) }
99
- self.remember_me = r if !r.nil?
114
+ r = values.find { |val| val.is_a?(TrueClass) || val.is_a?(FalseClass) }
115
+ self.remember_me = r unless r.nil?
100
116
  end
101
117
  end
102
118
 
@@ -172,6 +188,21 @@ module Authlogic
172
188
  httponly == true || httponly == "true" || httponly == "1"
173
189
  end
174
190
 
191
+ # If the cookie should be marked as SameSite with 'Lax' or 'Strict' flag.
192
+ def same_site
193
+ return @same_site if defined?(@same_site)
194
+ @same_site = self.class.same_site(nil)
195
+ end
196
+
197
+ # Accepts nil, 'Lax' or 'Strict' as possible flags.
198
+ def same_site=(value)
199
+ unless VALID_SAME_SITE_VALUES.include?(value)
200
+ msg = "Invalid same_site value: #{value}. Valid: #{VALID_SAME_SITE_VALUES.inspect}"
201
+ raise ArgumentError.new(msg)
202
+ end
203
+ @same_site = value
204
+ end
205
+
175
206
  # If the cookie should be signed
176
207
  def sign_cookie
177
208
  return @sign_cookie if defined?(@sign_cookie)
@@ -196,12 +227,16 @@ module Authlogic
196
227
  end
197
228
 
198
229
  def cookie_credentials
230
+ cookie = cookie_jar[cookie_key]
231
+ cookie && cookie.split("::")
232
+ end
233
+
234
+ def cookie_jar
199
235
  if self.class.sign_cookie
200
- cookie = controller.cookies.signed[cookie_key]
236
+ controller.cookies.signed
201
237
  else
202
- cookie = controller.cookies[cookie_key]
238
+ controller.cookies
203
239
  end
204
- cookie && cookie.split("::")
205
240
  end
206
241
 
207
242
  # Tries to validate the session from information in the cookie
@@ -225,18 +260,24 @@ module Authlogic
225
260
  end
226
261
 
227
262
  def generate_cookie_for_saving
228
- remember_me_until_value = "::#{remember_me_until.iso8601}" if remember_me?
263
+ value = format(
264
+ "%s::%s%s",
265
+ record.persistence_token,
266
+ record.send(record.class.primary_key),
267
+ remember_me? ? "::#{remember_me_until.iso8601}" : ""
268
+ )
229
269
  {
230
- :value => "#{record.persistence_token}::#{record.send(record.class.primary_key)}#{remember_me_until_value}",
231
- :expires => remember_me_until,
232
- :secure => secure,
233
- :httponly => httponly,
234
- :domain => controller.cookie_domain
270
+ value: value,
271
+ expires: remember_me_until,
272
+ secure: secure,
273
+ httponly: httponly,
274
+ same_site: same_site,
275
+ domain: controller.cookie_domain
235
276
  }
236
277
  end
237
278
 
238
279
  def destroy_cookie
239
- controller.cookies.delete cookie_key, :domain => controller.cookie_domain
280
+ controller.cookies.delete cookie_key, domain: controller.cookie_domain
240
281
  end
241
282
  end
242
283
  end
@@ -1,10 +1,16 @@
1
1
  module Authlogic
2
2
  module Session
3
- # Provides methods to create and destroy objects. Basically controls their "existence".
3
+ # Provides methods to create and destroy objects. Basically controls their
4
+ # "existence".
4
5
  module Existence
5
6
  class SessionInvalidError < ::StandardError # :nodoc:
6
7
  def initialize(session)
7
- super("Your session is invalid and has the following errors: #{session.errors.full_messages.to_sentence}")
8
+ message = I18n.t(
9
+ 'error_messages.session_invalid',
10
+ default: "Your session is invalid and has the following errors:"
11
+ )
12
+ message += " #{session.errors.full_messages.to_sentence}"
13
+ super message
8
14
  end
9
15
  end
10
16
 
@@ -40,8 +46,9 @@ module Authlogic
40
46
  end
41
47
 
42
48
  module InstanceMethods
43
- # Clears all errors and the associated record, you should call this terminate a session, thus requiring
44
- # the user to authenticate again if it is needed.
49
+ # Clears all errors and the associated record, you should call this
50
+ # terminate a session, thus requiring the user to authenticate again if
51
+ # it is needed.
45
52
  def destroy
46
53
  before_destroy
47
54
  save_record
@@ -51,16 +58,18 @@ module Authlogic
51
58
  true
52
59
  end
53
60
 
54
- # Returns true if the session is new, meaning no action has been taken on it and a successful save
55
- # has not taken place.
61
+ # Returns true if the session is new, meaning no action has been taken
62
+ # on it and a successful save has not taken place.
56
63
  def new_session?
57
64
  new_session != false
58
65
  end
59
66
 
60
- # After you have specified all of the details for your session you can try to save it. This will
61
- # run validation checks and find the associated record, if all validation passes. If validation
62
- # does not pass, the save will fail and the errors will be stored in the errors object.
63
- def save(&block)
67
+ # After you have specified all of the details for your session you can
68
+ # try to save it. This will run validation checks and find the
69
+ # associated record, if all validation passes. If validation does not
70
+ # pass, the save will fail and the errors will be stored in the errors
71
+ # object.
72
+ def save
64
73
  result = nil
65
74
  if valid?
66
75
  self.record = attempted_record
@@ -81,7 +90,8 @@ module Authlogic
81
90
  result
82
91
  end
83
92
 
84
- # Same as save but raises an exception of validation errors when validation fails
93
+ # Same as save but raises an exception of validation errors when
94
+ # validation fails
85
95
  def save!
86
96
  result = save
87
97
  raise SessionInvalidError.new(self) unless result
@@ -12,6 +12,37 @@ module Authlogic
12
12
  end
13
13
 
14
14
  module InstanceMethods
15
+ E_AC_PARAMETERS = <<-EOS.strip_heredoc.freeze
16
+ Passing an ActionController::Parameters to Authlogic is not allowed.
17
+
18
+ In Authlogic 3, especially during the transition of rails to Strong
19
+ Parameters, it was common for Authlogic users to forget to `permit`
20
+ their params. They would pass their params into Authlogic, we'd call
21
+ `to_h`, and they'd be surprised when authentication failed.
22
+
23
+ In 2018, people are still making this mistake. We'd like to help them
24
+ and make authlogic a little simpler at the same time, so in Authlogic
25
+ 3.7.0, we deprecated the use of ActionController::Parameters. Instead,
26
+ pass a plain Hash. Please replace:
27
+
28
+ UserSession.new(user_session_params)
29
+ UserSession.create(user_session_params)
30
+
31
+ with
32
+
33
+ UserSession.new(user_session_params.to_h)
34
+ UserSession.create(user_session_params.to_h)
35
+
36
+ And don't forget to `permit`!
37
+
38
+ We discussed this issue thoroughly between late 2016 and early
39
+ 2018. Notable discussions include:
40
+
41
+ - https://github.com/binarylogic/authlogic/issues/512
42
+ - https://github.com/binarylogic/authlogic/pull/558
43
+ - https://github.com/binarylogic/authlogic/pull/577
44
+ EOS
45
+
15
46
  def initialize(*args)
16
47
  self.credentials = args
17
48
  end
@@ -22,22 +53,37 @@ module Authlogic
22
53
  []
23
54
  end
24
55
 
25
- # Set your credentials before you save your session. You can pass a hash of
26
- # credentials:
56
+ # Set your credentials before you save your session. There are many
57
+ # method signatures.
27
58
  #
28
- # session.credentials = {:login => "my login", :password => "my password", :remember_me => true}
59
+ # ```
60
+ # # A hash of credentials is most common
61
+ # session.credentials = { login: "foo", password: "bar", remember_me: true }
29
62
  #
30
- # or you can pass an array of objects:
63
+ # # You must pass an actual Hash, `ActionController::Parameters` is
64
+ # # specifically not allowed.
31
65
  #
32
- # session.credentials = [my_user_object, true]
66
+ # # You can pass an array of objects:
67
+ # session.credentials = [my_user_object, true]
33
68
  #
34
- # and if you need to set an id, just pass it last. This value need be the last
35
- # item in the array you pass, since the id is something that you control yourself,
36
- # it should never be set from a hash or a form. Examples:
69
+ # # If you need to set an id (see `Authlogic::Session::Id`) pass it
70
+ # # last. It needs be the last item in the array you pass, since the id
71
+ # # is something that you control yourself, it should never be set from
72
+ # # a hash or a form. Examples:
73
+ # session.credentials = [
74
+ # {:login => "foo", :password => "bar", :remember_me => true},
75
+ # :my_id
76
+ # ]
77
+ # session.credentials = [my_user_object, true, :my_id]
37
78
  #
38
- # session.credentials = [{:login => "my login", :password => "my password", :remember_me => true}, :my_id]
39
- # session.credentials = [my_user_object, true, :my_id]
79
+ # # Finally, there's priority_record
80
+ # [{ priority_record: my_object }, :my_id]
81
+ # ```
40
82
  def credentials=(values)
83
+ normalized = Array.wrap(values)
84
+ if normalized.first.class.name == "ActionController::Parameters"
85
+ raise TypeError.new(E_AC_PARAMETERS)
86
+ end
41
87
  end
42
88
 
43
89
  def inspect