authlogic 6.0.0 → 6.4.1

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: 755961398552a88cf3761088e4521e71e243249b3a131632e281679d723c82fe
4
- data.tar.gz: 2b739ad482ecdaad8218065a4c03882e730c86825bc7140691e451fc26032815
3
+ metadata.gz: e65309a22f2adc25c9c61f10910d6db41fe2c3f6b9d8037977bdfa094b90dd53
4
+ data.tar.gz: 2e5bb549974be424ad83ae20de60775bcb1b66c207031919caf978de7e5801ff
5
5
  SHA512:
6
- metadata.gz: 52e998e1210ac287f2bc91d01d2afba9416f5b0eee54cff13d89c6c9affdd2ff2a88ac1a80e78ce100c2a43dcbcf777a5f22dd3d72ff3571bc69c5242a89d97c
7
- data.tar.gz: 6152232cf873d2c9be4fa24584b3d8bf8013f95ae58a8117f4494c3a6632df814e36b85d02c67efc0e2b73849a43333c0b9bcf9cb1d379f10587147aa69f808e
6
+ metadata.gz: 1198b8ff9bf45e98e8748365ba67112f249c738952b84a39a32b835353481a37b3b006106ff31031d5a78f2952fffa746c8764639f50ebc3cc0ae99f84f74b98
7
+ data.tar.gz: 9ab8911d8838f3ddf6b8b011d498b08941caba5e20d2c62c1d2ec9c8b17f446c68f7585db88bd0c3497ae26cd11cd6f565eb9da3ae27bd3faee5de7815c62fd6
@@ -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,7 +102,7 @@ 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
@@ -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
@@ -8,6 +8,7 @@ module Authlogic
8
8
  class AbstractAdapter
9
9
  E_COOKIE_DOMAIN_ADAPTER = "The cookie_domain method has not been " \
10
10
  "implemented by the controller adapter"
11
+ ENV_SESSION_OPTIONS = "rack.session.options"
11
12
 
12
13
  attr_accessor :controller
13
14
 
@@ -44,6 +45,26 @@ module Authlogic
44
45
  request.content_type
45
46
  end
46
47
 
48
+ # Inform Rack that we would like a new session ID to be assigned. Changes
49
+ # the ID, but not the contents of the session.
50
+ #
51
+ # The `:renew` option is read by `rack/session/abstract/id.rb`.
52
+ #
53
+ # This is how Devise (via warden) implements defense against Session
54
+ # Fixation. Our implementation is copied directly from the warden gem
55
+ # (set_user in warden/proxy.rb)
56
+ def renew_session_id
57
+ env = request.env
58
+ options = env[ENV_SESSION_OPTIONS]
59
+ if options
60
+ if options.frozen?
61
+ env[ENV_SESSION_OPTIONS] = options.merge(renew: true).freeze
62
+ else
63
+ options[:renew] = true
64
+ end
65
+ end
66
+ end
67
+
47
68
  def session
48
69
  controller.session
49
70
  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
@@ -20,7 +20,7 @@ module Authlogic
20
20
  def encrypt(*tokens)
21
21
  digest = tokens.flatten.join(join_token)
22
22
  stretches.times { digest = Digest::MD5.digest(digest) }
23
- digest.unpack("H*")[0]
23
+ digest.unpack1("H*")
24
24
  end
25
25
 
26
26
  # Does the crypted password match the tokens? Uses the same tokens that
@@ -26,7 +26,7 @@ module Authlogic
26
26
  stretches.times do
27
27
  digest = Digest::SHA1.digest([digest, *tokens].join(join_token))
28
28
  end
29
- digest.unpack("H*")[0]
29
+ digest.unpack1("H*")
30
30
  end
31
31
 
32
32
  # Does the crypted password match the tokens? Uses the same tokens that
@@ -43,7 +43,7 @@ module Authlogic
43
43
  def encrypt(*tokens)
44
44
  digest = tokens.flatten.join(join_token)
45
45
  stretches.times { digest = Digest::SHA256.digest(digest) }
46
- digest.unpack("H*")[0]
46
+ digest.unpack1("H*")
47
47
  end
48
48
 
