authlogic 5.1.0 → 6.3.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: 599fa6150e2129f6366ed5cee0db158d6e7152a523174c1df72cd28b4877bf07
4
- data.tar.gz: 4bb06ac0d0d6d34ff7ced64861823c03920256df29961fd19ebd0ac820e04cd0
3
+ metadata.gz: f51d7731ff8fa94fae297857416a4f8e2d3ff6a36be6c95151100928f4bde5a1
4
+ data.tar.gz: 1cc8a04722128c14023fb31b2109ad31138e74b887a8e89bf4a7400b841cc708
5
5
  SHA512:
6
- metadata.gz: 35e71d8dc041a482b80127e36a0bd60296df1b472db130b481c843cccaa2969c946f936ca9e9faf2603de62c4dae1f20967fa0aa929e24429b7204b331761204
7
- data.tar.gz: dcf067170c35323dd8159bef69e2053af0b9c4fee96d5886d0b00404dd63b8eafc44601799c6f1a4261f9680b231fd5c4fa05eb64121eea9cb20ad8433df256f
6
+ metadata.gz: bb9684e8af955d1bff59dd4f3b4f803cea8e405f411d3faff59d47fd228520be810bc220a1d39434539b416b844ce901044c742f3dc4a4a6fa0fdcbdc4637f89
7
+ data.tar.gz: b0a2dba042bd7802dc33d837c14fa103f5e7f0f0eb28d1c26f66eb0d5dd8ce950aab37cddca7d75956b2afa9e4c6588b3790cd781157200c4b1fee7394d1232b
@@ -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,13 +979,27 @@ 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)
961
986
  end
962
987
  alias sign_cookie= sign_cookie
963
988
 
989
+ # Should the cookie be encrypted? If the controller adapter supports it, this is a
990
+ # measure to hide the contents of the cookie (e.g. persistence_token)
991
+ def encrypt_cookie(value = nil)
992
+ if value && controller && !controller.cookies.respond_to?(:encrypted)
993
+ raise "Encrypted cookies not supported with #{controller.class}!"
994
+ end
995
+ if value && sign_cookie
996
+ raise "It is recommended to use encrypt_cookie instead of sign_cookie. " \
997
+ "You may not enable both options."
998
+ end
999
+ rw_config(:encrypt_cookie, value, false)
1000
+ end
1001
+ alias encrypt_cookie= encrypt_cookie
1002
+
964
1003
  # Works exactly like cookie_key, but for sessions. See cookie_key for more info.
965
1004
  #
966
1005
  # * <tt>Default:</tt> cookie_key
@@ -1065,24 +1104,10 @@ module Authlogic
1065
1104
  # Constructor
1066
1105
  # ===========
1067
1106
 
1068
- # rubocop:disable Metrics/AbcSize
1069
1107
  def initialize(*args)
1070
1108
  @id = nil
1071
1109
  self.scope = self.class.scope
1072
-
1073
- # Creating an alias method for the "record" method based on the klass
1074
- # name, so that we can do:
1075
- #
1076
- # session.user
1077
- #
1078
- # instead of:
1079
- #
1080
- # session.record
1081
- unless self.class.configured_klass_methods
1082
- self.class.send(:alias_method, klass_name.demodulize.underscore.to_sym, :record)
1083
- self.class.configured_klass_methods = true
1084
- end
1085
-
1110
+ define_record_alias_method
1086
1111
  raise Activation::NotActivatedError unless self.class.activated?
1087
1112
  unless self.class.configured_password_methods
1088
1113
  configure_password_methods
@@ -1091,7 +1116,6 @@ module Authlogic
1091
1116
  instance_variable_set("@#{password_field}", nil)
1092
1117
  self.credentials = args
1093
1118
  end
1094
- # rubocop:enable Metrics/AbcSize
1095
1119
 
1096
1120
  # Public instance methods
1097
1121
  # =======================
@@ -1480,6 +1504,23 @@ module Authlogic
1480
1504
  sign_cookie == true || sign_cookie == "true" || sign_cookie == "1"
1481
1505
  end
1482
1506
 
1507
+ # If the cookie should be encrypted
1508
+ def encrypt_cookie
1509
+ return @encrypt_cookie if defined?(@encrypt_cookie)
1510
+ @encrypt_cookie = self.class.encrypt_cookie
1511
+ end
1512
+
1513
+ # Accepts a boolean as to whether the cookie should be encrypted. If true
1514
+ # the cookie will be saved in an encrypted state.
1515
+ def encrypt_cookie=(value)
1516
+ @encrypt_cookie = value
1517
+ end
1518
+
1519
+ # See encrypt_cookie
1520
+ def encrypt_cookie?
1521
+ encrypt_cookie == true || encrypt_cookie == "true" || encrypt_cookie == "1"
1522
+ end
1523
+
1483
1524
  # The scope of the current object
