[Python-modules-commits] [python-django] 01/01: LTS security upload: https://www.djangoproject.com/weblog/2014/aug/20/security/
Raphaël Hertzog
hertzog at moszumanska.debian.org
Mon Sep 29 08:21:51 UTC 2014
This is an automated email from the git hooks/post-receive script.
hertzog pushed a commit to branch debian/squeeze
in repository python-django.
commit 22968976e824d8544c116fe9468a3f181a18f93a
Author: Raphaël Hertzog <hertzog at debian.org>
Date: Mon Sep 29 07:56:22 2014 +0000
LTS security upload: https://www.djangoproject.com/weblog/2014/aug/20/security/
* LTS security upload:
https://www.djangoproject.com/weblog/2014/aug/20/security/
- CVE-2014-0480.patch: reverse() generating URLs pointing
to other hosts
- CVE-2014-0481.patch: file upload denial of service
- CVE-2014-0482.patch: RemoteUserMiddleware session hijacking
- CVE-2014-0483.patch: data leakage via querystring manipulation in admin
* Add no-network-access-on-builder.patch to disable regression tests
using the network.
* Add get-random-string-backport.patch to backport get_random_string()
needed by CVE-2014-0481.patch.
---
debian/changelog | 16 ++
debian/patches/CVE-2014-0480.patch | 96 ++++++++++
debian/patches/CVE-2014-0481.patch | 211 ++++++++++++++++++++++
debian/patches/CVE-2014-0482.patch | 92 ++++++++++
debian/patches/CVE-2014-0483.patch | 43 +++++
debian/patches/get-random-string-backport.patch | 53 ++++++
debian/patches/no-network-access-on-builder.patch | 30 +++
debian/patches/series | 6 +
8 files changed, 547 insertions(+)
diff --git a/debian/changelog b/debian/changelog
index 8bcfc7d..dba59dd 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,19 @@
+python-django (1.2.3-3+squeeze11) squeeze-lts; urgency=low
+
+ * LTS security upload:
+ https://www.djangoproject.com/weblog/2014/aug/20/security/
+ - CVE-2014-0480.patch: reverse() generating URLs pointing
+ to other hosts
+ - CVE-2014-0481.patch: file upload denial of service
+ - CVE-2014-0482.patch: RemoteUserMiddleware session hijacking
+ - CVE-2014-0483.patch: data leakage via querystring manipulation in admin
+ * Add no-network-access-on-builder.patch to disable regression tests
+ using the network.
+ * Add get-random-string-backport.patch to backport get_random_string()
+ needed by CVE-2014-0481.patch.
+
+ -- Raphaël Hertzog <hertzog at debian.org> Mon, 29 Sep 2014 07:52:17 +0000
+
python-django (1.2.3-3+squeeze10) squeeze-security; urgency=high
* Non-maintainer upload by the Security Team.
diff --git a/debian/patches/CVE-2014-0480.patch b/debian/patches/CVE-2014-0480.patch
new file mode 100644
index 0000000..4f7834d
--- /dev/null
+++ b/debian/patches/CVE-2014-0480.patch
@@ -0,0 +1,96 @@
+Description: Prevented data leakage in contrib.admin via query string manipulation
+ Patch backported by Thorsten Alteholz and Raphaël Hertzog.
+Origin: backport, https://github.com/django/django/commit/027bd348642007617518379f8b02546abacaa6e0
+Author: Simon Charette <charette.s at gmail.com>
+Last-Update: 2014-09-26
+
+--- /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
+@@ -306,6 +306,24 @@ class ModelAdmin(BaseModelAdmin):
+ return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
+ media = property(_media)
+
++ 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."
+ opts = self.opts
+--- a/django/contrib/admin/views/main.py
++++ b/django/contrib/admin/views/main.py
+@@ -1,4 +1,5 @@
+ from django.contrib.admin.filterspecs import FilterSpec
++from django.contrib.admin.exceptions import DisallowedModelAdminToField
+ from django.contrib.admin.options import IncorrectLookupParameters
+ from django.contrib.admin.util import quote
+ from django.core.exceptions import SuspiciousOperation
+@@ -50,7 +51,10 @@ class ChangeList(object):
+ 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
+@@ -11,6 +11,8 @@ from django.contrib.contenttypes.models
+ 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 TO_FIELD_VAR
++from django.contrib.admin.exceptions import DisallowedModelAdminToField
+ from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
+ from django.forms.util import ErrorList
+ from django.test import TestCase
+@@ -303,6 +305,22 @@ class AdminViewBasicTest(TestCase):
+ self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
+ )
+
++ def test_disallowed_to_field(self):
++ self.assertRaises(DisallowedModelAdminToField,
++ 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.
++ self.assertRaises(DisallowedModelAdminToField,
++ 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)
++
+ class SaveAsTests(TestCase):
+ fixtures = ['admin-views-users.xml','admin-views-person.xml']
+
diff --git a/debian/patches/CVE-2014-0481.patch b/debian/patches/CVE-2014-0481.patch
new file mode 100644
index 0000000..6448a4b
--- /dev/null
+++ b/debian/patches/CVE-2014-0481.patch
@@ -0,0 +1,211 @@
+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,12 +1,12 @@
+ import os
+ import errno
+ import urlparse
+-import itertools
+
+ 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
+ from django.utils.functional import LazyObject
+ from django.utils.importlib import import_module
+@@ -66,13 +66,12 @@ class Storage(object):
+ """
+ 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 @@ the provided filename into account. The
+ 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/tests/regressiontests/file_storage/tests.py
++++ b/tests/regressiontests/file_storage/tests.py
+@@ -1,5 +1,6 @@
+ # -*- coding: utf-8 -*-
+ import os
++import re
+ import shutil
+ import sys
+ import tempfile
+@@ -31,15 +32,15 @@ except ImportError:
+
+ class FileStorageTests(unittest.TestCase):
+ storage_class = FileSystemStorage
+-
++
+ def setUp(self):
+ self.temp_dir = tempfile.mktemp()
+ os.makedirs(self.temp_dir)
+ self.storage = self.storage_class(location=self.temp_dir)
+-
++
+ def tearDown(self):
+ os.rmdir(self.temp_dir)
+-
++
+ def test_file_access_options(self):
+ """
+ Standard file access options are available, and work as expected.
+@@ -53,7 +54,7 @@ class FileStorageTests(unittest.TestCase
+ f = self.storage.open('storage_test', 'r')
+ self.assertEqual(f.read(), 'storage contents')
+ f.close()
+-
++
+ self.storage.delete('storage_test')
+ self.failIf(self.storage.exists('storage_test'))
+
+@@ -81,7 +82,7 @@ class CustomStorage(FileSystemStorage):
+
+ class CustomStorageTests(FileStorageTests):
+ storage_class = CustomStorage
+-
++
+ def test_custom_get_available_name(self):
+ first = self.storage.save('custom_storage', ContentFile('custom contents'))
+ self.assertEqual(first, 'custom_storage')
+@@ -109,6 +110,9 @@ class SlowFile(ContentFile):
+ time.sleep(1)
+ return super(ContentFile, self).chunks()
+
++FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}'
++
++
+ class FileSaveRaceConditionTest(TestCase):
+ def setUp(self):
+ self.storage_dir = tempfile.mkdtemp()
+@@ -125,10 +129,9 @@ class FileSaveRaceConditionTest(TestCase
+ self.thread.start()
+ name = self.save_file('conflict')
+ self.thread.join()
+- self.assert_(self.storage.exists('conflict'))
+- self.assert_(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')
++ self.assertTrue(re.match('conflict_%s' % FILE_SUFFIX_REGEX, files[1]))
+
+ class FileStoragePermissions(TestCase):
+ def setUp(self):
+@@ -165,9 +168,11 @@ class FileStoragePathParsing(TestCase):
+ self.storage.save('dotted.path/test', ContentFile("1"))
+ self.storage.save('dotted.path/test', ContentFile("2"))
+
+- self.failIf(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path')))
+- self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test')))
+- self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1')))
++ 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.assertEqual(files[0], 'test')
++ self.assertTrue(re.match('test_%s' % FILE_SUFFIX_REGEX, files[1]))
++
+
+ def test_first_character_dot(self):
+ """
+@@ -180,10 +185,13 @@ class FileStoragePathParsing(TestCase):
+ self.assert_(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.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/_1.test')))
++ self.assertTrue(re.match('_%s.test' % FILE_SUFFIX_REGEX, files[1]))
+ else:
+- self.assert_(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1')))
++ self.assertTrue(re.match('.test_%s' % FILE_SUFFIX_REGEX, files[1]))
++
+
+ if Image is not None:
+ class DimensionClosingBug(TestCase):
+--- a/tests/modeltests/files/models.py
++++ b/tests/modeltests/files/models.py
+@@ -90,8 +90,8 @@ True
+
+ >>> obj2 = Storage()
+ >>> obj2.normal.save('django_test.txt', ContentFile('more content'))
+->>> obj2.normal
+-<FieldFile: tests/django_test_1.txt>
++>>> obj2.normal # doctest: +ELLIPSIS
++<FieldFile: tests/django_test_....txt>
+ >>> obj2.normal.size
+ 12
+
+@@ -100,24 +100,26 @@ True
+ >>> from django.core.cache import cache
+ >>> cache.set('obj1', obj1)
+ >>> cache.set('obj2', obj2)
+->>> cache.get('obj2').normal
+-<FieldFile: tests/django_test_1.txt>
++>>> cache.get('obj2').normal # doctest: +ELLIPSIS
++<FieldFile: tests/django_test_....txt>
+
+ # Deleting an object deletes the file it uses, if there are no other objects
+ # still using that file.
+
+ >>> obj2.delete()
+ >>> obj2.normal.save('django_test.txt', ContentFile('more content'))
+->>> obj2.normal
+-<FieldFile: tests/django_test_1.txt>
++>>> obj2.normal # doctest: +ELLIPSIS
++<FieldFile: tests/django_test_....txt>
+
+ # Multiple files with the same name get _N appended to them.
+
+ >>> objs = [Storage() for i in range(3)]
+ >>> for o in objs:
+ ... o.normal.save('multiple_files.txt', ContentFile('Same Content'))
+->>> [o.normal for o in objs]
+-[<FieldFile: tests/multiple_files.txt>, <FieldFile: tests/multiple_files_1.txt>, <FieldFile: tests/multiple_files_2.txt>]
++>>> [o.normal for o in objs] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
++[<FieldFile: tests/multiple_files.txt>,
++ <FieldFile: tests/multiple_files_....txt>,
++ <FieldFile: tests/multiple_files_....txt>]
+ >>> for o in objs:
+ ... o.delete()
+
diff --git a/debian/patches/CVE-2014-0482.patch b/debian/patches/CVE-2014-0482.patch
new file mode 100644
index 0000000..4c53d34
--- /dev/null
+++ b/debian/patches/CVE-2014-0482.patch
@@ -0,0 +1,92 @@
+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.
+
+Index: python-django-1.2.3/django/contrib/auth/middleware.py
+===================================================================
+--- python-django-1.2.3.orig/django/contrib/auth/middleware.py 2014-08-29 19:49:28.000000000 +0200
++++ python-django-1.2.3/django/contrib/auth/middleware.py 2014-08-29 19:49:28.000000000 +0200
+@@ -1,4 +1,5 @@
+ from django.contrib import auth
++from django.contrib.auth.backends import RemoteUserBackend
+ from django.core.exceptions import ImproperlyConfigured
+
+
+@@ -48,9 +49,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
+@@ -58,6 +61,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)
+@@ -79,3 +87,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)
+Index: python-django-1.2.3/django/contrib/auth/tests/remote_user.py
+===================================================================
+--- python-django-1.2.3.orig/django/contrib/auth/tests/remote_user.py 2014-08-29 19:49:28.000000000 +0200
++++ python-django-1.2.3/django/contrib/auth/tests/remote_user.py 2014-08-29 19:49:28.000000000 +0200
+@@ -92,6 +92,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/CVE-2014-0483.patch b/debian/patches/CVE-2014-0483.patch
new file mode 100644
index 0000000..ac43bfb
--- /dev/null
+++ b/debian/patches/CVE-2014-0483.patch
@@ -0,0 +1,43 @@
+Description: Prevented reverse() from generating URLs pointing to other hosts
+ Backport of made by Raphaël Hertzog.
+Origin: backport, https://github.com/django/django/commit/c2fe73133b62a1d9e8f7a6b43966570b14618d7e
+Forwarded: not-needed
+Author: Florian Apolloner <florian at apolloner.eu>
+
+--- a/django/core/urlresolvers.py
++++ b/django/core/urlresolvers.py
+@@ -307,6 +307,8 @@ class RegexURLResolver(object):
+ unicode_kwargs = dict([(k, force_unicode(v)) for (k, v) in kwargs.items()])
+ candidate = result % unicode_kwargs
+ if re.search(u'^%s' % pattern, candidate, re.UNICODE):
++ if candidate.startswith('/'):
++ candidate = '%%2F%s' % candidate[1:]
+ 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
+@@ -106,7 +106,10 @@ test_data = (
+ ('kwargs_view', '/arg_view/10/', [], {'arg1':10}),
+ ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/', [], {}),
+ ('regressiontests.urlpatterns_reverse.views.absolute_kwargs_view', '/absolute_arg_view/10/', [], {'arg1':10}),
+- ('non_path_include', '/includes/non_path_include/', [], {})
++ ('non_path_include', '/includes/non_path_include/', [], {}),
++
++ # Security tests
++ ('security', '/%2Fexample.com/security/', ['/example.com'], {}),
+
+ )
+
+--- a/tests/regressiontests/urlpatterns_reverse/urls.py
++++ b/tests/regressiontests/urlpatterns_reverse/urls.py
+@@ -63,6 +63,9 @@ urlpatterns = patterns('',
+
+ url('^includes/', include(other_patterns)),
+
++
++ # Security tests
++ url('(.+)/security/$', empty_view, name='security'),
+ )
+
+
diff --git a/debian/patches/get-random-string-backport.patch b/debian/patches/get-random-string-backport.patch
new file mode 100644
index 0000000..4d56780
--- /dev/null
+++ b/debian/patches/get-random-string-backport.patch
@@ -0,0 +1,53 @@
+Description: Backport get_random_string from django.utils.crypto
+ It's needed by on the the following security fixes.
+Author: Raphaël Hertzog <hertzog at debian.org>, Thorsten Alteholz <alteholz at debian.org>
+Origin: backport, file copy + hand edit
+Last-Update: 2014-09-29
+--- /dev/null
++++ b/django/utils/crypto.py
+@@ -0,0 +1,45 @@
++"""
++Django's standard crypto functions and utilities.
++"""
++
++import hashlib
++import time
++
++# Use the system PRNG if possible
++import random
++try:
++ random = random.SystemRandom()
++ using_sysrandom = True
++except NotImplementedError:
++ import warnings
++ warnings.warn('A secure pseudo-random number generator is not available '
++ 'on your system. Falling back to Mersenne Twister.')
++ using_sysrandom = False
++
++from django.conf import settings
++
++
++def get_random_string(length=12,
++ allowed_chars='abcdefghijklmnopqrstuvwxyz'
++ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'):
++ """
++ Returns a securely generated random string.
++
++ The default length of 12 with the a-z, A-Z, 0-9 character set returns
++ a 71-bit value. log_2((26+26+10)^12) =~ 71 bits
++ """
++ if not using_sysrandom:
++ # This is ugly, and a hack, but it makes things better than
++ # the alternative of predictability. This re-seeds the PRNG
++ # using a value that is hard for an attacker to predict, every
++ # time a random string is required. This may change the
++ # properties of the chosen random sequence slightly, but this
++ # is better than absolute predictability.
++ random.seed(
++ hashlib.sha256(
++ "%s%s%s" % (
++ random.getstate(),
++ time.time(),
++ settings.SECRET_KEY)
++ ).digest())
++ return ''.join([random.choice(allowed_chars) for i in range(length)])
diff --git a/debian/patches/no-network-access-on-builder.patch b/debian/patches/no-network-access-on-builder.patch
new file mode 100644
index 0000000..d808005
--- /dev/null
+++ b/debian/patches/no-network-access-on-builder.patch
@@ -0,0 +1,30 @@
+Index: python-django-1.2.3/tests/regressiontests/forms/error_messages.py
+===================================================================
+--- python-django-1.2.3.orig/tests/regressiontests/forms/error_messages.py 2010-01-05 04:56:19.000000000 +0100
++++ python-django-1.2.3/tests/regressiontests/forms/error_messages.py 2014-09-18 15:47:17.000000000 +0200
+@@ -224,25 +224,6 @@
+ ...
+ ValidationError: [u'EMPTY FILE']
+
+-# URLField ##################################################################
+-
+->>> e = {'required': 'REQUIRED'}
+->>> e['invalid'] = 'INVALID'
+->>> e['invalid_link'] = 'INVALID LINK'
+->>> f = URLField(verify_exists=True, error_messages=e)
+->>> f.clean('')
+-Traceback (most recent call last):
+-...
+-ValidationError: [u'REQUIRED']
+->>> f.clean('abc.c')
+-Traceback (most recent call last):
+-...
+-ValidationError: [u'INVALID']
+->>> f.clean('http://www.broken.djangoproject.com')
+-Traceback (most recent call last):
+-...
+-ValidationError: [u'INVALID LINK']
+-
+ # BooleanField ################################################################
+
+ >>> e = {'required': 'REQUIRED'}
diff --git a/debian/patches/series b/debian/patches/series
index 2b3f5c1..b263721 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -31,3 +31,9 @@ CVE-2014-0473.patch
CVE-2014-0474.patch
CVE-2014-1418.patch
CVE-2014-3730.patch
+no-network-access-on-builder.patch
+CVE-2014-0482.patch
+CVE-2014-0483.patch
+CVE-2014-0480.patch
+get-random-string-backport.patch
+CVE-2014-0481.patch
--
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