49
49
  # Does the crypted password match the tokens? Uses the same tokens that
@@ -24,7 +24,7 @@ module Authlogic
24
24
  stretches.times do
25
25
  digest = Digest::SHA512.digest(digest)
26
26
  end
27
- digest.unpack("H*")[0]
27
+ digest.unpack1("H*")
28
28
  end
29
29
 
30
30
  # Does the crypted password match the tokens? Uses the same tokens that
@@ -18,7 +18,7 @@ module Authlogic
18
18
  this default, then, in your User model (or equivalent), please set the
19
19
  following:
20
20
 
21
- acts_as_authentic do |config|
21
+ acts_as_authentic do |c|
22
22
  c.crypto_provider = ::Authlogic::CryptoProviders::SCrypt
23
23
  end
24
24
 
@@ -32,4 +32,19 @@ module Authlogic
32
32
  EOS
33
33
  end
34
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
35
50
  end
@@ -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,11 @@ 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
427
+ after_create :renew_session_id
420
428
 
421
- after_destroy :destroy_cookie
429
+ after_destroy :destroy_cookie, if: :cookie_enabled?
422
430
  after_destroy :update_session
423
431
 
424
432
  # `validate` callbacks, in deliberate order. For example,
@@ -438,8 +446,7 @@ module Authlogic
438
446
 
439
447
  class << self
440
448
  attr_accessor(
441
- :configured_password_methods,
442
- :configured_klass_methods
449
+ :configured_password_methods
443
450
  )
444
451
  end
