[Python-modules-commits] [python-django] 01/02: releasing package python-django version 1.4.5-1+deb7u8
Luke Faraone
lfaraone at moszumanska.debian.org
Wed Jan 28 20:40:39 UTC 2015
This is an automated email from the git hooks/post-receive script.
lfaraone pushed a commit to branch debian/wheezy-lfaraone
in repository python-django.
commit 25ea0f8f8aee523866f57a894cb969f53d2e35e7
Author: Luke Faraone <lfaraone at dropbox.com>
Date: Tue Aug 19 22:56:31 2014 -0700
releasing package python-django version 1.4.5-1+deb7u8
---
debian/changelog | 10 +
.../patches/02_disable-sources-in-sphinxdoc.diff | 2 +-
.../06_use_debian_geoip_database_as_default.diff | 8 +-
debian/patches/admin-data-leak-1.4.diff | 115 +++++
debian/patches/cache-csrf-1.4.x.patch | 14 +-
debian/patches/drop_fix_ie_for_vary_1_4.diff | 12 +-
debian/patches/file-upload-1.4.diff | 247 +++++++++++
debian/patches/is_safe_url-1.4.diff | 14 +-
debian/patches/is_safe_url_1_4.diff | 18 +-
debian/patches/mysql-typecast-1.4.x.diff | 30 +-
debian/patches/remote-user-1.4.diff | 88 ++++
debian/patches/reverse-1.4.diff | 41 ++
debian/patches/reverse-execution-1.4.x.patch | 29 +-
debian/patches/series | 4 +
debian/patches/six-regex.patch | 490 +++++++++++++++++++++
debian/patches/ssi-tag-1.4.diff | 8 +-
16 files changed, 1039 insertions(+), 91 deletions(-)
diff --git a/debian/changelog b/debian/changelog
index a616732..38a8623 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,13 @@
+python-django (1.4.5-1+deb7u8) wheezy-security; urgency=high
+
+ * New upstream security release.
+ - reverse() can generate URLs pointing to other hosts (CVE-2014-0480)
+ - file upload denial of service (CVE-2014-0481)
+ - RemoteUserMiddleware session hijacking (CVE-2014-0482)
+ - data leakage via querystring manipulation in admin (CVE-2014-0483)
+
+ -- Luke Faraone <lfaraone at debian.org> Wed, 20 Aug 2014 01:46:17 -0700
+
python-django (1.4.5-1+deb7u7) stable-security; urgency=high
* New upstream security release.
diff --git a/debian/patches/02_disable-sources-in-sphinxdoc.diff b/debian/patches/02_disable-sources-in-sphinxdoc.diff
index 9541dd7..5904095 100644
--- a/debian/patches/02_disable-sources-in-sphinxdoc.diff
+++ b/debian/patches/02_disable-sources-in-sphinxdoc.diff
@@ -9,7 +9,7 @@ Origin: vendor
--- a/docs/conf.py
+++ b/docs/conf.py
-@@ -168,7 +168,10 @@ html_additional_pages = {}
+@@ -168,7 +168,10 @@
#html_split_index = False
# If true, links to the reST sources are added to the pages.
diff --git a/debian/patches/06_use_debian_geoip_database_as_default.diff b/debian/patches/06_use_debian_geoip_database_as_default.diff
index 44503f1..570cc9f 100644
--- a/debian/patches/06_use_debian_geoip_database_as_default.diff
+++ b/debian/patches/06_use_debian_geoip_database_as_default.diff
@@ -9,7 +9,7 @@ Author: Tapio Rantala <tapio.rantala at iki.fi>
--- a/django/contrib/gis/geoip/base.py
+++ b/django/contrib/gis/geoip/base.py
-@@ -61,7 +61,8 @@ class GeoIP(object):
+@@ -61,7 +61,8 @@
* path: Base directory to where GeoIP data is located or the full path
to where the city or country data files (*.dat) are located.
Assumes that both the city and country data sets are located in
@@ -19,7 +19,7 @@ Author: Tapio Rantala <tapio.rantala at iki.fi>
* cache: The cache settings when opening up the GeoIP datasets,
and may be an integer in (0, 1, 2, 4, 8) corresponding to
-@@ -70,11 +71,13 @@ class GeoIP(object):
+@@ -70,11 +71,13 @@
settings, respectively. Defaults to 0, meaning that the data is read
from the disk.
@@ -38,7 +38,7 @@ Author: Tapio Rantala <tapio.rantala at iki.fi>
"""
# Checking the given cache option.
if cache in self.cache_options:
-@@ -84,8 +87,7 @@ class GeoIP(object):
+@@ -84,8 +87,7 @@
# Getting the GeoIP data path.
if not path:
@@ -48,7 +48,7 @@ Author: Tapio Rantala <tapio.rantala at iki.fi>
if not isinstance(path, basestring):
raise TypeError('Invalid path type: %s' % type(path).__name__)
-@@ -98,7 +100,7 @@ class GeoIP(object):
+@@ -98,7 +100,7 @@
self._country = GeoIP_open(country_db, cache)
self._country_file = country_db
diff --git a/debian/patches/admin-data-leak-1.4.diff b/debian/patches/admin-data-leak-1.4.diff
new file mode 100644
index 0000000..7e9b714
--- /dev/null
+++ b/debian/patches/admin-data-leak-1.4.diff
@@ -0,0 +1,115 @@
+commit 027bd348642007617518379f8b02546abacaa6e0
+Author: Simon Charette <charette.s at gmail.com>
+Date: Mon Aug 11 15:36:16 2014 -0400
+
+ [1.4.x] Prevented data leakage in contrib.admin via query string manipulation.
+
+ This is a security fix. Disclosure following shortly.
+
+--- /dev/null
++++ b/django/contrib/admin/exceptions.py
+@@ -0,0 +1,6 @@
++from django.core.exceptions import SuspiciousOperation
++
++
++class DisallowedModelAdminToField(SuspiciousOperation):
++ """Invalid to_field was passed to admin view via URL query string"""
++ pass
+--- a/django/contrib/admin/options.py
++++ b/django/contrib/admin/options.py
+@@ -269,6 +269,24 @@
+ clean_lookup = LOOKUP_SEP.join(parts)
+ return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
+
++ def to_field_allowed(self, request, to_field):
++ opts = self.model._meta
++
++ try:
++ field = opts.get_field(to_field)
++ except FieldDoesNotExist:
++ return False
++
++ # Make sure at least one of the models registered for this site
++ # references this field.
++ registered_models = self.admin_site._registry
++ for related_object in opts.get_all_related_objects():
++ if (related_object.model in registered_models and
++ field == related_object.field.rel.get_related_field()):
++ return True
++
++ return False
++
+ def has_add_permission(self, request):
+ """
+ Returns True if the given request has permission to add an object.
+--- a/django/contrib/admin/views/main.py
++++ b/django/contrib/admin/views/main.py
+@@ -10,6 +10,7 @@
+ from django.utils.http import urlencode
+
+ from django.contrib.admin import FieldListFilter
++from django.contrib.admin.exceptions import DisallowedModelAdminToField
+ from django.contrib.admin.options import IncorrectLookupParameters
+ from django.contrib.admin.util import (quote, get_fields_from_path,
+ lookup_needs_distinct, prepare_lookup_value)
+@@ -56,7 +57,10 @@
+ self.page_num = 0
+ self.show_all = ALL_VAR in request.GET
+ self.is_popup = IS_POPUP_VAR in request.GET
+- self.to_field = request.GET.get(TO_FIELD_VAR)
++ to_field = request.GET.get(TO_FIELD_VAR)
++ if to_field and not model_admin.to_field_allowed(request, to_field):
++ raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)
++ self.to_field = to_field
+ self.params = dict(request.GET.items())
+ if PAGE_VAR in self.params:
+ del self.params[PAGE_VAR]
+--- a/tests/regressiontests/admin_views/tests.py
++++ b/tests/regressiontests/admin_views/tests.py
+@@ -13,11 +13,12 @@
+ from django.core.urlresolvers import reverse
+ # Register auth models with the admin.
+ from django.contrib import admin
++from django.contrib.admin.exceptions import DisallowedModelAdminToField
+ from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
+ from django.contrib.admin.models import LogEntry, DELETION
+ from django.contrib.admin.sites import LOGIN_FORM_KEY
+ from django.contrib.admin.util import quote
+-from django.contrib.admin.views.main import IS_POPUP_VAR
++from django.contrib.admin.views.main import IS_POPUP_VAR, TO_FIELD_VAR
+ from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
+ from django.contrib.auth import REDIRECT_FIELD_NAME
+ from django.contrib.auth.models import Group, User, Permission, UNUSABLE_PASSWORD
+@@ -572,6 +573,19 @@
+ response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
+ self.assertEqual(response.status_code, 200)
+
++ def test_disallowed_to_field(self):
++ with self.assertRaises(DisallowedModelAdminToField):
++ response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'missing_field'})
++
++ # Specifying a field that is not refered by any other model registered
++ # to this admin site should raise an exception.
++ with self.assertRaises(DisallowedModelAdminToField):
++ response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'name'})
++
++ # Specifying a field referenced by another model should be allowed.
++ response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'id'})
++ self.assertEqual(response.status_code, 200)
++
+ def test_allowed_filtering_15103(self):
+ """
+ Regressions test for ticket 15103 - filtering on fields defined in a
+@@ -2061,10 +2075,9 @@
+ """Ensure that the to_field GET parameter is preserved when a search
+ is performed. Refs #10918.
+ """
+- from django.contrib.admin.views.main import TO_FIELD_VAR
+- response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR)
++ response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR)
+ self.assertContains(response, "\n1 user\n")
+- self.assertContains(response, '<input type="hidden" name="t" value="username"/>', html=True)
++ self.assertContains(response, '<input type="hidden" name="%s" value="id"/>' % TO_FIELD_VAR, html=True)
+
+ def test_exact_matches(self):
+ response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
diff --git a/debian/patches/cache-csrf-1.4.x.patch b/debian/patches/cache-csrf-1.4.x.patch
index 5261baa..92c93bd 100644
--- a/debian/patches/cache-csrf-1.4.x.patch
+++ b/debian/patches/cache-csrf-1.4.x.patch
@@ -29,11 +29,9 @@ responses. The heuristic for this will be:
3. The ``Vary: Cookie`` header is set on the response, then the
response will not be cached.
-diff --git a/django/middleware/cache.py b/django/middleware/cache.py
-index 34bf0ca..760ba4e 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
-@@ -50,7 +50,8 @@ More details about how the caching works:
+@@ -50,7 +50,8 @@
from django.conf import settings
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
@@ -43,7 +41,7 @@ index 34bf0ca..760ba4e 100644
class UpdateCacheMiddleware(object):
-@@ -93,8 +94,15 @@ class UpdateCacheMiddleware(object):
+@@ -93,8 +94,15 @@
if not self._should_update_cache(request, response):
# We don't need to update the cache, just return.
return response
@@ -59,11 +57,9 @@ index 34bf0ca..760ba4e 100644
# Try to get the timeout from the "max-age" section of the "Cache-
# Control" header before reverting to using the default cache_timeout
# length.
-diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
-index bd29cde..b0be259 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
-@@ -17,10 +17,12 @@ from django.core import management
+@@ -17,10 +17,12 @@
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
from django.core.cache.backends.base import (CacheKeyWarning,
InvalidCacheBackendError)
@@ -76,7 +72,7 @@ index bd29cde..b0be259 100644
from django.template import Template
from django.template.response import TemplateResponse
from django.test import TestCase, TransactionTestCase, RequestFactory
-@@ -1418,6 +1420,10 @@ def hello_world_view(request, value):
+@@ -1418,6 +1420,10 @@
return HttpResponse('Hello World %s' % value)
@@ -87,7 +83,7 @@ index bd29cde..b0be259 100644
class CacheMiddlewareTest(TestCase):
def setUp(self):
-@@ -1635,6 +1641,27 @@ class CacheMiddlewareTest(TestCase):
+@@ -1635,6 +1641,27 @@
response = other_with_timeout_view(request, '18')
self.assertEqual(response.content, 'Hello World 18')
diff --git a/debian/patches/drop_fix_ie_for_vary_1_4.diff b/debian/patches/drop_fix_ie_for_vary_1_4.diff
index 460b7f4..0ff5985 100644
--- a/debian/patches/drop_fix_ie_for_vary_1_4.diff
+++ b/debian/patches/drop_fix_ie_for_vary_1_4.diff
@@ -22,11 +22,9 @@ longer stripped from the response. In addition, modifications to the
``Content-Disposition`` header, have also been removed as they were
found to have similar issues.
-diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
-index a0918bf..99f81a6 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
-@@ -14,8 +14,6 @@ class BaseHandler(object):
+@@ -14,8 +14,6 @@
response_fixes = [
http.fix_location_header,
http.conditional_content_removal,
@@ -35,11 +33,9 @@ index a0918bf..99f81a6 100644
]
def __init__(self):
-diff --git a/django/http/utils.py b/django/http/utils.py
-index 0180864..f98ca93 100644
--- a/django/http/utils.py
+++ b/django/http/utils.py
-@@ -31,57 +31,3 @@ def conditional_content_removal(request, response):
+@@ -31,57 +31,3 @@
if request.method == 'HEAD':
response.content = ''
return response
@@ -97,11 +93,9 @@ index 0180864..f98ca93 100644
-
- return response
-
-diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py
-index 16c7daa..9e05a94 100644
--- a/tests/regressiontests/utils/http.py
+++ b/tests/regressiontests/utils/http.py
-@@ -56,50 +56,6 @@ class TestUtilsHttp(unittest.TestCase):
+@@ -56,50 +56,6 @@
]
self.assertTrue(result in acceptable_results)
diff --git a/debian/patches/file-upload-1.4.diff b/debian/patches/file-upload-1.4.diff
new file mode 100644
index 0000000..57eeb83
--- /dev/null
+++ b/debian/patches/file-upload-1.4.diff
@@ -0,0 +1,247 @@
+commit 30042d475bf084c6723c6217a21598d9247a9c41
+Author: Tim Graham <timograham at gmail.com>
+Date: Fri Aug 8 10:20:08 2014 -0400
+
+ [1.4.x] Fixed #23157 -- Removed O(n) algorithm when uploading duplicate file names.
+
+ This is a security fix. Disclosure following shortly.
+
+Debian note: This patch also includes the minimum backport of additional
+functionality to django.utils.six to support this patch, but does not change
+any functionality therein.
+
+--- a/django/core/files/storage.py
++++ b/django/core/files/storage.py
+@@ -1,13 +1,13 @@
+ import os
+ import errno
+ import urlparse
+-import itertools
+ from datetime import datetime
+
+ from django.conf import settings
+ from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
+ from django.core.files import locks, File
+ from django.core.files.move import file_move_safe
++from django.utils.crypto import get_random_string
+ from django.utils.encoding import force_unicode, filepath_to_uri
+ from django.utils.functional import LazyObject
+ from django.utils.importlib import import_module
+@@ -63,13 +63,12 @@
+ """
+ dir_name, file_name = os.path.split(name)
+ file_root, file_ext = os.path.splitext(file_name)
+- # If the filename already exists, add an underscore and a number (before
+- # the file extension, if one exists) to the filename until the generated
+- # filename doesn't exist.
+- count = itertools.count(1)
++ # If the filename already exists, add an underscore and a random 7
++ # character alphanumeric string (before the file extension, if one
++ # exists) to the filename until the generated filename doesn't exist.
+ while self.exists(name):
+ # file_ext includes the dot.
+- name = os.path.join(dir_name, "%s_%s%s" % (file_root, count.next(), file_ext))
++ name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext))
+
+ return name
+
+--- a/docs/howto/custom-file-storage.txt
++++ b/docs/howto/custom-file-storage.txt
+@@ -86,5 +86,13 @@
+ will have already cleaned to a filename valid for the storage system, according
+ to the ``get_valid_name()`` method described above.
+
+-The code provided on ``Storage`` simply appends ``"_1"``, ``"_2"``, etc. to the
+-filename until it finds one that's available in the destination directory.
++.. versionchanged:: 1.4.14
++
++ If a file with ``name`` already exists, an underscore plus a random 7
++ character alphanumeric string is appended to the filename before the
++ extension.
++
++ Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``,
++ etc.) was appended to the filename until an avaible name in the destination
++ directory was found. A malicious user could exploit this deterministic
++ algorithm to create a denial-of-service attack.
+--- a/docs/ref/files/storage.txt
++++ b/docs/ref/files/storage.txt
+@@ -18,7 +18,7 @@
+ .. function:: get_storage_class([import_path=None])
+
+ Returns a class or module which implements the storage API.
+-
++
+ When called without the ``import_path`` parameter ``get_storage_class``
+ will return the current default storage system as defined by
+ :setting:`DEFAULT_FILE_STORAGE`. If ``import_path`` is provided,
+@@ -35,9 +35,9 @@
+ basic file storage on a local filesystem. It inherits from
+ :class:`~django.core.files.storage.Storage` and provides implementations
+ for all the public methods thereof.
+-
++
+ .. note::
+-
++
+ The :class:`FileSystemStorage.delete` method will not raise
+ raise an exception if the given file name does not exist.
+
+@@ -85,6 +85,16 @@
+ available for new content to be written to on the target storage
+ system.
+
++ .. versionchanged:: 1.4.14
++
++ If a file with ``name`` already exists, an underscore plus a random 7
++ character alphanumeric string is appended to the filename before the
++ extension.
++
++ Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``,
++ etc.) was appended to the filename until an avaible name in the
++ destination directory was found. A malicious user could exploit this
++ deterministic algorithm to create a denial-of-service attack.
+
+ .. method:: get_valid_name(name)
+
+--- a/tests/modeltests/files/tests.py
++++ b/tests/modeltests/files/tests.py
+@@ -8,10 +8,14 @@
+ from django.core.files.base import ContentFile
+ from django.core.files.uploadedfile import SimpleUploadedFile
+ from django.test import TestCase
++from django.utils import six
+
+ from .models import Storage, temp_storage, temp_storage_location
+
+
++FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}'
++
++
+ class FileTests(TestCase):
+ def tearDown(self):
+ shutil.rmtree(temp_storage_location)
+@@ -57,27 +61,28 @@
+ # Save another file with the same name.
+ obj2 = Storage()
+ obj2.normal.save("django_test.txt", ContentFile("more content"))
+- self.assertEqual(obj2.normal.name, "tests/django_test_1.txt")
++ obj2_name = obj2.normal.name
++ six.assertRegex(self, obj2_name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX)
+ self.assertEqual(obj2.normal.size, 12)
+
+ # Push the objects into the cache to make sure they pickle properly
+ cache.set("obj1", obj1)
+ cache.set("obj2", obj2)
+- self.assertEqual(cache.get("obj2").normal.name, "tests/django_test_1.txt")
++ six.assertRegex(self, cache.get("obj2").normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX)
+
+ # Deleting an object does not delete the file it uses.
+ obj2.delete()
+ obj2.normal.save("django_test.txt", ContentFile("more content"))
+- self.assertEqual(obj2.normal.name, "tests/django_test_2.txt")
++ self.assertNotEqual(obj2_name, obj2.normal.name)
++ six.assertRegex(self, obj2.normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX)
+
+ # Multiple files with the same name get _N appended to them.
+- objs = [Storage() for i in range(3)]
++ objs = [Storage() for i in range(2)]
+ for o in objs:
+ o.normal.save("multiple_files.txt", ContentFile("Same Content"))
+- self.assertEqual(
+- [o.normal.name for o in objs],
+- ["tests/multiple_files.txt", "tests/multiple_files_1.txt", "tests/multiple_files_2.txt"]
+- )
++ names = [o.normal.name for o in objs]
++ self.assertEqual(names[0], "tests/multiple_files.txt")
++ six.assertRegex(self, names[1], "tests/multiple_files_%s.txt" % FILE_SUFFIX_REGEX)
+ for o in objs:
+ o.delete()
+
+--- a/tests/regressiontests/file_storage/tests.py
++++ b/tests/regressiontests/file_storage/tests.py
+@@ -23,7 +23,7 @@
+ from django.core.files.storage import FileSystemStorage, get_storage_class
+ from django.core.files.uploadedfile import UploadedFile
+ from django.test import SimpleTestCase
+-from django.utils import unittest
++from django.utils import six, unittest
+
+ # Try to import PIL in either of the two ways it can end up installed.
+ # Checking for the existence of Image is enough for CPython, but
+@@ -37,6 +37,9 @@
+ Image = None
+
+
++FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}'
++
++
+ class GetStorageClassTests(SimpleTestCase):
+
+ def test_get_filesystem_storage(self):
+@@ -417,10 +420,9 @@
+ self.thread.start()
+ name = self.save_file('conflict')
+ self.thread.join()
+- self.assertTrue(self.storage.exists('conflict'))
+- self.assertTrue(self.storage.exists('conflict_1'))
+- self.storage.delete('conflict')
+- self.storage.delete('conflict_1')
++ files = sorted(os.listdir(self.storage_dir))
++ self.assertEqual(files[0], 'conflict')
++ six.assertRegex(self, files[1], 'conflict_%s' % FILE_SUFFIX_REGEX)
+
+ class FileStoragePermissions(unittest.TestCase):
+ def setUp(self):
+@@ -457,9 +459,10 @@
+ self.storage.save('dotted.path/test', ContentFile("1"))
+ self.storage.save('dotted.path/test', ContentFile("2"))
+
++ files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path')))
+ self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path')))
+- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test')))
+- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1')))
++ self.assertEqual(files[0], 'test')
++ six.assertRegex(self, files[1], 'test_%s' % FILE_SUFFIX_REGEX)
+
+ def test_first_character_dot(self):
+ """
+@@ -472,10 +475,12 @@
+ self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test')))
+ # Before 2.6, a leading dot was treated as an extension, and so
+ # underscore gets added to beginning instead of end.
++ files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path')))
++ self.assertEqual(files[0], '.test')
+ if sys.version_info < (2, 6):
+- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_1.test')))
++ six.assertRegex(self, files[1], '_%s.test' % FILE_SUFFIX_REGEX)
+ else:
+- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1')))
++ six.assertRegex(self, files[1], '.test_%s' % FILE_SUFFIX_REGEX)
+
+ class DimensionClosingBug(unittest.TestCase):
+ """
+--- a/django/utils/six.py
++++ b/django/utils/six.py
+@@ -370,13 +370,22 @@
+
+ if PY3:
+ _iterlists = "lists"
++ _assertRaisesRegex = "assertRaisesRegex"
++ _assertRegex = "assertRegex"
+ else:
++ _assertRaisesRegex = "assertRaisesRegexp"
+ _iterlists = "iterlists"
++ _assertRegex = "assertRegexpMatches"
+
+ def iterlists(d):
+ """Return an iterator over the values of a MultiValueDict."""
+ return getattr(d, _iterlists)()
+
++def assertRaisesRegex(self, *args, **kwargs):
++ return getattr(self, _assertRaisesRegex)(*args, **kwargs)
++
++def assertRegex(self, *args, **kwargs):
++ return getattr(self, _assertRegex)(*args, **kwargs)
+
+ add_move(MovedModule("_dummy_thread", "dummy_thread"))
+ add_move(MovedModule("_thread", "thread"))
diff --git a/debian/patches/is_safe_url-1.4.diff b/debian/patches/is_safe_url-1.4.diff
index 757cac0..e55478c 100644
--- a/debian/patches/is_safe_url-1.4.diff
+++ b/debian/patches/is_safe_url-1.4.diff
@@ -26,11 +26,9 @@ To remedy this issue, the ``is_safe_url()`` function will be modified
to properly recognize and reject URLs which specify a scheme other
than HTTP or HTTPS.
-diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py
-index 603d380..6d7029b 100644
--- a/django/contrib/auth/tests/views.py
+++ b/django/contrib/auth/tests/views.py
-@@ -309,7 +309,8 @@ class LoginTest(AuthViewsTestCase):
+@@ -309,7 +309,8 @@
for bad_url in ('http://example.com',
'https://example.com',
'ftp://exampel.com',
@@ -40,7 +38,7 @@ index 603d380..6d7029b 100644
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
'url': login_url,
-@@ -330,6 +331,7 @@ class LoginTest(AuthViewsTestCase):
+@@ -330,6 +331,7 @@
'/view?param=ftp://exampel.com',
'view/?param=//example.com',
'https:///',
@@ -48,7 +46,7 @@ index 603d380..6d7029b 100644
'//testserver/',
'/url%20with%20spaces/'): # see ticket #12534
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
-@@ -467,7 +469,8 @@ class LogoutTest(AuthViewsTestCase):
+@@ -467,7 +469,8 @@
for bad_url in ('http://example.com',
'https://example.com',
'ftp://exampel.com',
@@ -58,7 +56,7 @@ index 603d380..6d7029b 100644
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
'url': logout_url,
'next': REDIRECT_FIELD_NAME,
-@@ -486,6 +489,7 @@ class LogoutTest(AuthViewsTestCase):
+@@ -486,6 +489,7 @@
'/view?param=ftp://exampel.com',
'view/?param=//example.com',
'https:///',
@@ -66,11 +64,9 @@ index 603d380..6d7029b 100644
'//testserver/',
'/url%20with%20spaces/'): # see ticket #12534
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
-diff --git a/django/utils/http.py b/django/utils/http.py
-index d2e4eb5..21c84dc 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
-@@ -228,11 +228,12 @@ else:
+@@ -228,11 +228,12 @@
def is_safe_url(url, host=None):
"""
Return ``True`` if the url is a safe redirection (i.e. it doesn't point to
diff --git a/debian/patches/is_safe_url_1_4.diff b/debian/patches/is_safe_url_1_4.diff
index c6af21d..7c450c1 100644
--- a/debian/patches/is_safe_url_1_4.diff
+++ b/debian/patches/is_safe_url_1_4.diff
@@ -17,11 +17,9 @@ accepted by some browsers with more liberal URL parsing.
To remedy this, the validation in ``is_safe_url()`` has been tightened
to be able to handle and correctly validate these malformed URLs.
-diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py
-index 6d7029b..2b72cd4 100644
--- a/django/contrib/auth/tests/views.py
+++ b/django/contrib/auth/tests/views.py
-@@ -307,8 +307,10 @@ class LoginTest(AuthViewsTestCase):
+@@ -307,8 +307,10 @@
# Those URLs should not pass the security check
for bad_url in ('http://example.com',
@@ -32,7 +30,7 @@ index 6d7029b..2b72cd4 100644
'//example.com',
'javascript:alert("XSS")'):
-@@ -330,8 +332,8 @@ class LoginTest(AuthViewsTestCase):
+@@ -330,8 +332,8 @@
'/view/?param=https://example.com',
'/view?param=ftp://exampel.com',
'view/?param=//example.com',
@@ -43,7 +41,7 @@ index 6d7029b..2b72cd4 100644
'//testserver/',
'/url%20with%20spaces/'): # see ticket #12534
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
-@@ -467,8 +469,10 @@ class LogoutTest(AuthViewsTestCase):
+@@ -467,8 +469,10 @@
# Those URLs should not pass the security check
for bad_url in ('http://example.com',
@@ -54,7 +52,7 @@ index 6d7029b..2b72cd4 100644
'//example.com',
'javascript:alert("XSS")'):
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
-@@ -488,8 +492,8 @@ class LogoutTest(AuthViewsTestCase):
+@@ -488,8 +492,8 @@
'/view/?param=https://example.com',
'/view?param=ftp://exampel.com',
'view/?param=//example.com',
@@ -65,11 +63,9 @@ index 6d7029b..2b72cd4 100644
'//testserver/',
'/url%20with%20spaces/'): # see ticket #12534
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
-diff --git a/django/utils/http.py b/django/utils/http.py
-index 21c84dc..2d40489 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
-@@ -234,6 +234,18 @@ def is_safe_url(url, host=None):
+@@ -234,6 +234,18 @@
"""
if not url:
return False
@@ -88,11 +84,9 @@ index 21c84dc..2d40489 100644
+ return False
return (not url_info[1] or url_info[1] == host) and \
(not url_info[0] or url_info[0] in ['http', 'https'])
-diff --git a/tests/regressiontests/utils/http.py b/tests/regressiontests/utils/http.py
-index 16c7daa..c50524e 100644
--- a/tests/regressiontests/utils/http.py
+++ b/tests/regressiontests/utils/http.py
-@@ -122,3 +122,33 @@ class TestUtilsHttp(unittest.TestCase):
+@@ -122,3 +122,33 @@
for n, b36 in [(0, '0'), (1, '1'), (42, '16'), (818469960, 'django')]:
self.assertEqual(http.int_to_base36(n), b36)
self.assertEqual(http.base36_to_int(b36), n)
diff --git a/debian/patches/mysql-typecast-1.4.x.diff b/debian/patches/mysql-typecast-1.4.x.diff
index 7fc339a..6a97fd0 100644
--- a/debian/patches/mysql-typecast-1.4.x.diff
+++ b/debian/patches/mysql-typecast-1.4.x.diff
@@ -29,11 +29,9 @@ appropriate type conversions, and users of the ``raw()`` and
SQL or SQL fragments -- will be advised to ensure they perform
appropriate manual type conversions prior to executing queries.
-diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
-index 527a3c0..690a671 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
-@@ -911,6 +911,12 @@ class FilePathField(Field):
+@@ -911,6 +911,12 @@
kwargs['max_length'] = kwargs.get('max_length', 100)
Field.__init__(self, verbose_name, name, **kwargs)
@@ -46,7 +44,7 @@ index 527a3c0..690a671 100644
def formfield(self, **kwargs):
defaults = {
'path': self.path,
-@@ -1010,6 +1016,12 @@ class IPAddressField(Field):
+@@ -1010,6 +1016,12 @@
kwargs['max_length'] = 15
Field.__init__(self, *args, **kwargs)
@@ -59,7 +57,7 @@ index 527a3c0..690a671 100644
def get_internal_type(self):
return "IPAddressField"
-@@ -1047,12 +1059,14 @@ class GenericIPAddressField(Field):
+@@ -1047,12 +1059,14 @@
return value or None
def get_prep_value(self, value):
@@ -75,11 +73,9 @@ index 527a3c0..690a671 100644
def formfield(self, **kwargs):
defaults = {'form_class': forms.GenericIPAddressField}
-diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt
-index daaede8..fcbda03 100644
--- a/docs/howto/custom-model-fields.txt
+++ b/docs/howto/custom-model-fields.txt
-@@ -482,6 +482,16 @@ For example::
+@@ -482,6 +482,16 @@
return ''.join([''.join(l) for l in (value.north,
value.east, value.south, value.west)])
@@ -96,11 +92,9 @@ index daaede8..fcbda03 100644
Converting query values to database values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt
-index 4c18658..2691979 100644
--- a/docs/ref/databases.txt
+++ b/docs/ref/databases.txt
-@@ -432,6 +432,22 @@ MySQL does not support the ``NOWAIT`` option to the ``SELECT ... FOR UPDATE``
+@@ -432,6 +432,22 @@
statement. If ``select_for_update()`` is used with ``nowait=True`` then a
``DatabaseError`` will be raised.
@@ -123,11 +117,9 @@ index 4c18658..2691979 100644
.. _sqlite-notes:
SQLite notes
-diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
-index 022a251..2decddb 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
-@@ -1041,6 +1041,16 @@ of the arguments is required, but you should use at least one of them.
+@@ -1041,6 +1041,16 @@
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
@@ -144,11 +136,9 @@ index 022a251..2decddb 100644
defer
~~~~~
-diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt
-index 80038e5..d387ad5 100644
--- a/docs/topics/db/sql.txt
+++ b/docs/topics/db/sql.txt
-@@ -69,6 +69,16 @@ options that make it very powerful.
+@@ -69,6 +69,16 @@
database, but does nothing to enforce that. If the query does not
return rows, a (possibly cryptic) error will result.
@@ -165,11 +155,9 @@ index 80038e5..d387ad5 100644
Mapping query fields to model fields
------------------------------------
-diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py
-index a71ea77..30f0e7d 100644
--- a/tests/regressiontests/model_fields/tests.py
+++ b/tests/regressiontests/model_fields/tests.py
-@@ -6,8 +6,15 @@ from decimal import Decimal
+@@ -6,8 +6,15 @@
from django import test
from django import forms
from django.core.exceptions import ValidationError
@@ -186,7 +174,7 @@ index a71ea77..30f0e7d 100644
from django.utils import unittest
from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post,
-@@ -373,3 +380,88 @@ class FileFieldTests(unittest.TestCase):
+@@ -373,3 +380,88 @@
field = d._meta.get_field('myfile')
field.save_form_data(d, 'else.txt')
self.assertEqual(d.myfile, 'else.txt')
diff --git a/debian/patches/remote-user-1.4.diff b/debian/patches/remote-user-1.4.diff
new file mode 100644
index 0000000..d12fa6e
--- /dev/null
+++ b/debian/patches/remote-user-1.4.diff
@@ -0,0 +1,88 @@
+commit c9e3b9949cd55f090591fbdc4a114fcb8368b6d9
+Author: Preston Holmes <preston at ptone.com>
+Date: Mon Aug 11 12:04:53 2014 -0400
+
+ [1.4.x] Fixed #23066 -- Modified RemoteUserMiddleware to logout on REMOTE_USE change.
+
+ This is a security fix. Disclosure following shortly.
+
+--- a/django/contrib/auth/middleware.py
++++ b/django/contrib/auth/middleware.py
+@@ -1,4 +1,5 @@
+ from django.contrib import auth
++from django.contrib.auth.backends import RemoteUserBackend
+ from django.core.exceptions import ImproperlyConfigured
+ from django.utils.functional import SimpleLazyObject
+
+@@ -47,9 +48,11 @@
+ try:
+ username = request.META[self.header]
+ except KeyError:
+- # If specified header doesn't exist then return (leaving
+- # request.user set to AnonymousUser by the
+- # AuthenticationMiddleware).
++ # If specified header doesn't exist then remove any existing
++ # authenticated remote-user, or return (leaving request.user set to
++ # AnonymousUser by the AuthenticationMiddleware).
++ if request.user.is_authenticated():
++ self._remove_invalid_user(request)
+ return
+ # If the user is already authenticated and that user is the user we are
+ # getting passed in the headers, then the correct user is already
+@@ -57,6 +60,11 @@
+ if request.user.is_authenticated():
+ if request.user.username == self.clean_username(username, request):
+ return
++ else:
++ # An authenticated user is associated with the request, but
++ # it does not match the authorized user in the header.
++ self._remove_invalid_user(request)
++
+ # We are seeing this user for the first time in this session, attempt
+ # to authenticate the user.
+ user = auth.authenticate(remote_user=username)
+@@ -78,3 +86,17 @@
+ except AttributeError: # Backend has no clean_username method.
+ pass
+ return username
++
++ def _remove_invalid_user(self, request):
++ """
++ Removes the current authenticated user in the request which is invalid
++ but only if the user is authenticated via the RemoteUserBackend.
++ """
++ try:
++ stored_backend = auth.load_backend(request.session.get(auth.BACKEND_SESSION_KEY, ''))
++ except ImproperlyConfigured:
++ # backend failed to load
++ auth.logout(request)
++ else:
++ if isinstance(stored_backend, RemoteUserBackend):
++ auth.logout(request)
+--- a/django/contrib/auth/tests/remote_user.py
++++ b/django/contrib/auth/tests/remote_user.py
+@@ -95,6 +95,24 @@
+ response = self.client.get('/remote_user/', REMOTE_USER=self.known_user)
+ self.assertEqual(default_login, response.context['user'].last_login)
+
++ def test_user_switch_forces_new_login(self):
++ """
++ Tests that if the username in the header changes between requests
++ that the original user is logged out
++ """
++ User.objects.create(username='knownuser')
++ # Known user authenticates
++ response = self.client.get('/remote_user/',
++ **{'REMOTE_USER': self.known_user})
++ self.assertEqual(response.context['user'].username, 'knownuser')
++ # During the session, the REMOTE_USER changes to a different user.
++ response = self.client.get('/remote_user/',
++ **{'REMOTE_USER': "newnewuser"})
++ # Ensure that the current user is not the prior remote_user
++ # In backends that create a new user, username is "newnewuser"
++ # In backends that do not create new users, it is '' (anonymous user)
++ self.assertNotEqual(response.context['user'].username, 'knownuser')
++
+ def tearDown(self):
+ """Restores settings to avoid breaking other tests."""
+ settings.MIDDLEWARE_CLASSES = self.curr_middleware
diff --git a/debian/patches/reverse-1.4.diff b/debian/patches/reverse-1.4.diff
new file mode 100644
index 0000000..7e0763f
--- /dev/null
+++ b/debian/patches/reverse-1.4.diff
@@ -0,0 +1,41 @@
+commit c2fe73133b62a1d9e8f7a6b43966570b14618d7e
+Author: Florian Apolloner <florian at apolloner.eu>
+Date: Thu Jul 17 21:59:28 2014 +0200
+
+ [1.4.x] Prevented reverse() from generating URLs pointing to other hosts.
+
+ This is a security fix. Disclosure following shortly.
+
+--- a/django/core/urlresolvers.py
++++ b/django/core/urlresolvers.py
+@@ -406,6 +406,8 @@
+ unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
+ candidate = (prefix_norm + result) % unicode_kwargs
+ if re.search(u'^%s%s' % (_prefix, pattern), candidate, re.UNICODE):
++ if candidate.startswith('//'):
++ candidate = '/%%2F%s' % candidate[2:]
+ return candidate
+ # lookup_view can be URL label, or dotted path, or callable, Any of
+ # these can be passed in at the top, but callables are not friendly in
+--- a/tests/regressiontests/urlpatterns_reverse/tests.py
++++ b/tests/regressiontests/urlpatterns_reverse/tests.py
+@@ -142,6 +142,9 @@
+ ('defaults', '/defaults_view2/3/', [], {'arg1': 3, 'arg2': 2}),
+ ('defaults', NoReverseMatch, [], {'arg1': 3, 'arg2': 3}),
+ ('defaults', NoReverseMatch, [], {'arg2': 1}),
++
++ # Security tests
++ ('security', '/%2Fexample.com/security/', ['/example.com'], {}),
+ )
+
+ class NoURLPatternsTests(TestCase):
+--- a/tests/regressiontests/urlpatterns_reverse/urls.py
++++ b/tests/regressiontests/urlpatterns_reverse/urls.py
+@@ -71,4 +71,7 @@
+ (r'defaults_view2/(?P<arg1>\d+)/', 'defaults_view', {'arg2': 2}, 'defaults'),
+
+ url('^includes/', include(other_patterns)),
++
++ # Security tests
++ url('(.+)/security/$', empty_view, name='security'),
+ )
diff --git a/debian/patches/reverse-execution-1.4.x.patch b/debian/patches/reverse-execution-1.4.x.patch
index da6e839..63e6418 100644
--- a/debian/patches/reverse-execution-1.4.x.patch
+++ b/debian/patches/reverse-execution-1.4.x.patch
@@ -38,11 +38,9 @@ paths based on the view-containing modules listed in the project's URL
pattern configuration, so as to ensure that only modules the developer
intended to be imported in this fashion can or will be imported.
-diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
-index 1497d43..6a2aec7 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
-@@ -230,6 +230,10 @@ class RegexURLResolver(LocaleRegexProvider):
+@@ -230,6 +230,10 @@
self._reverse_dict = {}
self._namespace_dict = {}
self._app_dict = {}
@@ -53,7 +51,7 @@ index 1497d43..6a2aec7 100644
def __repr__(self):
return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
-@@ -240,6 +244,15 @@ class RegexURLResolver(LocaleRegexProvider):
+@@ -240,6 +244,15 @@
apps = {}
language_code = get_language()
for pattern in reversed(self.url_patterns):
@@ -69,7 +67,7 @@ index 1497d43..6a2aec7 100644
p_pattern = pattern.regex.pattern
if p_pattern.startswith('^'):
p_pattern = p_pattern[1:]
-@@ -260,6 +273,7 @@ class RegexURLResolver(LocaleRegexProvider):
+@@ -260,6 +273,7 @@
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
for app_name, namespace_list in pattern.app_dict.items():
apps.setdefault(app_name, []).extend(namespace_list)
@@ -77,7 +75,7 @@ index 1497d43..6a2aec7 100644
else:
bits = normalize(p_pattern)
lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
-@@ -268,6 +282,7 @@ class RegexURLResolver(LocaleRegexProvider):
+@@ -268,6 +282,7 @@
self._reverse_dict[language_code] = lookups
self._namespace_dict[language_code] = namespaces
self._app_dict[language_code] = apps
@@ -85,7 +83,7 @@ index 1497d43..6a2aec7 100644
@property
def reverse_dict(self):
-@@ -356,8 +371,13 @@ class RegexURLResolver(LocaleRegexProvider):
+@@ -356,8 +371,13 @@
def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs):
if args and kwargs:
raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
@@ -100,17 +98,12 @@ index 1497d43..6a2aec7 100644
... 601 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-django.git
More information about the Python-modules-commits
mailing list