1484
1525
  def scope
1485
1526
  @scope ||= {}
@@ -1497,24 +1538,21 @@ module Authlogic
1497
1538
  # Determines if the information you provided for authentication is valid
1498
1539
  # or not. If there is a problem with the information provided errors will
1499
1540
  # be added to the errors object and this method will return false.
1541
+ #
1542
+ # @api public
1500
1543
  def valid?
1501
1544
  errors.clear
1502
1545
  self.attempted_record = nil
1503
-
1504
- run_callbacks(:before_validation)
1505
- run_callbacks(new_session? ? :before_validation_on_create : :before_validation_on_update)
1546
+ run_the_before_validation_callbacks
1506
1547
 
1507
1548
  # Run the `validate` callbacks, eg. `validate_by_password`.
1508
1549
  # This is when `attempted_record` is set.
1509
1550
  run_callbacks(:validate)
1510
1551
 
1511
1552
  ensure_authentication_attempted
1512
-
1513
1553
  if errors.empty?
1514
- run_callbacks(new_session? ? :after_validation_on_create : :after_validation_on_update)
1515
- run_callbacks(:after_validation)
1554
+ run_the_after_validation_callbacks
1516
1555
  end
1517
-
1518
1556
  save_record(attempted_record)
1519
1557
  errors.empty?
1520
1558
  end
@@ -1616,14 +1654,22 @@ module Authlogic
1616
1654
  # @api private
1617
1655
  # @return ::Authlogic::CookieCredentials or if no cookie is found, nil
1618
1656
  def cookie_credentials
1657
+ return unless cookie_enabled?
1658
+
1619
1659
  cookie_value = cookie_jar[cookie_key]
1620
1660
  unless cookie_value.nil?
1621
1661
  ::Authlogic::CookieCredentials.parse(cookie_value)
1622
1662
  end
1623
1663
  end
1624
1664
 
1665
+ def cookie_enabled?
1666
+ !controller.cookies.nil?
1667
+ end
1668
+
1625
1669
  def cookie_jar
1626
- if self.class.sign_cookie
1670
+ if self.class.encrypt_cookie
1671
+ controller.cookies.encrypted
1672
+ elsif self.class.sign_cookie
1627
1673
  controller.cookies.signed
1628
1674
  else
1629
1675
  controller.cookies
@@ -1641,15 +1687,23 @@ module Authlogic
1641
1687
  self.class.send(:attr_reader, login_field) unless respond_to?(login_field)
1642
1688
  end
1643
1689
 
1690
+ # @api private
1644
1691
  def define_password_field_methods
1645
1692
  return unless password_field
1646
- self.class.send(:attr_writer, password_field) unless respond_to?("#{password_field}=")
1647
- 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
1648
1696
 
1649
- # The password should not be accessible publicly. This way forms
1650
- # using form_for don't fill the password with the attempted
1651
- # password. To prevent this we just create this method that is
1652
- # 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
1653
1707
  self.class.class_eval(
1654
1708
  <<-EOS, __FILE__, __LINE__ + 1
1655
1709
  private
@@ -1660,6 +1714,28 @@ module Authlogic
1660
1714
  )
1661
1715
  end
1662
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
+
1663
1739
  def destroy_cookie
1664
1740
  controller.cookies.delete cookie_key, domain: controller.cookie_domain
1665
1741
  end
@@ -1695,8 +1771,10 @@ module Authlogic
1695
1771
  attempted_record.failed_login_count >= consecutive_failed_logins_limit
1696
1772
  end
1697
1773
 
1774
+ # @deprecated in favor of `self.class.record_selection_method`
1698
1775
  def find_by_login_method
1699
- self.class.find_by_login_method
1776
+ ::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
1777
+ self.class.record_selection_method
1700
1778
  end
1701
1779
 
1702
1780
  def generalize_credentials_error_messages?
@@ -1705,13 +1783,8 @@ module Authlogic
1705
1783
 
1706
1784
  # @api private
1707
1785
  def generate_cookie_for_saving
