Selaa lähdekoodia

Add backward compatible order verification hash checking.

This can be enabled by specifying a `OSCAR_DEPRECATED_ORDER_VERIFY_KEY` setting, which is not set by default.

This allows projects to continue validating old order verification hashes while still changing the `SECRET_KEY`.
master
Samir Shah 7 vuotta sitten
vanhempi
commit
47fbac61a2

+ 20
- 1
docs/source/releases/v1.6.rst Näytä tiedosto

@@ -109,13 +109,32 @@ Minor changes
109 109
    allowed quantity in it based on product availability and basket threshold
110 110
    (see :issue:`1412`).
111 111
 
112
- - An unused setting ``OSCAR_SETTINGS`` was removed from ``oscar.core.defaults``.   
112
+ - An unused setting ``OSCAR_SETTINGS`` was removed from ``oscar.core.defaults``.
113 113
 
114 114
 .. _incompatible_in_1.6:
115 115
 
116 116
 Backwards incompatible changes in Oscar 1.6
117 117
 -------------------------------------------
118 118
 
119
+ - The mechanism used to generate verification hashes for anonymous orders was
120
+   changed due to a security vulnerability.
121
+   ``oscar.apps.order.Order.verification_hash()`` now uses
122
+   ``django.core.signing`` instead of generating its own MD5 hash for
123
+    tracking URLs for anonymous orders.
124
+
125
+   Projects that allow anonymous checkout are **strongly recommended** to
126
+   generate a new ``SECRET_KEY``, as the vulnerability exposed the
127
+   ``SECRET_KEY`` to potential exposure due to weaknesses in the hash generation
128
+   algorithm.
129
+
130
+   As a result of this change, order verification hashes generated previously
131
+   will no longer validate by default, and URLs generated with the old hash will
132
+   not be accessible.
133
+
134
+   Projects that wish to allow validation of old hashes
135
+   must specify a ``OSCAR_DEPRECATED_ORDER_VERIFY_KEY`` setting that is equal to
136
+   the ``SECRET_KEY`` that was in use prior to applying this change.
137
+
119 138
  - ``oscar.apps.customer.auth_backends.EmailBackend`` now rejects inactive users
120 139
    (where ``User.is_active`` is ``False``).
121 140
 

+ 33
- 0
src/oscar/apps/order/abstract_models.py Näytä tiedosto

@@ -1,7 +1,10 @@
1
+import hashlib
2
+import logging
1 3
 from collections import OrderedDict
2 4
 from decimal import Decimal as D
3 5
 
4 6
 from django.conf import settings
7
+from django.core.exceptions import ImproperlyConfigured
5 8
 from django.core.signing import BadSignature, Signer
6 9
 from django.db import models
7 10
 from django.db.models import Sum
@@ -22,6 +25,9 @@ from oscar.models.fields import AutoSlugField
22 25
 from . import exceptions
23 26
 
24 27
 
28
+logger = logging.getLogger('oscar.order')
29
+
30
+
25 31
 @python_2_unicode_compatible
26 32
 class AbstractOrder(models.Model):
27 33
     """
@@ -303,11 +309,38 @@ class AbstractOrder(models.Model):
303 309
         signer = Signer(salt='oscar.apps.order.Order')
304 310
         return signer.sign(self.number)
305 311
 
312
+    def check_deprecated_verification_hash(self, hash_to_check):
313
+        """
314
+        Backward compatible check for md5 hashes that were generated in
315
+        Oscar 1.5 and lower.
316
+
317
+        This must explicitly be enabled by setting OSCAR_DEPRECATED_ORDER_VERIFY_KEY,
318
+        which must not be equal to SECRET_KEY - i.e., the project must
319
+        have changed its SECRET_KEY since this change was applied.
320
+
321
+        TODO: deprecate this method in Oscar 2.0, and remove it in Oscar 2.1.
322
+        """
323
+        old_verification_key = getattr(settings, 'OSCAR_DEPRECATED_ORDER_VERIFY_KEY', None)
324
+        if old_verification_key is None:
325
+            return False
326
+
327
+        if old_verification_key == settings.SECRET_KEY:
328
+            raise ImproperlyConfigured(
329
+                'OSCAR_DEPRECATED_ORDER_VERIFY_KEY cannot be equal to SECRET_KEY')
330
+
331
+        logger.warning('Using insecure md5 hashing for order URL hash verification.')
332
+        string_to_hash = '%s%s' % (self.number, old_verification_key)
333
+        order_hash = hashlib.md5(string_to_hash.encode('utf8')).hexdigest()
334
+        return constant_time_compare(order_hash, hash_to_check)
335
+
306 336
     def check_verification_hash(self, hash_to_check):
307 337
         """
308 338
         Checks the received verification hash against this order number.
309 339
         Returns False if the verification failed, True otherwise.
310 340
         """
341
+        if self.check_deprecated_verification_hash(hash_to_check):
342
+            return True
343
+
311 344
         signer = Signer(salt='oscar.apps.order.Order')
312 345
         try:
313 346
             signed_number = signer.unsign(hash_to_check)

+ 27
- 0
tests/integration/order/test_models.py Näytä tiedosto

@@ -1,6 +1,7 @@
1 1
 from datetime import timedelta, datetime
2 2
 from decimal import Decimal as D
3 3
 
4
+from django.core.exceptions import ImproperlyConfigured
4 5
 from django.test import TestCase, override_settings
5 6
 from django.utils import timezone
6 7
 from django.utils.translation import ugettext_lazy as _
@@ -412,3 +413,29 @@ class OrderTests(TestCase):
412 413
         order = OrderFactory(number='111000')
413 414
         # Hash is valid, but it is for a different order number
414 415
         self.assertFalse(order.check_verification_hash('222000:knvoMB1KAiJu8meWtGce00Y88j4'))
416
+
417
+    @override_settings(OSCAR_DEPRECATED_ORDER_VERIFY_KEY='deprecated_order_hash_secret')
418
+    def test_check_deprecated_hash_verification(self):
419
+        order = OrderFactory(number='100001')
420
+        # Check that check_deprecated_verification_hash validates the hash
421
+        self.assertTrue(
422
+            order.check_deprecated_verification_hash('3efd0339e8c789447469f37851cbaaaf')
423
+        )
424
+        # Check that check_verification_hash calls it correctly
425
+        self.assertTrue(order.check_verification_hash('3efd0339e8c789447469f37851cbaaaf'))
426
+
427
+    def test_check_deprecated_hash_verification_without_old_key(self):
428
+        order = OrderFactory(number='100001')
429
+        # Check that check_deprecated_verification_hash validates the hash
430
+        self.assertFalse(
431
+            order.check_deprecated_verification_hash('3efd0339e8c789447469f37851cbaaaf')
432
+        )
433
+
434
+    @override_settings(
435
+        OSCAR_DEPRECATED_ORDER_VERIFY_KEY='deprecated_order_hash_secret',
436
+        SECRET_KEY='deprecated_order_hash_secret')
437
+    def test_check_deprecated_hash_verification_old_key_matches_new(self):
438
+        order = OrderFactory(number='100001')
439
+        # OSCAR_DEPRECATED_ORDER_VERIFY_KEY must not be equal to SECRET_KEY.
440
+        with self.assertRaises(ImproperlyConfigured):
441
+            order.check_deprecated_verification_hash('3efd0339e8c789447469f37851cbaaaf')

Loading…
Peruuta
Tallenna