Hello community, here is the log from the commit of package python-django-debreach for openSUSE:Factory checked in at 2019-10-30 14:49:00 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-django-debreach (Old) and /work/SRC/openSUSE:Factory/.python-django-debreach.new.2990 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-debreach" Wed Oct 30 14:49:00 2019 rev:4 rq:744147 version:2.0.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-django-debreach/python-django-debreach.changes 2019-10-21 12:29:15.496019025 +0200 +++ /work/SRC/openSUSE:Factory/.python-django-debreach.new.2990/python-django-debreach.changes 2019-10-30 14:49:02.594260405 +0100 @@ -1,0 +2,7 @@ +Wed Oct 30 12:04:59 UTC 2019 - Tomáš Chvátal <tchva...@suse.com> + +- Update to 2.0.1: + * Drop python2 support upstream + * Various tests and description fixes + +------------------------------------------------------------------- Old: ---- django-debreach-1.5.2.tar.gz New: ---- django-debreach-2.0.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-django-debreach.spec ++++++ --- /var/tmp/diff_new_pack.3GjH7d/_old 2019-10-30 14:49:03.886261779 +0100 +++ /var/tmp/diff_new_pack.3GjH7d/_new 2019-10-30 14:49:03.886261779 +0100 @@ -19,18 +19,16 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-django-debreach -Version: 1.5.2 +Version: 2.0.1 Release: 0 Summary: Middleware to protect against the BREACH attack in Django License: BSD-2-Clause -Group: Development/Languages/Python -URL: http://github.com/lpomfrey/django-debreach +URL: https://github.com/lpomfrey/django-debreach Source: https://files.pythonhosted.org/packages/source/d/django-debreach/django-debreach-%{version}.tar.gz BuildRequires: %{python_module Django} BuildRequires: %{python_module setuptools} BuildRequires: fdupes BuildRequires: python-rpm-macros -BuildRequires: python python3 Requires: python-Django BuildArch: noarch %python_subpackages ++++++ django-debreach-1.5.2.tar.gz -> django-debreach-2.0.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/PKG-INFO new/django-debreach-2.0.1/PKG-INFO --- old/django-debreach-1.5.2/PKG-INFO 2018-08-31 15:32:05.000000000 +0200 +++ new/django-debreach-2.0.1/PKG-INFO 2019-10-10 11:09:54.000000000 +0200 @@ -1,7 +1,7 @@ Metadata-Version: 1.1 Name: django-debreach -Version: 1.5.2 -Summary: Adds middleware and context processors to give some protection against the BREACH attack in Django. +Version: 2.0.1 +Summary: Adds middleware to give some added protection against the BREACH attack in Django. Home-page: http://github.com/lpomfrey/django-debreach Author: Luke Pomfrey Author-email: lpomf...@gmail.com @@ -14,14 +14,9 @@ Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Framework :: Django -Classifier: Framework :: Django :: 1.8 -Classifier: Framework :: Django :: 1.11 -Classifier: Framework :: Django :: 2.0 +Classifier: Framework :: Django :: 2.2 Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/README.rst new/django-debreach-2.0.1/README.rst --- old/django-debreach-1.5.2/README.rst 2016-08-13 13:15:42.000000000 +0200 +++ new/django-debreach-2.0.1/README.rst 2019-10-10 09:58:23.000000000 +0200 @@ -4,6 +4,12 @@ Basic/extra mitigation against the `BREACH attack <http://breachattack.com/>`_ for Django projects. +django-debreach provides additional protection to Django's built in CSRF +token masking by randomising the content length of each response. This is +acheived by adding a random string of between 12 and 25 characters as a +comment to the end of the HTML content. Note that this will only be applied to +responses with a content type of ``text/html``. + When combined with rate limiting in your web-server, or by using something like `django-ratelimit <http://django-ratelimit.readthedocs.org/>`_, the techniques here should provide at least some protection against the BREACH @@ -20,89 +26,13 @@ :target: https://coveralls.io/r/lpomfrey/django-debreach?branch=master :alt: Coverage -Installation ------------- +Installation & Usage +-------------------- Install from PyPI using:: $ pip install django-debreach -Add to your `INSTALLED_APPS`:: - - INSTALLED_APPS = ( - ... - 'debreach', - ... - ) - -Configuration -------------- - -CSRF token masking (for Django < 1.10) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Django 1.10+ provides built-in support for masking CSRF tokens so you should -use that. Including the middleware in a Django 1.10 project will raise an -``ImproperlyConfigured`` exception. - -To mask CSRF tokens in the template add the -``debreach.context_processors.csrf`` context processor to the end of your -`TEMPLATE_CONTEXT_PROCESSORS`:: - - TEMPLATE_CONTEXT_PROCESSORS = ( - ... - 'debreach.context_processors.csrf', - ) - -And add the ``debreach.middleware.CSRFCryptMiddleware`` to your middleware, -*before* ``django.middleware.csrf.CSRFMiddleware``:: - - MIDDLEWARE_CLASSES = ( - 'debreach.middleware.CSRFCryptMiddleware', - ... - 'django.middleware.csrf.CSRFMiddleware', - ... - ) - -This works by xor-ing the CSRF token when it is added to the template, -so that ``{% csrf_token %}`` now produces a hidden field with a value that is -``"<random-string>$<actual-csrf-token-xor-ed-with-random-string>"``. -Then, when the form is POSTed, the middleware xors the CSRF token back into -it's original form. This ensures that the CSRF content is never the same -between requests. If you are passing the token using the ``X-CSRFToken`` -header (e.g. using XHR) that header will also be processed in the same way. - -Note that values that are unchanged by django-debreach, or rather, don't -contain a delimiting ``$``, will be left unmodified. The middleware will -also not operate on views marked as being exempt from CSRF protection -using the ``django.views.decorators.csrf.csrf_exempt`` decorator. - -CSRF protection using csrf_protect -"""""""""""""""""""""""""""""""""" - -If you don't use the CSRF middleware from django but, instead, apply the -``django.views.decorators.csrf.csrf_protect`` decorator to selected -views, and don't want to use the ``debreach.middleware.CSRFCryptMiddleware``, -then you can use the ``debreach.decorators.csrf_decrypt`` decorator. - -To use the ``debreach.decorators.csrf_decrypt`` decorator simply wrap -your CSRF protected view with the decorator, like so:: - - @csrf_protect - @csrf_decrypt - def view(request, *args, **kwargs): - return HttpResponse('') - - -Content length modification -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -django-debreach also enables you to counter the BREACH attack by randomising the -content length of each response. This is acheived by adding a random string of -between 12 and 25 characters as a comment to the end of the HTML content. Note -that this will only be applied to responses with a content type of -``text/html``. - To enable content length modification for all responses, add the ``debreach.middleware.RandomCommentMiddleware`` to the *start* of your middleware, but *after* the ``GzipMiddleware`` if you are using that.:: @@ -127,3 +57,9 @@ then it may be easier to not use the middleware, but to selectively apply the ``debreach.decorators.append_random_comment`` decorator to the views you want protected. + +Python 2 and Django < 2.0 support +--------------------------------- + +Version 2.0.0 drops all support for Python 2 and Django < 2.0. If you need +support for those versions continue using ``django-debreach>=1.5.2,<2.0``. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/debreach/__init__.py new/django-debreach-2.0.1/debreach/__init__.py --- old/django-debreach-1.5.2/debreach/__init__.py 2018-08-31 15:31:47.000000000 +0200 +++ new/django-debreach-2.0.1/debreach/__init__.py 2019-10-10 11:08:57.000000000 +0200 @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from distutils import version -__version__ = '1.5.2' +__version__ = '2.0.1' version_info = version.StrictVersion(__version__).version default_app_config = 'debreach.apps.DebreachConfig' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/debreach/apps.py new/django-debreach-2.0.1/debreach/apps.py --- old/django-debreach-1.5.2/debreach/apps.py 2016-01-10 17:05:37.000000000 +0100 +++ new/django-debreach-2.0.1/debreach/apps.py 2019-10-10 09:55:51.000000000 +0200 @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.apps import AppConfig diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/debreach/context_processors.py new/django-debreach-2.0.1/debreach/context_processors.py --- old/django-debreach-1.5.2/debreach/context_processors.py 2016-08-13 12:47:13.000000000 +0200 +++ new/django-debreach-2.0.1/debreach/context_processors.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.core.signing import b64_encode -from django.middleware.csrf import get_token -from django.utils.crypto import get_random_string -from django.utils.encoding import force_bytes, force_text -from django.utils.functional import lazy -from django.utils.six import text_type - -from debreach.utils import xor - - -def csrf(request): - """ - Context processor that provides a CSRF token, or the string 'NOTPROVIDED' - if it has not been provided by either a view decorator or the middleware - """ - def _get_val(): - token = get_token(request) - if token is None: - # In order to be able to provide debugging info in the - # case of misconfiguration, we use a sentinel value - # instead of returning an empty dict. - return 'NOTPROVIDED' - else: - token = force_bytes(token, encoding='latin-1') - key = force_bytes( - get_random_string(len(token)), - encoding='latin-1' - ) - value = b64_encode(xor(token, key)) - return force_text(b'$'.join((key, value)), encoding='latin-1') - _get_val = lazy(_get_val, text_type) - - return {'csrf_token': _get_val()} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/debreach/decorators.py new/django-debreach-2.0.1/debreach/decorators.py --- old/django-debreach-1.5.2/debreach/decorators.py 2016-08-13 12:50:35.000000000 +0200 +++ new/django-debreach-2.0.1/debreach/decorators.py 2019-10-10 11:08:27.000000000 +0200 @@ -1,12 +1,9 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - from functools import wraps -import django -from django.utils.decorators import available_attrs, decorator_from_middleware +from django.utils.decorators import decorator_from_middleware -from debreach.middleware import CSRFCryptMiddleware, RandomCommentMiddleware +from debreach.middleware import RandomCommentMiddleware append_random_comment = decorator_from_middleware(RandomCommentMiddleware) @@ -27,14 +24,4 @@ response = view_func(*args, **kwargs) response._random_comment_exempt = True return response - return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view) - - -if django.VERSION < (1, 10): - csrf_decrypt = decorator_from_middleware(CSRFCryptMiddleware) - append_random_comment.__name__ = str('append_random_comment') - append_random_comment.__doc__ = ''' - Performs the function of the CSRFCryptMiddleware, xor-ing the crypted CSRF - token back into its original form. Using both, or using the decorator - multiple times is harmless and efficient. - ''' + return wraps(view_func)(wrapped_view) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/debreach/middleware.py new/django-debreach-2.0.1/debreach/middleware.py --- old/django-debreach-1.5.2/debreach/middleware.py 2018-08-24 14:55:16.000000000 +0200 +++ new/django-debreach-2.0.1/debreach/middleware.py 2019-10-10 09:54:15.000000000 +0200 @@ -1,87 +1,28 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - import logging import random -import django -from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation -from django.core.signing import b64_decode from django.utils.crypto import get_random_string -from django.utils.encoding import force_bytes, force_text -from django.utils.six import binary_type, string_types - -from debreach.utils import xor - -try: - from django.utils.deprecation import MiddlewareMixin -except ImportError: - class MiddlewareMixin(object): - pass +from django.utils.deprecation import MiddlewareMixin +from django.utils.encoding import force_str log = logging.getLogger(__name__) -class CSRFCryptMiddleware(object): - - def __init__(self, *args, **kwargs): - if django.VERSION >= (1, 10): - raise ImproperlyConfigured( - "Django 1.10 and above provides it's own CSRF mechanism to " - "mitigate the BREACH attack, please remove the " - "{}.{} middleware." - .format(self.__module__, self.__class__.__name__) - ) - - def _decode(self, token): - key, value = force_bytes(token, encoding='latin-1').split(b'$', 1) - return force_text(xor(b64_decode(value), key), encoding='latin-1') - - def process_view(self, request, view, view_args, view_kwargs): - if getattr(view, 'csrf_exempt', False): - return None - if request.POST.get('csrfmiddlewaretoken') \ - and '$' in request.POST.get('csrfmiddlewaretoken'): - try: - post_was_mutable = request.POST._mutable - POST = request.POST.copy() - token = POST.get('csrfmiddlewaretoken') - POST['csrfmiddlewaretoken'] = self._decode(token) - POST._mutable = post_was_mutable - request.POST = POST - except: - log.exception('Error decoding csrfmiddlewaretoken') - raise SuspiciousOperation( - 'csrfmiddlewaretoken has been tampered with') - if request.META.get('HTTP_X_CSRFTOKEN') \ - and '$' in request.META.get('HTTP_X_CSRFTOKEN'): - try: - META = request.META.copy() - token = META.get('HTTP_X_CSRFTOKEN') - META['HTTP_X_CSRFTOKEN'] = self._decode(token) - request.META = META - except: - log.exception('Error decoding csrfmiddlewaretoken') - raise SuspiciousOperation( - 'X-CSRFToken header has been tampered with') - return None - - class RandomCommentMiddleware(MiddlewareMixin): def process_response(self, request, response): - str_types = string_types + (binary_type,) if not getattr(response, 'streaming', False) \ and response.get('Content-Type', '').startswith('text/html') \ and response.content \ - and isinstance(response.content, str_types) \ + and isinstance(response.content, (bytes, str)) \ and not getattr(response, '_random_comment_exempt', False) \ and not getattr(response, '_random_comment_applied', False): comment = '<!-- {0} -->'.format( get_random_string(random.choice(range(12, 25)))) response.content = '{0}{1}'.format( - force_text(response.content), comment) + force_str(response.content), comment) response._random_comment_applied = True if response.has_header('Content-Length'): response['Content-Length'] = str(len(response.content)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/debreach/models.py new/django-debreach-2.0.1/debreach/models.py --- old/django-debreach-1.5.2/debreach/models.py 2016-01-10 17:05:37.000000000 +0100 +++ new/django-debreach-2.0.1/debreach/models.py 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -# -*- coding: utf-8 -*- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/debreach/tests.py new/django-debreach-2.0.1/debreach/tests.py --- old/django-debreach-1.5.2/debreach/tests.py 2017-12-21 18:33:54.000000000 +0100 +++ new/django-debreach-2.0.1/debreach/tests.py 2019-10-10 09:56:08.000000000 +0200 @@ -1,124 +1,21 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os -import re import unittest -import django -from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.http import HttpResponse from django.test import TestCase from django.test.client import RequestFactory -from django.utils.crypto import get_random_string -from django.utils.encoding import force_text -from django.views.decorators.csrf import csrf_exempt +from django.urls import reverse +from django.utils.encoding import force_str -from debreach.context_processors import csrf from debreach.decorators import append_random_comment, random_comment_exempt -from debreach.middleware import CSRFCryptMiddleware, RandomCommentMiddleware - - -try: - from django.urls import reverse -except ImportError: - from django.core.urlresolvers import reverse - - -try: - unichr -except NameError: - pass -else: - chr = unichr +from debreach.middleware import RandomCommentMiddleware def test_view(request): return HttpResponse() -class TestCSRFCryptMiddleware(TestCase): - - if django.VERSION < (1, 10): - - def test_not_encoded(self): - request = RequestFactory().post( - '/', {'csrfmiddlewaretoken': 'abc123'} - ) - middleware = CSRFCryptMiddleware() - middleware.process_view(request, test_view, (), {}) - self.assertEqual(request.POST.get('csrfmiddlewaretoken'), 'abc123') - - def test_encoded(self): - request = RequestFactory().post( - '/', - {'csrfmiddlewaretoken': 'aBcDeF$ACAAdVd1'} - ) - middleware = CSRFCryptMiddleware() - middleware.process_view(request, test_view, (), {}) - self.assertEqual(request.POST.get('csrfmiddlewaretoken'), 'abc123') - - def test_mutable_status(self): - request = RequestFactory().post( - '/', - {'csrfmiddlewaretoken': 'aBcDeF$ACAAdVd1'} - ) - request.POST._mutable = False - middleware = CSRFCryptMiddleware() - middleware.process_view(request, test_view, (), {}) - self.assertFalse(request.POST._mutable) - request = RequestFactory().post( - '/', - {'csrfmiddlewaretoken': 'aBcDeF$ACAAdVd1'} - ) - request.POST._mutable = True - middleware = CSRFCryptMiddleware() - middleware.process_view(request, test_view, (), {}) - self.assertTrue(request.POST._mutable) - - def test_header_not_encoded(self): - request = RequestFactory().post('/', HTTP_X_CSRFTOKEN='abc123') - middleware = CSRFCryptMiddleware() - middleware.process_view(request, test_view, (), {}) - self.assertEqual(request.META.get('HTTP_X_CSRFTOKEN'), 'abc123') - - def test_header_encoded(self): - request = RequestFactory().post( - '/', HTTP_X_CSRFTOKEN='aBcDeF$ACAAdVd1', - ) - middleware = CSRFCryptMiddleware() - middleware.process_view(request, test_view, (), {}) - self.assertEqual(request.META.get('HTTP_X_CSRFTOKEN'), 'abc123') - - def test_tampering(self): - request = RequestFactory().post( - '/', {'csrfmiddlewaretoken': '123456$abc'}) - middleware = CSRFCryptMiddleware() - with self.assertRaises(SuspiciousOperation): - middleware.process_view(request, test_view, (), {}) - - def test_header_tampering(self): - request = RequestFactory().post('/', HTTP_X_CSRFTOKEN='123456$abc') - middleware = CSRFCryptMiddleware() - with self.assertRaises(SuspiciousOperation): - middleware.process_view(request, test_view, (), {}) - - def test_csrf_exempt(self): - # This is an odd test. We're testing that, when a view is - # csrf_exempt, process_view will bail without performing any - # processing. - request = RequestFactory().post('/', HTTP_X_CSRFTOKEN="aB$AHM") - middleware = CSRFCryptMiddleware() - middleware.process_view(request, csrf_exempt(test_view), (), {}) - self.assertEqual("aB$AHM", request.META['HTTP_X_CSRFTOKEN']) - - else: - - def test_middleware_raises_improperly_configured(self): - with self.assertRaises(ImproperlyConfigured): - CSRFCryptMiddleware() - - class TestRandomCommentMiddleware(TestCase): def test_noop_on_wrong_content_type(self): @@ -160,7 +57,7 @@ request = RequestFactory().get('/') middleware = RandomCommentMiddleware() response = middleware.process_response(request, response) - self.assertNotEqual(force_text(response.content), force_text(html)) + self.assertNotEqual(force_str(response.content), force_str(html)) def test_exemption(self): html = '''<html> @@ -172,7 +69,7 @@ request = RequestFactory().get('/') middleware = RandomCommentMiddleware() response = middleware.process_response(request, response) - self.assertEqual(force_text(response.content), html) + self.assertEqual(force_str(response.content), html) def test_missing_content_type(self): request = RequestFactory().get('/') @@ -204,9 +101,9 @@ request = RequestFactory().get('/') response = test_view(request) - self.assertNotEqual(force_text(response.content), html) - self.assertIn('<!-- ', force_text(response.content)) - self.assertIn(' -->', force_text(response.content)) + self.assertNotEqual(force_str(response.content), html) + self.assertIn('<!-- ', force_str(response.content)) + self.assertIn(' -->', force_str(response.content)) def test_random_comment_exempt(self): html = '''<html> @@ -223,26 +120,6 @@ self.assertTrue(response._random_comment_exempt) -class TestContextProcessor(TestCase): - - def test_csrf(self): - request = RequestFactory().get('/') - request.META['CSRF_COOKIE'] = 'abc123' - context = csrf(request) - self.assertTrue(force_text(context['csrf_token'])) - self.assertNotEqual(force_text(context['csrf_token']), 'abc123') - - @unittest.skipUnless( - django.VERSION < (1, 9), - 'The CSRF token is always present in Django 1.9+' - ) - def test_no_token_csrf(self): - request = RequestFactory().get('/') - context = csrf(request) - self.assertTrue(force_text(context['csrf_token'])) - self.assertEqual(force_text(context['csrf_token']), 'NOTPROVIDED') - - @unittest.skipUnless( 'test_project' in os.environ.get('DJANGO_SETTINGS_MODULE', ''), 'Not running in test_project' @@ -252,74 +129,3 @@ def test_adds_comment(self): resp = self.client.get(reverse('home')) self.assertFalse(resp.content.endswith(b'</html>')) - - if django.VERSION < (1, 10): - def test_crypt_csrf_token(self): - resp = self.client.get(reverse('test_form')) - m = re.search( - r'value=\'(.*\$.*)\'', - force_text(resp.content), - re.MULTILINE | re.DOTALL - ) - self.assertEqual(len(m.groups()), 1) - token = m.groups()[0].strip() - post_resp = self.client.post( - reverse('test_form'), - {'csrfmiddlewaretoken': token, 'message': 'Some rubbish'} - ) - self.assertRedirects(post_resp, reverse('home')) - - def test_crypt_csrf_header(self): - resp = self.client.get(reverse('test_form')) - m = re.search( - r'value=\'(.*\$.*)\'', - force_text(resp.content), - re.MULTILINE | re.DOTALL - ) - self.assertEqual(len(m.groups()), 1) - token = m.groups()[0].strip() - post_resp = self.client.post( - reverse('test_form'), - {'message': 'Some rubbish'}, - X_CSRFTOKEN=token, - ) - self.assertRedirects(post_resp, reverse('home')) - - def test_round_trip_loop(self): - ''' - Checks a wide range of input tokens and keys - ''' - for _ in range(1000): - request = RequestFactory().get('/') - csrf_token = get_random_string(32) - request.META['CSRF_COOKIE'] = csrf_token - token = force_text(csrf(request)['csrf_token']) - request = RequestFactory().post( - '/', {'csrfmiddlewaretoken': token}) - middleware = CSRFCryptMiddleware() - middleware.process_view(request, test_view, (), {}) - self.assertEqual( - force_text(request.POST.get('csrfmiddlewaretoken')), - force_text(csrf_token) - ) - - def test_round_trip_loop_header(self): - ''' - Checks a wide range of input tokens and keys - ''' - for _ in range(1000): - request = RequestFactory().get('/') - csrf_token = get_random_string(32) - request.META['CSRF_COOKIE'] = csrf_token - token = csrf(request)['csrf_token'] - request = RequestFactory().post( - '/', - HTTP_X_CSRFTOKEN=force_text(token), - HTTP_X_REQUESTED_WITH='XMLHttpRequest' - ) - middleware = CSRFCryptMiddleware() - middleware.process_view(request, test_view, (), {}) - self.assertEqual( - force_text(request.META.get('HTTP_X_CSRFTOKEN')), - force_text(csrf_token) - ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/debreach/utils.py new/django-debreach-2.0.1/debreach/utils.py --- old/django-debreach-1.5.2/debreach/utils.py 2016-01-10 17:05:37.000000000 +0100 +++ new/django-debreach-2.0.1/debreach/utils.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.utils.encoding import force_bytes -from django.utils.six import binary_type - - -def xor(s, pad): - '''XOR a given string ``s`` with the one-time-pad ``pad``''' - from itertools import cycle - s = bytearray(force_bytes(s, encoding='latin-1')) - pad = bytearray(force_bytes(pad, encoding='latin-1')) - return binary_type(bytearray(x ^ y for x, y in zip(s, cycle(pad)))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/django_debreach.egg-info/PKG-INFO new/django-debreach-2.0.1/django_debreach.egg-info/PKG-INFO --- old/django-debreach-1.5.2/django_debreach.egg-info/PKG-INFO 2018-08-31 15:32:05.000000000 +0200 +++ new/django-debreach-2.0.1/django_debreach.egg-info/PKG-INFO 2019-10-10 11:09:53.000000000 +0200 @@ -1,7 +1,7 @@ Metadata-Version: 1.1 Name: django-debreach -Version: 1.5.2 -Summary: Adds middleware and context processors to give some protection against the BREACH attack in Django. +Version: 2.0.1 +Summary: Adds middleware to give some added protection against the BREACH attack in Django. Home-page: http://github.com/lpomfrey/django-debreach Author: Luke Pomfrey Author-email: lpomf...@gmail.com @@ -14,14 +14,9 @@ Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Framework :: Django -Classifier: Framework :: Django :: 1.8 -Classifier: Framework :: Django :: 1.11 -Classifier: Framework :: Django :: 2.0 +Classifier: Framework :: Django :: 2.2 Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/django_debreach.egg-info/SOURCES.txt new/django-debreach-2.0.1/django_debreach.egg-info/SOURCES.txt --- old/django-debreach-1.5.2/django_debreach.egg-info/SOURCES.txt 2018-08-31 15:32:05.000000000 +0200 +++ new/django-debreach-2.0.1/django_debreach.egg-info/SOURCES.txt 2019-10-10 11:09:53.000000000 +0200 @@ -5,12 +5,9 @@ setup.py debreach/__init__.py debreach/apps.py -debreach/context_processors.py debreach/decorators.py debreach/middleware.py -debreach/models.py debreach/tests.py -debreach/utils.py django_debreach.egg-info/PKG-INFO django_debreach.egg-info/SOURCES.txt django_debreach.egg-info/dependency_links.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/docs/index.rst new/django-debreach-2.0.1/docs/index.rst --- old/django-debreach-1.5.2/docs/index.rst 2016-08-13 13:15:42.000000000 +0200 +++ new/django-debreach-2.0.1/docs/index.rst 2019-10-10 09:58:23.000000000 +0200 @@ -4,6 +4,12 @@ Basic/extra mitigation against the `BREACH attack <http://breachattack.com/>`_ for Django projects. +django-debreach provides additional protection to Django's built in CSRF +token masking by randomising the content length of each response. This is +acheived by adding a random string of between 12 and 25 characters as a +comment to the end of the HTML content. Note that this will only be applied to +responses with a content type of ``text/html``. + When combined with rate limiting in your web-server, or by using something like `django-ratelimit <http://django-ratelimit.readthedocs.org/>`_, the techniques here should provide at least some protection against the BREACH @@ -20,89 +26,13 @@ :target: https://coveralls.io/r/lpomfrey/django-debreach?branch=master :alt: Coverage -Installation ------------- +Installation & Usage +-------------------- Install from PyPI using:: $ pip install django-debreach -Add to your `INSTALLED_APPS`:: - - INSTALLED_APPS = ( - ... - 'debreach', - ... - ) - -Configuration -------------- - -CSRF token masking (for Django < 1.10) -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Django 1.10+ provides built-in support for masking CSRF tokens so you should -use that. Including the middleware in a Django 1.10 project will raise an -``ImproperlyConfigured`` exception. - -To mask CSRF tokens in the template add the -``debreach.context_processors.csrf`` context processor to the end of your -`TEMPLATE_CONTEXT_PROCESSORS`:: - - TEMPLATE_CONTEXT_PROCESSORS = ( - ... - 'debreach.context_processors.csrf', - ) - -And add the ``debreach.middleware.CSRFCryptMiddleware`` to your middleware, -*before* ``django.middleware.csrf.CSRFMiddleware``:: - - MIDDLEWARE_CLASSES = ( - 'debreach.middleware.CSRFCryptMiddleware', - ... - 'django.middleware.csrf.CSRFMiddleware', - ... - ) - -This works by xor-ing the CSRF token when it is added to the template, -so that ``{% csrf_token %}`` now produces a hidden field with a value that is -``"<random-string>$<actual-csrf-token-xor-ed-with-random-string>"``. -Then, when the form is POSTed, the middleware xors the CSRF token back into -it's original form. This ensures that the CSRF content is never the same -between requests. If you are passing the token using the ``X-CSRFToken`` -header (e.g. using XHR) that header will also be processed in the same way. - -Note that values that are unchanged by django-debreach, or rather, don't -contain a delimiting ``$``, will be left unmodified. The middleware will -also not operate on views marked as being exempt from CSRF protection -using the ``django.views.decorators.csrf.csrf_exempt`` decorator. - -CSRF protection using csrf_protect -"""""""""""""""""""""""""""""""""" - -If you don't use the CSRF middleware from django but, instead, apply the -``django.views.decorators.csrf.csrf_protect`` decorator to selected -views, and don't want to use the ``debreach.middleware.CSRFCryptMiddleware``, -then you can use the ``debreach.decorators.csrf_decrypt`` decorator. - -To use the ``debreach.decorators.csrf_decrypt`` decorator simply wrap -your CSRF protected view with the decorator, like so:: - - @csrf_protect - @csrf_decrypt - def view(request, *args, **kwargs): - return HttpResponse('') - - -Content length modification -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -django-debreach also enables you to counter the BREACH attack by randomising the -content length of each response. This is acheived by adding a random string of -between 12 and 25 characters as a comment to the end of the HTML content. Note -that this will only be applied to responses with a content type of -``text/html``. - To enable content length modification for all responses, add the ``debreach.middleware.RandomCommentMiddleware`` to the *start* of your middleware, but *after* the ``GzipMiddleware`` if you are using that.:: @@ -127,3 +57,9 @@ then it may be easier to not use the middleware, but to selectively apply the ``debreach.decorators.append_random_comment`` decorator to the views you want protected. + +Python 2 and Django < 2.0 support +--------------------------------- + +Version 2.0.0 drops all support for Python 2 and Django < 2.0. If you need +support for those versions continue using ``django-debreach>=1.5.2,<2.0``. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-debreach-1.5.2/setup.py new/django-debreach-2.0.1/setup.py --- old/django-debreach-1.5.2/setup.py 2017-12-21 18:53:44.000000000 +0100 +++ new/django-debreach-2.0.1/setup.py 2019-10-10 10:15:28.000000000 +0200 @@ -50,8 +50,8 @@ version=version, url='http://github.com/lpomfrey/django-debreach', license='BSD', - description='Adds middleware and context processors to give some ' - 'protection against the BREACH attack in Django.', + description='Adds middleware to give some added protection against the ' + 'BREACH attack in Django.', author='Luke Pomfrey', author_email='lpomf...@gmail.com', packages=find_packages(exclude=('test_project', 'docs')), @@ -67,14 +67,9 @@ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Framework :: Django', - 'Framework :: Django :: 1.8', - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.0', + 'Framework :: Django :: 2.2', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: Implementation :: CPython',