Ticket #11025: 11025.diff

File 11025.diff, 14.9 KB (added by Chris Beaven, 14 years ago)
  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    index cd85ce0..e4b0c92 100644
    a b LOGOUT_URL = '/accounts/logout/'  
    473473
    474474LOGIN_REDIRECT_URL = '/accounts/profile/'
    475475
     476LOGIN_URL_NEXT_ARG = 'next'
     477
    476478# The number of days a password reset link is valid for
    477479PASSWORD_RESET_TIMEOUT_DAYS = 3
    478480
  • django/contrib/auth/__init__.py

    diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
    index a184aea..861bad6 100644
    a b  
    11import datetime
    22from warnings import warn
     3from django.conf import settings
    34from django.core.exceptions import ImproperlyConfigured
    45from django.utils.importlib import import_module
    56
    67SESSION_KEY = '_auth_user_id'
    78BACKEND_SESSION_KEY = '_auth_user_backend'
    8 REDIRECT_FIELD_NAME = 'next'
     9REDIRECT_FIELD_NAME = settings.LOGIN_URL_NEXT_ARG
    910
    1011def load_backend(path):
    1112    i = path.rfind('.')
    def load_backend(path):  
    3233    return cls()
    3334
    3435def get_backends():
    35     from django.conf import settings
    3636    backends = []
    3737    for backend_path in settings.AUTHENTICATION_BACKENDS:
    3838        backends.append(load_backend(backend_path))
  • django/contrib/auth/decorators.py

    diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py
    index 7d7a0cd..64b77a5 100644
    a b  
     1import urlparse
    12try:
    2     from functools import update_wrapper, wraps
     3    from functools import wraps
    34except ImportError:
    4     from django.utils.functional import update_wrapper, wraps  # Python 2.4 fallback.
     5    from django.utils.functional import wraps  # Python 2.4 fallback.
    56
     7from django.conf import settings
    68from django.contrib.auth import REDIRECT_FIELD_NAME
    7 from django.http import HttpResponseRedirect
    89from django.utils.decorators import available_attrs
    9 from django.utils.http import urlquote
    1010
    1111
    1212def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
    def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE  
    1515    redirecting to the log-in page if necessary. The test should be a callable
    1616    that takes the user object and returns True if the user passes.
    1717    """
    18     if not login_url:
    19         from django.conf import settings
    20         login_url = settings.LOGIN_URL
    2118
    2219    def decorator(view_func):
     20        @wraps(view_func, assigned=available_attrs(view_func))
    2321        def _wrapped_view(request, *args, **kwargs):
    2422            if test_func(request.user):
    2523                return view_func(request, *args, **kwargs)
    26             path = urlquote(request.get_full_path())
    27             tup = login_url, redirect_field_name, path
    28             return HttpResponseRedirect('%s?%s=%s' % tup)
    29         return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view)
     24            path = request.build_absolute_uri()
     25            # If the login url is the same scheme and net location then just
     26            # use the path as the "next" url.
     27            login_scheme, login_netloc = urlparse.urlparse(login_url or
     28                                                        settings.LOGIN_URL)[:2]
     29            current_scheme, current_netloc = urlparse.urlparse(path)[:2]
     30            if ((not login_scheme or login_scheme == current_scheme) and
     31                (not login_netloc or login_netloc == current_netloc)):
     32                path = request.get_full_path()
     33            from django.contrib.auth.views import redirect_to_login
     34            return redirect_to_login(path, login_url, redirect_field_name)
     35        return _wrapped_view
    3036    return decorator
    3137
    3238
  • django/contrib/auth/tests/__init__.py

    diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
    index a1d02b6..b969b52 100644
    a b from django.contrib.auth.tests.remote_user \  
    66        import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
    77from django.contrib.auth.tests.models import ProfileTestCase
    88from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS
    9 from django.contrib.auth.tests.views \
    10         import PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest
     9from django.contrib.auth.tests.views import PasswordResetTest, \
     10    ChangePasswordTest, LoginTest, LogoutTest, LoginURLSettings
    1111
    1212# The password for the fixture data users is 'password'
    1313
  • django/contrib/auth/tests/decorators.py

    diff --git a/django/contrib/auth/tests/decorators.py b/django/contrib/auth/tests/decorators.py
    index 0240a76..e58bbfa 100644
    a b class LoginRequiredTestCase(AuthViewsTestCase):  
    4242        login_required decorator with a login_url set.
    4343        """
    4444        self.testLoginRequired(view_url='/login_required_login_url/',
    45             login_url='/somewhere/')
    46  No newline at end of file
     45            login_url='/somewhere/')
  • django/contrib/auth/tests/views.py

    diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py
    index 42f7f12..681d2cc 100644
    a b import urllib  
    55from django.conf import settings
    66from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
    77from django.contrib.auth.forms import AuthenticationForm
    8 from django.contrib.sites.models import Site, RequestSite
     8from django.contrib.sites.models import Site
    99from django.contrib.auth.models import User
    1010from django.test import TestCase
    1111from django.core import mail
    1212from django.core.urlresolvers import reverse
     13from django.http import QueryDict
    1314
    1415class AuthViewsTestCase(TestCase):
    1516    """
    class AuthViewsTestCase(TestCase):  
    2526        settings.LANGUAGE_CODE = 'en'
    2627        self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
    2728        settings.TEMPLATE_DIRS = (
    28             os.path.join(
    29                 os.path.dirname(__file__),
    30                 'templates'
    31             )
    32         ,)
     29            os.path.join(os.path.dirname(__file__), 'templates'),
     30        )
     31        self.old_LOGIN_URL_NEXT_ARG = settings.LOGIN_URL_NEXT_ARG
     32        settings.LOGIN_URL_NEXT_ARG = 'next'
    3333
    3434    def tearDown(self):
    3535        settings.LANGUAGES = self.old_LANGUAGES
    3636        settings.LANGUAGE_CODE = self.old_LANGUAGE_CODE
    3737        settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS
     38        settings.LOGIN_URL_NEXT_ARG = self.old_LOGIN_URL_NEXT_ARG
    3839
    3940    def login(self, password='password'):
    4041        response = self.client.post('/login/', {
    class LoginTest(AuthViewsTestCase):  
    214215                }
    215216            )
    216217            self.assertEquals(response.status_code, 302)
    217             self.assertFalse(bad_url in response['Location'], "%s should be blocked" % bad_url)
    218 
    219         # Now, these URLs have an other URL as a GET parameter and therefore
    220         # should be allowed
    221         for url_ in ('http://example.com', 'https://example.com',
    222                     'ftp://exampel.com',  '//example.com'):
    223             safe_url = '%(url)s?%(next)s=/view/?param=%(safe_param)s' % {
     218            self.assertFalse(bad_url in response['Location'],
     219                             "%s should be blocked" % bad_url)
     220
     221        # These URLs *should* still pass the security check
     222        for good_url in ('/view/?param=http://example.com',
     223                         '/view/?param=https://example.com',
     224                         '/view?param=ftp://exampel.com',
     225                         'view/?param=//example.com',
     226                         'https:///',
     227                         '//testserver/'):
     228            safe_url = '%(url)s?%(next)s=%(good_url)s' % {
    224229                'url': login_url,
    225230                'next': REDIRECT_FIELD_NAME,
    226                 'safe_param': urllib.quote(url_)
     231                'good_url': urllib.quote(good_url)
    227232            }
    228233            response = self.client.post(safe_url, {
    229234                    'username': 'testclient',
    class LoginTest(AuthViewsTestCase):  
    231236                }
    232237            )
    233238            self.assertEquals(response.status_code, 302)
    234             self.assertTrue('/view/?param=%s' % url_ in response['Location'], "/view/?param=%s should be allowed" % url_)
     239            self.assertTrue(good_url in response['Location'],
     240                            "%s should be allowed" % good_url)
    235241
     242class LoginURLSettings(AuthViewsTestCase):
     243    urls = 'django.contrib.auth.tests.urls'
     244   
     245    def setUp(self):
     246        super(LoginURLSettings, self).setUp()
     247        self.old_LOGIN_URL = settings.LOGIN_URL
     248
     249    def tearDown(self):
     250        super(LoginURLSettings, self).tearDown()
     251        settings.LOGIN_URL = self.old_LOGIN_URL
     252
     253    def get_login_required_url(self, login_url):
     254        settings.LOGIN_URL = login_url
     255        response = self.client.get('/login_required/')
     256        self.assertEquals(response.status_code, 302)
     257        return response['Location']
     258
     259    def test_standard_login_url(self):
     260        login_url = '/login/'
     261        login_required_url = self.get_login_required_url(login_url)
     262        querystring = QueryDict('', mutable=True)
     263        querystring['next'] = '/login_required/'
     264        self.assertEqual(login_required_url,
     265             'http://testserver%s?%s' % (login_url, querystring.urlencode()))
     266
     267    def test_remote_login_url(self):
     268        login_url = 'http://remote.example.com/login'
     269        login_required_url = self.get_login_required_url(login_url)
     270        querystring = QueryDict('', mutable=True)
     271        querystring['next'] = 'http://testserver/login_required/'
     272        self.assertEqual(login_required_url,
     273                         '%s?%s' % (login_url, querystring.urlencode()))
     274
     275    def test_https_login_url(self):
     276        login_url = 'https:///login/'
     277        login_required_url = self.get_login_required_url(login_url)
     278        querystring = QueryDict('', mutable=True)
     279        querystring['next'] = 'http://testserver/login_required/'
     280        self.assertEqual(login_required_url,
     281                         '%s?%s' % (login_url, querystring.urlencode()))
     282
     283    def test_login_url_with_querystring(self):
     284        login_url = '/login/?pretty=1'
     285        login_required_url = self.get_login_required_url(login_url)
     286        querystring = QueryDict('pretty=1', mutable=True)
     287        querystring['next'] = '/login_required/'
     288        self.assertEqual(login_required_url, 'http://testserver/login/?%s' %
     289                         querystring.urlencode())
     290
     291    def test_remote_login_url_with_next_querystring(self):
     292        login_url = 'http://remote.example.com/login/'
     293        login_required_url = self.get_login_required_url('%s?next=/default/' %
     294                                                         login_url)
     295        querystring = QueryDict('', mutable=True)
     296        querystring['next'] = 'http://testserver/login_required/'
     297        self.assertEqual(login_required_url, '%s?%s' % (login_url,
     298                                                    querystring.urlencode()))
    236299       
    237300class LogoutTest(AuthViewsTestCase):
    238301    urls = 'django.contrib.auth.tests.urls'
  • django/contrib/auth/views.py

    diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
    index a55c866..f55ca79 100644
    a b  
    11import re
     2import urlparse
    23from django.conf import settings
    34from django.contrib.auth import REDIRECT_FIELD_NAME
    45# Avoid shadowing the login() view below.
    from django.views.decorators.csrf import csrf_protect  
    1112from django.core.urlresolvers import reverse
    1213from django.shortcuts import render_to_response, get_object_or_404
    1314from django.contrib.sites.models import get_current_site
    14 from django.http import HttpResponseRedirect, Http404
     15from django.http import HttpResponseRedirect, Http404, QueryDict
    1516from django.template import RequestContext
    16 from django.utils.http import urlquote, base36_to_int
     17from django.utils.http import base36_to_int
    1718from django.utils.translation import ugettext as _
    1819from django.contrib.auth.models import User
    1920from django.views.decorators.cache import never_cache
    def login(request, template_name='registration/login.html',  
    3031    if request.method == "POST":
    3132        form = authentication_form(data=request.POST)
    3233        if form.is_valid():
     34            netloc = urlparse.urlparse(redirect_to)[1]
     35
    3336            # Light security check -- make sure redirect_to isn't garbage.
    3437            if not redirect_to or ' ' in redirect_to:
    3538                redirect_to = settings.LOGIN_REDIRECT_URL
    3639
    37             # Heavier security check -- redirects to http://example.com should
    38             # not be allowed, but things like /view/?param=http://example.com
    39             # should be allowed. This regex checks if there is a '//' *before* a
    40             # question mark.
    41             elif '//' in redirect_to and re.match(r'[^\?]*//', redirect_to):
    42                     redirect_to = settings.LOGIN_REDIRECT_URL
     40            # Heavier security check -- don't allow redirection to a different
     41            # host.
     42            elif netloc and netloc != request.get_host():
     43                redirect_to = settings.LOGIN_REDIRECT_URL
    4344
    4445            # Okay, security checks complete. Log the user in.
    4546            auth_login(request, form.get_user())
    def logout_then_login(request, login_url=None):  
    8889        login_url = settings.LOGIN_URL
    8990    return logout(request, login_url)
    9091
    91 def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
     92def redirect_to_login(next, login_url=None,
     93                      redirect_field_name=REDIRECT_FIELD_NAME):
    9294    "Redirects the user to the login page, passing the given 'next' page"
    9395    if not login_url:
    9496        login_url = settings.LOGIN_URL
    95     return HttpResponseRedirect('%s?%s=%s' % (login_url, urlquote(redirect_field_name), urlquote(next)))
     97
     98    login_url_parts = list(urlparse.urlparse(login_url))
     99    if redirect_field_name:
     100        querystring = QueryDict(login_url_parts[4], mutable=True)
     101        querystring[redirect_field_name] = next
     102        login_url_parts[4] = querystring.urlencode()
     103
     104    return HttpResponseRedirect(urlparse.urlunparse(login_url_parts))
    96105
    97106# 4 views for password reset:
    98107# - password_reset sends the mail
  • docs/ref/settings.txt

    diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
    index c72b171..f4ebed2 100644
    a b Default: ``'/accounts/login/'``  
    10671067The URL where requests are redirected for login, especially when using the
    10681068:func:`~django.contrib.auth.decorators.login_required` decorator.
    10691069
     1070.. setting:: LOGIN_URL_NEXT_ARG
     1071
     1072LOGIN_URL_NEXT_ARG
     1073------------------
     1074
     1075.. versionadded:: 1.3
     1076
     1077Default: ``'next'``
     1078
     1079The argument used when building the "next url" querystring for the login URL.
     1080For example, with the default settings an anonymous user accessing a URL of
     1081``/secure_page/`` which maps to a view decorated with
     1082:func:`~django.contrib.auth.decorators.login_required` will be redirected to:
     1083``/accounts/login/?next=/secure_page/``.
     1084
    10701085.. setting:: LOGOUT_URL
    10711086
    10721087LOGOUT_URL
Back to Top