Hello community,
here is the log from the commit of package python-django.4317 for openSUSE:13.1:Update checked in at 2015-12-04 11:41:27
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:13.1:Update/python-django.4317 (Old)
and /work/SRC/openSUSE:13.1:Update/.python-django.4317.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django.4317"
Changes:
--------
New Changes file:
--- /dev/null 2015-11-02 12:10:47.524024255 +0100
+++ /work/SRC/openSUSE:13.1:Update/.python-django.4317.new/python-django.changes 2015-12-04 11:41:28.000000000 +0100
@@ -0,0 +1,367 @@
+-------------------------------------------------------------------
+Wed Nov 18 09:40:55 UTC 2015 - bwiedemann@suse.com
+
+- add 0010-1.5.x-Fixed-a-settings-leak-possibility-in-the-date-.patch
+ to prevent settings leak in date template filter (bnc#955412, CVE-2015-8213)
+
+-------------------------------------------------------------------
+Mon Oct 12 12:49:26 UTC 2015 - bwiedemann@suse.com
+
+- add 0009-1.5.x-Prevented-newlines-from-being-accepted-in-some.patch
+ to prevent Header injection possibility (bnc#937523, CVE-2015-5144)
+- add 0008-1.5.x-Fixed-19324-Avoided-creating-a-session-record-.patch
+ to prevent Denial-of-service possibility by filling session store
+ (bnc#937522, CVE-2015-5143)
+
+-------------------------------------------------------------------
+Wed Sep 9 11:12:40 UTC 2015 - bwiedemann@suse.com
+
+- Add 0007-1.6.x-Fixed-DoS-possiblity-in-contrib.auth.views.log.patch
+ (bnc#941587, CVE-2015-5963)
+
+-------------------------------------------------------------------
+Fri Mar 20 12:56:53 UTC 2015 - bwiedemann@suse.com
+
+- Made is_safe_url() reject URLs that start with control characters
+ to mitigate possible XSS attack via user-supplied redirect URLs
+ (bnc#923176, CVE-2015-2317)
+ + Add 0006-1.5.x-Made-is_safe_url-reject-URLs-that-start-with-c.patch
+
+-------------------------------------------------------------------
+Wed Jan 28 16:21:41 UTC 2015 - mjura@suse.com
+
+- Method check_for_test_cookie is deprecated, bnc#914706
+ + Add 0005-1.6.x-Method-check_for_test_cookie-is-deprecated.patch
+
+-------------------------------------------------------------------
+Fri Jan 23 08:41:48 UTC 2015 - bwiedemann@suse.com
+
+- security fix backports
+ add 0001-1.5.x-Stripped-headers-containing-underscores-to-pre.patch (bnc#913053, CVE-2015-0219)
+ add 0002-1.5.x-Fixed-is_safe_url-to-handle-leading-whitespace.patch (bnc#913054, CVE-2015-0220)
+ add 0003-1.5.x-Prevented-views.static.serve-from-using-large-.patch (bnc#913056, CVE-2015-0221)
+ add 0004-1.5.x-Fixed-DoS-possibility-in-ModelMultipleChoiceFi.patch (bnc#913055, CVE-2015-0222)
+
+-------------------------------------------------------------------
+Wed Jan 21 09:57:12 UTC 2015 - bwiedemann@suse.com
+
+- Update to version 1.5.12:
+ + Fixed a regression with dynamically generated inlines and allowed field
+ references in the admin
+ + Allowed related many-to-many fields to be referenced in the admin
+ + Allowed inline and hidden references to admin fields
+
+-------------------------------------------------------------------
+Wed Sep 3 12:15:52 UTC 2014 - bwiedemann@suse.com
+
+- Update to version 1.5.10:
+ + Prevented reverse() from generating URLs pointing to other hosts
+ to prevent phishing attacks (bnc#893087, CVE-2014-0480)
+ + Removed O(n) algorithm when uploading duplicate file names
+ to fix file upload denial of service (bnc#893088, CVE-2014-0481)
+ + Modified RemoteUserMiddleware to logout on REMOTE_USE change
+ to prevent session hijacking (bnc#893089, CVE-2014-0482)
+ + Prevented data leakage in contrib.admin via query string manipulation
+ (bnc#893090, CVE-2014-0483)
+
+-------------------------------------------------------------------
+Mon May 26 07:22:53 UTC 2014 - bwiedemann@suse.com
+
+- Update to version 1.5.8:
+ + Fixed: Caches may incorrectly be allowed to store and serve private data
+ (bnc#877993, CVE-2014-1418)
+ + Fixed: Malformed redirect URLs from user input not correctly validated
+ (bnc#878641, CVE-2014-3730)
+ + Fixed queries that may return unexpected results on MySQL
+ due to typecasting (bnc#874956, CVE-2014-0474)
+ + Prevented leaking the CSRF token through caching
+ (bnc#874955, CVE-2014-0473)
+ + Fixed a remote code execution vulnerabilty in URL reversing
+ (bnc#874950, CVE-2014-0472)
+
+-------------------------------------------------------------------
+Thu Oct 31 14:14:58 UTC 2013 - mcihar@suse.cz
+
+- Update to version 1.5.5:
+ + Readdressed denial-of-service via password hashers
+ + Properly rotate CSRF token on login
+
+-------------------------------------------------------------------
+Tue Sep 17 12:37:53 UTC 2013 - speilicke@suse.com
+
+- Update to version 1.5.4:
+ + Fixed denial-of-service via large passwords
+- Changes from version 1.5.3:
+ + Fixed directory traversal with ssi template tag
+
+-------------------------------------------------------------------
+Wed Aug 14 05:49:54 UTC 2013 - alexandre@exatati.com.br
+
+- Update to 1.5.2:
+ - Security release, please check release notes for details:
+ https://www.djangoproject.com/weblog/2013/aug/13/security-releases-issued
+
+-------------------------------------------------------------------
+Thu Mar 28 23:27:01 UTC 2013 - alexandre@exatati.com.br
+
+- Update to 1.5.1:
+ - Memory leak fix, please read release announcement at
+ https://www.djangoproject.com/weblog/2013/mar/28/django-151.
+
+-------------------------------------------------------------------
+Tue Feb 26 19:49:02 UTC 2013 - alexandre@exatati.com.br
+
+- Update to 1.5:
+ - Please read the release notes
+ https://docs.djangoproject.com/en/1.5/releases/1.5
+
+-------------------------------------------------------------------
+Tue Dec 11 12:27:50 UTC 2012 - alexandre@exatati.com.br
+
+- Update to 1.4.3:
+ - Security release:
+ - Host header poisoning
+ - Redirect poisoning
+ - Please check release notes for details:
+ https://www.djangoproject.com/weblog/2012/dec/10/security
+
+-------------------------------------------------------------------
+Sat Oct 20 13:41:10 UTC 2012 - saschpe@suse.de
+
+- Add a symlink from /usr/bin/django-admin.py to /usr/bin/django-admin
+
+-------------------------------------------------------------------
+Wed Oct 17 22:51:36 UTC 2012 - alexandre@exatati.com.br
+
+- Update to 1.4.2:
+ - Security release:
+ - Host header poisoning
+ - Please check release notes for details:
+ https://www.djangoproject.com/weblog/2012/oct/17/security
+
+-------------------------------------------------------------------
+Mon Jul 30 21:38:31 UTC 2012 - alexandre@exatati.com.br
+
+- Update to 1.4.1:
+ - Security release:
+ - Cross-site scripting in authentication views
+ - Denial-of-service in image validation
+ - Denial-of-service via get_image_dimensions()
+ - Please check release notes for details:
+ https://www.djangoproject.com/weblog/2012/jul/30/security-releases-issued
+
+-------------------------------------------------------------------
+Tue Jun 19 11:27:33 UTC 2012 - saschpe@suse.de
+
+- Add patch to support CSRF_COOKIE_HTTPONLY config
+
+-------------------------------------------------------------------
+Fri Mar 23 18:39:40 UTC 2012 - alexandre@exatati.com.br
+
+- Update to 1.4:
+ - Please read the release notes
+ https://docs.djangoproject.com/en/dev/releases/1.4
+- Removed Patch2, it was merged on upstream,
+
+-------------------------------------------------------------------
+Thu Nov 24 12:30:40 UTC 2011 - saschpe@suse.de
+
+- Set license to SDPX style (BSD-3-Clause)
+- Package AUTHORS, LICENE and README files
+- No CFLAGS for noarch package
+- Drop runtime dependency on gettext-tools
+
+-------------------------------------------------------------------
+Sat Sep 10 12:05:07 UTC 2011 - alexandre@exatati.com.br
+
+- Update to 1.3.1 to fix security issues, please read
+ https://www.djangoproject.com/weblog/2011/sep/09/security-releases-issued.
+
+-------------------------------------------------------------------
+Thu Mar 31 15:09:16 UTC 2011 - alexandre@exatati.com.br
+
+- Fix build on SLES_9.
+
+-------------------------------------------------------------------
+Wed Mar 23 11:39:53 UTC 2011 - alexandre@exatati.com.br
+
+- Update to 1.3 final;
+- Refresh patch empty-ip-2.diff.
+
+-------------------------------------------------------------------
+Fri Mar 18 03:45:45 UTC 2011 - alexandre@exatati.com.br
+
+- Update to 1.3-rc1;
+- Regenerated spec file with py2pack;
+- No more need to fix wrong line endings;
+- Refresh patch empty-ip-2.diff with -p0.
++++ 170 more lines (skipped)
++++ between /dev/null
++++ and /work/SRC/openSUSE:13.1:Update/.python-django.4317.new/python-django.changes
New:
----
0001-1.5.x-Stripped-headers-containing-underscores-to-pre.patch
0002-1.5.x-Fixed-is_safe_url-to-handle-leading-whitespace.patch
0003-1.5.x-Prevented-views.static.serve-from-using-large-.patch
0004-1.5.x-Fixed-DoS-possibility-in-ModelMultipleChoiceFi.patch
0005-1.6.x-Method-check_for_test_cookie-is-deprecated.patch
0006-1.5.x-Made-is_safe_url-reject-URLs-that-start-with-c.patch
0007-1.6.x-Fixed-DoS-possiblity-in-contrib.auth.views.log.patch
0008-1.5.x-Fixed-19324-Avoided-creating-a-session-record-.patch
0009-1.5.x-Prevented-newlines-from-being-accepted-in-some.patch
0010-1.5.x-Fixed-a-settings-leak-possibility-in-the-date-.patch
Django-1.2-completion-only-for-bash.patch
Django-1.4-CSRF_COOKIE_HTTPONLY-support.patch
Django-1.5.12.tar.gz
python-django-rpmlintrc
python-django.changes
python-django.spec
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-django.spec ++++++
#
# spec file for package python-django
#
# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany.
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
# upon. The license for this file, and modifications and additions to the
# file, is the same license as for the pristine package itself (unless the
# license for the pristine package is not an Open Source License, in which
# case the license is the MIT License). An "Open Source License" is a
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
# Please submit bugfixes or comments via http://bugs.opensuse.org/
#
Name: python-django
Version: 1.5.12
Release: 0.<RELEASE7>
Summary: A high-level Python Web framework
License: BSD-3-Clause
Group: Development/Languages/Python
Url: http://www.djangoproject.com
# https://www.djangoproject.com/download/1.5.10/tarball/
Source: Django-%{version}.tar.gz
Source1: python-django-rpmlintrc
# PATCH-FIX-UPSTREAM Django-1.2-completion-only-for-bash.patch
Patch1: Django-1.2-completion-only-for-bash.patch
# PATCH-FIX-UPSTREAM - see https://github.com/django/django/pull/150/files
Patch2: Django-1.4-CSRF_COOKIE_HTTPONLY-support.patch
# PATCH-BACKPORTS from 1.6.x
Patch11: 0001-1.5.x-Stripped-headers-containing-underscores-to-pre.patch
Patch12: 0002-1.5.x-Fixed-is_safe_url-to-handle-leading-whitespace.patch
Patch13: 0003-1.5.x-Prevented-views.static.serve-from-using-large-.patch
Patch14: 0004-1.5.x-Fixed-DoS-possibility-in-ModelMultipleChoiceFi.patch
Patch15: 0005-1.6.x-Method-check_for_test_cookie-is-deprecated.patch
Patch16: 0006-1.5.x-Made-is_safe_url-reject-URLs-that-start-with-c.patch
Patch17: 0007-1.6.x-Fixed-DoS-possiblity-in-contrib.auth.views.log.patch
Patch18: 0008-1.5.x-Fixed-19324-Avoided-creating-a-session-record-.patch
Patch19: 0009-1.5.x-Prevented-newlines-from-being-accepted-in-some.patch
Patch20: 0010-1.5.x-Fixed-a-settings-leak-possibility-in-the-date-.patch
BuildRequires: python-devel
Requires: python-xml
BuildRoot: %{_tmppath}/%{name}-%{version}-build
#Requires: gettext-tools
%if 0%{?suse_version}
%py_requires
%if 0%{?suse_version} > 1110
BuildArch: noarch
%endif
%endif
%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
%description
Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design.
%prep
%setup -q -n Django-%{version}
%patch1
%patch2
%patch11 -p1
%patch12 -p1
%patch13 -p1
%patch14 -p1
%patch15 -p1
%patch16 -p1
%patch17 -p1
%patch18 -p1
%patch19 -p1
%patch20 -p1
%build
python setup.py build
%install
python setup.py install --prefix=%{_prefix} --root=%{buildroot}
install -D -m 0755 extras/django_bash_completion %{buildroot}%{_sysconfdir}/bash_completion.d/django_bash_completion.sh
ln -s %{_bindir}/django-admin.py %{buildroot}%{_bindir}/django-admin
%files
%defattr(-,root,root,-)
%doc AUTHORS LICENSE README.rst
%{_bindir}/django-admin*
%{python_sitelib}/*
%{_sysconfdir}/bash_completion.d/django_bash_completion.sh
%changelog
++++++ 0001-1.5.x-Stripped-headers-containing-underscores-to-pre.patch ++++++
From 3feaba416575f6197b6e08ecb239cf8b9317bbff Mon Sep 17 00:00:00 2001
From: Carl Meyer
Date: Wed, 10 Sep 2014 11:06:19 -0600
Subject: [PATCH 1/4] [1.5.x] Stripped headers containing underscores to
prevent spoofing in WSGI environ. (bnc#913053, CVE-2015-0219)
This is a security fix.
WSGI header spoofing via underscore/dash conflation.
`Full description https://www.djangoproject.com/weblog/2015/jan/13/security/`
Thanks to Jedediah Smith for the report.
cherry-picked-from: d7597b31d5c03106eeba4be14a33b32a5e25f4ee
---
django/core/servers/basehttp.py | 11 +++++++
docs/howto/auth-remote-user.txt | 15 +++++++++
tests/servers/test_basehttp.py | 67 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 93 insertions(+)
create mode 100644 tests/servers/test_basehttp.py
diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py
index 9aed637..efc13d2 100644
--- a/django/core/servers/basehttp.py
+++ b/django/core/servers/basehttp.py
@@ -174,6 +174,17 @@ class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):
sys.stderr.write(msg)
+ def get_environ(self):
+ # Strip all headers with underscores in the name before constructing
+ # the WSGI environ. This prevents header-spoofing based on ambiguity
+ # between underscores and dashes both normalized to underscores in WSGI
+ # env vars. Nginx and Apache 2.4+ both do this as well.
+ for k, v in self.headers.items():
+ if '_' in k:
+ del self.headers[k]
+
+ return super(WSGIRequestHandler, self).get_environ()
+
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
server_address = (addr, port)
diff --git a/docs/howto/auth-remote-user.txt b/docs/howto/auth-remote-user.txt
index 09ae928..cbfc526 100644
--- a/docs/howto/auth-remote-user.txt
+++ b/docs/howto/auth-remote-user.txt
@@ -66,6 +66,21 @@ If your authentication mechanism uses a custom HTTP header and not
class CustomHeaderMiddleware(RemoteUserMiddleware):
header = 'HTTP_AUTHUSER'
+.. warning::
+
+ Be very careful if using a ``RemoteUserMiddleware`` subclass with a custom
+ HTTP header. You must be sure that your front-end web server always sets or
+ strips that header based on the appropriate authentication checks, never
+ permitting an end-user to submit a fake (or "spoofed") header value. Since
+ the HTTP headers ``X-Auth-User`` and ``X-Auth_User`` (for example) both
+ normalize to the ``HTTP_X_AUTH_USER`` key in ``request.META``, you must
+ also check that your web server doesn't allow a spoofed header using
+ underscores in place of dashes.
+
+ This warning doesn't apply to ``RemoteUserMiddleware`` in its default
+ configuration with ``header = 'REMOTE_USER'``, since a key that doesn't
+ start with ``HTTP_`` in ``request.META`` can only be set by your WSGI
+ server, not directly from an HTTP request header.
``RemoteUserBackend``
=====================
diff --git a/tests/servers/test_basehttp.py b/tests/servers/test_basehttp.py
new file mode 100644
index 0000000..6bca608
--- /dev/null
+++ b/tests/servers/test_basehttp.py
@@ -0,0 +1,67 @@
+import sys
+
+from django.core.servers.basehttp import WSGIRequestHandler
+from django.test import TestCase
+from django.utils.six import BytesIO, StringIO
+
+
+class Stub(object):
+ def __init__(self, **kwargs):
+ self.__dict__.update(kwargs)
+
+
+class WSGIRequestHandlerTestCase(TestCase):
+
+ def test_strips_underscore_headers(self):
+ """WSGIRequestHandler ignores headers containing underscores.
+
+ This follows the lead of nginx and Apache 2.4, and is to avoid
+ ambiguity between dashes and underscores in mapping to WSGI environ,
+ which can have security implications.
+ """
+ def test_app(environ, start_response):
+ """A WSGI app that just reflects its HTTP environ."""
+ start_response('200 OK', [])
+ http_environ_items = sorted(
+ '%s:%s' % (k, v) for k, v in environ.items()
+ if k.startswith('HTTP_')
+ )
+ yield (','.join(http_environ_items)).encode('utf-8')
+
+ rfile = BytesIO()
+ rfile.write(b"GET / HTTP/1.0\r\n")
+ rfile.write(b"Some-Header: good\r\n")
+ rfile.write(b"Some_Header: bad\r\n")
+ rfile.write(b"Other_Header: bad\r\n")
+ rfile.seek(0)
+
+ # WSGIRequestHandler closes the output file; we need to make this a
+ # no-op so we can still read its contents.
+ class UnclosableBytesIO(BytesIO):
+ def close(self):
+ pass
+
+ wfile = UnclosableBytesIO()
+
+ def makefile(mode, *a, **kw):
+ if mode == 'rb':
+ return rfile
+ elif mode == 'wb':
+ return wfile
+
+ request = Stub(makefile=makefile)
+ server = Stub(base_environ={}, get_app=lambda: test_app)
+
+ # We don't need to check stderr, but we don't want it in test output
+ old_stderr = sys.stderr
+ sys.stderr = StringIO()
+ try:
+ # instantiating a handler runs the request as side effect
+ WSGIRequestHandler(request, '192.168.0.2', server)
+ finally:
+ sys.stderr = old_stderr
+
+ wfile.seek(0)
+ body = list(wfile.readlines())[-1]
+
+ self.assertEqual(body, b'HTTP_SOME_HEADER:good')
--
1.8.1.4
++++++ 0002-1.5.x-Fixed-is_safe_url-to-handle-leading-whitespace.patch ++++++
From 4eec954e2ad330d7cd4429450f2d49b414bc72eb Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 3 Dec 2014 16:14:00 -0500
Subject: [PATCH 2/4] [1.5.x] Fixed is_safe_url() to handle leading whitespace.
(bnc#913054, CVE-2015-0220)
This is a security fix.
Mitigated possible XSS attack via user-supplied redirect URLs. `Full description https://www.djangoproject.com/weblog/2015/jan/13/security/
cherry-picked-from: 72e0b033662faa11bb7f516f18a132728aa0ae28
---
django/utils/http.py | 1 +
tests/regressiontests/utils/http.py | 3 ++-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/django/utils/http.py b/django/utils/http.py
index 67912f7..f690a79 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -237,6 +237,7 @@ def is_safe_url(url, host=None):
"""
if not url:
return False
+ url = url.strip()
# Chrome treats \ completely as /
url = url.replace('\\', '/')
# Chrome considers any URL with more than two slashes to be absolute, but
diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py
index 87a6ba4..88bcfb8 100644
--- a/tests/regressiontests/utils/http.py
+++ b/tests/regressiontests/utils/http.py
@@ -109,7 +109,8 @@ class TestUtilsHttp(unittest.TestCase):
'http:/\//example.com',
'http:\/example.com',
'http:/\example.com',
- 'javascript:alert("XSS")'):
+ 'javascript:alert("XSS")',
+ '\njavascript:alert(x)'):
self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url)
for good_url in ('/view/?param=http://example.com',
'/view/?param=https://example.com',
--
1.8.1.4
++++++ 0003-1.5.x-Prevented-views.static.serve-from-using-large-.patch ++++++
From bf621e35f145412e509fddf73f6adaf5901f103f Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Tue, 9 Dec 2014 15:32:03 -0500
Subject: [PATCH 3/4] [1.5.x] Prevented views.static.serve() from using large
memory on large files. (bnc#913056, CVE-2015-0221)
This is a security fix.
Denial-of-service attack against ``django.views.static.serve()``.
Full description https://www.djangoproject.com/weblog/2015/jan/13/security/
Conflicts:
django/views/static.py
cherry-picked-from: 553779c4055e8742cc832ed525b9ee34b174934f
---
django/views/static.py | 7 ++++++-
tests/regressiontests/views/tests/static.py | 10 +++++++++-
tests/view_tests/media/long-line.txt | 1 +
3 files changed, 16 insertions(+), 2 deletions(-)
create mode 100644 tests/view_tests/media/long-line.txt
diff --git a/django/views/static.py b/django/views/static.py
index f61ba28..eae8cf1 100644
--- a/django/views/static.py
+++ b/django/views/static.py
@@ -20,6 +20,9 @@ from django.template import loader, Template, Context, TemplateDoesNotExist
from django.utils.http import http_date, parse_http_date
from django.utils.translation import ugettext as _, ugettext_noop
+STREAM_CHUNK_SIZE = 4096
+
+
def serve(request, path, document_root=None, show_indexes=False):
"""
Serve static files below a given point in the directory structure.
@@ -63,7 +66,9 @@ def serve(request, path, document_root=None, show_indexes=False):
if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
statobj.st_mtime, statobj.st_size):
return HttpResponseNotModified()
- response = CompatibleStreamingHttpResponse(open(fullpath, 'rb'), content_type=mimetype)
+ f = open(fullpath, 'rb')
+ response = CompatibleStreamingHttpResponse(iter(lambda: f.read(STREAM_CHUNK_SIZE), b''),
+ content_type=mimetype)
response["Last-Modified"] = http_date(statobj.st_mtime)
if stat.S_ISREG(statobj.st_mode):
response["Content-Length"] = statobj.st_size
diff --git a/tests/regressiontests/views/tests/static.py b/tests/regressiontests/views/tests/static.py
index 8b8ef8b..eb2a46f 100644
--- a/tests/regressiontests/views/tests/static.py
+++ b/tests/regressiontests/views/tests/static.py
@@ -9,7 +9,7 @@ from django.http import HttpResponseNotModified
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.http import http_date
-from django.views.static import was_modified_since
+from django.views.static import was_modified_since, STREAM_CHUNK_SIZE
from .. import urls
from ..urls import media_dir
@@ -34,6 +34,14 @@ class StaticTests(TestCase):
self.assertEqual(len(response_content), int(response['Content-Length']))
self.assertEqual(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None))
+ def test_chunked(self):
+ "The static view should stream files in chunks to avoid large memory usage"
+ response = self.client.get('/views/%s/%s' % (self.prefix, 'long-line.txt'))
+ first_chunk = next(response.streaming_content)
+ self.assertEqual(len(first_chunk), STREAM_CHUNK_SIZE)
+ second_chunk = next(response.streaming_content)
+ self.assertEqual(len(second_chunk), 1451)
+
def test_unknown_mime_type(self):
response = self.client.get('/views/%s/file.unknown' % self.prefix)
response.close()
diff --git a/tests/view_tests/media/long-line.txt b/tests/view_tests/media/long-line.txt
new file mode 100644
index 0000000..b4e1948
--- /dev/null
+++ b/tests/view_tests/media/long-line.txt
@@ -0,0 +1 @@
+lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua hic tempora est veritatis culpa fugiat doloribus fugit in sed harum veniam porro eveniet maxime labore assumenda non illum possimus aut vero laudantium cum magni numquam dolorem explicabo quidem quasi nesciunt ipsum deleniti facilis neque similique nisi ad magnam accusamus quae provident dolor ab atque modi laboriosam fuga suscipit ea beatae ipsam consequatur saepe dolore nulla error quo iusto expedita nemo commodi aspernatur aliquam enim reiciendis rerum necessitatibus recusandae sint amet placeat temporibus autem iste deserunt esse dolores reprehenderit doloremque pariatur velit maiores repellat dignissimos asperiores aperiam alias a corporis id praesentium voluptatibus soluta voluptatem sit molestiae quas odio facere nostrum laborum incidunt eaque nihil ullam rem mollitia at cumque iure tenetur tempore totam repudiandae quisquam quod architecto officia vitae consectetur cupiditate molestias delectus voluptates earum et impedit quibusdam odit sequi perferendis eius perspiciatis eos quam quaerat officiis sunt ratione consequuntur quia quis obcaecati repellendus exercitationem vel minima libero blanditiis eligendi minus dicta voluptas excepturi nam eum inventore voluptatum ducimus sapiente dolorum itaque ipsa qui omnis debitis voluptate quos aliquid accusantium ex illo corrupti ut adipisci natus animi distinctio optio nobis unde similique excepturi vero culpa molestias fugit dolorum non amet iure inventore nihil suscipit explicabo veritatis officiis distinctio nesciunt saepe incidunt reprehenderit porro vitae cumque alias ut deleniti expedita ratione odio magnam eligendi a nostrum laborum minus esse sit libero quaerat qui id illo voluptates soluta neque odit dolore consectetur ducimus nulla est nisi impedit quia sapiente ullam temporibus ipsam repudiandae delectus fugiat blanditiis maxime voluptatibus aspernatur ea ipsum quisquam sunt eius ipsa accusantium enim corporis earum sed sequi dicta accusamus dignissimos illum pariatur quos aut reiciendis obcaecati perspiciatis consequuntur nam modi praesentium cum repellat possimus iste atque quidem architecto recusandae harum eaque sint quae optio voluptate quod quasi beatae magni necessitatibus facilis aperiam repellendus nemo aliquam et quibusdam debitis itaque cupiditate laboriosam unde tempora commodi laudantium in placeat ad vel maiores aliquid hic tempore provident quas officia adipisci rem corrupti iusto natus eum rerum at ex quam eveniet totam dolor assumenda error eos doloribus labore fuga facere deserunt ab dolores consequatur veniam animi exercitationem asperiores mollitia minima numquam voluptatem voluptatum nobis molestiae voluptas omnis velit quis quo tenetur perferendis autem dolorem doloremque sequi vitae laudantium magnam quae adipisci expedita doloribus minus perferendis vero animi at quos iure facere nihil veritatis consectetur similique porro tenetur nobis fugiat quo ducimus qui soluta maxime placeat error sunt ullam quaerat provident eos minima ab harum ratione inventore unde sint dolorum deserunt veniam laborum quasi suscipit facilis eveniet voluptatibus est ipsum sapiente omnis vel repellat perspiciatis illo voluptate aliquid magni alias modi odit ea a voluptatem reiciendis recusandae mollitia eius distinctio amet atque voluptates obcaecati deleniti eligendi commodi debitis dolore laboriosam nam illum pariatur earum exercitationem velit in quas explicabo fugit asperiores itaque quam sit dolorem beatae quod cumque necessitatibus tempora dolores hic aperiam ex tempore ut neque maiores ad dicta voluptatum eum officia assumenda reprehenderit nisi cum molestiae et iusto quidem consequuntur repellendus saepe corrupti numquam culpa rerum incidunt dolor impedit iste sed non praesentium ipsam consequatur eaque possimus quia quibusdam excepturi aspernatur voluptas quisquam autem molestias aliquam corporis delectus nostrum labore nesciunt blanditiis quis enim accusamus nulla architecto fuga natus ipsa repudiandae cupiditate temporibus aut libero optio id officiis esse dignissimos odio totam doloremque accusantium nemo rem repudiandae aliquam accusamus autem minima reiciendis debitis quis ut ducimus quas dolore ratione neque velit repellat natus est error ea nam consequuntur rerum excepturi aspernatur quaerat cumque voluptatibus rem quasi eos unde architecto animi sunt veritatis delectus nulla at iusto repellendus dolorum obcaecati commodi earum assumenda quisquam cum officiis modi ab tempora harum vitae voluptatem explicabo alias maxime nostrum iure consectetur incidunt laudantium distinctio deleniti iste facere fugit libero illo nobis expedita perferendis labore similique beatae sint dicta dignissimos sapiente dolor soluta perspiciatis aut ad illum facilis totam necessitatibus eveniet temporibus reprehenderit quidem fugiat magni dolorem doloribus quibusdam eligendi fuga quae recusandae eum amet dolores asperiores voluptas inventore officia sit vel id vero nihil optio nisi magnam deserunt odit corrupti adipisci aliquid odio enim pariatur cupiditate suscipit voluptatum corporis porro mollitia eaque quia non quod consequatur ipsa nesciunt itaque exercitationem molestias molestiae atque in numquam quo ipsam nemo ex tempore ipsum saepe esse sed veniam a voluptates placeat accusantium quos laboriosam voluptate provident hic sequi quam doloremque eius impedit omnis possimus laborum tenetur praesentium et minus ullam blanditiis culpa qui aperiam maiores quidem numquam nulla
--
1.8.1.4
++++++ 0004-1.5.x-Fixed-DoS-possibility-in-ModelMultipleChoiceFi.patch ++++++
From 8d6dfa690412465cf41b2524d84d727e121a8576 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 11 Dec 2014 08:31:03 -0500
Subject: [PATCH 4/4] [1.5.x] Fixed DoS possibility in
ModelMultipleChoiceField. (bnc#913055, CVE-2015-0222)
This is a security fix.
Database denial-of-service with ``ModelMultipleChoiceField``.
Full description https://www.djangoproject.com/weblog/2015/jan/13/security/
Thanks Keryn Knight for the report and initial patch.
cherry-picked-from: d7a06ee7e571b6dad07c0f5b519b1db02e2a476c
by aplanas
Conflicts:
django/forms/models.py
---
django/forms/models.py | 25 ++++++++++++++++++++++---
tests/modeltests/model_forms/tests.py | 21 +++++++++++++++++++++
2 files changed, 43 insertions(+), 3 deletions(-)
diff --git a/django/forms/models.py b/django/forms/models.py
index 1e7888a..f0a6dfc 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -1039,7 +1039,29 @@ class ModelMultipleChoiceField(ModelChoiceField):
return self.queryset.none()
if not isinstance(value, (list, tuple)):
raise ValidationError(self.error_messages['list'])
+ qs = self._check_values(value)
+ # Since this overrides the inherited ModelChoiceField.clean
+ # we run custom validators here
+ self.run_validators(value)
+ return qs
+
+ def _check_values(self, value):
+ """
+ Given a list of possible PK values, returns a QuerySet of the
+ corresponding objects. Raises a ValidationError if a given value is
+ invalid (not a valid PK, not in the queryset, etc.)
+ """
key = self.to_field_name or 'pk'
+ # deduplicate given values to avoid creating many querysets or
+ # requiring the database backend deduplicate efficiently.
+ try:
+ value = frozenset(value)
+ except TypeError:
+ # list of lists isn't hashable, for example
+ raise ValidationError(
+ self.error_messages['list'],
+ code='list',
+ )
for pk in value:
try:
self.queryset.filter(**{key: pk})
@@ -1050,9 +1072,6 @@ class ModelMultipleChoiceField(ModelChoiceField):
for val in value:
if force_text(val) not in pks:
raise ValidationError(self.error_messages['invalid_choice'] % val)
- # Since this overrides the inherited ModelChoiceField.clean
- # we run custom validators here
- self.run_validators(value)
return qs
def prepare_value(self, value):
diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py
index d8f2f76..424f971 100644
--- a/tests/modeltests/model_forms/tests.py
+++ b/tests/modeltests/model_forms/tests.py
@@ -1150,6 +1150,27 @@ class OldFormForXTests(TestCase):
</select></p>
<p><label for="id_age">Age:</label> <input type="text" name="age" value="65" id="id_age" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk))
+ def test_show_hidden_initial_changed_queries_efficiently(self):
+ class WriterForm(forms.Form):
+ persons = forms.ModelMultipleChoiceField(
+ show_hidden_initial=True, queryset=Writer.objects.all())
+
+ writers = (Writer.objects.create(name=str(x)) for x in range(0, 50))
+ writer_pks = tuple(x.pk for x in writers)
+ form = WriterForm(data={'initial-persons': writer_pks})
+ with self.assertNumQueries(1):
+ self.assertTrue(form.has_changed())
+
+ def test_clean_does_deduplicate_values(self):
+ class WriterForm(forms.Form):
+ persons = forms.ModelMultipleChoiceField(queryset=Writer.objects.all())
+
+ person1 = Writer.objects.create(name="Person 1")
+ form = WriterForm(data={})
+ queryset = form.fields['persons'].clean([str(person1.pk)] * 50)
+ sql, params = queryset.query.sql_with_params()
+ self.assertEqual(len(params), 1)
+
def test_file_field(self):
# Test conditions when files is either not given or empty.
--
1.8.1.4
++++++ 0005-1.6.x-Method-check_for_test_cookie-is-deprecated.patch ++++++
Index: Django-1.5.12/django/contrib/auth/forms.py
===================================================================
--- Django-1.5.12.orig/django/contrib/auth/forms.py
+++ Django-1.5.12/django/contrib/auth/forms.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
+import warnings
+
from django import forms
from django.forms.util import flatatt
from django.template import loader
@@ -150,8 +152,6 @@ class AuthenticationForm(forms.Form):
error_messages = {
'invalid_login': _("Please enter a correct %(username)s and password. "
"Note that both fields may be case-sensitive."),
- 'no_cookies': _("Your Web browser doesn't appear to have cookies "
- "enabled. Cookies are required for logging in."),
'inactive': _("This account is inactive."),
}
@@ -186,12 +186,11 @@ class AuthenticationForm(forms.Form):
})
elif not self.user_cache.is_active:
raise forms.ValidationError(self.error_messages['inactive'])
- self.check_for_test_cookie()
return self.cleaned_data
def check_for_test_cookie(self):
- if self.request and not self.request.session.test_cookie_worked():
- raise forms.ValidationError(self.error_messages['no_cookies'])
+ warnings.warn("check_for_test_cookie is deprecated; ensure your login "
+ "view is CSRF-protected.", DeprecationWarning)
def get_user_id(self):
if self.user_cache:
++++++ 0006-1.5.x-Made-is_safe_url-reject-URLs-that-start-with-c.patch ++++++
From b35f28462f7ae376714fad5187bae894aafb606a Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 9 Mar 2015 20:05:13 -0400
Subject: [PATCH 6/6] [1.5.x] Made is_safe_url() reject URLs that start with
control characters. (bnc#923176)
https://bugzilla.suse.com/show_bug.cgi?id=923176 CVE-2015-2317
This is a security fix; disclosure to follow shortly.
Mitigated possible XSS attack via user-supplied redirect URLs
Django relies on user input in some cases (e.g. django.contrib.auth.views.login and i18n) to redirect the user to an "on success" URL. The security checks for these redirects (namely django.utils.http.is_safe_url()) accepted URLs with leading control characters and so considered URLs like \x08javascript:... safe. This issue doesn't affect Django currently, since we only put this URL into the Location response header and browsers seem to ignore JavaScript there. Browsers we tested also treat URLs prefixed with control characters such as %08//example.com as relative paths so redirection to an unsafe target isn't a problem either.
However, if a developer relies on is_safe_url() to provide safe redirect targets and puts such a URL into a link, they could suffer from an XSS attack as some browsers such as Google Chrome ignore control characters at the start of a URL in an anchor href.
Thanks Daniel Chatfield for reporting the issue.
Conflicts:
django/utils/http.py
docs/releases/1.4.20.txt
docs/releases/1.6.11.txt
---
django/utils/http.py | 14 ++++++++++----
tests/regressiontests/utils/http.py | 4 +++-
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/django/utils/http.py b/django/utils/http.py
index f690a79..c0ce299 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -4,6 +4,7 @@ import calendar
import datetime
import re
import sys
+import unicodedata
try:
from urllib import parse as urllib_parse
except ImportError: # Python 2
@@ -11,7 +12,6 @@ except ImportError: # Python 2
import urlparse
urllib_parse.urlparse = urlparse.urlparse
-
from email.utils import formatdate
from django.utils.datastructures import MultiValueDict
@@ -235,9 +235,10 @@ def is_safe_url(url, host=None):
Always returns ``False`` on an empty url.
"""
+ if url is not None:
+ url = url.strip()
if not url:
return False
- url = url.strip()
# Chrome treats \ completely as /
url = url.replace('\\', '/')
# Chrome considers any URL with more than two slashes to be absolute, but
@@ -251,5 +252,10 @@ def is_safe_url(url, host=None):
# allow this syntax.
if not url_info.netloc and url_info.scheme:
return False
- return (not url_info.netloc or url_info.netloc == host) and \
- (not url_info.scheme or url_info.scheme in ['http', 'https'])
+ # Forbid URLs that start with control characters. Some browsers (like
+ # Chrome) ignore quite a few control characters at the start of a
+ # URL and might consider the URL as scheme relative.
+ if unicodedata.category(url[0])[0] == 'C':
+ return False
+ return ((not url_info.netloc or url_info.netloc == host) and
+ (not url_info.scheme or url_info.scheme in ['http', 'https']))
diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py
index 88bcfb8..4b62c6c 100644
--- a/tests/regressiontests/utils/http.py
+++ b/tests/regressiontests/utils/http.py
@@ -110,7 +110,9 @@ class TestUtilsHttp(unittest.TestCase):
'http:\/example.com',
'http:/\example.com',
'javascript:alert("XSS")',
- '\njavascript:alert(x)'):
+ '\njavascript:alert(x)',
+ '\x08//example.com',
+ '\n'):
self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url)
for good_url in ('/view/?param=http://example.com',
'/view/?param=https://example.com',
--
2.1.4
++++++ 0007-1.6.x-Fixed-DoS-possiblity-in-contrib.auth.views.log.patch ++++++
From 128178b61c7f5d7efd861b93a460a259558e0132 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 5 Aug 2015 17:44:48 -0400
Subject: [PATCH 7/7] [1.6.x] Fixed DoS possiblity in
contrib.auth.views.logout()
Refs #20936 -- When logging out/ending a session, don't create a new, empty session.
Previously, when logging out, the existing session was overwritten by a
new sessionid instead of deleting the session altogether.
This behavior added overhead by creating a new session record in
whichever backend was in use: db, cache, etc.
This extra session is unnecessary at the time since no session data is
meant to be preserved when explicitly logging out.
Backport of 393c0e24223c701edeb8ce7dc9d0f852f0c081ad,
088579638b160f3716dc81d194be70c72743593f, and
2dee853ed4def42b7ef1b3b472b395055543cc00 from master
Thanks Florian Apolloner and Carl Meyer for review.
This is a security fix.
---
django/contrib/sessions/backends/base.py | 9 +++-
django/contrib/sessions/backends/cached_db.py | 2 +-
django/contrib/sessions/middleware.py | 50 +++++++++++--------
django/contrib/sessions/tests.py | 70 +++++++++++++++++++++++++++
docs/topics/http/sessions.txt | 14 ++++--
5 files changed, 118 insertions(+), 27 deletions(-)
diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py
index 44b06d6..e99f11c 100644
--- a/django/contrib/sessions/backends/base.py
+++ b/django/contrib/sessions/backends/base.py
@@ -134,6 +134,13 @@ class SessionBase(object):
self.accessed = True
self.modified = True
+ def is_empty(self):
+ "Returns True when there is no session_key and the session is empty"
+ try:
+ return not bool(self._session_key) and not self._session_cache
+ except AttributeError:
+ return True
+
def _get_new_session_key(self):
"Returns session key that isn't being used."
while True:
@@ -260,7 +267,7 @@ class SessionBase(object):
"""
self.clear()
self.delete()
- self.create()
+ self._session_key = None
def cycle_key(self):
"""
diff --git a/django/contrib/sessions/backends/cached_db.py b/django/contrib/sessions/backends/cached_db.py
index 31c6fbf..91485ae 100644
--- a/django/contrib/sessions/backends/cached_db.py
+++ b/django/contrib/sessions/backends/cached_db.py
@@ -70,7 +70,7 @@ class SessionStore(DBStore):
"""
self.clear()
self.delete(self.session_key)
- self.create()
+ self._session_key = None
# At bottom to avoid circular import
diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py
index 9f65255..94a82df 100644
--- a/django/contrib/sessions/middleware.py
+++ b/django/contrib/sessions/middleware.py
@@ -14,32 +14,40 @@ class SessionMiddleware(object):
def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
- session every time, save the changes and set a session cookie.
+ session every time, save the changes and set a session cookie or delete
+ the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
+ empty = request.session.is_empty()
except AttributeError:
pass
else:
- if accessed:
- patch_vary_headers(response, ('Cookie',))
- if modified or settings.SESSION_SAVE_EVERY_REQUEST:
- if request.session.get_expire_at_browser_close():
- max_age = None
- expires = None
- else:
- max_age = request.session.get_expiry_age()
- expires_time = time.time() + max_age
- expires = cookie_date(expires_time)
- # Save the session data and refresh the client cookie.
- # Skip session save for 500 responses, refs #3881.
- if response.status_code != 500:
- request.session.save()
- response.set_cookie(settings.SESSION_COOKIE_NAME,
- request.session.session_key, max_age=max_age,
- expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
- path=settings.SESSION_COOKIE_PATH,
- secure=settings.SESSION_COOKIE_SECURE or None,
- httponly=settings.SESSION_COOKIE_HTTPONLY or None)
+ # First check if we need to delete this cookie.
+ # The session should be deleted only if the session is entirely empty
+ if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
+ response.delete_cookie(settings.SESSION_COOKIE_NAME,
+ domain=settings.SESSION_COOKIE_DOMAIN)
+ else:
+ if accessed:
+ patch_vary_headers(response, ('Cookie',))
+ if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
+ if request.session.get_expire_at_browser_close():
+ max_age = None
+ expires = None
+ else:
+ max_age = request.session.get_expiry_age()
+ expires_time = time.time() + max_age
+ expires = cookie_date(expires_time)
+ # Save the session data and refresh the client cookie.
+ # Skip session save for 500 responses, refs #3881.
+ if response.status_code != 500:
+ request.session.save()
+ response.set_cookie(settings.SESSION_COOKIE_NAME,
+ request.session.session_key, max_age=max_age,
+ expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
+ path=settings.SESSION_COOKIE_PATH,
+ secure=settings.SESSION_COOKIE_SECURE or None,
+ httponly=settings.SESSION_COOKIE_HTTPONLY or None)
return response
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index 829eb82..1dc92cb 100644
--- a/django/contrib/sessions/tests.py
+++ b/django/contrib/sessions/tests.py
@@ -154,6 +154,7 @@ class SessionTestsMixin(object):
self.session.flush()
self.assertFalse(self.session.exists(prev_key))
self.assertNotEqual(self.session.session_key, prev_key)
+ self.assertIsNone(self.session.session_key)
self.assertTrue(self.session.modified)
self.assertTrue(self.session.accessed)
@@ -547,6 +548,75 @@ class SessionMiddlewareTests(unittest.TestCase):
# Check that the value wasn't saved above.
self.assertNotIn('hello', request.session.load())
+ def test_session_delete_on_end(self):
+ request = RequestFactory().get('/')
+ response = HttpResponse('Session test')
+ middleware = SessionMiddleware()
+
+ # Before deleting, there has to be an existing cookie
+ request.COOKIES[settings.SESSION_COOKIE_NAME] = 'abc'
+
+ # Simulate a request that ends the session
+ middleware.process_request(request)
+ request.session.flush()
+
+ # Handle the response through the middleware
+ response = middleware.process_response(request, response)
+
+ # Check that the cookie was deleted, not recreated.
+ # A deleted cookie header looks like:
+ # Set-Cookie: sessionid=; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
+ self.assertEqual(
+ 'Set-Cookie: {0}=; expires=Thu, 01-Jan-1970 00:00:00 GMT; '
+ 'Max-Age=0; Path=/'.format(settings.SESSION_COOKIE_NAME),
+ str(response.cookies[settings.SESSION_COOKIE_NAME])
+ )
+
+ @override_settings(SESSION_COOKIE_DOMAIN='.example.local')
+ def test_session_delete_on_end_with_custom_domain(self):
+ request = RequestFactory().get('/')
+ response = HttpResponse('Session test')
+ middleware = SessionMiddleware()
+
+ # Before deleting, there has to be an existing cookie
+ request.COOKIES[settings.SESSION_COOKIE_NAME] = 'abc'
+
+ # Simulate a request that ends the session
+ middleware.process_request(request)
+ request.session.flush()
+
+ # Handle the response through the middleware
+ response = middleware.process_response(request, response)
+
+ # Check that the cookie was deleted, not recreated.
+ # A deleted cookie header with a custom domain looks like:
+ # Set-Cookie: sessionid=; Domain=.example.local;
+ # expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
+ self.assertEqual(
+ 'Set-Cookie: {}=; Domain=.example.local; expires=Thu, '
+ '01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/'.format(
+ settings.SESSION_COOKIE_NAME,
+ ),
+ str(response.cookies[settings.SESSION_COOKIE_NAME])
+ )
+
+ def test_flush_empty_without_session_cookie_doesnt_set_cookie(self):
+ request = RequestFactory().get('/')
+ response = HttpResponse('Session test')
+ middleware = SessionMiddleware()
+
+ # Simulate a request that ends the session
+ middleware.process_request(request)
+ request.session.flush()
+
+ # Handle the response through the middleware
+ response = middleware.process_response(request, response)
+
+ # A cookie should not be set.
+ self.assertEqual(response.cookies, {})
+ # The session is accessed so "Vary: Cookie" should be set.
+ self.assertEqual(response['Vary'], 'Cookie')
+
class CookieSessionTests(SessionTestsMixin, TestCase):
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
index 71f6247..a1cf8d3 100644
--- a/docs/topics/http/sessions.txt
+++ b/docs/topics/http/sessions.txt
@@ -222,12 +222,18 @@ You can edit it multiple times.
.. method:: flush()
- Delete the current session data from the session and regenerate the
- session key value that is sent back to the user in the cookie. This is
- used if you want to ensure that the previous session data can't be
- accessed again from the user's browser (for example, the
+ Deletes the current session data from the session and deletes the session
+ cookie. This is used if you want to ensure that the previous session data
+ can't be accessed again from the user's browser (for example, the
:func:`django.contrib.auth.logout()` function calls it).
+ .. versionchanged:: 1.7.10
+
+ Deletion of the session cookie was added. Previously, the behavior
+ was to regenerate the session key value that was sent back to the
+ user in the cookie, but this could be a denial-of-service
+ vulnerability.
+
.. method:: set_test_cookie()
Sets a test cookie to determine whether the user's browser supports
--
2.1.4
++++++ 0008-1.5.x-Fixed-19324-Avoided-creating-a-session-record-.patch ++++++
From 49b743094cfe7dd6925e1a8e57e8cf5e9918b3ec Mon Sep 17 00:00:00 2001
From: Carl Meyer
Date: Wed, 10 Jun 2015 15:45:20 -0600
Subject: [PATCH 8/9] [1.5.x] Fixed #19324 -- Avoided creating a session record
when loading the session. (bnc#937522, CVE-2015-5143)
The session record is now only created if/when the session is modified. This
prevents a potential DoS via creation of many empty session records.
Denial-of-service possibility by filling session store
In previous versions of Django, the session backends created a new empty record in the session storage anytime request.session was accessed and there was a session key provided in the request cookies that didn't already have a session record. This could allow an attacker to easily create many new session records simply by sending repeated requests with unknown session keys, potentially filling up the session store or causing other users' session records to be evicted.
The built-in session backends now create a session record only if the session is actually modified; empty session records are not created. Thus this potential DoS is now only possible if the site chooses to expose a session-modifying view to anonymous users.
As each built-in session backend was fixed separately (rather than a fix in the core sessions framework), maintainers of third-party session backends should check whether the same vulnerability is present in their backend and correct it if so.
Thanks Eric Peterson and Lin Hua Cheng for reporting the issue.
This is a security fix
(cherry picked from commit 1828f4341ec53a8684112d24031b767eba557663)
---
django/contrib/sessions/backends/cache.py | 6 ++++--
django/contrib/sessions/backends/cached_db.py | 6 +++---
django/contrib/sessions/backends/db.py | 5 +++--
django/contrib/sessions/backends/file.py | 5 +++--
django/contrib/sessions/tests.py | 20 ++++++++++++++++++++
5 files changed, 33 insertions(+), 9 deletions(-)
diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py
index 26ae6c4..09eae30 100644
--- a/django/contrib/sessions/backends/cache.py
+++ b/django/contrib/sessions/backends/cache.py
@@ -27,7 +27,7 @@ class SessionStore(SessionBase):
session_data = None
if session_data is not None:
return session_data
- self.create()
+ self._session_key = None
return {}
def create(self):
@@ -47,6 +47,8 @@ class SessionStore(SessionBase):
raise RuntimeError("Unable to create a new session key.")
def save(self, must_create=False):
+ if self.session_key is None:
+ return self.create()
if must_create:
func = self._cache.add
else:
@@ -58,7 +60,7 @@ class SessionStore(SessionBase):
raise CreateError
def exists(self, session_key):
- return (KEY_PREFIX + session_key) in self._cache
+ return session_key and (KEY_PREFIX + session_key) in self._cache
def delete(self, session_key=None):
if session_key is None:
diff --git a/django/contrib/sessions/backends/cached_db.py b/django/contrib/sessions/backends/cached_db.py
index 91485ae..4b98a01 100644
--- a/django/contrib/sessions/backends/cached_db.py
+++ b/django/contrib/sessions/backends/cached_db.py
@@ -40,14 +40,14 @@ class SessionStore(DBStore):
)
data = self.decode(s.session_data)
cache.set(self.cache_key, data,
- self.get_expiry_age(expiry=s.expire_date))
+ self.get_expiry_age(expiry=s.expire_date))
except (Session.DoesNotExist, SuspiciousOperation):
- self.create()
+ self._session_key = None
data = {}
return data
def exists(self, session_key):
- if (KEY_PREFIX + session_key) in cache:
+ if session_key and (KEY_PREFIX + session_key) in cache:
return True
return super(SessionStore, self).exists(session_key)
diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py
index 47e89b6..fb89c31 100644
--- a/django/contrib/sessions/backends/db.py
+++ b/django/contrib/sessions/backends/db.py
@@ -19,7 +19,7 @@ class SessionStore(SessionBase):
)
return self.decode(s.session_data)
except (Session.DoesNotExist, SuspiciousOperation):
- self.create()
+ self._session_key = None
return {}
def exists(self, session_key):
@@ -36,7 +36,6 @@ class SessionStore(SessionBase):
# Key wasn't unique. Try again.
continue
self.modified = True
- self._session_cache = {}
return
def save(self, must_create=False):
@@ -46,6 +45,8 @@ class SessionStore(SessionBase):
create a *new* entry (as opposed to possibly updating an existing
entry).
"""
+ if self.session_key is None:
+ return self.create()
obj = Session(
session_key=self._get_or_create_session_key(),
session_data=self.encode(self._get_session(no_load=must_create)),
diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py
index 7d933c6..83a0456 100644
--- a/django/contrib/sessions/backends/file.py
+++ b/django/contrib/sessions/backends/file.py
@@ -86,7 +86,7 @@ class SessionStore(SessionBase):
self.delete()
self.create()
except IOError:
- self.create()
+ self._session_key = None
return session_data
def create(self):
@@ -97,10 +97,11 @@ class SessionStore(SessionBase):
except CreateError:
continue
self.modified = True
- self._session_cache = {}
return
def save(self, must_create=False):
+ if self.session_key is None:
+ return self.create()
# Get the session data now, before we start messing
# with the file it is stored within.
session_data = self._get_session(no_load=must_create)
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index 1dc92cb..230777b 100644
--- a/django/contrib/sessions/tests.py
+++ b/django/contrib/sessions/tests.py
@@ -167,6 +167,11 @@ class SessionTestsMixin(object):
self.assertNotEqual(self.session.session_key, prev_key)
self.assertEqual(list(self.session.items()), prev_data)
+ def test_save_doesnt_clear_data(self):
+ self.session['a'] = 'b'
+ self.session.save()
+ self.assertEqual(self.session['a'], 'b')
+
def test_invalid_key(self):
# Submitting an invalid session key (either by guessing, or if the db has
# removed the key) results in a new key being generated.
@@ -294,6 +299,21 @@ class SessionTestsMixin(object):
self.session.delete(old_session_key)
self.session.delete(new_session_key)
+ def test_session_load_does_not_create_record(self):
+ """
+ Loading an unknown session key does not create a session record.
+
+ Creating session records on load is a DOS vulnerability.
+ """
+ if self.backend is CookieSession:
+ raise unittest.SkipTest("Cookie backend doesn't have an external store to create records in.")
+ session = self.backend('someunknownkey')
+ session.load()
+
+ self.assertFalse(session.exists(session.session_key))
+ # provided unknown key was cycled, not reused
+ self.assertNotEqual(session.session_key, 'someunknownkey')
+
class DatabaseSessionTests(SessionTestsMixin, TestCase):
--
2.1.4
++++++ 0009-1.5.x-Prevented-newlines-from-being-accepted-in-some.patch ++++++
From 38960f1d924c3dcc3b2d38ea591cc7019568fc96 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 12 Jun 2015 13:49:31 -0400
Subject: [PATCH 9/9] [1.5.x] Prevented newlines from being accepted in some
validators. (bnc#937523, CVE-2015-5144)
Header injection possibility since validators accept newlines in input
Some of Django's built-in validators (django.core.validators.EmailValidator, most seriously) didn't prohibit newline characters (due to the usage of $ instead of \Z in the regular expressions). If you use values with newlines in HTTP response or email headers, you can suffer from header injection attacks. Django itself isn't vulnerable because django.http.HttpResponse and the mail sending utilities in django.core.mail prohibit newlines in HTTP and SMTP headers, respectively. While the validators have been fixed in Django, if you're creating HTTP responses or email messages in other ways, it's a good idea to ensure that those methods prohibit newlines as well. You might also want to validate that any existing data in your application doesn't contain unexpected newlines.
django.core.validators.validate_ipv4_address(), django.core.validators.validate_slug(), and django.core.validators.URLValidator are also affected, however, as of Django 1.6 the GenericIPAddresseField, IPAddressField, SlugField, and URLField form fields which use these validators all strip the input, so the possibility of newlines entering your data only exists if you are using these validators outside of the form fields.
The undocumented, internally unused validate_integer() function is now stricter as it validates using a regular expression instead of simply casting the value using int() and checking if an exception was raised.
This is a security fix
Thanks to Sjoerd Job Postmus for the report and draft patch.
Conflicts:
django/core/validators.py
docs/releases/1.4.21.txt
---
django/core/validators.py | 25 +++++++++++++++----------
tests/modeltests/validators/tests.py | 16 +++++++++++++++-
2 files changed, 30 insertions(+), 11 deletions(-)
diff --git a/django/core/validators.py b/django/core/validators.py
index 251b5d8..ff9dfdb 100644
--- a/django/core/validators.py
+++ b/django/core/validators.py
@@ -49,7 +49,7 @@ class URLValidator(RegexValidator):
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
r'(?::\d+)?' # optional port
- r'(?:/?|[/?]\S+)$', re.IGNORECASE)
+ r'(?:/?|[/?]\S+)\Z', re.IGNORECASE)
def __call__(self, value):
try:
@@ -71,11 +71,16 @@ class URLValidator(RegexValidator):
url = value
+integer_validator = RegexValidator(
+ re.compile('^-?\d+\Z'),
+ message=_('Enter a valid integer.'),
+ code='invalid',
+)
+
+
def validate_integer(value):
- try:
- int(value)
- except (ValueError, TypeError):
- raise ValidationError('')
+ return integer_validator(value)
+
class EmailValidator(RegexValidator):
@@ -99,14 +104,14 @@ email_re = re.compile(
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom
# quoted-string, see also http://tools.ietf.org/html/rfc2822#section-3.2.5
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"'
- r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)$)' # domain
- r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3)
+ r')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)\Z)' # domain
+ r'|\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z', re.IGNORECASE) # literal form, ipv4 address (SMTP 4.1.3)
validate_email = EmailValidator(email_re, _('Enter a valid email address.'), 'invalid')
-slug_re = re.compile(r'^[-a-zA-Z0-9_]+$')
+slug_re = re.compile(r'^[-a-zA-Z0-9_]+\Z')
validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
-ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
+ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z')
validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid')
@@ -147,7 +152,7 @@ def ip_address_validators(protocol, unpack_ipv4):
raise ValueError("The protocol '%s' is unknown. Supported: %s"
% (protocol, list(ip_address_validator_map)))
-comma_separated_int_list_re = re.compile('^[\d,]+$')
+comma_separated_int_list_re = re.compile('^[\d,]+\Z')
validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid')
diff --git a/tests/modeltests/validators/tests.py b/tests/modeltests/validators/tests.py
index 0174a60..a417515 100644
--- a/tests/modeltests/validators/tests.py
+++ b/tests/modeltests/validators/tests.py
@@ -15,13 +15,16 @@ NOW = datetime.now()
TEST_DATA = (
# (validator, value, expected),
+ # (validator, value, expected),
(validate_integer, '42', None),
(validate_integer, '-42', None),
(validate_integer, -42, None),
- (validate_integer, -42.5, None),
+ (validate_integer, -42.5, ValidationError),
(validate_integer, None, ValidationError),
(validate_integer, 'a', ValidationError),
+ (validate_integer, '\n42', ValidationError),
+ (validate_integer, '42\n', ValidationError),
(validate_email, 'email@here.com', None),
(validate_email, 'weirder-email@here.and.there.com', None),
@@ -46,6 +49,11 @@ TEST_DATA = (
# Quoted-string format (CR not allowed)
(validate_email, '"\\\011"@here.com', None),
(validate_email, '"\\\012"@here.com', ValidationError),
+ # Trailing newlines in username or domain not allowed
+ (validate_email, 'a@b.com\n', ValidationError),
+ (validate_email, 'a\n@b.com', ValidationError),
+ (validate_email, '"test@test"\n@example.com', ValidationError),
+ (validate_email, 'a@[127.0.0.1]\n', ValidationError),
(validate_slug, 'slug-ok', None),
(validate_slug, 'longer-slug-still-ok', None),
@@ -58,6 +66,7 @@ TEST_DATA = (
(validate_slug, 'some@mail.com', ValidationError),
(validate_slug, '你好', ValidationError),
(validate_slug, '\n', ValidationError),
+ (validate_slug, 'trailing-newline\n', ValidationError),
(validate_ipv4_address, '1.1.1.1', None),
(validate_ipv4_address, '255.0.0.0', None),
@@ -67,6 +76,7 @@ TEST_DATA = (
(validate_ipv4_address, '25.1.1.', ValidationError),
(validate_ipv4_address, '25,1,1,1', ValidationError),
(validate_ipv4_address, '25.1 .1.1', ValidationError),
+ (validate_ipv4_address, '1.1.1.1\n', ValidationError),
# validate_ipv6_address uses django.utils.ipv6, which
# is tested in much greater detail in it's own testcase
@@ -100,6 +110,7 @@ TEST_DATA = (
(validate_comma_separated_integer_list, '', ValidationError),
(validate_comma_separated_integer_list, 'a,b,c', ValidationError),
(validate_comma_separated_integer_list, '1, 2, 3', ValidationError),
+ (validate_comma_separated_integer_list, '1,2,3\n', ValidationError),
(MaxValueValidator(10), 10, None),
(MaxValueValidator(10), -10, None),
@@ -151,6 +162,9 @@ TEST_DATA = (
(URLValidator(), 'http://-invalid.com', ValidationError),
(URLValidator(), 'http://inv-.alid-.com', ValidationError),
(URLValidator(), 'http://inv-.-alid.com', ValidationError),
+ # Trailing newlines not accepted
+ (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError),
+ (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError),
(BaseValidator(True), True, None),
(BaseValidator(True), False, ValidationError),
--
2.1.4
++++++ 0010-1.5.x-Fixed-a-settings-leak-possibility-in-the-date-.patch ++++++
From 9c2fa72cb5da9a44f04dce3eeb9c5408b6d9d895 Mon Sep 17 00:00:00 2001
From: "Bernhard M. Wiedemann"
Date: Wed, 18 Nov 2015 10:04:24 +0100
Subject: [PATCH 10/10] [1.5.x] Fixed a settings leak possibility in the date
template filter.
This is a security fix.
bnc#955412
CVE-2015-8213: Settings leak possibility in ``date`` template filter
====================================================================
If an application allows users to specify an unvalidated format for
dates and passes this format to the ``date`` filter, e.g.
``{{ last_updated|date:user_date_format }}``, then a malicious user
could obtain any secret in the application's settings by specifying a
settings key instead of a date format. e.g. ``"SECRET_KEY"`` instead
of ``"j/m/Y"``.
To remedy this, the underlying function used by the ``date`` template
filter, ``django.utils.formats.get_format()``, now only allows
accessing the date/time formatting settings.
---
django/utils/formats.py | 21 +++++++++++++++++++++
tests/regressiontests/i18n/tests.py | 4 ++++
2 files changed, 25 insertions(+)
diff --git a/django/utils/formats.py b/django/utils/formats.py
index 03b9918..4c04473 100644
--- a/django/utils/formats.py
+++ b/django/utils/formats.py
@@ -27,6 +27,25 @@ ISO_INPUT_FORMATS = {
),
}
+
+FORMAT_SETTINGS = frozenset([
+ 'DECIMAL_SEPARATOR',
+ 'THOUSAND_SEPARATOR',
+ 'NUMBER_GROUPING',
+ 'FIRST_DAY_OF_WEEK',
+ 'MONTH_DAY_FORMAT',
+ 'TIME_FORMAT',
+ 'DATE_FORMAT',
+ 'DATETIME_FORMAT',
+ 'SHORT_DATE_FORMAT',
+ 'SHORT_DATETIME_FORMAT',
+ 'YEAR_MONTH_FORMAT',
+ 'DATE_INPUT_FORMATS',
+ 'TIME_INPUT_FORMATS',
+ 'DATETIME_INPUT_FORMATS',
+])
+
+
def reset_format_cache():
"""Clear any cached formats.
@@ -78,6 +97,8 @@ def get_format(format_type, lang=None, use_l10n=None):
be localized (or not), overriding the value of settings.USE_L10N.
"""
format_type = force_str(format_type)
+ if format_type not in FORMAT_SETTINGS:
+ return format_type
if use_l10n or (use_l10n is None and settings.USE_L10N):
if lang is None:
lang = get_language()
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index d8af6f1..b974b08 100644
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -693,6 +693,10 @@ class FormattingTests(TestCase):
self.assertEqual(template2.render(context), output2)
self.assertEqual(template3.render(context), output3)
+ def test_format_arbitrary_settings(self):
+ self.assertEqual(get_format('DEBUG'), 'DEBUG')
+
+
class MiscTests(TestCase):
def setUp(self):
--
2.6.2
++++++ Django-1.2-completion-only-for-bash.patch ++++++
Index: extras/django_bash_completion
===================================================================
--- extras/django_bash_completion.orig
+++ extras/django_bash_completion
@@ -31,6 +31,8 @@
#
# To uninstall, just remove the line from your .bash_profile and .bashrc.
+test -z "$BASH_VERSION" && return
+
_django_completion()
{
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \
++++++ Django-1.4-CSRF_COOKIE_HTTPONLY-support.patch ++++++
Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py.orig
+++ django/conf/global_settings.py
@@ -535,6 +535,7 @@ CSRF_COOKIE_NAME = 'csrftoken'
CSRF_COOKIE_DOMAIN = None
CSRF_COOKIE_PATH = '/'
CSRF_COOKIE_SECURE = False
+CSRF_COOKIE_HTTPONLY = False
############
# MESSAGES #
Index: django/middleware/csrf.py
===================================================================
--- django/middleware/csrf.py.orig
+++ django/middleware/csrf.py
@@ -208,7 +208,8 @@ class CsrfViewMiddleware(object):
max_age = 60 * 60 * 24 * 7 * 52,
domain=settings.CSRF_COOKIE_DOMAIN,
path=settings.CSRF_COOKIE_PATH,
- secure=settings.CSRF_COOKIE_SECURE
+ secure=settings.CSRF_COOKIE_SECURE,
+ httponly=settings.CSRF_COOKIE_HTTPONLY
)
# Content varies with the CSRF cookie, so set the Vary header.
patch_vary_headers(response, ('Cookie',))
Index: docs/ref/contrib/csrf.txt
===================================================================
--- docs/ref/contrib/csrf.txt.orig
+++ docs/ref/contrib/csrf.txt
@@ -543,6 +543,17 @@ Whether to use a secure cookie for the C
the cookie will be marked as "secure," which means browsers may ensure that the
cookie is only sent under an HTTPS connection.
+CSRF_COOKIE_HTTPONLY
+------------------
+
+.. versionadded:: 1.5
+
+Default: ``False``
+
+Whether to use HttpOnly flag on the CSRF cookie. If this is set to
+``True``, client-side JavaScript will not to be able to access the
+session cookie.
+
CSRF_FAILURE_VIEW
-----------------
Index: docs/ref/settings.txt
===================================================================
--- docs/ref/settings.txt.orig
+++ docs/ref/settings.txt
@@ -362,6 +362,19 @@ Whether to use a secure cookie for the C
the cookie will be marked as "secure," which means browsers may ensure that the
cookie is only sent under an HTTPS connection.
+.. setting:: CSRF_COOKIE_HTTPONLY
+
+CSRF_COOKIE_HTTPONLY
+------------------
+
+.. versionadded:: 1.5
+
+Default: ``False``
+
+Whether to use HttpOnly flag on the CSRF cookie. If this is set to
+``True``, client-side JavaScript will not to be able to access the
+session cookie. See :setting:`SESSION_COOKIE_HTTPONLY`.
+
.. setting:: CSRF_FAILURE_VIEW
CSRF_FAILURE_VIEW
Index: tests/regressiontests/csrf_tests/tests.py
===================================================================
--- tests/regressiontests/csrf_tests/tests.py.orig
+++ tests/regressiontests/csrf_tests/tests.py
@@ -101,7 +101,8 @@ class CsrfViewMiddlewareTest(TestCase):
with self.settings(CSRF_COOKIE_NAME='myname',
CSRF_COOKIE_DOMAIN='.example.com',
CSRF_COOKIE_PATH='/test/',
- CSRF_COOKIE_SECURE=True):
+ CSRF_COOKIE_SECURE=True,
+ CSRF_COOKIE_HTTPONLY=True):
# token_view calls get_token() indirectly
CsrfViewMiddleware().process_view(req, token_view, (), {})
resp = token_view(req)
@@ -110,6 +111,7 @@ class CsrfViewMiddlewareTest(TestCase):
self.assertNotEqual(csrf_cookie, False)
self.assertEqual(csrf_cookie['domain'], '.example.com')
self.assertEqual(csrf_cookie['secure'], True)
+ self.assertEqual(csrf_cookie['httponly'], True)
self.assertEqual(csrf_cookie['path'], '/test/')
self.assertTrue('Cookie' in resp2.get('Vary',''))
++++++ python-django-rpmlintrc ++++++
addFilter("file-not-in-%lang")
addFilter("zero-length")