authlogic 5.2.0 → 6.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4ede36ceee21501805105c66349d58ba6c6d9d483d5fefc41b8d4cbd5189944
4
- data.tar.gz: f323b446e8f8e2a722bb9d896249170596f64ebf8ff441b2740cb834c2b085b8
3
+ metadata.gz: aa943f509bfe67c9cd576f8201f0fce5f0dd8c417458a629185590257a34f0af
4
+ data.tar.gz: 01bb91b140d913dc7c75c1c78d70b70ae23ea3254c01c8165a2c29e785a00a9e
5
5
  SHA512:
6
- metadata.gz: 26cda51c7c8e2b5a8be9395ef13e3d39d9ee1c73429542d107bb229747b9a004800771a96b8f516e4f994c8926b1d0ef2fb1bf45e38102646451c48a3e4fb453
7
- data.tar.gz: 971d54be27f18e12d8d7eafad90e50627098c689b34e047c0ddea4a96f82abf8c80e45766f49fdd82a9efb45fd3508ca312ee4b0cc0508d06a37887d45f9fcb0
6
+ metadata.gz: efb0ecbdb2a535817297ccbb3d687b25387353a02aaa7ee5d0d05a1cc629adc5e52d81edb7a166ef74f071cf9e584bd4a3f994690f4a82e60c7f54fc9a1df880
7
+ data.tar.gz: 85de14bc97e1e3eecb6f77b346d7953e1f64504c9e91373167555dbb9e352b59cc61a85933135d6b358b049a0b482268f630e9ea6aed1fb341554dd6b7ab6feb
@@ -13,6 +13,7 @@ require "active_record"
13
13
  path = File.dirname(__FILE__) + "/authlogic/"
14
14
 