445
452
  attr_accessor(
@@ -664,35 +671,10 @@ module Authlogic
664
671
  end
665
672
  end
666
673
 
667
- # Authlogic tries to validate the credentials passed to it. One part of
668
- # validation is actually finding the user and making sure it exists.
669
- # What method it uses the do this is up to you.
670
- #
671
- # Let's say you have a UserSession that is authenticating a User. By
672
- # default UserSession will call User.find_by_login(login). You can
673
- # change what method UserSession calls by specifying it here. Then in
674
- # your User model you can make that method do anything you want, giving
675
- # you complete control of how users are found by the UserSession.
676
- #
677
- # Let's take an example: You want to allow users to login by username or
678
- # email. Set this to the name of the class method that does this in the
679
- # User model. Let's call it "find_by_username_or_email"
680
- #
681
- # class User < ActiveRecord::Base
682
- # def self.find_by_username_or_email(login)
683
- # find_by_username(login) || find_by_email(login)
684
- # end
685
- # end
686
- #
687
- # Now just specify the name of this method for this configuration option
688
- # and you are all set. You can do anything you want here. Maybe you
689
- # allow users to have multiple logins and you want to search a has_many
690
- # relationship, etc. The sky is the limit.
691
- #
692
- # * <tt>Default:</tt> "find_by_smart_case_login_field"
693
- # * <tt>Accepts:</tt> Symbol or String
674
+ # @deprecated in favor of record_selection_method
694
675
  def find_by_login_method(value = nil)
695
- rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
676
+ ::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
677
+ record_selection_method(value)
696
678
  end
697
679
  alias find_by_login_method= find_by_login_method
698
680
 
@@ -777,15 +759,23 @@ module Authlogic
777
759
  # example, the UserSession class will authenticate with the User class
778
760
  # unless you specify otherwise in your configuration. See
779
761
  # authenticate_with for information on how to change this value.
762
+ #
763
+ # @api public
780
764
  def klass
781
765
  @klass ||= klass_name ? klass_name.constantize : nil
782
766
  end
783
767
 
784
- # The string of the model name class guessed from the actual session class name.
768
+ # The model name, guessed from the session class name, e.g. "User",
769
+ # from "UserSession".
770
+ #
771
+ # TODO: This method can return nil. We should explore this. It seems
772
+ # likely to cause a NoMethodError later, so perhaps we should raise an
773
+ # error instead.
774
+ #
775
+ # @api private
785
776
  def klass_name
786
- return @klass_name if defined?(@klass_name)
787
- @klass_name = name.scan(/(.*)Session/)[0]
788
- @klass_name = klass_name ? klass_name[0] : nil
777
+ return @klass_name if instance_variable_defined?(:@klass_name)
778
+ @klass_name = name.scan(/(.*)Session/)[0]&.first
789
779
  end
790
780
 
791
781
  # The name of the method you want Authlogic to create for storing the
@@ -793,8 +783,8 @@ module Authlogic
793
783
  # Authlogic::Session, if you want it can be something completely
794
784
  # different than the field in your model. So if you wanted people to
795
785
  # login with a field called "login" and then find users by email this is
796
- # completely doable. See the find_by_login_method configuration option
797
- # for more details.
786
+ # completely doable. See the `record_selection_method` configuration
787
+ # option for details.
798
788
  #
799
789
  # * <tt>Default:</tt> klass.login_field || klass.email_field
800
790
  # * <tt>Accepts:</tt> Symbol or String
@@ -877,6 +867,47 @@ module Authlogic
877
867
  end
878
868
  alias password_field= password_field
879
869
 
870
+ # Authlogic tries to validate the credentials passed to it. One part of
871
+ # validation is actually finding the user and making sure it exists.
872
+ # What method it uses the do this is up to you.
873
+ #
874
+ # ```
875
+ # # user_session.rb
876
+ # record_selection_method :find_by_email
877
+ # ```
878
+ #
879
+ # This is the recommended way to find the user by email address.
880
+ # The resulting query will be `User.find_by_email(send(login_field))`.
881
+ # (`login_field` will fall back to `email_field` if there's no `login`
882
+ # or `username` column).
883
+ #
884
+ # In your User model you can make that method do anything you want,
885
+ # giving you complete control of how users are found by the UserSession.
886
+ #
887
+ # Let's take an example: You want to allow users to login by username or
888
+ # email. Set this to the name of the class method that does this in the
889
+ # User model. Let's call it "find_by_username_or_email"
890
+ #
891
+ # ```
892
+ # class User < ActiveRecord::Base
893
+ # def self.find_by_username_or_email(login)
894
+ # find_by_username(login) || find_by_email(login)
895
+ # end
896
+ # end
897
+ # ```
898
+ #
899
+ # Now just specify the name of this method for this configuration option
900
+ # and you are all set. You can do anything you want here. Maybe you
901
+ # allow users to have multiple logins and you want to search a has_many
902
+ # relationship, etc. The sky is the limit.
903
+ #
904
+ # * <tt>Default:</tt> "find_by_smart_case_login_field"
905
+ # * <tt>Accepts:</tt> Symbol or String
906
+ def record_selection_method(value = nil)
907
+ rw_config(:record_selection_method, value, "find_by_smart_case_login_field")
908
+ end
909
+ alias record_selection_method= record_selection_method
910
+
880
911
  # Whether or not to request HTTP authentication
881
912
  #
882
913
  # If set to true and no HTTP authentication credentials are sent with
@@ -946,16 +977,40 @@ module Authlogic
946
977
  end
947
978
  alias secure= secure
948
979
 
980
+ # Should the Rack session ID be reset after authentication, to protect
981
+ # against Session Fixation attacks?
982
+ #
983
+ # * <tt>Default:</tt> true
984
+ # * <tt>Accepts:</tt> Boolean
985
+ def session_fixation_defense(value = nil)
986
+ rw_config(:session_fixation_defense, value, true)
987
+ end
988
+ alias session_fixation_defense= session_fixation_defense
989
+
949
990
  # Should the cookie be signed? If the controller adapter supports it, this is a
950
991
  # measure against cookie tampering.
951
992
  def sign_cookie(value = nil)
952
- if value && !controller.cookies.respond_to?(:signed)
993
+ if value && controller && !controller.cookies.respond_to?(:signed)
953
994
  raise "Signed cookies not supported with #{controller.class}!"
954
995
  end
955
996
  rw_config(:sign_cookie, value, false)
956
997
  end
957
998
  alias sign_cookie= sign_cookie
958
999
 
1000
+ # Should the cookie be encrypted? If the controller adapter supports it, this is a
1001
+ # measure to hide the contents of the cookie (e.g. persistence_token)
1002
+ def encrypt_cookie(value = nil)
1003
+ if value && controller && !controller.cookies.respond_to?(:encrypted)
1004
+ raise "Encrypted cookies not supported with #{controller.class}!"
1005
+ end
1006
+ if value && sign_cookie
1007
+ raise "It is recommended to use encrypt_cookie instead of sign_cookie. " \
1008
+ "You may not enable both options."
1009
+ end
1010
+ rw_config(:encrypt_cookie, value, false)
1011
+ end
1012
+ alias encrypt_cookie= encrypt_cookie
1013
+
959
1014
  # Works exactly like cookie_key, but for sessions. See cookie_key for more info.
960
1015
  #
961
1016
  # * <tt>Default:</tt> cookie_key
@@ -1060,24 +1115,10 @@ module Authlogic
1060
1115
  # Constructor
1061
1116
  # ===========
1062
1117
 
1063
- # rubocop:disable Metrics/AbcSize
1064
1118
  def initialize(*args)
1065
1119
  @id = nil
1066
1120
  self.scope = self.class.scope
1067
-
1068
- # Creating an alias method for the "record" method based on the klass
1069
- # name, so that we can do:
1070
- #
1071
- # session.user
1072
- #
1073
- # instead of:
1074
- #
1075
- # session.record
1076
- unless self.class.configured_klass_methods
1077
- self.class.send(:alias_method, klass_name.demodulize.underscore.to_sym, :record)
1078
- self.class.configured_klass_methods = true
1079
- end
1080
-
1121
+ define_record_alias_method
1081
1122
  raise Activation::NotActivatedError unless self.class.activated?
1082
1123
  unless self.class.configured_password_methods
1083
1124
  configure_password_methods
@@ -1086,7 +1127,6 @@ module Authlogic
1086
1127
  instance_variable_set("@#{password_field}", nil)
1087
1128
  self.credentials = args
1088
1129
  end
1089
- # rubocop:enable Metrics/AbcSize
1090
1130
 
1091
1131
  # Public instance methods
1092
1132
  # =======================
@@ -1475,6 +1515,23 @@ module Authlogic
1475
1515
  sign_cookie == true || sign_cookie == "true" || sign_cookie == "1"
1476
1516
  end
1477
1517
 
1518
+ # If the cookie should be encrypted
1519
+ def encrypt_cookie
1520
+ return @encrypt_cookie if defined?(@encrypt_cookie)
1521
+ @encrypt_cookie = self.class.encrypt_cookie
1522
+ end
1523
+
1524
+ # Accepts a boolean as to whether the cookie should be encrypted. If true
1525
+ # the cookie will be saved in an encrypted state.
1526
+ def encrypt_cookie=(value)
1527
+ @encrypt_cookie = value
1528
+ end
1529
+
1530
+ # See encrypt_cookie
1531
+ def encrypt_cookie?
1532
+ encrypt_cookie == true || encrypt_cookie == "true" || encrypt_cookie == "1"
1533
+ end
1534
+
1478
1535
  # The scope of the current object
1479
1536
  def scope
1480
1537
  @scope ||= {}
@@ -1492,24 +1549,21 @@ module Authlogic
1492
1549
  # Determines if the information you provided for authentication is valid
1493
1550
  # or not. If there is a problem with the information provided errors will
1494
1551
  # be added to the errors object and this method will return false.
1552
+ #
1553
+ # @api public
1495
1554
  def valid?
1496
1555
  errors.clear
1497
1556
  self.attempted_record = nil
1498
-
1499
- run_callbacks(:before_validation)
1500
- run_callbacks(new_session? ? :before_validation_on_create : :before_validation_on_update)
1557
+ run_the_before_validation_callbacks
1501
1558
 
1502
1559
  # Run the `validate` callbacks, eg. `validate_by_password`.
1503
1560
  # This is when `attempted_record` is set.
1504
1561
  run_callbacks(:validate)
1505
1562
 
1506
1563
  ensure_authentication_attempted
1507
-
1508
1564
  if errors.empty?
1509
- run_callbacks(new_session? ? :after_validation_on_create : :after_validation_on_update)
1510
- run_callbacks(:after_validation)
1565
+ run_the_after_validation_callbacks
1511
1566
  end
1512
-
1513
1567
  save_record(attempted_record)
1514
1568
  errors.empty?
1515
1569
  end
@@ -1611,14 +1665,22 @@ module Authlogic
1611
1665
  # @api private
1612
1666
  # @return ::Authlogic::CookieCredentials or if no cookie is found, nil
1613
1667
  def cookie_credentials
1668
+ return unless cookie_enabled?
1669
+
1614
1670
  cookie_value = cookie_jar[cookie_key]
1615
1671
  unless cookie_value.nil?
1616
1672
  ::Authlogic::CookieCredentials.parse(cookie_value)
1617
1673
  end
1618
1674
  end
1619
1675
 
1676
+ def cookie_enabled?
1677
+ !controller.cookies.nil?
1678
+ end
1679
+
1620
1680
  def cookie_jar
1621
- if self.class.sign_cookie
1681
+ if self.class.encrypt_cookie
1682
+ controller.cookies.encrypted
1683
+ elsif self.class.sign_cookie
1622
1684
  controller.cookies.signed
1623
1685
  else
1624
1686
  controller.cookies
@@ -1630,21 +1692,36 @@ module Authlogic
1630
1692
  define_password_field_methods
1631
1693
  end
1632
1694
 
1695
+ # Assign a new controller-session ID, to defend against Session Fixation.
1696
+ # https://guides.rubyonrails.org/v6.0/security.html#session-fixation
1697
+ def renew_session_id
1698
+ return unless self.class.session_fixation_defense
1699
+ controller.renew_session_id
1700
+ end
1701
+
1633
1702
  def define_login_field_methods
1634
1703
  return unless login_field
1635
1704
  self.class.send(:attr_writer, login_field) unless respond_to?("#{login_field}=")
1636
1705
  self.class.send(:attr_reader, login_field) unless respond_to?(login_field)
1637
1706
  end
1638
1707
 
1708
+ # @api private
1639
1709
  def define_password_field_methods
1640
1710
  return unless password_field
1641
- self.class.send(:attr_writer, password_field) unless respond_to?("#{password_field}=")
1642
- self.class.send(:define_method, password_field) {} unless respond_to?(password_field)
1711
+ define_password_field_writer_method
1712
+ define_password_field_reader_methods
1713
+ end
1643
1714
 
1644
- # The password should not be accessible publicly. This way forms
1645
- # using form_for don't fill the password with the attempted
1646
- # password. To prevent this we just create this method that is
1647
- # private.
1715
+ # The password should not be accessible publicly. This way forms using
1716
+ # form_for don't fill the password with the attempted password. To prevent
1717
+ # this we just create this method that is private.
1718
+ #
1719
+ # @api private
1720
+ def define_password_field_reader_methods
1721
+ unless respond_to?(password_field)
1722
+ # Deliberate no-op method, see rationale above.
1723
+ self.class.send(:define_method, password_field) {}
1724
+ end
1648
1725
  self.class.class_eval(
1649
1726
  <<-EOS, __FILE__, __LINE__ + 1
1650
1727
  private
@@ -1655,6 +1732,28 @@ module Authlogic
1655
1732
  )
1656
1733
  end
1657
1734
 
1735
+ def define_password_field_writer_method
1736
+ unless respond_to?("#{password_field}=")
1737
+ self.class.send(:attr_writer, password_field)
1738
+ end
1739
+ end
1740
+
1741
+ # Creating an alias method for the "record" method based on the klass
1742
+ # name, so that we can do:
1743
+ #
1744
+ # session.user
1745
+ #
1746
+ # instead of:
1747
+ #
1748
+ # session.record
1749
+ #
1750
+ # @api private
1751
+ def define_record_alias_method
1752
+ noun = klass_name.demodulize.underscore.to_sym
1753
+ return if respond_to?(noun)
1754
+ self.class.send(:alias_method, noun, :record)
1755
+ end
1756
+
1658
1757
  def destroy_cookie
1659
1758
  controller.cookies.delete cookie_key, domain: controller.cookie_domain
1660
1759
  end
@@ -1690,8 +1789,10 @@ module Authlogic
1690
1789
  attempted_record.failed_login_count >= consecutive_failed_logins_limit
1691
1790
  end
1692
1791
 
1792
+ # @deprecated in favor of `self.class.record_selection_method`
1693
1793
  def find_by_login_method
1694
- self.class.find_by_login_method
1794
+ ::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
1795
+ self.class.record_selection_method
1695
1796
  end
1696
1797
 
1697
1798
  def generalize_credentials_error_messages?
@@ -1700,13 +1801,8 @@ module Authlogic
1700
1801
 
1701
1802
  # @api private
1702
1803
  def generate_cookie_for_saving
1703
- creds = ::Authlogic::CookieCredentials.new(
1704
- record.persistence_token,
1705
- record.send(record.class.primary_key),
1706
- remember_me? ? remember_me_until : nil
1707
- )
1708
1804
  {
1709
- value: creds.to_s,
1805
+ value: generate_cookie_value.to_s,
1710
1806
  expires: remember_me_until,
1711
1807
  secure: secure,
1712
1808
  httponly: httponly,
@@ -1715,6 +1811,14 @@ module Authlogic
1715
1811
  }
1716
1812
  end
1717
1813
 
1814
+ def generate_cookie_value
1815
+ ::Authlogic::CookieCredentials.new(
1816
+ record.persistence_token,
1817
+ record.send(record.class.primary_key),
1818
+ remember_me? ? remember_me_until : nil
1819
+ )
1820
+ end
1821
+
1718
1822
  # Returns a Proc to be executed by
1719
1823
  # `ActionController::HttpAuthentication::Basic` when credentials are
1720
1824
  # present in the HTTP request.
@@ -1742,7 +1846,7 @@ module Authlogic
1742
1846
  end
1743
1847
  end
1744
1848
 
1745
- def increment_login_cout
1849
+ def increment_login_count
1746
1850
  if record.respond_to?(:login_count)
1747
1851
  record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1)
