mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Fixed #33199 -- Deprecated passing positional arguments to Signer/TimestampSigner.
Thanks Jacob Walls for the implementation idea.
This commit is contained in:
parent
3fec3bf90b
commit
b8738aea14
1
AUTHORS
1
AUTHORS
@ -11,6 +11,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Abeer Upadhyay <ab.esquarer@gmail.com>
|
Abeer Upadhyay <ab.esquarer@gmail.com>
|
||||||
Abhijeet Viswa <abhijeetviswa@gmail.com>
|
Abhijeet Viswa <abhijeetviswa@gmail.com>
|
||||||
Abhinav Patil <https://github.com/ubadub/>
|
Abhinav Patil <https://github.com/ubadub/>
|
||||||
|
Abhinav Yadav <abhinav.sny.2002+django@gmail.com>
|
||||||
Abhishek Gautam <abhishekg1128@yahoo.com>
|
Abhishek Gautam <abhishekg1128@yahoo.com>
|
||||||
Abhyudai <https://github.com/abhiabhi94>
|
Abhyudai <https://github.com/abhiabhi94>
|
||||||
Adam Allred <adam.w.allred@gmail.com>
|
Adam Allred <adam.w.allred@gmail.com>
|
||||||
|
@ -37,10 +37,12 @@ import base64
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import warnings
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.crypto import constant_time_compare, salted_hmac
|
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.encoding import force_bytes
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.regex_helper import _lazy_re_compile
|
from django.utils.regex_helper import _lazy_re_compile
|
||||||
@ -147,7 +149,7 @@ def dumps(
|
|||||||
|
|
||||||
The serializer is expected to return a bytestring.
|
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
|
obj, serializer=serializer, compress=compress
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -165,7 +167,9 @@ def loads(
|
|||||||
|
|
||||||
The serializer is expected to accept a bytestring.
|
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,
|
s,
|
||||||
serializer=serializer,
|
serializer=serializer,
|
||||||
max_age=max_age,
|
max_age=max_age,
|
||||||
@ -173,8 +177,13 @@ def loads(
|
|||||||
|
|
||||||
|
|
||||||
class Signer:
|
class Signer:
|
||||||
|
# RemovedInDjango51Warning: When the deprecation ends, replace with:
|
||||||
|
# def __init__(
|
||||||
|
# self, *, key=None, sep=":", salt=None, algorithm=None, fallback_keys=None
|
||||||
|
# ):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
*args,
|
||||||
key=None,
|
key=None,
|
||||||
sep=":",
|
sep=":",
|
||||||
salt=None,
|
salt=None,
|
||||||
@ -188,16 +197,29 @@ class Signer:
|
|||||||
else settings.SECRET_KEY_FALLBACKS
|
else settings.SECRET_KEY_FALLBACKS
|
||||||
)
|
)
|
||||||
self.sep = sep
|
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.salt = salt or "%s.%s" % (
|
||||||
self.__class__.__module__,
|
self.__class__.__module__,
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
)
|
)
|
||||||
self.algorithm = algorithm or "sha256"
|
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):
|
def signature(self, value, key=None):
|
||||||
key = key or self.key
|
key = key or self.key
|
||||||
|
@ -42,6 +42,9 @@ details on these changes.
|
|||||||
* Support for passing encoded JSON string literals to ``JSONField`` and
|
* Support for passing encoded JSON string literals to ``JSONField`` and
|
||||||
associated lookups and expressions will be removed.
|
associated lookups and expressions will be removed.
|
||||||
|
|
||||||
|
* Support for passing positional arguments to ``Signer`` and
|
||||||
|
``TimestampSigner`` will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-5.0:
|
.. _deprecation-removed-in-5.0:
|
||||||
|
|
||||||
5.0
|
5.0
|
||||||
|
@ -540,3 +540,6 @@ Miscellaneous
|
|||||||
|
|
||||||
* ``TransactionTestCase.assertQuerysetEqual()`` is deprecated in favor of
|
* ``TransactionTestCase.assertQuerysetEqual()`` is deprecated in favor of
|
||||||
``assertQuerySetEqual()``.
|
``assertQuerySetEqual()``.
|
||||||
|
|
||||||
|
* Passing positional arguments to ``Signer`` and ``TimestampSigner`` is
|
||||||
|
deprecated in favor of keyword-only arguments.
|
||||||
|
@ -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
|
generate signatures. You can use a different secret by passing it to the
|
||||||
``Signer`` constructor::
|
``Signer`` constructor::
|
||||||
|
|
||||||
>>> signer = Signer('my-other-secret')
|
>>> signer = Signer(key='my-other-secret')
|
||||||
>>> value = signer.sign('My string')
|
>>> value = signer.sign('My string')
|
||||||
>>> value
|
>>> value
|
||||||
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
|
'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
|
Returns a signer which uses ``key`` to generate signatures and ``sep`` to
|
||||||
separate values. ``sep`` cannot be in the :rfc:`URL safe base64 alphabet
|
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.
|
The ``fallback_keys`` argument was added.
|
||||||
|
|
||||||
|
.. deprecated:: 4.2
|
||||||
|
|
||||||
|
Support for passing positional arguments is deprecated.
|
||||||
|
|
||||||
Using the ``salt`` argument
|
Using the ``salt`` argument
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
@ -172,7 +176,7 @@ created within a specified period of time::
|
|||||||
>>> signer.unsign(value, max_age=timedelta(seconds=20))
|
>>> signer.unsign(value, max_age=timedelta(seconds=20))
|
||||||
'hello'
|
'hello'
|
||||||
|
|
||||||
.. class:: TimestampSigner(key=None, sep=':', salt=None, algorithm='sha256')
|
.. class:: TimestampSigner(*, key=None, sep=':', salt=None, algorithm='sha256')
|
||||||
|
|
||||||
.. method:: sign(value)
|
.. method:: sign(value)
|
||||||
|
|
||||||
@ -195,6 +199,10 @@ created within a specified period of time::
|
|||||||
otherwise raises ``SignatureExpired``. The ``max_age`` parameter can
|
otherwise raises ``SignatureExpired``. The ``max_age`` parameter can
|
||||||
accept an integer or a :py:class:`datetime.timedelta` object.
|
accept an integer or a :py:class:`datetime.timedelta` object.
|
||||||
|
|
||||||
|
.. deprecated:: 4.2
|
||||||
|
|
||||||
|
Support for passing positional arguments is deprecated.
|
||||||
|
|
||||||
.. _signing-complex-data:
|
.. _signing-complex-data:
|
||||||
|
|
||||||
Protecting complex data structures
|
Protecting complex data structures
|
||||||
|
@ -2,15 +2,16 @@ import datetime
|
|||||||
|
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.test import SimpleTestCase, override_settings
|
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.crypto import InvalidAlgorithm
|
||||||
|
from django.utils.deprecation import RemovedInDjango51Warning
|
||||||
|
|
||||||
|
|
||||||
class TestSigner(SimpleTestCase):
|
class TestSigner(SimpleTestCase):
|
||||||
def test_signature(self):
|
def test_signature(self):
|
||||||
"signature() method should generate a signature"
|
"signature() method should generate a signature"
|
||||||
signer = signing.Signer("predictable-secret")
|
signer = signing.Signer(key="predictable-secret")
|
||||||
signer2 = signing.Signer("predictable-secret2")
|
signer2 = signing.Signer(key="predictable-secret2")
|
||||||
for s in (
|
for s in (
|
||||||
b"hello",
|
b"hello",
|
||||||
b"3098247:529:087:",
|
b"3098247:529:087:",
|
||||||
@ -28,8 +29,7 @@ class TestSigner(SimpleTestCase):
|
|||||||
self.assertNotEqual(signer.signature(s), signer2.signature(s))
|
self.assertNotEqual(signer.signature(s), signer2.signature(s))
|
||||||
|
|
||||||
def test_signature_with_salt(self):
|
def test_signature_with_salt(self):
|
||||||
"signature(value, salt=...) should work"
|
signer = signing.Signer(key="predictable-secret", salt="extra-salt")
|
||||||
signer = signing.Signer("predictable-secret", salt="extra-salt")
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
signer.signature("hello"),
|
signer.signature("hello"),
|
||||||
signing.base64_hmac(
|
signing.base64_hmac(
|
||||||
@ -40,12 +40,12 @@ class TestSigner(SimpleTestCase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertNotEqual(
|
self.assertNotEqual(
|
||||||
signing.Signer("predictable-secret", salt="one").signature("hello"),
|
signing.Signer(key="predictable-secret", salt="one").signature("hello"),
|
||||||
signing.Signer("predictable-secret", salt="two").signature("hello"),
|
signing.Signer(key="predictable-secret", salt="two").signature("hello"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_custom_algorithm(self):
|
def test_custom_algorithm(self):
|
||||||
signer = signing.Signer("predictable-secret", algorithm="sha512")
|
signer = signing.Signer(key="predictable-secret", algorithm="sha512")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
signer.signature("hello"),
|
signer.signature("hello"),
|
||||||
"Usf3uVQOZ9m6uPfVonKR-EBXjPe7bjMbp3_Fq8MfsptgkkM1ojidN0BxYaT5HAEN1"
|
"Usf3uVQOZ9m6uPfVonKR-EBXjPe7bjMbp3_Fq8MfsptgkkM1ojidN0BxYaT5HAEN1"
|
||||||
@ -53,14 +53,14 @@ class TestSigner(SimpleTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_algorithm(self):
|
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."
|
msg = "'whatever' is not an algorithm accepted by the hashlib module."
|
||||||
with self.assertRaisesMessage(InvalidAlgorithm, msg):
|
with self.assertRaisesMessage(InvalidAlgorithm, msg):
|
||||||
signer.sign("hello")
|
signer.sign("hello")
|
||||||
|
|
||||||
def test_sign_unsign(self):
|
def test_sign_unsign(self):
|
||||||
"sign/unsign should be reversible"
|
"sign/unsign should be reversible"
|
||||||
signer = signing.Signer("predictable-secret")
|
signer = signing.Signer(key="predictable-secret")
|
||||||
examples = [
|
examples = [
|
||||||
"q;wjmbk;wkmb",
|
"q;wjmbk;wkmb",
|
||||||
"3098247529087",
|
"3098247529087",
|
||||||
@ -75,7 +75,7 @@ class TestSigner(SimpleTestCase):
|
|||||||
self.assertEqual(example, signer.unsign(signed))
|
self.assertEqual(example, signer.unsign(signed))
|
||||||
|
|
||||||
def test_sign_unsign_non_string(self):
|
def test_sign_unsign_non_string(self):
|
||||||
signer = signing.Signer("predictable-secret")
|
signer = signing.Signer(key="predictable-secret")
|
||||||
values = [
|
values = [
|
||||||
123,
|
123,
|
||||||
1.23,
|
1.23,
|
||||||
@ -91,7 +91,7 @@ class TestSigner(SimpleTestCase):
|
|||||||
|
|
||||||
def test_unsign_detects_tampering(self):
|
def test_unsign_detects_tampering(self):
|
||||||
"unsign should raise an exception if the value has been tampered with"
|
"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"
|
value = "Another string"
|
||||||
signed_value = signer.sign(value)
|
signed_value = signer.sign(value)
|
||||||
transforms = (
|
transforms = (
|
||||||
@ -106,7 +106,7 @@ class TestSigner(SimpleTestCase):
|
|||||||
signer.unsign(transform(signed_value))
|
signer.unsign(transform(signed_value))
|
||||||
|
|
||||||
def test_sign_unsign_object(self):
|
def test_sign_unsign_object(self):
|
||||||
signer = signing.Signer("predictable-secret")
|
signer = signing.Signer(key="predictable-secret")
|
||||||
tests = [
|
tests = [
|
||||||
["a", "list"],
|
["a", "list"],
|
||||||
"a string \u2019",
|
"a string \u2019",
|
||||||
@ -155,7 +155,7 @@ class TestSigner(SimpleTestCase):
|
|||||||
def test_works_with_non_ascii_keys(self):
|
def test_works_with_non_ascii_keys(self):
|
||||||
binary_key = b"\xe7" # Set some binary (non-ASCII key)
|
binary_key = b"\xe7" # Set some binary (non-ASCII key)
|
||||||
|
|
||||||
s = signing.Signer(binary_key)
|
s = signing.Signer(key=binary_key)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"foo:EE4qGC5MEKyQG5msxYA0sBohAxLC0BJf8uRhemh0BGU",
|
"foo:EE4qGC5MEKyQG5msxYA0sBohAxLC0BJf8uRhemh0BGU",
|
||||||
s.sign("foo"),
|
s.sign("foo"),
|
||||||
@ -164,7 +164,7 @@ class TestSigner(SimpleTestCase):
|
|||||||
def test_valid_sep(self):
|
def test_valid_sep(self):
|
||||||
separators = ["/", "*sep*", ","]
|
separators = ["/", "*sep*", ","]
|
||||||
for sep in separators:
|
for sep in separators:
|
||||||
signer = signing.Signer("predictable-secret", sep=sep)
|
signer = signing.Signer(key="predictable-secret", sep=sep)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"foo%sjZQoX_FtSO70jX9HLRGg2A_2s4kdDBxz1QoO_OpEQb0" % sep,
|
"foo%sjZQoX_FtSO70jX9HLRGg2A_2s4kdDBxz1QoO_OpEQb0" % sep,
|
||||||
signer.sign("foo"),
|
signer.sign("foo"),
|
||||||
@ -181,16 +181,16 @@ class TestSigner(SimpleTestCase):
|
|||||||
signing.Signer(sep=sep)
|
signing.Signer(sep=sep)
|
||||||
|
|
||||||
def test_verify_with_non_default_key(self):
|
def test_verify_with_non_default_key(self):
|
||||||
old_signer = signing.Signer("secret")
|
old_signer = signing.Signer(key="secret")
|
||||||
new_signer = signing.Signer(
|
new_signer = signing.Signer(
|
||||||
"newsecret", fallback_keys=["othersecret", "secret"]
|
key="newsecret", fallback_keys=["othersecret", "secret"]
|
||||||
)
|
)
|
||||||
signed = old_signer.sign("abc")
|
signed = old_signer.sign("abc")
|
||||||
self.assertEqual(new_signer.unsign(signed), "abc")
|
self.assertEqual(new_signer.unsign(signed), "abc")
|
||||||
|
|
||||||
def test_sign_unsign_multiple_keys(self):
|
def test_sign_unsign_multiple_keys(self):
|
||||||
"""The default key is a valid verification key."""
|
"""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")
|
signed = signer.sign("abc")
|
||||||
self.assertEqual(signer.unsign(signed), "abc")
|
self.assertEqual(signer.unsign(signed), "abc")
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ class TestSigner(SimpleTestCase):
|
|||||||
SECRET_KEY_FALLBACKS=["oldsecret"],
|
SECRET_KEY_FALLBACKS=["oldsecret"],
|
||||||
)
|
)
|
||||||
def test_sign_unsign_ignore_secret_key_fallbacks(self):
|
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")
|
signed = old_signer.sign("abc")
|
||||||
signer = signing.Signer(fallback_keys=[])
|
signer = signing.Signer(fallback_keys=[])
|
||||||
with self.assertRaises(signing.BadSignature):
|
with self.assertRaises(signing.BadSignature):
|
||||||
@ -210,7 +210,7 @@ class TestSigner(SimpleTestCase):
|
|||||||
SECRET_KEY_FALLBACKS=["oldsecret"],
|
SECRET_KEY_FALLBACKS=["oldsecret"],
|
||||||
)
|
)
|
||||||
def test_default_keys_verification(self):
|
def test_default_keys_verification(self):
|
||||||
old_signer = signing.Signer("oldsecret")
|
old_signer = signing.Signer(key="oldsecret")
|
||||||
signed = old_signer.sign("abc")
|
signed = old_signer.sign("abc")
|
||||||
signer = signing.Signer()
|
signer = signing.Signer()
|
||||||
self.assertEqual(signer.unsign(signed), "abc")
|
self.assertEqual(signer.unsign(signed), "abc")
|
||||||
@ -220,9 +220,9 @@ class TestTimestampSigner(SimpleTestCase):
|
|||||||
def test_timestamp_signer(self):
|
def test_timestamp_signer(self):
|
||||||
value = "hello"
|
value = "hello"
|
||||||
with freeze_time(123456789):
|
with freeze_time(123456789):
|
||||||
signer = signing.TimestampSigner("predictable-key")
|
signer = signing.TimestampSigner(key="predictable-key")
|
||||||
ts = signer.sign(value)
|
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)
|
self.assertEqual(signer.unsign(ts), value)
|
||||||
|
|
||||||
with freeze_time(123456800):
|
with freeze_time(123456800):
|
||||||
@ -240,3 +240,23 @@ class TestBase62(SimpleTestCase):
|
|||||||
tests = [-(10**10), 10**10, 1620378259, *range(-100, 100)]
|
tests = [-(10**10), 10**10, 1620378259, *range(-100, 100)]
|
||||||
for i in tests:
|
for i in tests:
|
||||||
self.assertEqual(i, signing.b62_decode(signing.b62_encode(i)))
|
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")
|
||||||
|
Loading…
Reference in New Issue
Block a user