1708
- creds = ::Authlogic::CookieCredentials.new(
1709
- record.persistence_token,
1710
- record.send(record.class.primary_key),
1711
- remember_me? ? remember_me_until : nil
1712
- )
1713
1786
  {
1714
- value: creds.to_s,
1787
+ value: generate_cookie_value.to_s,
1715
1788
  expires: remember_me_until,
1716
1789
  secure: secure,
1717
1790
  httponly: httponly,
@@ -1720,6 +1793,14 @@ module Authlogic
1720
1793
  }
1721
1794
  end
1722
1795
 
1796
+ def generate_cookie_value
1797
+ ::Authlogic::CookieCredentials.new(
1798
+ record.persistence_token,
1799
+ record.send(record.class.primary_key),
1800
+ remember_me? ? remember_me_until : nil
1801
+ )
1802
+ end
1803
+
1723
1804
  # Returns a Proc to be executed by
1724
1805
  # `ActionController::HttpAuthentication::Basic` when credentials are
1725
1806
  # present in the HTTP request.
@@ -1747,7 +1828,7 @@ module Authlogic
1747
1828
  end
1748
1829
  end
1749
1830
 
1750
- def increment_login_cout
1831
+ def increment_login_count
1751
1832
  if record.respond_to?(:login_count)
1752
1833
  record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1)
1753
1834
  end
@@ -1898,6 +1979,18 @@ module Authlogic
1898
1979
  attempted_record.failed_login_count = 0
1899
1980
  end
1900
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
+
1901
1994
  # `args[0]` is the name of a model method, like
1902
1995
  # `find_by_single_access_token` or `find_by_smart_case_login_field`.
1903
1996
  def search_for_record(*args)
@@ -1935,11 +2028,7 @@ module Authlogic
1935
2028
  end
1936
2029
 
1937
2030
  def save_cookie
1938
- if sign_cookie?
1939
- controller.cookies.signed[cookie_key] = generate_cookie_for_saving
1940
- else
1941
- controller.cookies[cookie_key] = generate_cookie_for_saving
1942
- end
2031
+ cookie_jar[cookie_key] = generate_cookie_for_saving
1943
2032
  end
1944
2033
 
1945
2034
  # @api private
@@ -1969,7 +2058,7 @@ module Authlogic
1969
2058
  end
1970
2059
 
1971
2060
  def update_info
1972
- increment_login_cout
2061
+ increment_login_count
1973
2062
  clear_failed_login_count
1974
2063
  update_login_timestamps
1975
2064
  update_login_ip_addresses
@@ -2016,7 +2105,10 @@ module Authlogic
2016
2105
  self.invalid_password = false
2017
2106
  validate_by_password__blank_fields
2018
2107
  return if errors.count > 0
2019
- 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
+ )
2020
2112
  if attempted_record.blank?
2021
2113
  add_login_not_found_error
2022
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
@@ -23,6 +23,10 @@ module Authlogic
23
23
  def signed
24
24
  @signed ||= MockSignedCookieJar.new(self)
25
25
  end
26
+
27
+ def encrypted
28
+ @encrypted ||= MockEncryptedCookieJar.new(self)
29
+ end
26
30
  end
27
31
 
28
32
  # A mock of `ActionDispatch::Cookies::SignedKeyRotatingCookieJar`
@@ -35,6 +39,7 @@ module Authlogic
35
39
 
36
40
  def initialize(parent_jar)
37
41
  @parent_jar = parent_jar
42
+ parent_jar.each { |k, v| self[k] = v }
38
43
  end
39
44
 
40
45
  def [](val)
@@ -51,5 +56,37 @@ module Authlogic
51
56
  @parent_jar[key] = options
52
57
  end
53
58
  end
59
+
60
+ # Which ActionDispatch class is this a mock of?
61
+ # TODO: Document as with other mocks above.
62
+ class MockEncryptedCookieJar < MockCookieJar
63
+ attr_reader :parent_jar # helper for testing
64
+
65
+ def initialize(parent_jar)
66
+ @parent_jar = parent_jar
67
+ parent_jar.each { |k, v| self[k] = v }
68
+ end
69
+
70
+ def [](val)
71
+ encrypted_message = @parent_jar[val]
72
+ if encrypted_message
73
+ self.class.decrypt(encrypted_message)
74
+ end
75
+ end
76
+
77
+ def []=(key, options)
78
+ options[:value] = self.class.encrypt(options[:value])
79
+ @parent_jar[key] = options
80
+ end
81
+
82
+ # simple caesar cipher for testing
83
+ def self.encrypt(str)
84
+ str.unpack("U*").map(&:succ).pack("U*")
85
+ end
86
+
87
+ def self.decrypt(str)
88
+ str.unpack("U*").map(&:pred).pack("U*")
89
+ end
90
+ end
54
91
  end