1748
1852
  end
@@ -1893,6 +1997,18 @@ module Authlogic
1893
1997
  attempted_record.failed_login_count = 0
1894
1998
  end
1895
1999
 
2000
+ # @api private
2001
+ def run_the_after_validation_callbacks
2002
+ run_callbacks(new_session? ? :after_validation_on_create : :after_validation_on_update)
2003
+ run_callbacks(:after_validation)
2004
+ end
2005
+
2006
+ # @api private
2007
+ def run_the_before_validation_callbacks
2008
+ run_callbacks(:before_validation)
2009
+ run_callbacks(new_session? ? :before_validation_on_create : :before_validation_on_update)
2010
+ end
2011
+
1896
2012
  # `args[0]` is the name of a model method, like
1897
2013
  # `find_by_single_access_token` or `find_by_smart_case_login_field`.
1898
2014
  def search_for_record(*args)
@@ -1930,11 +2046,7 @@ module Authlogic
1930
2046
  end
1931
2047
 
1932
2048
  def save_cookie
1933
- if sign_cookie?
1934
- controller.cookies.signed[cookie_key] = generate_cookie_for_saving
1935
- else
1936
- controller.cookies[cookie_key] = generate_cookie_for_saving
1937
- end
2049
+ cookie_jar[cookie_key] = generate_cookie_for_saving
1938
2050
  end
