authlogic 3.8.0 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
  3. data/.github/ISSUE_TEMPLATE/feature_proposal.md +32 -0
  4. data/.github/triage.md +86 -0
  5. data/.gitignore +4 -3
  6. data/.rubocop.yml +109 -9
  7. data/.rubocop_todo.yml +38 -355
  8. data/.travis.yml +11 -35
  9. data/CHANGELOG.md +345 -2
  10. data/CONTRIBUTING.md +45 -14
  11. data/Gemfile +3 -2
  12. data/README.md +244 -90
  13. data/Rakefile +10 -10
  14. data/UPGRADING.md +22 -0
  15. data/authlogic.gemspec +34 -21
  16. data/doc/use_normal_rails_validation.md +82 -0
  17. data/gemfiles/Gemfile.rails-4.2.x +6 -0
  18. data/{test/gemfiles → gemfiles}/Gemfile.rails-5.1.x +2 -2
  19. data/{test/gemfiles → gemfiles}/Gemfile.rails-5.2.x +2 -2
  20. data/lib/authlogic/acts_as_authentic/base.rb +36 -24
  21. data/lib/authlogic/acts_as_authentic/email.rb +65 -31
  22. data/lib/authlogic/acts_as_authentic/logged_in_status.rb +14 -9
  23. data/lib/authlogic/acts_as_authentic/login.rb +61 -45
  24. data/lib/authlogic/acts_as_authentic/magic_columns.rb +6 -6
  25. data/lib/authlogic/acts_as_authentic/password.rb +267 -146
  26. data/lib/authlogic/acts_as_authentic/perishable_token.rb +24 -19
  27. data/lib/authlogic/acts_as_authentic/persistence_token.rb +10 -15
  28. data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +67 -0
  29. data/lib/authlogic/acts_as_authentic/restful_authentication.rb +50 -14
  30. data/lib/authlogic/acts_as_authentic/session_maintenance.rb +88 -60
  31. data/lib/authlogic/acts_as_authentic/single_access_token.rb +23 -11
  32. data/lib/authlogic/acts_as_authentic/validations_scope.rb +9 -6
  33. data/lib/authlogic/authenticates_many/association.rb +7 -7
  34. data/lib/authlogic/authenticates_many/base.rb +37 -21
  35. data/lib/authlogic/config.rb +21 -10
  36. data/lib/authlogic/controller_adapters/abstract_adapter.rb +38 -11
  37. data/lib/authlogic/controller_adapters/rack_adapter.rb +9 -5
  38. data/lib/authlogic/controller_adapters/rails_adapter.rb +12 -7
  39. data/lib/authlogic/controller_adapters/sinatra_adapter.rb +2 -2
  40. data/lib/authlogic/crypto_providers/aes256.rb +37 -32
  41. data/lib/authlogic/crypto_providers/bcrypt.rb +21 -15
  42. data/lib/authlogic/crypto_providers/md5.rb +4 -2
  43. data/lib/authlogic/crypto_providers/scrypt.rb +22 -17
  44. data/lib/authlogic/crypto_providers/sha1.rb +11 -5
  45. data/lib/authlogic/crypto_providers/sha256.rb +13 -9
  46. data/lib/authlogic/crypto_providers/sha512.rb +0 -21
  47. data/lib/authlogic/crypto_providers/wordpress.rb +32 -3
  48. data/lib/authlogic/crypto_providers.rb +91 -0
  49. data/lib/authlogic/i18n.rb +26 -19
  50. data/lib/authlogic/random.rb +10 -28
  51. data/lib/authlogic/regex.rb +59 -28
  52. data/lib/authlogic/session/activation.rb +10 -7
  53. data/lib/authlogic/session/active_record_trickery.rb +13 -9
  54. data/lib/authlogic/session/base.rb +15 -4
  55. data/lib/authlogic/session/brute_force_protection.rb +40 -33
  56. data/lib/authlogic/session/callbacks.rb +94 -46
  57. data/lib/authlogic/session/cookies.rb +130 -45
  58. data/lib/authlogic/session/existence.rb +21 -11
  59. data/lib/authlogic/session/foundation.rb +64 -14
  60. data/lib/authlogic/session/http_auth.rb +35 -28
  61. data/lib/authlogic/session/id.rb +9 -4
  62. data/lib/authlogic/session/klass.rb +15 -12
  63. data/lib/authlogic/session/magic_columns.rb +58 -55
  64. data/lib/authlogic/session/magic_states.rb +25 -19
  65. data/lib/authlogic/session/params.rb +42 -28
  66. data/lib/authlogic/session/password.rb +130 -120
  67. data/lib/authlogic/session/perishable_token.rb +5 -4
  68. data/lib/authlogic/session/persistence.rb +18 -12
  69. data/lib/authlogic/session/priority_record.rb +15 -12
  70. data/lib/authlogic/session/scopes.rb +51 -32
  71. data/lib/authlogic/session/session.rb +38 -28
  72. data/lib/authlogic/session/timeout.rb +13 -13
  73. data/lib/authlogic/session/unauthorized_record.rb +18 -13
  74. data/lib/authlogic/session/validation.rb +9 -9
  75. data/lib/authlogic/test_case/mock_controller.rb +5 -4
  76. data/lib/authlogic/test_case/mock_cookie_jar.rb +47 -3
  77. data/lib/authlogic/test_case/mock_request.rb +6 -3
  78. data/lib/authlogic/test_case/rails_request_adapter.rb +3 -2
  79. data/lib/authlogic/test_case.rb +70 -2
  80. data/lib/authlogic/version.rb +21 -0
  81. data/lib/authlogic.rb +51 -49
  82. data/test/acts_as_authentic_test/base_test.rb +3 -1
  83. data/test/acts_as_authentic_test/email_test.rb +43 -42
  84. data/test/acts_as_authentic_test/logged_in_status_test.rb +6 -4
  85. data/test/acts_as_authentic_test/login_test.rb +77 -80
  86. data/test/acts_as_authentic_test/magic_columns_test.rb +3 -1
  87. data/test/acts_as_authentic_test/password_test.rb +51 -37
  88. data/test/acts_as_authentic_test/perishable_token_test.rb +13 -5
  89. data/test/acts_as_authentic_test/persistence_token_test.rb +7 -1
  90. data/test/acts_as_authentic_test/restful_authentication_test.rb +14 -3
  91. data/test/acts_as_authentic_test/session_maintenance_test.rb +69 -15
  92. data/test/acts_as_authentic_test/single_access_test.rb +3 -1
  93. data/test/adapter_test.rb +23 -0
  94. data/test/authenticates_many_test.rb +3 -1
  95. data/test/config_test.rb +11 -9
  96. data/test/crypto_provider_test/aes256_test.rb +3 -1
  97. data/test/crypto_provider_test/bcrypt_test.rb +3 -1
  98. data/test/crypto_provider_test/scrypt_test.rb +3 -1
  99. data/test/crypto_provider_test/sha1_test.rb +3 -1
  100. data/test/crypto_provider_test/sha256_test.rb +3 -1
  101. data/test/crypto_provider_test/sha512_test.rb +3 -1
  102. data/test/crypto_provider_test/wordpress_test.rb +26 -0
  103. data/test/fixtures/companies.yml +2 -2
  104. data/test/fixtures/employees.yml +1 -1
  105. data/test/i18n_test.rb +6 -4
  106. data/test/libs/affiliate.rb +2 -0
  107. data/test/libs/company.rb +4 -2
  108. data/test/libs/employee.rb +2 -0
  109. data/test/libs/employee_session.rb +2 -0
  110. data/test/libs/ldaper.rb +2 -0
  111. data/test/libs/project.rb +2 -0
  112. data/test/libs/user.rb +2 -0
  113. data/test/libs/user_session.rb +4 -2
  114. data/test/random_test.rb +10 -38
  115. data/test/session_test/activation_test.rb +3 -1
  116. data/test/session_test/active_record_trickery_test.rb +7 -4
  117. data/test/session_test/brute_force_protection_test.rb +11 -9
  118. data/test/session_test/callbacks_test.rb +12 -4
  119. data/test/session_test/cookies_test.rb +48 -5
  120. data/test/session_test/existence_test.rb +18 -5
  121. data/test/session_test/foundation_test.rb +19 -1
  122. data/test/session_test/http_auth_test.rb +11 -7
  123. data/test/session_test/id_test.rb +3 -1
  124. data/test/session_test/klass_test.rb +3 -1
  125. data/test/session_test/magic_columns_test.rb +13 -13
  126. data/test/session_test/magic_states_test.rb +3 -1
  127. data/test/session_test/params_test.rb +13 -5
  128. data/test/session_test/password_test.rb +10 -8
  129. data/test/session_test/perishability_test.rb +3 -1
  130. data/test/session_test/persistence_test.rb +4 -1
  131. data/test/session_test/scopes_test.rb +16 -8
  132. data/test/session_test/session_test.rb +6 -4
  133. data/test/session_test/timeout_test.rb +4 -2
  134. data/test/session_test/unauthorized_record_test.rb +4 -2
  135. data/test/session_test/validation_test.rb +3 -1
  136. data/test/test_helper.rb +84 -45
  137. metadata +87 -73
  138. data/.github/ISSUE_TEMPLATE.md +0 -13
  139. data/test/gemfiles/Gemfile.rails-3.2.x +0 -7
  140. data/test/gemfiles/Gemfile.rails-4.0.x +0 -7
  141. data/test/gemfiles/Gemfile.rails-4.1.x +0 -7
  142. data/test/gemfiles/Gemfile.rails-4.2.x +0 -7
  143. data/test/gemfiles/Gemfile.rails-5.0.x +0 -6
