tests.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import datetime
  2. from django.core import signing
  3. from django.test import SimpleTestCase
  4. from django.test.utils import freeze_time, ignore_warnings
  5. from django.utils.crypto import InvalidAlgorithm
  6. from django.utils.deprecation import RemovedInDjango40Warning
  7. class TestSigner(SimpleTestCase):
  8. def test_signature(self):
  9. "signature() method should generate a signature"
  10. signer = signing.Signer('predictable-secret')
  11. signer2 = signing.Signer('predictable-secret2')
  12. for s in (
  13. b'hello',
  14. b'3098247:529:087:',
  15. '\u2019'.encode(),
  16. ):
  17. self.assertEqual(
  18. signer.signature(s),
  19. signing.base64_hmac(
  20. signer.salt + 'signer',
  21. s,
  22. 'predictable-secret',
  23. algorithm=signer.algorithm,
  24. )
  25. )
  26. self.assertNotEqual(signer.signature(s), signer2.signature(s))
  27. def test_signature_with_salt(self):
  28. "signature(value, salt=...) should work"
  29. signer = signing.Signer('predictable-secret', salt='extra-salt')
  30. self.assertEqual(
  31. signer.signature('hello'),
  32. signing.base64_hmac(
  33. 'extra-salt' + 'signer',
  34. 'hello',
  35. 'predictable-secret',
  36. algorithm=signer.algorithm,
  37. )
  38. )
  39. self.assertNotEqual(
  40. signing.Signer('predictable-secret', salt='one').signature('hello'),
  41. signing.Signer('predictable-secret', salt='two').signature('hello'))
  42. def test_custom_algorithm(self):
  43. signer = signing.Signer('predictable-secret', algorithm='sha512')
  44. self.assertEqual(
  45. signer.signature('hello'),
  46. 'Usf3uVQOZ9m6uPfVonKR-EBXjPe7bjMbp3_Fq8MfsptgkkM1ojidN0BxYaT5HAEN1'
  47. 'VzO9_jVu7R-VkqknHYNvw',
  48. )
  49. @ignore_warnings(category=RemovedInDjango40Warning)
  50. def test_default_hashing_algorithm(self):
  51. signer = signing.Signer('predictable-secret', algorithm='sha1')
  52. signature_sha1 = signer.signature('hello')
  53. with self.settings(DEFAULT_HASHING_ALGORITHM='sha1'):
  54. signer = signing.Signer('predictable-secret')
  55. self.assertEqual(signer.signature('hello'), signature_sha1)
  56. def test_invalid_algorithm(self):
  57. signer = signing.Signer('predictable-secret', algorithm='whatever')
  58. msg = "'whatever' is not an algorithm accepted by the hashlib module."
  59. with self.assertRaisesMessage(InvalidAlgorithm, msg):
  60. signer.sign('hello')
  61. def test_legacy_signature(self):
  62. # RemovedInDjango40Warning: pre-Django 3.1 signatures won't be
  63. # supported.
  64. signer = signing.Signer()
  65. sha1_sig = 'foo:l-EMM5FtewpcHMbKFeQodt3X9z8'
  66. self.assertNotEqual(signer.sign('foo'), sha1_sig)
  67. self.assertEqual(signer.unsign(sha1_sig), 'foo')
  68. def test_sign_unsign(self):
  69. "sign/unsign should be reversible"
  70. signer = signing.Signer('predictable-secret')
  71. examples = [
  72. 'q;wjmbk;wkmb',
  73. '3098247529087',
  74. '3098247:529:087:',
  75. 'jkw osanteuh ,rcuh nthu aou oauh ,ud du',
  76. '\u2019',
  77. ]
  78. for example in examples:
  79. signed = signer.sign(example)
  80. self.assertIsInstance(signed, str)
  81. self.assertNotEqual(example, signed)
  82. self.assertEqual(example, signer.unsign(signed))
  83. def test_sign_unsign_non_string(self):
  84. signer = signing.Signer('predictable-secret')
  85. values = [
  86. 123,
  87. 1.23,
  88. True,
  89. datetime.date.today(),
  90. ]
  91. for value in values:
  92. with self.subTest(value):
  93. signed = signer.sign(value)
  94. self.assertIsInstance(signed, str)
  95. self.assertNotEqual(signed, value)
  96. self.assertEqual(signer.unsign(signed), str(value))
  97. def test_unsign_detects_tampering(self):
  98. "unsign should raise an exception if the value has been tampered with"
  99. signer = signing.Signer('predictable-secret')
  100. value = 'Another string'
  101. signed_value = signer.sign(value)
  102. transforms = (
  103. lambda s: s.upper(),
  104. lambda s: s + 'a',
  105. lambda s: 'a' + s[1:],
  106. lambda s: s.replace(':', ''),
  107. )
  108. self.assertEqual(value, signer.unsign(signed_value))
  109. for transform in transforms:
  110. with self.assertRaises(signing.BadSignature):
  111. signer.unsign(transform(signed_value))
  112. def test_dumps_loads(self):
  113. "dumps and loads be reversible for any JSON serializable object"
  114. objects = [
  115. ['a', 'list'],
  116. 'a string \u2019',
  117. {'a': 'dictionary'},
  118. ]
  119. for o in objects:
  120. self.assertNotEqual(o, signing.dumps(o))
  121. self.assertEqual(o, signing.loads(signing.dumps(o)))
  122. self.assertNotEqual(o, signing.dumps(o, compress=True))
  123. self.assertEqual(o, signing.loads(signing.dumps(o, compress=True)))
  124. def test_dumps_loads_legacy_signature(self):
  125. # RemovedInDjango40Warning: pre-Django 3.1 signatures won't be
  126. # supported.
  127. value = 'a string \u2020'
  128. # SHA-1 signed value.
  129. signed = 'ImEgc3RyaW5nIFx1MjAyMCI:1k1beT:ZfNhN1kdws7KosUleOvuYroPHEc'
  130. self.assertEqual(signing.loads(signed), value)
  131. @ignore_warnings(category=RemovedInDjango40Warning)
  132. def test_dumps_loads_default_hashing_algorithm_sha1(self):
  133. value = 'a string \u2020'
  134. with self.settings(DEFAULT_HASHING_ALGORITHM='sha1'):
  135. signed = signing.dumps(value)
  136. self.assertEqual(signing.loads(signed), value)
  137. def test_decode_detects_tampering(self):
  138. "loads should raise exception for tampered objects"
  139. transforms = (
  140. lambda s: s.upper(),
  141. lambda s: s + 'a',
  142. lambda s: 'a' + s[1:],
  143. lambda s: s.replace(':', ''),
  144. )
  145. value = {
  146. 'foo': 'bar',
  147. 'baz': 1,
  148. }
  149. encoded = signing.dumps(value)
  150. self.assertEqual(value, signing.loads(encoded))
  151. for transform in transforms:
  152. with self.assertRaises(signing.BadSignature):
  153. signing.loads(transform(encoded))
  154. def test_works_with_non_ascii_keys(self):
  155. binary_key = b'\xe7' # Set some binary (non-ASCII key)
  156. s = signing.Signer(binary_key)
  157. self.assertEqual(
  158. 'foo:EE4qGC5MEKyQG5msxYA0sBohAxLC0BJf8uRhemh0BGU',
  159. s.sign('foo'),
  160. )
  161. def test_valid_sep(self):
  162. separators = ['/', '*sep*', ',']
  163. for sep in separators:
  164. signer = signing.Signer('predictable-secret', sep=sep)
  165. self.assertEqual(
  166. 'foo%sjZQoX_FtSO70jX9HLRGg2A_2s4kdDBxz1QoO_OpEQb0' % sep,
  167. signer.sign('foo'),
  168. )
  169. def test_invalid_sep(self):
  170. """should warn on invalid separator"""
  171. msg = 'Unsafe Signer separator: %r (cannot be empty or consist of only A-z0-9-_=)'
  172. separators = ['', '-', 'abc']
  173. for sep in separators:
  174. with self.assertRaisesMessage(ValueError, msg % sep):
  175. signing.Signer(sep=sep)
  176. class TestTimestampSigner(SimpleTestCase):
  177. def test_timestamp_signer(self):
  178. value = 'hello'
  179. with freeze_time(123456789):
  180. signer = signing.TimestampSigner('predictable-key')
  181. ts = signer.sign(value)
  182. self.assertNotEqual(ts, signing.Signer('predictable-key').sign(value))
  183. self.assertEqual(signer.unsign(ts), value)
  184. with freeze_time(123456800):
  185. self.assertEqual(signer.unsign(ts, max_age=12), value)
  186. # max_age parameter can also accept a datetime.timedelta object
  187. self.assertEqual(signer.unsign(ts, max_age=datetime.timedelta(seconds=11)), value)
  188. with self.assertRaises(signing.SignatureExpired):
  189. signer.unsign(ts, max_age=10)