File 0004-1.5.x-Fixed-DoS-possibility-in-ModelMultipleChoiceFi.patch of Package python-django.openSUSE_13.1_Update

From 8d6dfa690412465cf41b2524d84d727e121a8576 Mon Sep 17 00:00:00 2001
From: Tim Graham <timograham@gmail.com>
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

openSUSE Build Service is sponsored by