15
15
  [
16
+ "errors",
16
17
  "i18n",
17
18
  "random",
18
19
  "config",
@@ -31,8 +31,8 @@ module Authlogic
31
31
  #
32
32
  # See the various sub modules for the configuration they provide.
33
33
  def acts_as_authentic
34
- return unless db_setup?
35
34
  yield self if block_given?
35
+ return unless db_setup?
36
36
  acts_as_authentic_modules.each { |mod| include mod }
37
37
  end
38
38
 
@@ -65,12 +65,27 @@ module Authlogic
65
65
  self.acts_as_authentic_modules = modules
66
66
  end
67
67
 
68
+ # Some Authlogic modules requires a database connection with a existing
69
+ # users table by the moment when you call the `acts_as_authentic`
70
+ # method. If you try to call `acts_as_authentic` without a database
71
+ # connection, it will raise a `Authlogic::ModelSetupError`.
72
+ #
73
+ # If you rely on the User model before the database is setup correctly,
74
+ # set this field to false.
75
+ # * <tt>Default:</tt> false
76
+ # * <tt>Accepts:</tt> Boolean
77
+ def raise_on_model_setup_error(value = nil)
78
+ rw_config(:raise_on_model_setup_error, value, false)
79
+ end
80
+ alias raise_on_model_setup_error= raise_on_model_setup_error
81
+
68
82
  private
69
83
 
70
84
  def db_setup?
71
85
  column_names
72
86
  true
73
87
  rescue StandardError
88
+ raise ModelSetupError if raise_on_model_setup_error
74
89
  false
75
90
  end
76
91
 
@@ -102,20 +102,30 @@ module Authlogic
102
102
  # The family of adaptive hash functions (BCrypt, SCrypt, PBKDF2) is the
103
103
  # best choice for password storage today. We recommend SCrypt. Other
104
104
  # one-way functions like SHA512 are inferior, but widely used.
105
- # Reverisbile functions like AES256 are the worst choice, and we no
105
+ # Reversible functions like AES256 are the worst choice, and we no
106
106
  # longer support them.
107
107
  #
108
108
  # You can use the `transition_from_crypto_providers` option to gradually
109
109
  # transition to a better crypto provider without causing your users any
110
110
  # pain.
111
111
  #
112
- # * <tt>Default:</tt> CryptoProviders::SCrypt
112
+ # * <tt>Default:</tt> There is no longer a default value. Prior to
113
+ # Authlogic 6, the default was `CryptoProviders::SCrypt`. If you try
114
+ # to read this config option before setting it, it will raise a
115
+ # `NilCryptoProvider` error. See that error's message for further
116
+ # details, and rationale for this change.
113
117
  # * <tt>Accepts:</tt> Class
114
- def crypto_provider(value = nil)
118
+ def crypto_provider
119
+ acts_as_authentic_config[:crypto_provider].tap { |provider|
120
+ raise NilCryptoProvider if provider.nil?
121
+ }
122
+ end
123
+
124
+ def crypto_provider=(value)
125
+ raise NilCryptoProvider if value.nil?
115
126
  CryptoProviders::Guidance.new(value).impart_wisdom
116
- rw_config(:crypto_provider, value, CryptoProviders::SCrypt)
127
+ rw_config(:crypto_provider, value)
117
128
  end
118
- alias crypto_provider= crypto_provider
119
129
 
120
130
  # Let's say you originally encrypted your passwords with Sha1. Sha1 is
121
131
  # starting to join the party with MD5 and you want to switch to
@@ -93,9 +93,9 @@ module Authlogic
93
93
  end
94
94
 
95
95
  # Save the record and skip session maintenance all together.
96
- def save_without_session_maintenance(*args)
96
+ def save_without_session_maintenance(**options)
97
97
  self.skip_session_maintenance = true
98
- result = save(*args)
98
+ result = save(**options)
99
99
  self.skip_session_maintenance = false
100
100
  result
101
101
  end
@@ -176,7 +176,9 @@ module Authlogic
176
176
  end
177
177
 
178
178
  def log_in_after_password_change?
179
- will_save_change_to_persistence_token? && self.class.log_in_after_password_change
179
+ persisted? &&
180
+ will_save_change_to_persistence_token? &&
181
+ self.class.log_in_after_password_change
180
182
  end
181
183
  end
182
184
  end
@@ -14,7 +14,7 @@ module Authlogic
14
14
  # Returns a `ActionDispatch::Cookies::CookieJar`. See the AC guide
15
15
  # http://guides.rubyonrails.org/action_controller_overview.html#cookies
16
16
  def cookies
17
- controller.send(:cookies)
17
+ controller.respond_to?(:cookies, true) ? controller.send(:cookies) : nil
18
18
  end
19
19
 
20
20
  def cookie_domain
@@ -6,6 +6,9 @@ module Authlogic
6
6
  module CryptoProviders
7
7
  # A poor choice. There are known attacks against this algorithm.
8
8
  class MD5
9
+ # V2 hashes the digest bytes in repeated stretches instead of hex characters.
10
+ autoload :V2, File.join(__dir__, "md5", "v2")
11
+
9
12
  class << self
10
13
  attr_accessor :join_token
11
14
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/md5"
4
+
5
+ module Authlogic
6
+ module CryptoProviders
7
+ class MD5
8
+ # A poor choice. There are known attacks against this algorithm.
9
+ class V2
10
+ class << self
11
+ attr_accessor :join_token
12
+
13
+ # The number of times to loop through the encryption.
14
+ def stretches
15
+ @stretches ||= 1
16
+ end
17
+ attr_writer :stretches
18
+
19
+ # Turns your raw password into a MD5 hash.
20
+ def encrypt(*tokens)
21
+ digest = tokens.flatten.join(join_token)
22
+ stretches.times { digest = Digest::MD5.digest(digest) }
23
+ digest.unpack1("H*")
24
+ end
25
+
26
+ # Does the crypted password match the tokens? Uses the same tokens that
27
+ # were used to encrypt.
28
+ def matches?(crypted, *tokens)
29
+ encrypt(*tokens) == crypted
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -6,6 +6,9 @@ module Authlogic
6
6
  module CryptoProviders
7
7
  # A poor choice. There are known attacks against this algorithm.
8
8
  class Sha1
9
+ # V2 hashes the digest bytes in repeated stretches instead of hex characters.
10
+ autoload :V2, File.join(__dir__, "sha1", "v2")
11
+
9
12
  class << self
10
13
  def join_token
11
14
  @join_token ||= "--"
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+
5
+ module Authlogic
6
+ module CryptoProviders
7
+ class Sha1
8
+ # A poor choice. There are known attacks against this algorithm.
9
+ class V2
10
+ class << self
11
+ def join_token
12
+ @join_token ||= "--"
13
+ end
14
+ attr_writer :join_token
15
+
16
+ # The number of times to loop through the encryption.
17
+ def stretches
18
+ @stretches ||= 10
19
+ end
20
+ attr_writer :stretches
21
+
22
+ # Turns your raw password into a Sha1 hash.
23
+ def encrypt(*tokens)
24
+ tokens = tokens.flatten
25
+ digest = tokens.shift
26
+ stretches.times do
27
+ digest = Digest::SHA1.digest([digest, *tokens].join(join_token))
28
+ end
29
+ digest.unpack1("H*")
30
+ end
31
+
32
+ # Does the crypted password match the tokens? Uses the same tokens that
33
+ # were used to encrypt.
34
+ def matches?(crypted, *tokens)
35
+ encrypt(*tokens) == crypted
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -29,6 +29,9 @@ module Authlogic
29
29
  #
30
30
  # Uses the Sha256 hash algorithm to encrypt passwords.
31
31
  class Sha256
32
+ # V2 hashes the digest bytes in repeated stretches instead of hex characters.
33
+ autoload :V2, File.join(__dir__, "sha256", "v2")
34
+
32
35
  class << self
33
36
  attr_accessor :join_token
34
37
 
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha2"
4
+
5
+ module Authlogic
6
+ # The acts_as_authentic method has a crypto_provider option. This allows you
7
+ # to use any type of encryption you like. Just create a class with a class
8
+ # level encrypt and matches? method. See example below.
9
+ #
10
+ # === Example
11
+ #
12
+ # class MyAwesomeEncryptionMethod
13
+ # def self.encrypt(*tokens)
14
+ # # the tokens passed will be an array of objects, what type of object
15
+ # # is irrelevant, just do what you need to do with them and return a
16
+ # # single encrypted string. for example, you will most likely join all
17
+ # # of the objects into a single string and then encrypt that string
18
+ # end
19
+ #
20
+ # def self.matches?(crypted, *tokens)
21
+ # # return true if the crypted string matches the tokens. Depending on
22
+ # # your algorithm you might decrypt the string then compare it to the
23
+ # # token, or you might encrypt the tokens and make sure it matches the
24
+ # # crypted string, its up to you.
25
+ # end
26
+ # end
27
+ module CryptoProviders
28
+ class Sha256
29
+ # = Sha256
30
+ #
31
+ # Uses the Sha256 hash algorithm to encrypt passwords.
32
+ class V2
33
+ class << self
34
+ attr_accessor :join_token
35
+
36
+ # The number of times to loop through the encryption.
37
+ def stretches
38
+ @stretches ||= 20
39
+ end
40
+ attr_writer :stretches
41
+
42
+ # Turns your raw password into a Sha256 hash.
43
+ def encrypt(*tokens)
44
+ digest = tokens.flatten.join(join_token)
45
+ stretches.times { digest = Digest::SHA256.digest(digest) }
46
+ digest.unpack1("H*")
47
+ end
48
+
49
+ # Does the crypted password match the tokens? Uses the same tokens that
50
+ # were used to encrypt.
51
+ def matches?(crypted, *tokens)
52
+ encrypt(*tokens) == crypted
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -8,6 +8,9 @@ module Authlogic
8
8
  # there are better choices. We recommend transitioning to a more secure,
9
9
  # adaptive hashing algorithm, like scrypt.
10
10
  class Sha512
11
+ # V2 hashes the digest bytes in repeated stretches instead of hex characters.
12
+ autoload :V2, File.join(__dir__, "sha512", "v2")
13
+
11
14
  class << self
12
15
  attr_accessor :join_token
13
16
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha2"
4
+
5
+ module Authlogic
6
+ module CryptoProviders
7
+ class Sha512
8
+ # SHA-512 does not have any practical known attacks against it. However,
9
+ # there are better choices. We recommend transitioning to a more secure,
10
+ # adaptive hashing algorithm, like scrypt.
11
+ class V2
12
+ class << self
13
+ attr_accessor :join_token
14
+
15
+ # The number of times to loop through the encryption.
16
+ def stretches
17
+ @stretches ||= 20
18
+ end
19
+ attr_writer :stretches
20
+
21
+ # Turns your raw password into a Sha512 hash.
22
+ def encrypt(*tokens)
23
+ digest = tokens.flatten.join(join_token)
24
+ stretches.times do
25
+ digest = Digest::SHA512.digest(digest)
26
+ end
27
+ digest.unpack1("H*")
28
+ end
29
+
30
+ # Does the crypted password match the tokens? Uses the same tokens that
31
+ # were used to encrypt.
32
+ def matches?(crypted, *tokens)
33
+ encrypt(*tokens) == crypted
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ # Parent class of all Authlogic errors.
5
+ class Error < StandardError
6
+ end
7
+
8
+ # :nodoc:
9
+ class InvalidCryptoProvider < Error
10
+ end
11
+
12
+ # :nodoc:
13
+ class NilCryptoProvider < InvalidCryptoProvider
14
+ def message
15
+ <<~EOS
16
+ In version 5, Authlogic used SCrypt by default. As of version 6, there
17
+ is no default. We still recommend SCrypt. If you previously relied on
18
+ this default, then, in your User model (or equivalent), please set the
19
+ following:
20
+
21
+ acts_as_authentic do |c|
22
+ c.crypto_provider = ::Authlogic::CryptoProviders::SCrypt
23
+ end
24
+
25
+ Furthermore, the authlogic gem no longer depends on the scrypt gem. In
26
+ your Gemfile, please add scrypt.
27
+
28
+ gem "scrypt", "~> 3.0"
29
+
30
+ We have made this change in Authlogic 6 so that users of other crypto
31
+ providers no longer need to install the scrypt gem.
32
+ EOS
33
+ end
34
+ end
35
+
36
+ # :nodoc:
37
+ class ModelSetupError < Error
38
+ def message
39
+ <<-EOS
40
+ You must establish a database connection and run the migrations before
41
+ using acts_as_authentic. If you need to load the User model before the
42
+ database is set up correctly, please set the following:
43
+
44
+ acts_as_authentic do |c|
45
+ c.raise_on_model_setup_error = false
46
+ end
47
+ EOS
48
+ end
49
+ end
50
+ end
@@ -8,7 +8,7 @@ module Authlogic
8
8
  # arguments, else returns +options[:default]+.
9
9
  def translate(key, options = {})
10
10
  if defined?(::I18n)
11
- ::I18n.translate key, options
11
+ ::I18n.translate key, **options
12
12
  else
13
13
  options[:default]
14
14
  end
@@ -198,7 +198,7 @@ module Authlogic
198
198
  # 2. Enable logging out on timeouts
199
199
  #
200
200
  # class UserSession < Authlogic::Session::Base
201
- # logout_on_timeout true # default if false
201
+ # logout_on_timeout true # default is false
202
202
  # end
203
203
  #
204
204
  # This will require a user to log back in if they are inactive for more than
@@ -351,6 +351,13 @@ module Authlogic
351
351
  - https://github.com/binarylogic/authlogic/pull/558
352
352
  - https://github.com/binarylogic/authlogic/pull/577
353
353
  EOS
354
+ E_DPR_FIND_BY_LOGIN_METHOD = <<~EOS.squish.freeze
355
+ find_by_login_method is deprecated in favor of record_selection_method,
356
+ to avoid confusion with ActiveRecord's "Dynamic Finders".
357
+ (https://guides.rubyonrails.org/v6.0/active_record_querying.html#dynamic-finders)
358
+ For example, rubocop-rails is confused by the deprecated method.
359
+ (https://github.com/rubocop-hq/rubocop-rails/blob/master/lib/rubocop/cop/rails/dynamic_find_by.rb)
360
+ EOS
354
361
  VALID_SAME_SITE_VALUES = [nil, "Lax", "Strict", "None"].freeze
355
362
 
356
363
  # Callbacks
@@ -415,10 +422,10 @@ module Authlogic
415
422
  before_save :set_last_request_at
416
423
 
417
424
  after_save :reset_perishable_token!
418
- after_save :save_cookie
425
+ after_save :save_cookie, if: :cookie_enabled?
419
426
  after_save :update_session
420
427
 
421
- after_destroy :destroy_cookie
428
+ after_destroy :destroy_cookie, if: :cookie_enabled?
422
429
  after_destroy :update_session
423
430
 
424
431
  # `validate` callbacks, in deliberate order. For example,
@@ -438,8 +445,7 @@ module Authlogic
438
445
 
439
446
  class << self
440
447
  attr_accessor(
441
- :configured_password_methods,
442
- :configured_klass_methods
448
+ :configured_password_methods
443
449
  )
444
450
  end
445
451
  attr_accessor(
@@ -472,14 +478,9 @@ module Authlogic
472
478
  !controller.nil?
473
479
  end
474
480
 
475
- # Do you want to allow your users to log in via HTTP basic auth?
476
- #
477
- # I recommend keeping this enabled. The only time I feel this should be
478
- # disabled is if you are not comfortable having your users provide their
479
- # raw username and password. Whatever the reason, you can disable it
480
- # here.
481
+ # Allow users to log in via HTTP basic authentication.
481
482
  #
482
- # * <tt>Default:</tt> true
483
+ # * <tt>Default:</tt> false
483
484
  # * <tt>Accepts:</tt> Boolean
484
485
  def allow_http_basic_auth(value = nil)
485
486
  rw_config(:allow_http_basic_auth, value, false)
@@ -669,35 +670,10 @@ module Authlogic
669
670
  end
670
671
  end
671
672
 
672
- # Authlogic tries to validate the credentials passed to it. One part of
673
- # validation is actually finding the user and making sure it exists.
674
- # What method it uses the do this is up to you.
675
- #
676
- # Let's say you have a UserSession that is authenticating a User. By
677
- # default UserSession will call User.find_by_login(login). You can
678
- # change what method UserSession calls by specifying it here. Then in
679
- # your User model you can make that method do anything you want, giving
680
- # you complete control of how users are found by the UserSession.
681
- #
682
- # Let's take an example: You want to allow users to login by username or
683
- # email. Set this to the name of the class method that does this in the
684
- # User model. Let's call it "find_by_username_or_email"
685
- #
686
- # class User < ActiveRecord::Base
687
- # def self.find_by_username_or_email(login)
688
- # find_by_username(login) || find_by_email(login)
689
- # end
690
- # end
691
- #
692
- # Now just specify the name of this method for this configuration option
693
- # and you are all set. You can do anything you want here. Maybe you
694
- # allow users to have multiple logins and you want to search a has_many
695
- # relationship, etc. The sky is the limit.
696
- #
697
- # * <tt>Default:</tt> "find_by_smart_case_login_field"
698
- # * <tt>Accepts:</tt> Symbol or String
673
+ # @deprecated in favor of record_selection_method
699
674
  def find_by_login_method(value = nil)
700
- rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
675
+ ::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
676
+ record_selection_method(value)
701
677
  end
702
678
  alias find_by_login_method= find_by_login_method
703
679
 
@@ -782,15 +758,23 @@ module Authlogic
782
758
  # example, the UserSession class will authenticate with the User class
783
759
  # unless you specify otherwise in your configuration. See
784
760
  # authenticate_with for information on how to change this value.
761
+ #
762
+ # @api public
785
763
  def klass
786
764
  @klass ||= klass_name ? klass_name.constantize : nil
787
765
  end
788
766
 
789
- # The string of the model name class guessed from the actual session class name.
767
+ # The model name, guessed from the session class name, e.g. "User",
768
+ # from "UserSession".
769
+ #
770
+ # TODO: This method can return nil. We should explore this. It seems
771
+ # likely to cause a NoMethodError later, so perhaps we should raise an
772
+ # error instead.
773
+ #
774
+ # @api private
790
775
  def klass_name
791
- return @klass_name if defined?(@klass_name)
792
- @klass_name = name.scan(/(.*)Session/)[0]
793
- @klass_name = klass_name ? klass_name[0] : nil
776
+ return @klass_name if instance_variable_defined?(:@klass_name)
777
+ @klass_name = name.scan(/(.*)Session/)[0]&.first
794
778
  end
795
779
 
796
780
  # The name of the method you want Authlogic to create for storing the
@@ -798,8 +782,8 @@ module Authlogic
798
782
  # Authlogic::Session, if you want it can be something completely
799
783
  # different than the field in your model. So if you wanted people to
800
784
  # login with a field called "login" and then find users by email this is
801
- # completely doable. See the find_by_login_method configuration option
802
- # for more details.
785
+ # completely doable. See the `record_selection_method` configuration
786
+ # option for details.
803
787
  #
804
788
  # * <tt>Default:</tt> klass.login_field || klass.email_field
805
789
  # * <tt>Accepts:</tt> Symbol or String
@@ -882,6 +866,47 @@ module Authlogic
882
866
  end
883
867
  alias password_field= password_field
884
868
 
869
+ # Authlogic tries to validate the credentials passed to it. One part of
870
+ # validation is actually finding the user and making sure it exists.
871
+ # What method it uses the do this is up to you.
872
+ #
873
+ # ```
874
+ # # user_session.rb
875
+ # record_selection_method :find_by_email
876
+ # ```
877
+ #
878
+ # This is the recommended way to find the user by email address.
879
+ # The resulting query will be `User.find_by_email(send(login_field))`.
880
+ # (`login_field` will fall back to `email_field` if there's no `login`
881
+ # or `username` column).
882
+ #
883
+ # In your User model you can make that method do anything you want,
884
+ # giving you complete control of how users are found by the UserSession.
885
+ #
886
+ # Let's take an example: You want to allow users to login by username or
887
+ # email. Set this to the name of the class method that does this in the
888
+ # User model. Let's call it "find_by_username_or_email"
889
+ #
890
+ # ```
891
+ # class User < ActiveRecord::Base
892
+ # def self.find_by_username_or_email(login)
893
+ # find_by_username(login) || find_by_email(login)
894
+ # end
895
+ # end
896
+ # ```
897
+ #
898
+ # Now just specify the name of this method for this configuration option
899
+ # and you are all set. You can do anything you want here. Maybe you
900
+ # allow users to have multiple logins and you want to search a has_many
901
+ # relationship, etc. The sky is the limit.
902
+ #
903
+ # * <tt>Default:</tt> "find_by_smart_case_login_field"
904
+ # * <tt>Accepts:</tt> Symbol or String
905
+ def record_selection_method(value = nil)
906
+ rw_config(:record_selection_method, value, "find_by_smart_case_login_field")
907
+ end
908
+ alias record_selection_method= record_selection_method
909
+
885
910
  # Whether or not to request HTTP authentication
886
911
  #
887
912
  # If set to true and no HTTP authentication credentials are sent with
@@ -954,7 +979,7 @@ module Authlogic
954
979
  # Should the cookie be signed? If the controller adapter supports it, this is a
955
980
  # measure against cookie tampering.
956
981
  def sign_cookie(value = nil)
957
- if value && !controller.cookies.respond_to?(:signed)
982
+ if value && controller && !controller.cookies.respond_to?(:signed)
958
983
  raise "Signed cookies not supported with #{controller.class}!"
959
984
  end
960
985
  rw_config(:sign_cookie, value, false)
@@ -964,7 +989,7 @@ module Authlogic
964
989
  # Should the cookie be encrypted? If the controller adapter supports it, this is a
965
990
  # measure to hide the contents of the cookie (e.g. persistence_token)
966
991
  def encrypt_cookie(value = nil)
967
- if value && !controller.cookies.respond_to?(:encrypted)
992
+ if value && controller && !controller.cookies.respond_to?(:encrypted)
968
993
  raise "Encrypted cookies not supported with #{controller.class}!"
969
994
  end
970
995
  if value && sign_cookie
@@ -973,7 +998,7 @@ module Authlogic
973
998
  end
974
999
  rw_config(:encrypt_cookie, value, false)
975
1000
  end
976
- alias_method :encrypt_cookie=, :encrypt_cookie
1001
+ alias encrypt_cookie= encrypt_cookie
977
1002
 
978
1003
  # Works exactly like cookie_key, but for sessions. See cookie_key for more info.
979
1004
  #
@@ -1079,24 +1104,10 @@ module Authlogic
1079
1104
  # Constructor
1080
1105
  # ===========
1081
1106
 
1082
- # rubocop:disable Metrics/AbcSize
1083
1107
  def initialize(*args)
1084
1108
  @id = nil
1085
1109
  self.scope = self.class.scope
1086
-
1087
- # Creating an alias method for the "record" method based on the klass
1088
- # name, so that we can do:
1089
- #
1090
- # session.user
1091
- #
1092
- # instead of:
1093
- #
1094
- # session.record
1095
- unless self.class.configured_klass_methods
1096
- self.class.send(:alias_method, klass_name.demodulize.underscore.to_sym, :record)
1097
- self.class.configured_klass_methods = true
1098
- end
1099
-
1110
+ define_record_alias_method
1100
1111
  raise Activation::NotActivatedError unless self.class.activated?
1101
1112
  unless self.class.configured_password_methods
1102
1113
  configure_password_methods
@@ -1105,7 +1116,6 @@ module Authlogic
1105
1116
  instance_variable_set("@#{password_field}", nil)
1106
1117
  self.credentials = args
1107
1118
  end
1108
- # rubocop:enable Metrics/AbcSize
1109
1119
 
1110
1120
  # Public instance methods
1111
1121
  # =======================
@@ -1528,24 +1538,21 @@ module Authlogic
1528
1538
  # Determines if the information you provided for authentication is valid
1529
1539
  # or not. If there is a problem with the information provided errors will
1530
1540
  # be added to the errors object and this method will return false.
1541
+ #
1542
+ # @api public
1531
1543
  def valid?
1532
1544
  errors.clear
1533
1545
  self.attempted_record = nil
1534
-
1535
- run_callbacks(:before_validation)
1536
- run_callbacks(new_session? ? :before_validation_on_create : :before_validation_on_update)
1546
+ run_the_before_validation_callbacks
1537
1547
 
1538
1548
  # Run the `validate` callbacks, eg. `validate_by_password`.
1539
1549
  # This is when `attempted_record` is set.
1540
1550
  run_callbacks(:validate)
1541
1551
 
1542
1552
  ensure_authentication_attempted
1543
-
1544
1553
  if errors.empty?
1545
- run_callbacks(new_session? ? :after_validation_on_create : :after_validation_on_update)
1546
- run_callbacks(:after_validation)
1554
+ run_the_after_validation_callbacks
1547
1555
  end
1548
-
1549
1556
  save_record(attempted_record)
1550
1557
  errors.empty?
1551
1558
  end
@@ -1647,12 +1654,18 @@ module Authlogic
1647
1654
  # @api private
1648
1655
  # @return ::Authlogic::CookieCredentials or if no cookie is found, nil
1649
1656
  def cookie_credentials
1657
+ return unless cookie_enabled?
1658
+
1650
1659
  cookie_value = cookie_jar[cookie_key]
1651
1660
  unless cookie_value.nil?
1652
1661
  ::Authlogic::CookieCredentials.parse(cookie_value)
1653
1662
  end
1654
1663
  end
1655
1664
 
1665
+ def cookie_enabled?
1666
+ !controller.cookies.nil?
1667
+ end
1668
+
1656
1669
  def cookie_jar
1657
1670
  if self.class.encrypt_cookie
1658
1671
  controller.cookies.encrypted
@@ -1674,15 +1687,23 @@ module Authlogic
1674
1687
  self.class.send(:attr_reader, login_field) unless respond_to?(login_field)
1675
1688
  end
1676
1689
 
1690
+ # @api private
1677
1691
  def define_password_field_methods
1678
1692
  return unless password_field
1679
- self.class.send(:attr_writer, password_field) unless respond_to?("#{password_field}=")
1680
- self.class.send(:define_method, password_field) {} unless respond_to?(password_field)
1693
+ define_password_field_writer_method
1694
+ define_password_field_reader_methods
1695
+ end
1681
1696
 
1682
- # The password should not be accessible publicly. This way forms
1683
- # using form_for don't fill the password with the attempted
1684
- # password. To prevent this we just create this method that is
1685
- # private.
1697
+ # The password should not be accessible publicly. This way forms using
1698
+ # form_for don't fill the password with the attempted password. To prevent
1699
+ # this we just create this method that is private.
1700
+ #
1701
+ # @api private
1702
+ def define_password_field_reader_methods
1703
+ unless respond_to?(password_field)
1704
+ # Deliberate no-op method, see rationale above.
1705
+ self.class.send(:define_method, password_field) {}
1706
+ end
1686
1707
  self.class.class_eval(
1687
1708
  <<-EOS, __FILE__, __LINE__ + 1
1688
1709
  private
@@ -1693,6 +1714,28 @@ module Authlogic
1693
1714
  )
1694
1715
  end
1695
1716
 
1717
+ def define_password_field_writer_method
1718
+ unless respond_to?("#{password_field}=")
1719
+ self.class.send(:attr_writer, password_field)
1720
+ end
1721
+ end
1722
+
1723
+ # Creating an alias method for the "record" method based on the klass
1724
+ # name, so that we can do:
1725
+ #
1726
+ # session.user
1727
+ #
1728
+ # instead of:
1729
+ #
1730
+ # session.record
1731
+ #
1732
+ # @api private
1733
+ def define_record_alias_method
1734
+ noun = klass_name.demodulize.underscore.to_sym
1735
+ return if respond_to?(noun)
1736
+ self.class.send(:alias_method, noun, :record)
1737
+ end
1738
+
1696
1739
  def destroy_cookie
1697
1740
  controller.cookies.delete cookie_key, domain: controller.cookie_domain
1698
1741
  end
@@ -1728,8 +1771,10 @@ module Authlogic
1728
1771
  attempted_record.failed_login_count >= consecutive_failed_logins_limit
1729
1772
  end
1730
1773
 
1774
+ # @deprecated in favor of `self.class.record_selection_method`
1731
1775
  def find_by_login_method
1732
- self.class.find_by_login_method
1776
+ ::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
1777
+ self.class.record_selection_method
1733
1778
  end
1734
1779
 
1735
1780
  def generalize_credentials_error_messages?
@@ -1783,7 +1828,7 @@ module Authlogic
1783
1828
  end
1784
1829
  end
1785
1830
 
1786
- def increment_login_cout
1831
+ def increment_login_count
1787
1832
  if record.respond_to?(:login_count)
1788
1833
  record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1)
1789
1834
  end
@@ -1934,6 +1979,18 @@ module Authlogic
1934
1979
  attempted_record.failed_login_count = 0
1935
1980
  end
1936
1981
 
1982
+ # @api private
1983
+ def run_the_after_validation_callbacks
1984
+ run_callbacks(new_session? ? :after_validation_on_create : :after_validation_on_update)
1985
+ run_callbacks(:after_validation)
1986
+ end
1987
+
1988
+ # @api private
1989
+ def run_the_before_validation_callbacks
1990
+ run_callbacks(:before_validation)
1991
+ run_callbacks(new_session? ? :before_validation_on_create : :before_validation_on_update)
1992
+ end
1993
+
1937
1994
  # `args[0]` is the name of a model method, like
1938
1995
  # `find_by_single_access_token` or `find_by_smart_case_login_field`.
1939
1996
  def search_for_record(*args)
@@ -2001,7 +2058,7 @@ module Authlogic
2001
2058
  end
2002
2059
 
2003
2060
  def update_info
2004
- increment_login_cout
2061
+ increment_login_count
2005
2062
  clear_failed_login_count
2006
2063
  update_login_timestamps
2007
2064
  update_login_ip_addresses
@@ -2048,7 +2105,10 @@ module Authlogic
2048
2105
  self.invalid_password = false
2049
2106
  validate_by_password__blank_fields
2050
2107
  return if errors.count > 0
2051
- self.attempted_record = search_for_record(find_by_login_method, send(login_field))
2108
+ self.attempted_record = search_for_record(
2109
+ self.class.record_selection_method,
2110
+ send(login_field)
2111
+ )
2052
2112
  if attempted_record.blank?
2053
2113
  add_login_not_found_error
2054
2114
  return
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require File.dirname(__FILE__) + "/test_case/rails_request_adapter"
4
+ require File.dirname(__FILE__) + "/test_case/mock_api_controller"
4
5
  require File.dirname(__FILE__) + "/test_case/mock_cookie_jar"
5
6
  require File.dirname(__FILE__) + "/test_case/mock_controller"
6
7
  require File.dirname(__FILE__) + "/test_case/mock_logger"
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authlogic
4
+ module TestCase
5
+ # Basically acts like an API controller but doesn't do anything.
6
+ # Authlogic can interact with this, do it's thing and then you can look at
7
+ # the controller object to see if anything changed.
8
+ class MockAPIController < ControllerAdapters::AbstractAdapter
9
+ attr_writer :request_content_type
10
+
11
+ def initialize
12
+ end
13
+
14
+ # Expected API controller has no cookies method.
15
+ undef :cookies
16
+
17
+ def cookie_domain
18
+ nil
19
+ end
20
+
21
+ def logger
22
+ @logger ||= MockLogger.new
23
+ end
24
+
25
+ def params
26
+ @params ||= {}
27
+ end
28
+
29
+ def request
30
+ @request ||= MockRequest.new(self)
31
+ end
32
+
33
+ def request_content_type
34
+ @request_content_type ||= "text/html"
35
+ end
36
+
37
+ def session
38
+ @session ||= {}
39
+ end
40
+
41
+ # If method is defined, it causes below behavior...
42
+ # controller = Authlogic::ControllerAdapters::RailsAdapter.new(
43
+ # Authlogic::TestCase::MockAPIController.new
44
+ # )
45
+ # controller.responds_to_single_access_allowed? #=> true
46
+ # controller.single_access_allowed?
47
+ # #=> NoMethodError: undefined method `single_access_allowed?' for nil:NilClass
48
+ #
49
+ undef :single_access_allowed?
50
+ end
51
+ end
52
+ end
@@ -39,7 +39,7 @@ module Authlogic
39
39
  end
40
40
 
41
41
  def request
42
- @request ||= MockRequest.new(controller)
42
+ @request ||= MockRequest.new(self)
43
43
  end
44
44
 
45
45
  def request_content_type
@@ -3,6 +3,7 @@
3
3
  module Authlogic
4
4
  module TestCase
5
5
  # A mock of `ActionDispatch::Cookies::CookieJar`.
6
+ # See action_dispatch/middleware/cookies.rb
6
7
  class MockCookieJar < Hash # :nodoc:
7
8
  attr_accessor :set_cookies
8
9
 
@@ -11,9 +12,12 @@ module Authlogic
11
12
  hash && hash[:value]
12
13
  end
13
14
 
15
+ # @param options - "the cookie's value [usually a string] or a hash of
16
+ # options as documented above [in action_dispatch/middleware/cookies.rb]"
14
17
  def []=(key, options)
15
- (@set_cookies ||= {})[key.to_s] = options
16
- super
18
+ opt = cookie_options_to_hash(options)
19
+ (@set_cookies ||= {})[key.to_s] = opt
20
+ super(key, opt)
17
21
  end
18
22
 
19
23
  def delete(key, _options = {})
@@ -27,6 +31,17 @@ module Authlogic
27
31
  def encrypted
28
32
  @encrypted ||= MockEncryptedCookieJar.new(self)
29
33
  end
34
+
35
+ private
36
+
37
+ # @api private
38
+ def cookie_options_to_hash(options)
39
+ if options.is_a?(Hash)
40
+ options
41
+ else
42
+ { value: options }
43
+ end
44
+ end
30
45
  end
31
46
 
32
47
  # A mock of `ActionDispatch::Cookies::SignedKeyRotatingCookieJar`
@@ -52,11 +67,14 @@ module Authlogic
52
67
  end
53
68
 
54
69
  def []=(key, options)
55
- options[:value] = "#{options[:value]}--#{Digest::SHA1.hexdigest options[:value]}"
56
- @parent_jar[key] = options
70
+ opt = cookie_options_to_hash(options)
71
+ opt[:value] = "#{opt[:value]}--#{Digest::SHA1.hexdigest opt[:value]}"
72
+ @parent_jar[key] = opt
57
73
  end
58
74
  end
59
75
 
76
+ # Which ActionDispatch class is this a mock of?
77
+ # TODO: Document as with other mocks above.
60
78
  class MockEncryptedCookieJar < MockCookieJar
61
79
  attr_reader :parent_jar # helper for testing
62
80
 
@@ -73,8 +91,9 @@ module Authlogic
73
91
  end
74
92
 
75
93
  def []=(key, options)
76
- options[:value] = self.class.encrypt(options[:value])
77
- @parent_jar[key] = options
94
+ opt = cookie_options_to_hash(options)
95
+ opt[:value] = self.class.encrypt(opt[:value])
96
+ @parent_jar[key] = opt
78
97
  end
79
98
 
80
99
  # simple caesar cipher for testing
@@ -9,6 +9,10 @@ module Authlogic
9
9
  self.controller = controller
10
10
  end
11
11
 
12
+ def format
13
+ controller.request_content_type if controller.respond_to? :request_content_type
14
+ end
15
+
12
16
  def ip
13
17
  controller&.respond_to?(:env) &&
14
18
  controller.env.is_a?(Hash) &&
@@ -12,7 +12,7 @@ module Authlogic
12
12
  def cookies
13
13
  new_cookies = MockCookieJar.new
14
14
  super.each do |key, value|
15
- new_cookies[key] = value[:value]
15
+ new_cookies[key] = cookie_value(value)
16
16
  end
17
17
  new_cookies
18
18
  end
@@ -28,6 +28,12 @@ module Authlogic
28
28
  def request_content_type
29
29
  request.format.to_s
30
30
  end
31
+
32
+ private
33
+
34
+ def cookie_value(value)
35
+ value.is_a?(Hash) ? value[:value] : value
36
+ end
31
37
  end
32
38
  end
33
39
  end
@@ -17,6 +17,6 @@ module Authlogic
17
17
  #
18
18
  # @api public
19
19
  def self.gem_version
20
- ::Gem::Version.new("5.2.0")
20
+ ::Gem::Version.new("6.4.0")
21
21
  end
22
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authlogic
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 6.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Johnson
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-05-08 00:00:00.000000000 Z
13
+ date: 2020-12-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activemodel
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '5.2'
22
22
  - - "<"
23
23
  - !ruby/object:Gem::Version
24
- version: '6.1'
24
+ version: '6.2'
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
@@ -31,7 +31,7 @@ dependencies:
31
31
  version: '5.2'
32
32
  - - "<"
33
33
  - !ruby/object:Gem::Version
34
- version: '6.1'
34
+ version: '6.2'
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: activerecord
37
37
  requirement: !ruby/object:Gem::Requirement
@@ -41,7 +41,7 @@ dependencies:
41
41
  version: '5.2'
42
42
  - - "<"
43
43
  - !ruby/object:Gem::Version
44
- version: '6.1'
44
+ version: '6.2'
45
45
  type: :runtime
46
46
  prerelease: false
47
47
  version_requirements: !ruby/object:Gem::Requirement
@@ -51,7 +51,7 @@ dependencies:
51
51
  version: '5.2'
52
52
  - - "<"
53
53
  - !ruby/object:Gem::Version
54
- version: '6.1'
54
+ version: '6.2'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: activesupport
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -61,7 +61,7 @@ dependencies:
61
61
  version: '5.2'
62
62
  - - "<"
63
63
  - !ruby/object:Gem::Version
64
- version: '6.1'
64
+ version: '6.2'
65
65
  type: :runtime
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
@@ -71,7 +71,7 @@ dependencies:
71
71
  version: '5.2'
72
72
  - - "<"
73
73
  - !ruby/object:Gem::Version
74
- version: '6.1'
74
+ version: '6.2'
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: request_store
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -86,26 +86,6 @@ dependencies:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
88
  version: '1.0'
89
- - !ruby/object:Gem::Dependency
90
- name: scrypt
91
- requirement: !ruby/object:Gem::Requirement
92
- requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- version: '1.2'
96
- - - "<"
97
- - !ruby/object:Gem::Version
98
- version: '4.0'
99
- type: :runtime
100
- prerelease: false
101
- version_requirements: !ruby/object:Gem::Requirement
102
- requirements:
103
- - - ">="
104
- - !ruby/object:Gem::Version
105
- version: '1.2'
106
- - - "<"
107
- - !ruby/object:Gem::Version
108
- version: '4.0'
109
89
  - !ruby/object:Gem::Dependency
110
90
  name: bcrypt
111
91
  requirement: !ruby/object:Gem::Requirement
@@ -190,20 +170,34 @@ dependencies:
190
170
  - - "~>"
191
171
  - !ruby/object:Gem::Version
192
172
  version: 1.1.4
173
+ - !ruby/object:Gem::Dependency
174
+ name: rake
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '13.0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '13.0'
193
187
  - !ruby/object:Gem::Dependency
194
188
  name: rubocop
195
189
  requirement: !ruby/object:Gem::Requirement
196
190
  requirements:
197
191
  - - "~>"
198
192
  - !ruby/object:Gem::Version
199
- version: 0.67.2
193
+ version: 0.80.1
200
194
  type: :development
201
195
  prerelease: false
202
196
  version_requirements: !ruby/object:Gem::Requirement
203
197
  requirements:
204
198
  - - "~>"
205
199
  - !ruby/object:Gem::Version
206
- version: 0.67.2
200
+ version: 0.80.1
207
201
  - !ruby/object:Gem::Dependency
208
202
  name: rubocop-performance
209
203
  requirement: !ruby/object:Gem::Requirement
@@ -218,6 +212,26 @@ dependencies:
218
212
  - - "~>"
219
213
  - !ruby/object:Gem::Version
220
214
  version: '1.1'
215
+ - !ruby/object:Gem::Dependency
216
+ name: scrypt
217
+ requirement: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - ">="
220
+ - !ruby/object:Gem::Version
221
+ version: '1.2'
222
+ - - "<"
223
+ - !ruby/object:Gem::Version
224
+ version: '4.0'
225
+ type: :development
226
+ prerelease: false
227
+ version_requirements: !ruby/object:Gem::Requirement
228
+ requirements:
229
+ - - ">="
230
+ - !ruby/object:Gem::Version
231
+ version: '1.2'
232
+ - - "<"
233
+ - !ruby/object:Gem::Version
234
+ version: '4.0'
221
235
  - !ruby/object:Gem::Dependency
222
236
  name: simplecov
223
237
  requirement: !ruby/object:Gem::Requirement
@@ -252,14 +266,14 @@ dependencies:
252
266
  requirements:
253
267
  - - "~>"
254
268
  - !ruby/object:Gem::Version
255
- version: 1.3.13
269
+ version: 1.4.0
256
270
  type: :development
257
271
  prerelease: false
258
272
  version_requirements: !ruby/object:Gem::Requirement
259
273
  requirements:
260
274
  - - "~>"
261
275
  - !ruby/object:Gem::Version
262
- version: 1.3.13
276
+ version: 1.4.0
263
277
  - !ruby/object:Gem::Dependency
264
278
  name: timecop
265
279
  requirement: !ruby/object:Gem::Requirement
@@ -305,16 +319,22 @@ files:
305
319
  - lib/authlogic/crypto_providers.rb
306
320
  - lib/authlogic/crypto_providers/bcrypt.rb
307
321
  - lib/authlogic/crypto_providers/md5.rb
322
+ - lib/authlogic/crypto_providers/md5/v2.rb
308
323
  - lib/authlogic/crypto_providers/scrypt.rb
309
324
  - lib/authlogic/crypto_providers/sha1.rb
325
+ - lib/authlogic/crypto_providers/sha1/v2.rb
310
326
  - lib/authlogic/crypto_providers/sha256.rb
327
+ - lib/authlogic/crypto_providers/sha256/v2.rb
311
328
  - lib/authlogic/crypto_providers/sha512.rb
329
+ - lib/authlogic/crypto_providers/sha512/v2.rb
330
+ - lib/authlogic/errors.rb
312
331
  - lib/authlogic/i18n.rb
313
332
  - lib/authlogic/i18n/translator.rb
314
333
  - lib/authlogic/random.rb
315
334
  - lib/authlogic/session/base.rb
316
335
  - lib/authlogic/session/magic_column/assigns_last_request_at.rb
317
336
  - lib/authlogic/test_case.rb
337
+ - lib/authlogic/test_case/mock_api_controller.rb
318
338
  - lib/authlogic/test_case/mock_controller.rb
319
339
  - lib/authlogic/test_case/mock_cookie_jar.rb
320
340
  - lib/authlogic/test_case/mock_logger.rb
@@ -333,7 +353,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
333
353
  requirements:
334
354
  - - ">="
335
355
  - !ruby/object:Gem::Version
336
- version: 2.3.0
356
+ version: 2.4.0
337
357
  required_rubygems_version: !ruby/object:Gem::Requirement
338
358
  requirements:
339
359
  - - ">="