File: 0025-CVE-2019-19844.patch

package info (click to toggle)
python-django 1%3A1.10.7-2%2Bdeb9u9
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 46,768 kB
  • sloc: python: 210,877; javascript: 18,032; xml: 201; makefile: 198; sh: 145
file content (103 lines) | stat: -rw-r--r-- 4,318 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
From: Chris Lamb <lamby@debian.org>
Date: Mon, 6 Jan 2020 17:50:09 +0000
Subject: CVE-2019-19844

---
 django/contrib/auth/forms.py   | 20 +++++++++++++++++++-
 tests/auth_tests/test_forms.py | 42 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index 18ea52c..d463ec9 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -19,9 +19,23 @@ from django.utils.encoding import force_bytes
 from django.utils.html import format_html, format_html_join
 from django.utils.http import urlsafe_base64_encode
 from django.utils.safestring import mark_safe
+from django.utils.six import PY3
 from django.utils.text import capfirst
 from django.utils.translation import ugettext, ugettext_lazy as _
 
+def _unicode_ci_compare(s1, s2):
+    """
+    Perform case-insensitive comparison of two identifiers, using the
+    recommended algorithm from Unicode Technical Report 36, section
+    2.11.2(B)(2).
+    """
+    normalized1 = unicodedata.normalize('NFKC', s1)
+    normalized2 = unicodedata.normalize('NFKC', s2)
+    if PY3:
+        return normalized1.casefold() == normalized2.casefold()
+    # lower() is the best alternative available on Python 2.
+    return normalized1.lower() == normalized2.lower()
+
 
 class ReadOnlyPasswordHashWidget(forms.Widget):
     def render(self, name, value, attrs):
@@ -255,7 +269,11 @@ class PasswordResetForm(forms.Form):
         """
         active_users = get_user_model()._default_manager.filter(
             email__iexact=email, is_active=True)
-        return (u for u in active_users if u.has_usable_password())
+        return (
+            u for u in active_users
+            if u.has_usable_password() and
+            _unicode_ci_compare(email, u.email)
+        )
 
     def save(self, domain_override=None,
              subject_template_name='registration/password_reset_subject.txt',
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index 64c095b..9664d83 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -626,6 +626,48 @@ class PasswordResetFormTest(TestDataMixin, TestCase):
         self.assertFalse(form.is_valid())
         self.assertEqual(form['email'].errors, [_('Enter a valid email address.')])
 
+    def test_user_email_unicode_collision(self):
+        User.objects.create_user('mike123', 'mike@example.org', 'test123')
+        User.objects.create_user('mike456', 'mıke@example.org', 'test123')
+        data = {'email': 'mıke@example.org'}
+        form = PasswordResetForm(data)
+        if six.PY2:
+            self.assertFalse(form.is_valid())
+        else:
+            self.assertTrue(form.is_valid())
+            form.save()
+            self.assertEqual(len(mail.outbox), 1)
+            self.assertEqual(mail.outbox[0].to, ['mıke@example.org'])
+
+    def test_user_email_domain_unicode_collision(self):
+        User.objects.create_user('mike123', 'mike@ixample.org', 'test123')
+        User.objects.create_user('mike456', 'mike@ıxample.org', 'test123')
+        data = {'email': 'mike@ıxample.org'}
+        form = PasswordResetForm(data)
+        self.assertTrue(form.is_valid())
+        form.save()
+        self.assertEqual(len(mail.outbox), 1)
+        self.assertEqual(mail.outbox[0].to, ['mike@ıxample.org'])
+
+    def test_user_email_unicode_collision_nonexistent(self):
+        User.objects.create_user('mike123', 'mike@example.org', 'test123')
+        data = {'email': 'mıke@example.org'}
+        form = PasswordResetForm(data)
+        if six.PY2:
+            self.assertFalse(form.is_valid())
+        else:
+            self.assertTrue(form.is_valid())
+            form.save()
+            self.assertEqual(len(mail.outbox), 0)
+
+    def test_user_email_domain_unicode_collision_nonexistent(self):
+        User.objects.create_user('mike123', 'mike@ixample.org', 'test123')
+        data = {'email': 'mike@ıxample.org'}
+        form = PasswordResetForm(data)
+        self.assertTrue(form.is_valid())
+        form.save()
+        self.assertEqual(len(mail.outbox), 0)
+
     def test_nonexistent_email(self):
         """
         Test nonexistent email address. This should not fail because it would