1939
2051
 
1940
2052
  # @api private
@@ -1964,7 +2076,7 @@ module Authlogic
1964
2076
  end
1965
2077
 
1966
2078
  def update_info
1967
- increment_login_cout
2079
+ increment_login_count
1968
2080
  clear_failed_login_count
1969
2081
  update_login_timestamps
1970
2082
  update_login_ip_addresses
@@ -2011,7 +2123,10 @@ module Authlogic
2011
2123
  self.invalid_password = false
2012
2124
  validate_by_password__blank_fields
2013
2125
  return if errors.count > 0
2014
- self.attempted_record = search_for_record(find_by_login_method, send(login_field))
2126
+ self.attempted_record = search_for_record(
2127
+ self.class.record_selection_method,
2128
+ send(login_field)
2129
+ )
2015
2130
  if attempted_record.blank?
2016
2131
  add_login_not_found_error
2017
2132
  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 = {})
@@ -23,6 +27,21 @@ module Authlogic
23
27
  def signed
24
28
  @signed ||= MockSignedCookieJar.new(self)
25
29
  end
30
+
31
+ def encrypted
32
+ @encrypted ||= MockEncryptedCookieJar.new(self)
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
26
45
  end
27
46
 
28
47
  # A mock of `ActionDispatch::Cookies::SignedKeyRotatingCookieJar`
