Code

Ticket #13751: openredirect-with-docs.diff

File openredirect-with-docs.diff, 4.5 KB (added by dpn, 4 years ago)
Line 
1diff -r e545a92e9079 django/conf/global_settings.py
2--- a/django/conf/global_settings.py    Fri Oct 01 09:10:27 2010 +1000
3+++ b/django/conf/global_settings.py    Sat Oct 02 09:28:51 2010 +1000
4@@ -521,3 +521,6 @@
5 
6 # The list of directories to search for fixtures
7 FIXTURE_DIRS = ()
8+
9+#Default url to redirect to to avoid Open Redirect issues. See: #13751
10+REDIRECT_FAILURE_URL = "/"
11diff -r e545a92e9079 django/http/__init__.py
12--- a/django/http/__init__.py   Fri Oct 01 09:10:27 2010 +1000
13+++ b/django/http/__init__.py   Sat Oct 02 09:28:51 2010 +1000
14@@ -3,7 +3,8 @@
15 from Cookie import BaseCookie, SimpleCookie, CookieError
16 from pprint import pformat
17 from urllib import urlencode
18-from urlparse import urljoin
19+from urlparse import urljoin, urlparse
20+import logging
21 try:
22     # The mod_python version is more efficient, so try importing it first.
23     from mod_python.util import parse_qsl
24@@ -432,9 +433,25 @@
25 class HttpResponseRedirect(HttpResponse):
26     status_code = 302
27 
28-    def __init__(self, redirect_to):
29+    def __init__(self, redirect_to, whitelist=[], fallback_to=None):
30         HttpResponse.__init__(self)
31         self['Location'] = iri_to_uri(redirect_to)
32+       
33+        if urlparse(self['Location']).scheme:
34+            effective_whitelist = whitelist + getattr(settings, 'REDIRECT_WHITELIST', [])
35+
36+            matched = False
37+
38+            for pattern in effective_whitelist:
39+                matched = re.compile(pattern).match(self['Location'])
40+
41+                if matched:
42+                    break
43+
44+            if not matched:
45+                logging.warn("Found open redirect attack to %s", self['Location'])
46+
47+                self['Location'] = fallback_to or settings.REDIRECT_FAILURE_URL
48 
49 class HttpResponsePermanentRedirect(HttpResponse):
50     status_code = 301
51diff -r e545a92e9079 docs/ref/request-response.txt
52--- a/docs/ref/request-response.txt     Fri Oct 01 09:10:27 2010 +1000
53+++ b/docs/ref/request-response.txt     Sat Oct 02 09:28:51 2010 +1000
54@@ -561,10 +561,19 @@
55 
56 .. class:: HttpResponseRedirect
57 
58-    The constructor takes a single argument -- the path to redirect to. This
59+    The constructor takes a single required argument -- the path to redirect to. This
60     can be a fully qualified URL (e.g. ``'http://www.yahoo.com/search/'``) or an
61-    absolute URL with no domain (e.g. ``'/search/'``). Note that this returns
62-    an HTTP status code 302.
63+    absolute URL with no domain (e.g. ``'/search/'``).
64+   
65+    Two optional parameters are ``whitelist`` and ``fallback_to``
66+    that are used to avoid the Open Redirect security issue. (see: http://www.google.com/support/webmasters/bin/answer.py?answer=171297 )
67+    The ``whitelist`` param is a list of regular expressions for whitelisted urls.
68+    A whitelist can also be defined in the :setting:`REDIRECT_WHITELIST` setting.
69+    Additionally, the ``fallback_to`` param is an optional URL to fallback to if
70+    none of the ``whitelist`` patterns match. You can also set the :setting:REDIRECT_FAILURE_URL
71+    setting to specify your own default fallback. By default this is set to "/".
72+   
73+    Note that this returns an HTTP status code 302.
74 
75 .. class:: HttpResponsePermanentRedirect
76 
77diff -r e545a92e9079 tests/regressiontests/httpwrappers/tests.py
78--- a/tests/regressiontests/httpwrappers/tests.py       Fri Oct 01 09:10:27 2010 +1000
79+++ b/tests/regressiontests/httpwrappers/tests.py       Sat Oct 02 09:28:51 2010 +1000
80@@ -1,7 +1,10 @@
81 import copy
82 import pickle
83 import unittest
84-from django.http import QueryDict, HttpResponse, CompatCookie, BadHeaderError
85+from django.http import QueryDict, HttpResponse, CompatCookie, BadHeaderError,\
86+    HttpResponseRedirect
87+from django.conf import settings
88+
89 
90 class QueryDictTests(unittest.TestCase):
91     def test_missing_key(self):
92@@ -254,3 +257,15 @@
93         c2 = CompatCookie()
94         c2.load(c.output())
95         self.assertEqual(c['test'].value, c2['test'].value)
96+
97+
98+class HttpResponseRedirectTests(unittest.TestCase):
99+    def test_open_redirect(self):
100+        self.assertEqual('/test', HttpResponseRedirect('/test')['Location'])
101+        self.assertEqual(settings.REDIRECT_FAILURE_URL, HttpResponseRedirect('http://djangoproject.com')['Location'])
102+
103+        r = HttpResponseRedirect('http://djangoproject.com', whitelist=[r'.*'])
104+        self.assertEqual('http://djangoproject.com', r['Location'])
105+
106+        setattr(settings, 'REDIRECT_WHITELIST', [r'.*'])
107+        self.assertEqual('http://djangoproject.com', HttpResponseRedirect('http://djangoproject.com')['Location'])