django/tests/signing/tests.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

263 lines
9.8 KiB
Python
Raw Normal View History

import datetime
from django.core import signing
from django.test import SimpleTestCase, override_settings
from django.test.utils import freeze_time, ignore_warnings
from django.utils.crypto import InvalidAlgorithm
from django.utils.deprecation import RemovedInDjango51Warning
class TestSigner(SimpleTestCase):
def test_signature(self):
"signature() method should generate a signature"
signer = signing.Signer(key="predictable-secret")
signer2 = signing.Signer(key="predictable-secret2")
for s in (
b"hello",
b"3098247:529:087:",
"\u2019".encode(),
):
self.assertEqual(
signer.signature(s),
signing.base64_hmac(
signer.salt + "signer",
s,
"predictable-secret",
algorithm=signer.algorithm,
),
)
self.assertNotEqual(signer.signature(s), signer2.signature(s))
def test_signature_with_salt(self):
signer = signing.Signer(key="predictable-secret", salt="extra-salt")
self.assertEqual(
signer.signature("hello"),
signing.base64_hmac(
"extra-salt" + "signer",
"hello",
"predictable-secret",
algorithm=signer.algorithm,
),
)
self.assertNotEqual(
signing.Signer(key="predictable-secret", salt="one").signature("hello"),
signing.Signer(key="predictable-secret", salt="two").signature("hello"),
)
def test_custom_algorithm(self):
signer = signing.Signer(key="predictable-secret", algorithm="sha512")
self.assertEqual(
signer.signature("hello"),
"Usf3uVQOZ9m6uPfVonKR-EBXjPe7bjMbp3_Fq8MfsptgkkM1ojidN0BxYaT5HAEN1"
"VzO9_jVu7R-VkqknHYNvw",
)
def test_invalid_algorithm(self):
signer = signing.Signer(key="predictable-secret", algorithm="whatever")
msg = "'whatever' is not an algorithm accepted by the hashlib module."
with self.assertRaisesMessage(InvalidAlgorithm, msg):
signer.sign("hello")
def test_sign_unsign(self):
"sign/unsign should be reversible"
signer = signing.Signer(key="predictable-secret")
examples = [
"q;wjmbk;wkmb",
"3098247529087",
"3098247:529:087:",
"jkw osanteuh ,rcuh nthu aou oauh ,ud du",
"\u2019",
]
for example in examples:
signed = signer.sign(example)
self.assertIsInstance(signed, str)
self.assertNotEqual(example, signed)
self.assertEqual(example, signer.unsign(signed))
def test_sign_unsign_non_string(self):
signer = signing.Signer(key="predictable-secret")
values = [
123,
1.23,
True,
datetime.date.today(),
]
for value in values:
with self.subTest(value):
signed = signer.sign(value)
self.assertIsInstance(signed, str)
self.assertNotEqual(signed, value)
self.assertEqual(signer.unsign(signed), str(value))
def test_unsign_detects_tampering(self):
"unsign should raise an exception if the value has been tampered with"
signer = signing.Signer(key="predictable-secret")
value = "Another string"
signed_value = signer.sign(value)
transforms = (
lambda s: s.upper(),
lambda s: s + "a",
lambda s: "a" + s[1:],
lambda s: s.replace(":", ""),
)
self.assertEqual(value, signer.unsign(signed_value))
for transform in transforms:
with self.assertRaises(signing.BadSignature):
signer.unsign(transform(signed_value))
def test_sign_unsign_object(self):
signer = signing.Signer(key="predictable-secret")
tests = [
["a", "list"],
"a string \u2019",
{"a": "dictionary"},
]
for obj in tests:
with self.subTest(obj=obj):
signed_obj = signer.sign_object(obj)
self.assertNotEqual(obj, signed_obj)
self.assertEqual(obj, signer.unsign_object(signed_obj))
signed_obj = signer.sign_object(obj, compress=True)
self.assertNotEqual(obj, signed_obj)
self.assertEqual(obj, signer.unsign_object(signed_obj))
def test_dumps_loads(self):
"dumps and loads be reversible for any JSON serializable object"
objects = [
["a", "list"],
"a string \u2019",
{"a": "dictionary"},
]
for o in objects:
self.assertNotEqual(o, signing.dumps(o))
self.assertEqual(o, signing.loads(signing.dumps(o)))
self.assertNotEqual(o, signing.dumps(o, compress=True))
self.assertEqual(o, signing.loads(signing.dumps(o, compress=True)))
def test_decode_detects_tampering(self):
"loads should raise exception for tampered objects"
transforms = (
lambda s: s.upper(),
lambda s: s + "a",
lambda s: "a" + s[1:],
lambda s: s.replace(":", ""),
)
value = {
"foo": "bar",
"baz": 1,
}
encoded = signing.dumps(value)
self.assertEqual(value, signing.loads(encoded))
for transform in transforms:
with self.assertRaises(signing.BadSignature):
signing.loads(transform(encoded))
def test_works_with_non_ascii_keys(self):
binary_key = b"\xe7" # Set some binary (non-ASCII key)
s = signing.Signer(key=binary_key)
self.assertEqual(
"foo:EE4qGC5MEKyQG5msxYA0sBohAxLC0BJf8uRhemh0BGU",
s.sign("foo"),
)
def test_valid_sep(self):
separators = ["/", "*sep*", ","]
for sep in separators:
signer = signing.Signer(key="predictable-secret", sep=sep)
self.assertEqual(
"foo%sjZQoX_FtSO70jX9HLRGg2A_2s4kdDBxz1QoO_OpEQb0" % sep,
signer.sign("foo"),
)
def test_invalid_sep(self):
"""should warn on invalid separator"""
msg = (
"Unsafe Signer separator: %r (cannot be empty or consist of only A-z0-9-_=)"
)
separators = ["", "-", "abc"]
for sep in separators:
with self.assertRaisesMessage(ValueError, msg % sep):
signing.Signer(sep=sep)
def test_verify_with_non_default_key(self):
old_signer = signing.Signer(key="secret")
new_signer = signing.Signer(
key="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(key="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(key="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(key="oldsecret")
signed = old_signer.sign("abc")
signer = signing.Signer()
self.assertEqual(signer.unsign(signed), "abc")
class TestTimestampSigner(SimpleTestCase):
def test_timestamp_signer(self):
value = "hello"
with freeze_time(123456789):
signer = signing.TimestampSigner(key="predictable-key")
ts = signer.sign(value)
self.assertNotEqual(ts, signing.Signer(key="predictable-key").sign(value))
self.assertEqual(signer.unsign(ts), value)
with freeze_time(123456800):
self.assertEqual(signer.unsign(ts, max_age=12), value)
# max_age parameter can also accept a datetime.timedelta object
self.assertEqual(
signer.unsign(ts, max_age=datetime.timedelta(seconds=11)), value
)
with self.assertRaises(signing.SignatureExpired):
signer.unsign(ts, max_age=10)
class TestBase62(SimpleTestCase):
def test_base62(self):
tests = [-(10**10), 10**10, 1620378259, *range(-100, 100)]
for i in tests:
self.assertEqual(i, signing.b62_decode(signing.b62_encode(i)))
class SignerPositionalArgumentsDeprecationTests(SimpleTestCase):
def test_deprecation(self):
msg = "Passing positional arguments to Signer is deprecated."
with self.assertRaisesMessage(RemovedInDjango51Warning, msg):
signing.Signer("predictable-secret")
msg = "Passing positional arguments to TimestampSigner is deprecated."
with self.assertRaisesMessage(RemovedInDjango51Warning, msg):
signing.TimestampSigner("predictable-secret")
@ignore_warnings(category=RemovedInDjango51Warning)
def test_positional_arguments(self):
signer = signing.Signer("secret", "/", "somesalt", "sha1", ["oldsecret"])
signed = signer.sign("xyz")
self.assertEqual(signed, "xyz/zzdO_8rk-NGnm8jNasXRTF2P5kY")
self.assertEqual(signer.unsign(signed), "xyz")
old_signer = signing.Signer("oldsecret", "/", "somesalt", "sha1")
signed = old_signer.sign("xyz")
self.assertEqual(signer.unsign(signed), "xyz")