authlogic 3.8.0 → 4.5.0

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 (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