@@ -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,56 +54,100 @@ 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
- 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
- ]
61
+ METHODS = %w[
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
- def self.included(base) #:nodoc:
65
- base.send :include, ActiveSupport::Callbacks
66
- 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 } }]
69
- 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 } }]
72
- else
73
- base.define_callbacks *METHODS + [{ :terminator => 'result == false' }]
74
- base.define_callbacks *['persist', { :terminator => 'result == true' }]
82
+ class << self
83
+ def included(base) #:nodoc:
84
+ base.send :include, ActiveSupport::Callbacks
85
+ define_session_callbacks(base)
86
+ define_session_callback_installation_methods(base)
75
87
  end
76
88
 
77
- # If Rails 3, support the new callback syntax
78
- if base.singleton_class.method_defined?(:set_callback)
89
+ private
90
+
91
+ # Defines the "callback installation methods". Other modules will use
92
+ # these class methods to install their callbacks. Examples:
93
+ #
94
+ # ```
95
+ # # session/timeout.rb, in `included`
96
+ # before_persisting :reset_stale_state
97
+ #
98
+ # # session/password.rb, in `included`
99
+ # validate :validate_by_password, if: :authenticating_with_password?
100
+ # ```
101
+ def define_session_callback_installation_methods(base)
79
102
  METHODS.each do |method|
