authlogic 5.0.4 → 6.4.2
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 +15 -5
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +5 -3
- 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 +35 -0
- data/lib/authlogic/crypto_providers/md5.rb +3 -0
- data/lib/authlogic/crypto_providers/sha1/v2.rb +41 -0
- data/lib/authlogic/crypto_providers/sha1.rb +3 -0
- data/lib/authlogic/crypto_providers/sha256/v2.rb +58 -0
- data/lib/authlogic/crypto_providers/sha256.rb +3 -0
- data/lib/authlogic/crypto_providers/sha512/v2.rb +39 -0
- data/lib/authlogic/crypto_providers/sha512.rb +3 -0
- data/lib/authlogic/errors.rb +50 -0
- data/lib/authlogic/i18n/translator.rb +1 -1
- data/lib/authlogic/session/base.rb +205 -95
- 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/test_case.rb +1 -0
- data/lib/authlogic/version.rb +1 -1
- data/lib/authlogic.rb +1 -0
- metadata +54 -34
@@ -198,7 +198,7 @@ module Authlogic
|
|
198
198
|
# 2. Enable logging out on timeouts
|
199
199
|
#
|
200
200
|
# class UserSession < Authlogic::Session::Base
|
201
|
-
# logout_on_timeout true # default
|
201
|
+
# logout_on_timeout true # default is false
|
202
202
|
# end
|
203
203
|
#
|
204
204
|
# This will require a user to log back in if they are inactive for more than
|
@@ -351,7 +351,14 @@ module Authlogic
|
|
351
351
|
- https://github.com/binarylogic/authlogic/pull/558
|
352
352
|
- https://github.com/binarylogic/authlogic/pull/577
|
353
353
|
EOS
|
354
|
-
|
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
|
361
|
+
VALID_SAME_SITE_VALUES = [nil, "Lax", "Strict", "None"].freeze
|
355
362
|
|
356
363
|
# Callbacks
|
357
364
|
# =========
|
@@ -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(
|
@@ -472,14 +479,9 @@ module Authlogic
|
|
472
479
|
!controller.nil?
|
473
480
|
end
|
474
481
|
|
475
|
-
#
|
476
|
-
#
|
477
|
-
# I recommend keeping this enabled. The only time I feel this should be
|
478
|
-
# disabled is if you are not comfortable having your users provide their
|
479
|
-
# raw username and password. Whatever the reason, you can disable it
|
480
|
-
# here.
|
482
|
+
# Allow users to log in via HTTP basic authentication.
|
481
483
|
#
|
482
|
-
# * <tt>Default:</tt>
|
484
|
+
# * <tt>Default:</tt> false
|
483
485
|
# * <tt>Accepts:</tt> Boolean
|
484
486
|
def allow_http_basic_auth(value = nil)
|
485
487
|
rw_config(:allow_http_basic_auth, value, false)
|
@@ -669,35 +671,10 @@ module Authlogic
|
|
669
671
|
end
|
670
672
|
end
|
671
673
|
|
672
|
-
#
|
673
|
-
# validation is actually finding the user and making sure it exists.
|
674
|
-
# What method it uses the do this is up to you.
|
675
|
-
#
|
676
|
-
# Let's say you have a UserSession that is authenticating a User. By
|
677
|
-
# default UserSession will call User.find_by_login(login). You can
|
678
|
-
# change what method UserSession calls by specifying it here. Then in
|
679
|
-
# your User model you can make that method do anything you want, giving
|
680
|
-
# you complete control of how users are found by the UserSession.
|
681
|
-
#
|
682
|
-
# Let's take an example: You want to allow users to login by username or
|
683
|
-
# email. Set this to the name of the class method that does this in the
|
684
|
-
# User model. Let's call it "find_by_username_or_email"
|
685
|
-
#
|
686
|
-
# class User < ActiveRecord::Base
|
687
|
-
# def self.find_by_username_or_email(login)
|
688
|
-
# find_by_username(login) || find_by_email(login)
|
689
|
-
# end
|
690
|
-
# end
|
691
|
-
#
|
692
|
-
# Now just specify the name of this method for this configuration option
|
693
|
-
# and you are all set. You can do anything you want here. Maybe you
|
694
|
-
# allow users to have multiple logins and you want to search a has_many
|
695
|
-
# relationship, etc. The sky is the limit.
|
696
|
-
#
|
697
|
-
# * <tt>Default:</tt> "find_by_smart_case_login_field"
|
698
|
-
# * <tt>Accepts:</tt> Symbol or String
|
674
|
+
# @deprecated in favor of record_selection_method
|
699
675
|
def find_by_login_method(value = nil)
|
700
|
-
|
676
|
+
::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
|
677
|
+
record_selection_method(value)
|
701
678
|
end
|
702
679
|
alias find_by_login_method= find_by_login_method
|
703
680
|
|
@@ -782,15 +759,23 @@ module Authlogic
|
|
782
759
|
# example, the UserSession class will authenticate with the User class
|
783
760
|
# unless you specify otherwise in your configuration. See
|
784
761
|
# authenticate_with for information on how to change this value.
|
762
|
+
#
|
763
|
+
# @api public
|
785
764
|
def klass
|
786
765
|
@klass ||= klass_name ? klass_name.constantize : nil
|
787
766
|
end
|
788
767
|
|
789
|
-
# 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
|
790
776
|
def klass_name
|
791
|
-
return @klass_name if
|
792
|
-
@klass_name = name.scan(/(.*)Session/)[0]
|
793
|
-
@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
|
794
779
|
end
|
795
780
|
|
796
781
|
# The name of the method you want Authlogic to create for storing the
|
@@ -798,8 +783,8 @@ module Authlogic
|
|
798
783
|
# Authlogic::Session, if you want it can be something completely
|
799
784
|
# different than the field in your model. So if you wanted people to
|
800
785
|
# login with a field called "login" and then find users by email this is
|
801
|
-
# completely doable. See the
|
802
|
-
# for
|
786
|
+
# completely doable. See the `record_selection_method` configuration
|
787
|
+
# option for details.
|
803
788
|
#
|
804
789
|
# * <tt>Default:</tt> klass.login_field || klass.email_field
|
805
790
|
# * <tt>Accepts:</tt> Symbol or String
|
@@ -882,6 +867,47 @@ module Authlogic
|
|
882
867
|
end
|
883
868
|
alias password_field= password_field
|
884
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
|
+
|
885
911
|
# Whether or not to request HTTP authentication
|
886
912
|
#
|
887
913
|
# If set to true and no HTTP authentication credentials are sent with
|
@@ -951,16 +977,40 @@ module Authlogic
|
|
951
977
|
end
|
952
978
|
alias secure= secure
|
953
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
|
+
|
954
990
|
# Should the cookie be signed? If the controller adapter supports it, this is a
|
955
991
|
# measure against cookie tampering.
|
956
992
|
def sign_cookie(value = nil)
|
957
|
-
if value && !controller.cookies.respond_to?(:signed)
|
993
|
+
if value && controller && !controller.cookies.respond_to?(:signed)
|
958
994
|
raise "Signed cookies not supported with #{controller.class}!"
|
959
995
|
end
|
960
996
|
rw_config(:sign_cookie, value, false)
|
961
997
|
end
|
962
998
|
alias sign_cookie= sign_cookie
|
963
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
|
+
|
964
1014
|
# Works exactly like cookie_key, but for sessions. See cookie_key for more info.
|
965
1015
|
#
|
966
1016
|
# * <tt>Default:</tt> cookie_key
|
@@ -1065,24 +1115,10 @@ module Authlogic
|
|
1065
1115
|
# Constructor
|
1066
1116
|
# ===========
|
1067
1117
|
|
1068
|
-
# rubocop:disable Metrics/AbcSize
|
1069
1118
|
def initialize(*args)
|
1070
1119
|
@id = nil
|
1071
1120
|
self.scope = self.class.scope
|
1072
|
-
|
1073
|
-
# Creating an alias method for the "record" method based on the klass
|
1074
|
-
# name, so that we can do:
|
1075
|
-
#
|
1076
|
-
# session.user
|
1077
|
-
#
|
1078
|
-
# instead of:
|
1079
|
-
#
|
1080
|
-
# session.record
|
1081
|
-
unless self.class.configured_klass_methods
|
1082
|
-
self.class.send(:alias_method, klass_name.demodulize.underscore.to_sym, :record)
|
1083
|
-
self.class.configured_klass_methods = true
|
1084
|
-
end
|
1085
|
-
|
1121
|
+
define_record_alias_method
|
1086
1122
|
raise Activation::NotActivatedError unless self.class.activated?
|
1087
1123
|
unless self.class.configured_password_methods
|
1088
1124
|
configure_password_methods
|
@@ -1091,7 +1127,6 @@ module Authlogic
|
|
1091
1127
|
instance_variable_set("@#{password_field}", nil)
|
1092
1128
|
self.credentials = args
|
1093
1129
|
end
|
1094
|
-
# rubocop:enable Metrics/AbcSize
|
1095
1130
|
|
1096
1131
|
# Public instance methods
|
1097
1132
|
# =======================
|
@@ -1480,6 +1515,23 @@ module Authlogic
|
|
1480
1515
|
sign_cookie == true || sign_cookie == "true" || sign_cookie == "1"
|
1481
1516
|
end
|
1482
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
|
+
|
1483
1535
|
# The scope of the current object
|
1484
1536
|
def scope
|
1485
1537
|
@scope ||= {}
|
@@ -1497,24 +1549,21 @@ module Authlogic
|
|
1497
1549
|
# Determines if the information you provided for authentication is valid
|
1498
1550
|
# or not. If there is a problem with the information provided errors will
|
1499
1551
|
# be added to the errors object and this method will return false.
|
1552
|
+
#
|
1553
|
+
# @api public
|
1500
1554
|
def valid?
|
1501
1555
|
errors.clear
|
1502
1556
|
self.attempted_record = nil
|
1503
|
-
|
1504
|
-
run_callbacks(:before_validation)
|
1505
|
-
run_callbacks(new_session? ? :before_validation_on_create : :before_validation_on_update)
|
1557
|
+
run_the_before_validation_callbacks
|
1506
1558
|
|
1507
1559
|
# Run the `validate` callbacks, eg. `validate_by_password`.
|
1508
1560
|
# This is when `attempted_record` is set.
|
1509
1561
|
run_callbacks(:validate)
|
1510
1562
|
|
1511
1563
|
ensure_authentication_attempted
|
1512
|
-
|
1513
1564
|
if errors.empty?
|
1514
|
-
|
1515
|
-
run_callbacks(:after_validation)
|
1565
|
+
run_the_after_validation_callbacks
|
1516
1566
|
end
|
1517
|
-
|
1518
1567
|
save_record(attempted_record)
|
1519
1568
|
errors.empty?
|
1520
1569
|
end
|
@@ -1616,14 +1665,22 @@ module Authlogic
|
|
1616
1665
|
# @api private
|
1617
1666
|
# @return ::Authlogic::CookieCredentials or if no cookie is found, nil
|
1618
1667
|
def cookie_credentials
|
1668
|
+
return unless cookie_enabled?
|
1669
|
+
|
1619
1670
|
cookie_value = cookie_jar[cookie_key]
|
1620
1671
|
unless cookie_value.nil?
|
1621
1672
|
::Authlogic::CookieCredentials.parse(cookie_value)
|
1622
1673
|
end
|
1623
1674
|
end
|
1624
1675
|
|
1676
|
+
def cookie_enabled?
|
1677
|
+
!controller.cookies.nil?
|
1678
|
+
end
|
1679
|
+
|
1625
1680
|
def cookie_jar
|
1626
|
-
if self.class.
|
1681
|
+
if self.class.encrypt_cookie
|
1682
|
+
controller.cookies.encrypted
|
1683
|
+
elsif self.class.sign_cookie
|
1627
1684
|
controller.cookies.signed
|
1628
1685
|
else
|
1629
1686
|
controller.cookies
|
@@ -1635,21 +1692,36 @@ module Authlogic
|
|
1635
1692
|
define_password_field_methods
|
1636
1693
|
end
|
1637
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
|
+
|
1638
1702
|
def define_login_field_methods
|
1639
1703
|
return unless login_field
|
1640
1704
|
self.class.send(:attr_writer, login_field) unless respond_to?("#{login_field}=")
|
1641
1705
|
self.class.send(:attr_reader, login_field) unless respond_to?(login_field)
|
1642
1706
|
end
|
1643
1707
|
|
1708
|
+
# @api private
|
1644
1709
|
def define_password_field_methods
|
1645
1710
|
return unless password_field
|
1646
|
-
|
1647
|
-
|
1711
|
+
define_password_field_writer_method
|
1712
|
+
define_password_field_reader_methods
|
1713
|
+
end
|
1648
1714
|
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
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
|
1653
1725
|
self.class.class_eval(
|
1654
1726
|
<<-EOS, __FILE__, __LINE__ + 1
|
1655
1727
|
private
|
@@ -1660,6 +1732,28 @@ module Authlogic
|
|
1660
1732
|
)
|
1661
1733
|
end
|
1662
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
|
+
|
1663
1757
|
def destroy_cookie
|
1664
1758
|
controller.cookies.delete cookie_key, domain: controller.cookie_domain
|
1665
1759
|
end
|
@@ -1695,8 +1789,10 @@ module Authlogic
|
|
1695
1789
|
attempted_record.failed_login_count >= consecutive_failed_logins_limit
|
1696
1790
|
end
|
1697
1791
|
|
1792
|
+
# @deprecated in favor of `self.class.record_selection_method`
|
1698
1793
|
def find_by_login_method
|
1699
|
-
|
1794
|
+
::ActiveSupport::Deprecation.warn(E_DPR_FIND_BY_LOGIN_METHOD)
|
1795
|
+
self.class.record_selection_method
|
1700
1796
|
end
|
1701
1797
|
|
1702
1798
|
def generalize_credentials_error_messages?
|
@@ -1705,13 +1801,8 @@ module Authlogic
|
|
1705
1801
|
|
1706
1802
|
# @api private
|
1707
1803
|
def generate_cookie_for_saving
|
1708
|
-
creds = ::Authlogic::CookieCredentials.new(
|
1709
|
-
record.persistence_token,
|
1710
|
-
record.send(record.class.primary_key),
|
1711
|
-
remember_me? ? remember_me_until : nil
|
1712
|
-
)
|
1713
1804
|
{
|
1714
|
-
value:
|
1805
|
+
value: generate_cookie_value.to_s,
|
1715
1806
|
expires: remember_me_until,
|
1716
1807
|
secure: secure,
|
1717
1808
|
httponly: httponly,
|
@@ -1720,6 +1811,14 @@ module Authlogic
|
|
1720
1811
|
}
|
1721
1812
|
end
|
1722
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
|
+
|
1723
1822
|
# Returns a Proc to be executed by
|
1724
1823
|
# `ActionController::HttpAuthentication::Basic` when credentials are
|
1725
1824
|
# present in the HTTP request.
|
@@ -1747,7 +1846,7 @@ module Authlogic
|
|
1747
1846
|
end
|
1748
1847
|
end
|
1749
1848
|
|
1750
|
-
def
|
1849
|
+
def increment_login_count
|
1751
1850
|
if record.respond_to?(:login_count)
|
1752
1851
|
record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1)
|
1753
1852
|
end
|
@@ -1898,6 +1997,18 @@ module Authlogic
|
|
1898
1997
|
attempted_record.failed_login_count = 0
|
1899
1998
|
end
|
1900
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
|
+
|
1901
2012
|
# `args[0]` is the name of a model method, like
|
1902
2013
|
# `find_by_single_access_token` or `find_by_smart_case_login_field`.
|
1903
2014
|
def search_for_record(*args)
|
@@ -1920,7 +2031,7 @@ module Authlogic
|
|
1920
2031
|
|
1921
2032
|
# @api private
|
1922
2033
|
def set_last_request_at
|
1923
|
-
current_time =
|
2034
|
+
current_time = Time.current
|
1924
2035
|
MagicColumn::AssignsLastRequestAt
|
1925
2036
|
.new(current_time, record, controller, last_request_at_threshold)
|
1926
2037
|
.assign
|
@@ -1935,11 +2046,7 @@ module Authlogic
|
|
1935
2046
|
end
|
1936
2047
|
|
1937
2048
|
def save_cookie
|
1938
|
-
|
1939
|
-
controller.cookies.signed[cookie_key] = generate_cookie_for_saving
|
1940
|
-
else
|
1941
|
-
controller.cookies[cookie_key] = generate_cookie_for_saving
|
1942
|
-
end
|
2049
|
+
cookie_jar[cookie_key] = generate_cookie_for_saving
|
1943
2050
|
end
|
1944
2051
|
|
1945
2052
|
# @api private
|
@@ -1969,7 +2076,7 @@ module Authlogic
|
|
1969
2076
|
end
|
1970
2077
|
|
1971
2078
|
def update_info
|
1972
|
-
|
2079
|
+
increment_login_count
|
1973
2080
|
clear_failed_login_count
|
1974
2081
|
update_login_timestamps
|
1975
2082
|
update_login_ip_addresses
|
@@ -1985,7 +2092,7 @@ module Authlogic
|
|
1985
2092
|
def update_login_timestamps
|
1986
2093
|
if record.respond_to?(:current_login_at)
|
1987
2094
|
record.last_login_at = record.current_login_at if record.respond_to?(:last_login_at)
|
1988
|
-
record.current_login_at =
|
2095
|
+
record.current_login_at = Time.current
|
1989
2096
|
end
|
1990
2097
|
end
|
1991
2098
|
|
@@ -2016,7 +2123,10 @@ module Authlogic
|
|
2016
2123
|
self.invalid_password = false
|
2017
2124
|
validate_by_password__blank_fields
|
2018
2125
|
return if errors.count > 0
|
2019
|
-
self.attempted_record = search_for_record(
|
2126
|
+
self.attempted_record = search_for_record(
|
2127
|
+
self.class.record_selection_method,
|
2128
|
+
send(login_field)
|
2129
|
+
)
|
2020
2130
|
if attempted_record.blank?
|
2021
2131
|
add_login_not_found_error
|
2022
2132
|
return
|
@@ -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
|