authlogic 5.2.0 → 6.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="