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 +4 -4
- data/lib/authlogic/acts_as_authentic/base.rb +16 -1
- data/lib/authlogic/acts_as_authentic/password.rb +1 -1
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +2 -2
- data/lib/authlogic/controller_adapters/abstract_adapter.rb +21 -0
- data/lib/authlogic/controller_adapters/rails_adapter.rb +1 -1
- data/lib/authlogic/crypto_providers/md5/v2.rb +1 -1
- data/lib/authlogic/crypto_providers/sha1/v2.rb +1 -1
- data/lib/authlogic/crypto_providers/sha256/v2.rb +1 -1
- data/lib/authlogic/crypto_providers/sha512/v2.rb +1 -1
- data/lib/authlogic/errors.rb +16 -1
- data/lib/authlogic/session/base.rb +199 -84
- data/lib/authlogic/test_case.rb +1 -0
- data/lib/authlogic/test_case/mock_api_controller.rb +52 -0
- data/lib/authlogic/test_case/mock_controller.rb +1 -1
- data/lib/authlogic/test_case/mock_cookie_jar.rb +58 -4
- data/lib/authlogic/test_case/mock_request.rb +10 -0
- data/lib/authlogic/test_case/rails_request_adapter.rb +7 -1
- data/lib/authlogic/version.rb +1 -1
- metadata +25 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e65309a22f2adc25c9c61f10910d6db41fe2c3f6b9d8037977bdfa094b90dd53
|
4
|
+
data.tar.gz: 2e5bb549974be424ad83ae20de60775bcb1b66c207031919caf978de7e5801ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
#
|
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(
|
96
|
+
def save_without_session_maintenance(**options)
|
97
97
|
self.skip_session_maintenance = true
|
98
|
-
result = save(
|
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.
|
23
|
+
digest.unpack1("H*")
|
24
24
|
end
|
25
25
|
|
26
26
|
# 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.
|
46
|
+
digest.unpack1("H*")
|
47
47
|
end
|
48
48
|
|
49
49
|
# Does the crypted password match the tokens? Uses the same tokens that
|
data/lib/authlogic/errors.rb
CHANGED
@@ -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 |
|
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
|
-
#
|
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
|
-
|
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
|
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
|
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
|
797
|
-
# for
|
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
|
-
|
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.
|
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
|
-
|
1642
|
-
|
1711
|
+
define_password_field_writer_method
|
1712
|
+
define_password_field_reader_methods
|
1713
|
+
end
|
1643
1714
|
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
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
|
-
|
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:
|
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
|
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
|
-
|
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
|
-
|
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(
|
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
|
data/lib/authlogic/test_case.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
16
|
-
|
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
|
-
|
51
|
-
|
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
|
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
|
data/lib/authlogic/version.rb
CHANGED
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.
|
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:
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|