80
- base.class_eval <<-"end_eval", __FILE__, __LINE__
81
- def self.#{method}(*methods, &block)
82
- set_callback :#{method}, *methods, &block
103
+ base.class_eval <<-EOS, __FILE__, __LINE__ + 1
104
+ def self.#{method}(*filter_list, &block)
105
+ set_callback(:#{method}, *filter_list, &block)
83
106
  end
84
- end_eval
107
+ EOS
85
108
  end
86
109
  end
87
- end
88
110
 
89
- private
111
+ # Defines session life cycle events that support callbacks.
112
+ def define_session_callbacks(base)
113
+ if Gem::Version.new(ActiveSupport::VERSION::STRING) >= Gem::Version.new("5")
114
+ base.define_callbacks(
115
+ *METHODS,
116
+ terminator: ->(_target, result_lambda) { result_lambda.call == false }
117
+ )
118
+ base.define_callbacks(
119
+ "persist",
120
+ terminator: ->(_target, result_lambda) { result_lambda.call == true }
121
+ )
122
+ else
123
+ base.define_callbacks(
124
+ *METHODS,
125
+ terminator: ->(_target, result) { result == false }
126
+ )
127
+ base.define_callbacks(
128
+ "persist",
129
+ terminator: ->(_target, result) { result == true }
130
+ )
131
+ end
132
+ end
133
+ end
90
134
 
91
- METHODS.each do |method|
92
- class_eval <<-"end_eval", __FILE__, __LINE__
135
+ METHODS.each do |method|
136
+ class_eval(
137
+ <<-EOS, __FILE__, __LINE__ + 1
93
138
  def #{method}
94
139
  run_callbacks(:#{method})
95
140
  end
96
- end_eval
97
- end
141
+ EOS
142
+ )
143
+ end
98
144
 
99
- def save_record(alternate_record = nil)
100
- r = alternate_record || record
101
- r.save_without_session_maintenance(:validate => false) if r && r.changed? && !r.readonly?
145
+ def save_record(alternate_record = nil)
146
+ r = alternate_record || record
147
+ if r&.changed? && !r.readonly?
148
+ r.save_without_session_maintenance(validate: false)
102
149
  end
150
+ end
103
151
  end
104
152
  end
105
153
  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", "None"].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)
@@ -80,6 +96,20 @@ module Authlogic
80
96
  rw_config(:sign_cookie, value, false)
81
97
  end
82
98
  alias_method :sign_cookie=, :sign_cookie
99
+
100
+ # Should the cookie be encrypted? If the controller adapter supports it, this is a
101
+ # measure to hide the contents of the cookie (e.g. persistence_token)"
102
+ def encrypt_cookie(value = nil)
103
+ if value && !controller.cookies.respond_to?(:encrypted)
104
+ raise "Encrypted cookies not supported with #{controller.class}!"
105
+ end
106
+ if value && sign_cookie
107
+ raise "It is recommended to use encrypt_cookie instead of sign_cookie. " \
108
+ "You may not enable both options."
109
+ end
110
+ rw_config(:encrypt_cookie, value, false)
111
+ end
112
+ alias_method :encrypt_cookie=, :encrypt_cookie
83
113
  end
84
114
 
85
115
  # The methods available for an Authlogic::Session::Base object that make up the
@@ -95,8 +125,8 @@ module Authlogic
95
125
  self.remember_me = values.first.with_indifferent_access[:remember_me]
96
126
  end
97
127
  else
98
- r = values.find { |value| value.is_a?(TrueClass) || value.is_a?(FalseClass) }
99
- self.remember_me = r if !r.nil?
128
+ r = values.find { |val| val.is_a?(TrueClass) || val.is_a?(FalseClass) }
129
+ self.remember_me = r unless r.nil?
100
130
  end
101
131
  end
102
132
 
@@ -172,6 +202,21 @@ module Authlogic
172
202
  httponly == true || httponly == "true" || httponly == "1"
173
203
  end
174
204
 
205
+ # If the cookie should be marked as SameSite with 'Lax' or 'Strict' flag.
206
+ def same_site
207
+ return @same_site if defined?(@same_site)
208
+ @same_site = self.class.same_site(nil)
209
+ end
210
+
211
+ # Accepts nil, 'Lax' or 'Strict' as possible flags.
212
+ def same_site=(value)
213
+ unless VALID_SAME_SITE_VALUES.include?(value)
214
+ msg = "Invalid same_site value: #{value}. Valid: #{VALID_SAME_SITE_VALUES.inspect}"
215
+ raise ArgumentError.new(msg)
216
+ end
217
+ @same_site = value
218
+ end
219
+
175
220
  # If the cookie should be signed
176
221
  def sign_cookie
177
222
  return @sign_cookie if defined?(@sign_cookie)
@@ -189,55 +234,95 @@ module Authlogic
189
234
  sign_cookie == true || sign_cookie == "true" || sign_cookie == "1"
190
235
  end
191
236
 
237
+ # If the cookie should be encrypted
238
+ def encrypt_cookie
239
+ return @encrypt_cookie if defined?(@encrypt_cookie)
240
+ @encrypt_cookie = self.class.encrypt_cookie
241
+ end
242
+
243
+ # Accepts a boolean as to whether the cookie should be encrypted. If true
244
+ # the cookie will be saved in an encrypted state.
245
+ def encrypt_cookie=(value)
246
+ @encrypt_cookie = value
247
+ end
248
+
249
+ # See encrypt_cookie
250
+ def encrypt_cookie?
251
+ encrypt_cookie == true || encrypt_cookie == "true" || encrypt_cookie == "1"
252
+ end
253
+
192
254
  private
193
255
 
194
- def cookie_key
195
- build_key(self.class.cookie_key)
196
- end
256
+ def cookie_key
257
+ build_key(self.class.cookie_key)
258
+ end
197
259
 
198
- def cookie_credentials
199
- if self.class.sign_cookie
200
- cookie = controller.cookies.signed[cookie_key]
201
- else
202
- cookie = controller.cookies[cookie_key]
203
- end
204
- cookie && cookie.split("::")
205
- end
260
+ # Returns an array of cookie elements. See cookie format in
261
+ # `generate_cookie_for_saving`. If no cookie is found, returns nil.
262
+ def cookie_credentials
263
+ cookie = cookie_jar[cookie_key]
264
+ cookie&.split("::")
265
+ end
206
266
 
207
- # Tries to validate the session from information in the cookie
208
- def persist_by_cookie
209
- persistence_token, record_id = cookie_credentials
210
- if persistence_token.present?
211
- record = search_for_record("find_by_#{klass.primary_key}", record_id)
212
- self.unauthorized_record = record if record && record.persistence_token == persistence_token
213
- valid?
214
- else
215
- false
216
- end
267
+ # The third element of the cookie indicates whether the user wanted
268
+ # to be remembered (Actually, it's a timestamp, `remember_me_until`)
269
+ # See cookie format in `generate_cookie_for_saving`.
270
+ def cookie_credentials_remember_me?
271
+ !cookie_credentials.nil? && !cookie_credentials[2].nil?
272
+ end
273
+
274
+ def cookie_jar
275
+ if self.class.encrypt_cookie
276
+ controller.cookies.encrypted
277
+ elsif self.class.sign_cookie
278
+ controller.cookies.signed
279
+ else
280
+ controller.cookies
217
281
  end
282
+ end
218
283
 
219
- def save_cookie
220
- if sign_cookie?
221
- controller.cookies.signed[cookie_key] = generate_cookie_for_saving
222
- else
223
- controller.cookies[cookie_key] = generate_cookie_for_saving
284
+ # Tries to validate the session from information in the cookie
285
+ def persist_by_cookie
286
+ persistence_token, record_id = cookie_credentials
287
+ if persistence_token.present?
288
+ record = search_for_record("find_by_#{klass.primary_key}", record_id)
289
+ if record && record.persistence_token == persistence_token
290
+ self.unauthorized_record = record
224
291
  end
292
+ valid?
293
+ else
294
+ false
225
295
  end
296
+ end
226
297
 
227
- def generate_cookie_for_saving
228
- remember_me_until_value = "::#{remember_me_until.iso8601}" if remember_me?
229
- {
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
235
- }
236
- end
298
+ def save_cookie
299
+ cookie_jar[cookie_key] = generate_cookie_for_saving
300
+ true
301
+ end
237
302
 
238
- def destroy_cookie
239
- controller.cookies.delete cookie_key, :domain => controller.cookie_domain
240
- end
303
+ def generate_cookie_for_saving
304
+ {
305
+ value: generate_cookie_value,
306
+ expires: remember_me_until,
307
+ secure: secure,
308
+ httponly: httponly,
309
+ same_site: same_site,
310
+ domain: controller.cookie_domain
311
+ }
312
+ end
313
+
314
+ def generate_cookie_value
315
+ format(
316
+ "%s::%s%s",
317
+ record.persistence_token.to_s,
318
+ record.send(record.class.primary_key).to_s,
319
+ remember_me? ? "::#{remember_me_until.iso8601}" : ""
320
+ )
321
+ end
322
+
323
+ def destroy_cookie
324
+ controller.cookies.delete cookie_key, domain: controller.cookie_domain
325
+ end
241
326
  end
242
327
  end
243
328
  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.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,33 +53,52 @@ 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
44
- "#<#{self.class.name}: #{credentials.blank? ? "no credentials provided" : credentials.inspect}>"
90
+ format(
91
+ "#<%s: %s>",
92
+ self.class.name,
93
+ credentials.blank? ? "no credentials provided" : credentials.inspect
94
+ )
45
95
  end
46
96
 
47
97
  private
48
98
 
49
- def build_key(last_part)
50
- last_part
51
- end
99
+ def build_key(last_part)
100
+ last_part
101
+ end
52
102
  end
53
103
  end
54
104
  end