55
92
  end
@@ -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) &&
@@ -17,6 +17,6 @@ module Authlogic
17
17
  #
18
18
  # @api public
19
19
  def self.gem_version
20
- ::Gem::Version.new("5.1.0")
20
+ ::Gem::Version.new("6.3.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.1.0
4
+ version: 6.3.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-03-24 00:00:00.000000000 Z
13
+ date: 2020-12-18 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
@@ -196,14 +176,14 @@ dependencies:
196
176
  requirements:
197
177
  - - "~>"
198
178
  - !ruby/object:Gem::Version
199
- version: 0.67.2
179
+ version: 0.80.1
200
180
  type: :development
201
181
  prerelease: false
202
182
  version_requirements: !ruby/object:Gem::Requirement
203
183
  requirements:
204
184
  - - "~>"
205
185
  - !ruby/object:Gem::Version
206
- version: 0.67.2
186
+ version: 0.80.1
207
187
  - !ruby/object:Gem::Dependency
208
188
  name: rubocop-performance
209
189
  requirement: !ruby/object:Gem::Requirement
@@ -218,6 +198,26 @@ dependencies:
218
198
  - - "~>"
219
199
  - !ruby/object:Gem::Version
220
200
  version: '1.1'
201
+ - !ruby/object:Gem::Dependency
202
+ name: scrypt
203
+ requirement: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '1.2'
208
+ - - "<"
209
+ - !ruby/object:Gem::Version
210
+ version: '4.0'
211
+ type: :development
212
+ prerelease: false
213
+ version_requirements: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: '1.2'
218
+ - - "<"
219
+ - !ruby/object:Gem::Version
220
+ version: '4.0'
221
221
  - !ruby/object:Gem::Dependency
222
222
  name: simplecov
223
223
  requirement: !ruby/object:Gem::Requirement
@@ -252,14 +252,14 @@ dependencies:
252
252
  requirements:
253
253
  - - "~>"
254
254
  - !ruby/object:Gem::Version
255
- version: 1.3.13
255
+ version: 1.4.0
256
256
  type: :development
257
257
  prerelease: false
258
258
  version_requirements: !ruby/object:Gem::Requirement
259
259
  requirements:
260
260
  - - "~>"
261
261
  - !ruby/object:Gem::Version
262
- version: 1.3.13
262
+ version: 1.4.0
263
263
  - !ruby/object:Gem::Dependency
264
264
  name: timecop
265
265
  requirement: !ruby/object:Gem::Requirement
@@ -305,16 +305,22 @@ files:
305
305
  - lib/authlogic/crypto_providers.rb
306
306
  - lib/authlogic/crypto_providers/bcrypt.rb
307
307
  - lib/authlogic/crypto_providers/md5.rb
308
+ - lib/authlogic/crypto_providers/md5/v2.rb
308
309
  - lib/authlogic/crypto_providers/scrypt.rb
309
310
  - lib/authlogic/crypto_providers/sha1.rb
311
+ - lib/authlogic/crypto_providers/sha1/v2.rb
310
312
  - lib/authlogic/crypto_providers/sha256.rb
313
+ - lib/authlogic/crypto_providers/sha256/v2.rb
311
314
  - lib/authlogic/crypto_providers/sha512.rb
315
+ - lib/authlogic/crypto_providers/sha512/v2.rb
316
+ - lib/authlogic/errors.rb
312
317
  - lib/authlogic/i18n.rb
313
318
  - lib/authlogic/i18n/translator.rb
314
319
  - lib/authlogic/random.rb
315
320
  - lib/authlogic/session/base.rb
316
321
  - lib/authlogic/session/magic_column/assigns_last_request_at.rb
317
322
  - lib/authlogic/test_case.rb
323
+ - lib/authlogic/test_case/mock_api_controller.rb
318
324
  - lib/authlogic/test_case/mock_controller.rb
319
325
  - lib/authlogic/test_case/mock_cookie_jar.rb
320
326
  - lib/authlogic/test_case/mock_logger.rb
@@ -333,7 +339,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
333
339
  requirements:
334
340
  - - ">="
335
341
  - !ruby/object:Gem::Version
336
- version: 2.3.0
342
+ version: 2.4.0
337
343
  required_rubygems_version: !ruby/object:Gem::Requirement
338
344
  requirements:
339
345
  - - ">="