@@ -35,6 +54,7 @@ module Authlogic
35
54
 
36
55
  def initialize(parent_jar)
37
56
  @parent_jar = parent_jar
57
+ parent_jar.each { |k, v| self[k] = v }
38
58
  end
39
59
 
40
60
  def [](val)
@@ -47,8 +67,42 @@ module Authlogic
47
67
  end
48
68
 
49
69
  def []=(key, options)
50
- options[:value] = "#{options[:value]}--#{Digest::SHA1.hexdigest options[:value]}"
51
- @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
73
+ end
74
+ end
75
+
76
+ # Which ActionDispatch class is this a mock of?
77
+ # TODO: Document as with other mocks above.
78
+ class MockEncryptedCookieJar < MockCookieJar
79
+ attr_reader :parent_jar # helper for testing
80
+
81
+ def initialize(parent_jar)
82
+ @parent_jar = parent_jar
83
+ parent_jar.each { |k, v| self[k] = v }
84
+ end
85
+
86
+ def [](val)
87
+ encrypted_message = @parent_jar[val]
88
+ if encrypted_message
89
+ self.class.decrypt(encrypted_message)
90
+ end
91
+ end
92
+
93
+ def []=(key, options)
94
+ opt = cookie_options_to_hash(options)
95
+ opt[:value] = self.class.encrypt(opt[:value])
96
+ @parent_jar[key] = opt
97
+ end
98
+
99
+ # simple caesar cipher for testing
100
+ def self.encrypt(str)
101
+ str.unpack("U*").map(&:succ).pack("U*")
102
+ end
103
+
104
+ def self.decrypt(str)
105
+ str.unpack("U*").map(&:pred).pack("U*")
52
106
  end
