authlogic 6.0.0 → 6.4.1

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