Fixed #33199 -- Deprecated passing positional arguments to Signer/TimestampSigner.

Thanks Jacob Walls for the implementation idea.
This commit is contained in:
SirAbhi13 2022-11-30 12:33:36 +05:30 committed by Mariusz Felisiak
parent 3fec3bf90b
commit b8738aea14
6 changed files with 89 additions and 32 deletions

View File

@ -11,6 +11,7 @@ answer newbie questions, and generally made Django that much better:
Abeer Upadhyay <ab.esquarer@gmail.com>
Abhijeet Viswa <abhijeetviswa@gmail.com>
Abhinav Patil <https://github.com/ubadub/>
Abhinav Yadav <abhinav.sny.2002+django@gmail.com>
Abhishek Gautam <abhishekg1128@yahoo.com>
Abhyudai <https://github.com/abhiabhi94>
Adam Allred <adam.w.allred@gmail.com>

View File

@ -37,10 +37,12 @@ import base64
import datetime
import json
import time
import warnings
import zlib
from django.conf import settings
from django.utils.crypto import constant_time_compare, salted_hmac
from django.utils.deprecation import RemovedInDjango51Warning
from django.utils.encoding import force_bytes
from django.utils.module_loading import import_string
from django.utils.regex_helper import _lazy_re_compile
@ -147,7 +149,7 @@ def dumps(
The serializer is expected to return a bytestring.
"""
return TimestampSigner(key, salt=salt).sign_object(
return TimestampSigner(key=key, salt=salt).sign_object(
obj, serializer=serializer, compress=compress
)
@ -165,7 +167,9 @@ def loads(
The serializer is expected to accept a bytestring.
"""
return TimestampSigner(key, salt=salt, fallback_keys=fallback_keys).unsign_object(
return TimestampSigner(
key=key, salt=salt, fallback_keys=fallback_keys
).unsign_object(
s,
serializer=serializer,
max_age=max_age,
@ -173,8 +177,13 @@ def loads(
class Signer:
# RemovedInDjango51Warning: When the deprecation ends, replace with:
# def __init__(
# self, *, key=None, sep=":", salt=None, algorithm=None, fallback_keys=None
# ):
def __init__(
self,
*args,
key=None,
sep=":",
salt=None,
@ -188,16 +197,29 @@ class Signer:
else settings.SECRET_KEY_FALLBACKS
)
self.sep = sep
if _SEP_UNSAFE.match(self.sep):
raise ValueError(
"Unsafe Signer separator: %r (cannot be empty or consist of "
"only A-z0-9-_=)" % sep,
)
self.salt = salt or "%s.%s" % (
self.__class__.__module__,
self.__class__.__name__,
)
self.algorithm = algorithm or "sha256"
# RemovedInDjango51Warning.
if args:
warnings.warn(
f"Passing positional arguments to {self.__class__.__name__} is "
f"deprecated.",
RemovedInDjango51Warning,
stacklevel=2,
)
for arg, attr in zip(
args, ["key", "sep", "salt", "algorithm", "fallback_keys"]
):
if arg or attr == "sep":
setattr(self, attr, arg)
if _SEP_UNSAFE.match(self.sep):
raise ValueError(
"Unsafe Signer separator: %r (cannot be empty or consist of "
"only A-z0-9-_=)" % sep,
)
def signature(self, value, key=None):
key = key or self.key

View File

@ -42,6 +42,9 @@ details on these changes.
* Support for passing encoded JSON string literals to ``JSONField`` and
associated lookups and expressions will be removed.
* Support for passing positional arguments to ``Signer`` and
``TimestampSigner`` will be removed.
.. _deprecation-removed-in-5.0:
5.0

View File

@ -540,3 +540,6 @@ Miscellaneous
* ``TransactionTestCase.assertQuerysetEqual()`` is deprecated in favor of
``assertQuerySetEqual()``.
* Passing positional arguments to ``Signer`` and ``TimestampSigner`` is
deprecated in favor of keyword-only arguments.

View File

@ -96,12 +96,12 @@ By default, the ``Signer`` class uses the :setting:`SECRET_KEY` setting to
generate signatures. You can use a different secret by passing it to the
``Signer`` constructor::
>>> signer = Signer('my-other-secret')
>>> signer = Signer(key='my-other-secret')
>>> value = signer.sign('My string')
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
.. class:: Signer(key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)
.. class:: Signer(*, key=None, sep=':', salt=None, algorithm=None, fallback_keys=None)
Returns a signer which uses ``key`` to generate signatures and ``sep`` to
separate values. ``sep`` cannot be in the :rfc:`URL safe base64 alphabet
@ -115,6 +115,10 @@ generate signatures. You can use a different secret by passing it to the
The ``fallback_keys`` argument was added.
.. deprecated:: 4.2
Support for passing positional arguments is deprecated.
Using the ``salt`` argument
---------------------------
@ -172,7 +176,7 @@ created within a specified period of time::
>>> signer.unsign(value, max_age=timedelta(seconds=20))
'hello'
.. class:: TimestampSigner(key=None, sep=':', salt=None, algorithm='sha256')
.. class:: TimestampSigner(*, key=None, sep=':', salt=None, algorithm='sha256')
.. method:: sign(value)
@ -195,6 +199,10 @@ created within a specified period of time::
otherwise raises ``SignatureExpired``. The ``max_age`` parameter can
accept an integer or a :py:class:`datetime.timedelta` object.
.. deprecated:: 4.2
Support for passing positional arguments is deprecated.
.. _signing-complex-data:
Protecting complex data structures

View File

@ -2,15 +2,16 @@ import datetime
from django.core import signing
from django.test import SimpleTestCase, override_settings
from django.test.utils import freeze_time
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("predictable-secret")
signer2 = signing.Signer("predictable-secret2")
signer = signing.Signer(key="predictable-secret")
signer2 = signing.Signer(key="predictable-secret2")
for s in (
b"hello",
b"3098247:529:087:",
@ -28,8 +29,7 @@ class TestSigner(SimpleTestCase):
self.assertNotEqual(signer.signature(s), signer2.signature(s))
def test_signature_with_salt(self):
"signature(value, salt=...) should work"
signer = signing.Signer("predictable-secret", salt="extra-salt")
signer = signing.Signer(key="predictable-secret", salt="extra-salt")
self.assertEqual(
signer.signature("hello"),
signing.base64_hmac(
@ -40,12 +40,12 @@ class TestSigner(SimpleTestCase):
),
)
self.assertNotEqual(
signing.Signer("predictable-secret", salt="one").signature("hello"),
signing.Signer("predictable-secret", salt="two").signature("hello"),
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("predictable-secret", algorithm="sha512")
signer = signing.Signer(key="predictable-secret", algorithm="sha512")
self.assertEqual(
signer.signature("hello"),
"Usf3uVQOZ9m6uPfVonKR-EBXjPe7bjMbp3_Fq8MfsptgkkM1ojidN0BxYaT5HAEN1"
@ -53,14 +53,14 @@ class TestSigner(SimpleTestCase):
)
def test_invalid_algorithm(self):
signer = signing.Signer("predictable-secret", algorithm="whatever")
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("predictable-secret")
signer = signing.Signer(key="predictable-secret")
examples = [
"q;wjmbk;wkmb",
"3098247529087",
@ -75,7 +75,7 @@ class TestSigner(SimpleTestCase):
self.assertEqual(example, signer.unsign(signed))
def test_sign_unsign_non_string(self):
signer = signing.Signer("predictable-secret")
signer = signing.Signer(key="predictable-secret")
values = [
123,
1.23,
@ -91,7 +91,7 @@ class TestSigner(SimpleTestCase):
def test_unsign_detects_tampering(self):
"unsign should raise an exception if the value has been tampered with"
signer = signing.Signer("predictable-secret")
signer = signing.Signer(key="predictable-secret")
value = "Another string"
signed_value = signer.sign(value)
transforms = (
@ -106,7 +106,7 @@ class TestSigner(SimpleTestCase):
signer.unsign(transform(signed_value))
def test_sign_unsign_object(self):
signer = signing.Signer("predictable-secret")
signer = signing.Signer(key="predictable-secret")
tests = [
["a", "list"],
"a string \u2019",
@ -155,7 +155,7 @@ class TestSigner(SimpleTestCase):
def test_works_with_non_ascii_keys(self):
binary_key = b"\xe7" # Set some binary (non-ASCII key)
s = signing.Signer(binary_key)
s = signing.Signer(key=binary_key)
self.assertEqual(
"foo:EE4qGC5MEKyQG5msxYA0sBohAxLC0BJf8uRhemh0BGU",
s.sign("foo"),
@ -164,7 +164,7 @@ class TestSigner(SimpleTestCase):
def test_valid_sep(self):
separators = ["/", "*sep*", ","]
for sep in separators:
signer = signing.Signer("predictable-secret", sep=sep)
signer = signing.Signer(key="predictable-secret", sep=sep)
self.assertEqual(
"foo%sjZQoX_FtSO70jX9HLRGg2A_2s4kdDBxz1QoO_OpEQb0" % sep,
signer.sign("foo"),
@ -181,16 +181,16 @@ class TestSigner(SimpleTestCase):
signing.Signer(sep=sep)
def test_verify_with_non_default_key(self):
old_signer = signing.Signer("secret")
old_signer = signing.Signer(key="secret")
new_signer = signing.Signer(
"newsecret", fallback_keys=["othersecret", "secret"]
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("secret", fallback_keys=["oldsecret"])
signer = signing.Signer(key="secret", fallback_keys=["oldsecret"])
signed = signer.sign("abc")
self.assertEqual(signer.unsign(signed), "abc")
@ -199,7 +199,7 @@ class TestSigner(SimpleTestCase):
SECRET_KEY_FALLBACKS=["oldsecret"],
)
def test_sign_unsign_ignore_secret_key_fallbacks(self):
old_signer = signing.Signer("oldsecret")
old_signer = signing.Signer(key="oldsecret")
signed = old_signer.sign("abc")
signer = signing.Signer(fallback_keys=[])
with self.assertRaises(signing.BadSignature):
@ -210,7 +210,7 @@ class TestSigner(SimpleTestCase):
SECRET_KEY_FALLBACKS=["oldsecret"],
)
def test_default_keys_verification(self):
old_signer = signing.Signer("oldsecret")
old_signer = signing.Signer(key="oldsecret")
signed = old_signer.sign("abc")
signer = signing.Signer()
self.assertEqual(signer.unsign(signed), "abc")
@ -220,9 +220,9 @@ class TestTimestampSigner(SimpleTestCase):
def test_timestamp_signer(self):
value = "hello"
with freeze_time(123456789):
signer = signing.TimestampSigner("predictable-key")
signer = signing.TimestampSigner(key="predictable-key")
ts = signer.sign(value)
self.assertNotEqual(ts, signing.Signer("predictable-key").sign(value))
self.assertNotEqual(ts, signing.Signer(key="predictable-key").sign(value))
self.assertEqual(signer.unsign(ts), value)
with freeze_time(123456800):
@ -240,3 +240,23 @@ class TestBase62(SimpleTestCase):
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")