tests.py 7.1 KB

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