summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authortschilling <schillingt@better-simple.com>2021-12-13 21:47:03 -0600
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2022-02-01 11:12:24 +0100
commit0dcd549bbe36c060f536ec270d34d9e7d4b8e6c7 (patch)
tree8b350cec1ab50d21cf6e3afd03f3db5f02fb1679 /tests
parentba4a6880d1783190de4081bd456d934beb45cb19 (diff)
Fixed #30360 -- Added support for secret key rotation.
Thanks Florian Apolloner for the implementation idea. Co-authored-by: Andreas Pelme <andreas@pelme.se> Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es> Co-authored-by: Vuyisile Ndlovu <terrameijar@gmail.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/auth_tests/test_tokens.py36
-rw-r--r--tests/check_framework/test_security.py75
-rw-r--r--tests/settings_tests/tests.py1
-rw-r--r--tests/signing/tests.py35
-rw-r--r--tests/view_tests/tests/test_debug.py2
5 files changed, 147 insertions, 2 deletions
diff --git a/tests/auth_tests/test_tokens.py b/tests/auth_tests/test_tokens.py
index ff26bd626b..2f3f52aac6 100644
--- a/tests/auth_tests/test_tokens.py
+++ b/tests/auth_tests/test_tokens.py
@@ -140,3 +140,39 @@ class TokenGeneratorTest(TestCase):
msg = 'The SECRET_KEY setting must not be empty.'
with self.assertRaisesMessage(ImproperlyConfigured, msg):
default_token_generator.secret
+
+ def test_check_token_secret_fallbacks(self):
+ user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw')
+ p1 = PasswordResetTokenGenerator()
+ p1.secret = 'secret'
+ tk = p1.make_token(user)
+ p2 = PasswordResetTokenGenerator()
+ p2.secret = 'newsecret'
+ p2.secret_fallbacks = ['secret']
+ self.assertIs(p1.check_token(user, tk), True)
+ self.assertIs(p2.check_token(user, tk), True)
+
+ @override_settings(
+ SECRET_KEY='secret',
+ SECRET_KEY_FALLBACKS=['oldsecret'],
+ )
+ def test_check_token_secret_key_fallbacks(self):
+ user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw')
+ p1 = PasswordResetTokenGenerator()
+ p1.secret = 'oldsecret'
+ tk = p1.make_token(user)
+ p2 = PasswordResetTokenGenerator()
+ self.assertIs(p2.check_token(user, tk), True)
+
+ @override_settings(
+ SECRET_KEY='secret',
+ SECRET_KEY_FALLBACKS=['oldsecret'],
+ )
+ def test_check_token_secret_key_fallbacks_override(self):
+ user = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw')
+ p1 = PasswordResetTokenGenerator()
+ p1.secret = 'oldsecret'
+ tk = p1.make_token(user)
+ p2 = PasswordResetTokenGenerator()
+ p2.secret_fallbacks = []
+ self.assertIs(p2.check_token(user, tk), False)
diff --git a/tests/check_framework/test_security.py b/tests/check_framework/test_security.py
index 774ba068f9..9002deefc5 100644
--- a/tests/check_framework/test_security.py
+++ b/tests/check_framework/test_security.py
@@ -1,5 +1,5 @@
from django.conf import settings
-from django.core.checks.messages import Error
+from django.core.checks.messages import Error, Warning
from django.core.checks.security import base, csrf, sessions
from django.core.management.utils import get_random_secret_key
from django.test import SimpleTestCase
@@ -414,6 +414,79 @@ class CheckSecretKeyTest(SimpleTestCase):
self.assertEqual(base.check_secret_key(None), [base.W009])
+class CheckSecretKeyFallbacksTest(SimpleTestCase):
+ @override_settings(SECRET_KEY_FALLBACKS=[('abcdefghijklmnopqrstuvwx' * 2) + 'ab'])
+ def test_okay_secret_key_fallbacks(self):
+ self.assertEqual(
+ len(settings.SECRET_KEY_FALLBACKS[0]),
+ base.SECRET_KEY_MIN_LENGTH,
+ )
+ self.assertGreater(
+ len(set(settings.SECRET_KEY_FALLBACKS[0])),
+ base.SECRET_KEY_MIN_UNIQUE_CHARACTERS,
+ )
+ self.assertEqual(base.check_secret_key_fallbacks(None), [])
+
+ def test_no_secret_key_fallbacks(self):
+ with self.settings(SECRET_KEY_FALLBACKS=None):
+ del settings.SECRET_KEY_FALLBACKS
+ self.assertEqual(base.check_secret_key_fallbacks(None), [
+ Warning(base.W025.msg % 'SECRET_KEY_FALLBACKS', id=base.W025.id),
+ ])
+
+ @override_settings(SECRET_KEY_FALLBACKS=[
+ base.SECRET_KEY_INSECURE_PREFIX + get_random_secret_key()
+ ])
+ def test_insecure_secret_key_fallbacks(self):
+ self.assertEqual(base.check_secret_key_fallbacks(None), [
+ Warning(base.W025.msg % 'SECRET_KEY_FALLBACKS[0]', id=base.W025.id),
+ ])
+
+ @override_settings(SECRET_KEY_FALLBACKS=[('abcdefghijklmnopqrstuvwx' * 2) + 'a'])
+ def test_low_length_secret_key_fallbacks(self):
+ self.assertEqual(
+ len(settings.SECRET_KEY_FALLBACKS[0]),
+ base.SECRET_KEY_MIN_LENGTH - 1,
+ )
+ self.assertEqual(base.check_secret_key_fallbacks(None), [
+ Warning(base.W025.msg % 'SECRET_KEY_FALLBACKS[0]', id=base.W025.id),
+ ])
+
+ @override_settings(SECRET_KEY_FALLBACKS=['abcd' * 20])
+ def test_low_entropy_secret_key_fallbacks(self):
+ self.assertGreater(
+ len(settings.SECRET_KEY_FALLBACKS[0]),
+ base.SECRET_KEY_MIN_LENGTH,
+ )
+ self.assertLess(
+ len(set(settings.SECRET_KEY_FALLBACKS[0])),
+ base.SECRET_KEY_MIN_UNIQUE_CHARACTERS,
+ )
+ self.assertEqual(base.check_secret_key_fallbacks(None), [
+ Warning(base.W025.msg % 'SECRET_KEY_FALLBACKS[0]', id=base.W025.id),
+ ])
+
+ @override_settings(SECRET_KEY_FALLBACKS=[
+ ('abcdefghijklmnopqrstuvwx' * 2) + 'ab',
+ 'badkey',
+ ])
+ def test_multiple_keys(self):
+ self.assertEqual(base.check_secret_key_fallbacks(None), [
+ Warning(base.W025.msg % 'SECRET_KEY_FALLBACKS[1]', id=base.W025.id),
+ ])
+
+ @override_settings(SECRET_KEY_FALLBACKS=[
+ ('abcdefghijklmnopqrstuvwx' * 2) + 'ab',
+ 'badkey1',
+ 'badkey2',
+ ])
+ def test_multiple_bad_keys(self):
+ self.assertEqual(base.check_secret_key_fallbacks(None), [
+ Warning(base.W025.msg % 'SECRET_KEY_FALLBACKS[1]', id=base.W025.id),
+ Warning(base.W025.msg % 'SECRET_KEY_FALLBACKS[2]', id=base.W025.id),
+ ])
+
+
class CheckDebugTest(SimpleTestCase):
@override_settings(DEBUG=True)
def test_debug_true(self):
diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py
index e958a984fa..ffe096087a 100644
--- a/tests/settings_tests/tests.py
+++ b/tests/settings_tests/tests.py
@@ -483,6 +483,7 @@ class TestListSettings(SimpleTestCase):
"INSTALLED_APPS",
"TEMPLATE_DIRS",
"LOCALE_PATHS",
+ "SECRET_KEY_FALLBACKS",
)
def test_tuple_settings(self):
diff --git a/tests/signing/tests.py b/tests/signing/tests.py
index 1bd7277bc6..c375f2dbf2 100644
--- a/tests/signing/tests.py
+++ b/tests/signing/tests.py
@@ -1,7 +1,7 @@
import datetime
from django.core import signing
-from django.test import SimpleTestCase
+from django.test import SimpleTestCase, override_settings
from django.test.utils import freeze_time
from django.utils.crypto import InvalidAlgorithm
@@ -178,6 +178,39 @@ class TestSigner(SimpleTestCase):
with self.assertRaisesMessage(ValueError, msg % sep):
signing.Signer(sep=sep)
+ def test_verify_with_non_default_key(self):
+ old_signer = signing.Signer('secret')
+ new_signer = signing.Signer('newsecret', fallback_keys=['othersecret', 'secret'])
+ signed = old_signer.sign('abc')
+ self.assertEqual(new_signer.unsign(signed), 'abc')
+
+ def test_sign_unsign_multiple_keys(self):
+ """The default key is a valid verification key."""
+ signer = signing.Signer('secret', fallback_keys=['oldsecret'])
+ signed = signer.sign('abc')
+ self.assertEqual(signer.unsign(signed), 'abc')
+
+ @override_settings(
+ SECRET_KEY='secret',
+ SECRET_KEY_FALLBACKS=['oldsecret'],
+ )
+ def test_sign_unsign_ignore_secret_key_fallbacks(self):
+ old_signer = signing.Signer('oldsecret')
+ signed = old_signer.sign('abc')
+ signer = signing.Signer(fallback_keys=[])
+ with self.assertRaises(signing.BadSignature):
+ signer.unsign(signed)
+
+ @override_settings(
+ SECRET_KEY='secret',
+ SECRET_KEY_FALLBACKS=['oldsecret'],
+ )
+ def test_default_keys_verification(self):
+ old_signer = signing.Signer('oldsecret')
+ signed = old_signer.sign('abc')
+ signer = signing.Signer()
+ self.assertEqual(signer.unsign(signed), 'abc')
+
class TestTimestampSigner(SimpleTestCase):
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index 8eda91ec35..84d675b79b 100644
--- a/tests/view_tests/tests/test_debug.py
+++ b/tests/view_tests/tests/test_debug.py
@@ -1459,6 +1459,7 @@ class ExceptionReporterFilterTests(ExceptionReportTestMixin, LoggingCaptureMixin
"""
sensitive_settings = [
'SECRET_KEY',
+ 'SECRET_KEY_FALLBACKS',
'PASSWORD',
'API_KEY',
'AUTH_TOKEN',
@@ -1475,6 +1476,7 @@ class ExceptionReporterFilterTests(ExceptionReportTestMixin, LoggingCaptureMixin
"""
sensitive_settings = [
'SECRET_KEY',
+ 'SECRET_KEY_FALLBACKS',
'PASSWORD',
'API_KEY',
'AUTH_TOKEN',