53
107
  end
54
108
  end
@@ -9,6 +9,16 @@ module Authlogic
9
9
  self.controller = controller
10
10
  end
11
11
 
12
+ def env
13
+ @env ||= {
14
+ ControllerAdapters::AbstractAdapter::ENV_SESSION_OPTIONS => {}
15
+ }
16
+ end
17
+
18
+ def format
19
+ controller.request_content_type if controller.respond_to? :request_content_type
20
+ end
21
+
12
22
  def ip
13
23
  controller&.respond_to?(:env) &&
14
24
  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("6.0.0")
20
+ ::Gem::Version.new("6.4.1")
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: 6.0.0
4
+ version: 6.4.1
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: 2021-02-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
@@ -170,20 +170,34 @@ dependencies:
170
170
  - - "~>"
171
171
  - !ruby/object:Gem::Version
172
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'
173
187
  - !ruby/object:Gem::Dependency
174
188
  name: rubocop
175
189
  requirement: !ruby/object:Gem::Requirement
176
190
  requirements:
177
191
  - - "~>"
178
192
  - !ruby/object:Gem::Version
179
- version: 0.67.2
193
+ version: 0.80.1
180
194
  type: :development
181
195
  prerelease: false
182
196
  version_requirements: !ruby/object:Gem::Requirement
183
197
  requirements:
184
198
  - - "~>"
185
199
  - !ruby/object:Gem::Version
186
- version: 0.67.2
200
+ version: 0.80.1
187
201
  - !ruby/object:Gem::Dependency
188
202
  name: rubocop-performance
189
203
  requirement: !ruby/object:Gem::Requirement
@@ -320,6 +334,7 @@ files:
320
334
  - lib/authlogic/session/base.rb
321
335
  - lib/authlogic/session/magic_column/assigns_last_request_at.rb
322
336
  - lib/authlogic/test_case.rb
337
+ - lib/authlogic/test_case/mock_api_controller.rb
323
338
  - lib/authlogic/test_case/mock_controller.rb
324
339
  - lib/authlogic/test_case/mock_cookie_jar.rb
325
340
  - lib/authlogic/test_case/mock_logger.rb