diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 3339e30eb2..e8cd821de5 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -15,8 +15,7 @@ from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.shortcuts import get_current_site from django.http import HttpResponseRedirect, QueryDict from django.shortcuts import resolve_url -from django.template.response import TemplateResponse -from django.urls import reverse, reverse_lazy +from django.urls import reverse_lazy from django.utils.decorators import method_decorator from django.utils.deprecation import RemovedInDjango21Warning from django.utils.http import is_safe_url, urlsafe_base64_decode @@ -107,23 +106,6 @@ class LoginView(SuccessURLAllowedHostsMixin, FormView): return context -def login(request, template_name='registration/login.html', - redirect_field_name=REDIRECT_FIELD_NAME, - authentication_form=AuthenticationForm, - extra_context=None, redirect_authenticated_user=False): - warnings.warn( - 'The login() view is superseded by the class-based LoginView().', - RemovedInDjango21Warning, stacklevel=2 - ) - return LoginView.as_view( - template_name=template_name, - redirect_field_name=redirect_field_name, - form_class=authentication_form, - extra_context=extra_context, - redirect_authenticated_user=redirect_authenticated_user, - )(request) - - class LogoutView(SuccessURLAllowedHostsMixin, TemplateView): """ Log out the user and display the 'You are logged out' message. @@ -184,22 +166,6 @@ class LogoutView(SuccessURLAllowedHostsMixin, TemplateView): return context -def logout(request, next_page=None, - template_name='registration/logged_out.html', - redirect_field_name=REDIRECT_FIELD_NAME, - extra_context=None): - warnings.warn( - 'The logout() view is superseded by the class-based LogoutView().', - RemovedInDjango21Warning, stacklevel=2 - ) - return LogoutView.as_view( - next_page=next_page, - template_name=template_name, - redirect_field_name=redirect_field_name, - extra_context=extra_context, - )(request) - - _sentinel = object() @@ -234,143 +200,6 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N return HttpResponseRedirect(urlunparse(login_url_parts)) -# 4 views for password reset: -# - password_reset sends the mail -# - password_reset_done shows a success message for the above -# - password_reset_confirm checks the link the user clicked and -# prompts for a new password -# - password_reset_complete shows a success message for the above - -@csrf_protect -def password_reset(request, - template_name='registration/password_reset_form.html', - email_template_name='registration/password_reset_email.html', - subject_template_name='registration/password_reset_subject.txt', - password_reset_form=PasswordResetForm, - token_generator=default_token_generator, - post_reset_redirect=None, - from_email=None, - extra_context=None, - html_email_template_name=None, - extra_email_context=None): - warnings.warn("The password_reset() view is superseded by the " - "class-based PasswordResetView().", - RemovedInDjango21Warning, stacklevel=2) - if post_reset_redirect is None: - post_reset_redirect = reverse('password_reset_done') - else: - post_reset_redirect = resolve_url(post_reset_redirect) - if request.method == "POST": - form = password_reset_form(request.POST) - if form.is_valid(): - opts = { - 'use_https': request.is_secure(), - 'token_generator': token_generator, - 'from_email': from_email, - 'email_template_name': email_template_name, - 'subject_template_name': subject_template_name, - 'request': request, - 'html_email_template_name': html_email_template_name, - 'extra_email_context': extra_email_context, - } - form.save(**opts) - return HttpResponseRedirect(post_reset_redirect) - else: - form = password_reset_form() - context = { - 'form': form, - 'title': _('Password reset'), - } - if extra_context is not None: - context.update(extra_context) - - return TemplateResponse(request, template_name, context) - - -def password_reset_done(request, - template_name='registration/password_reset_done.html', - extra_context=None): - warnings.warn("The password_reset_done() view is superseded by the " - "class-based PasswordResetDoneView().", - RemovedInDjango21Warning, stacklevel=2) - context = { - 'title': _('Password reset sent'), - } - if extra_context is not None: - context.update(extra_context) - - return TemplateResponse(request, template_name, context) - - -# Doesn't need csrf_protect since no-one can guess the URL -@sensitive_post_parameters() -@never_cache -def password_reset_confirm(request, uidb64=None, token=None, - template_name='registration/password_reset_confirm.html', - token_generator=default_token_generator, - set_password_form=SetPasswordForm, - post_reset_redirect=None, - extra_context=None): - """ - Check the hash in a password reset link and present a form for entering a - new password. - """ - warnings.warn("The password_reset_confirm() view is superseded by the " - "class-based PasswordResetConfirmView().", - RemovedInDjango21Warning, stacklevel=2) - assert uidb64 is not None and token is not None # checked by URLconf - if post_reset_redirect is None: - post_reset_redirect = reverse('password_reset_complete') - else: - post_reset_redirect = resolve_url(post_reset_redirect) - try: - # urlsafe_base64_decode() decodes to bytestring - uid = urlsafe_base64_decode(uidb64).decode() - user = UserModel._default_manager.get(pk=uid) - except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist): - user = None - - if user is not None and token_generator.check_token(user, token): - validlink = True - title = _('Enter new password') - if request.method == 'POST': - form = set_password_form(user, request.POST) - if form.is_valid(): - form.save() - return HttpResponseRedirect(post_reset_redirect) - else: - form = set_password_form(user) - else: - validlink = False - form = None - title = _('Password reset unsuccessful') - context = { - 'form': form, - 'title': title, - 'validlink': validlink, - } - if extra_context is not None: - context.update(extra_context) - - return TemplateResponse(request, template_name, context) - - -def password_reset_complete(request, - template_name='registration/password_reset_complete.html', - extra_context=None): - warnings.warn("The password_reset_complete() view is superseded by the " - "class-based PasswordResetCompleteView().", - RemovedInDjango21Warning, stacklevel=2) - context = { - 'login_url': resolve_url(settings.LOGIN_URL), - 'title': _('Password reset complete'), - } - if extra_context is not None: - context.update(extra_context) - - return TemplateResponse(request, template_name, context) - - # Class-based password reset views # - PasswordResetView sends the mail # - PasswordResetDoneView shows a success message for the above @@ -511,57 +340,6 @@ class PasswordResetCompleteView(PasswordContextMixin, TemplateView): return context -@sensitive_post_parameters() -@csrf_protect -@login_required -def password_change(request, - template_name='registration/password_change_form.html', - post_change_redirect=None, - password_change_form=PasswordChangeForm, - extra_context=None): - warnings.warn("The password_change() view is superseded by the " - "class-based PasswordChangeView().", - RemovedInDjango21Warning, stacklevel=2) - if post_change_redirect is None: - post_change_redirect = reverse('password_change_done') - else: - post_change_redirect = resolve_url(post_change_redirect) - if request.method == "POST": - form = password_change_form(user=request.user, data=request.POST) - if form.is_valid(): - form.save() - # Updating the password logs out all other sessions for the user - # except the current one. - update_session_auth_hash(request, form.user) - return HttpResponseRedirect(post_change_redirect) - else: - form = password_change_form(user=request.user) - context = { - 'form': form, - 'title': _('Password change'), - } - if extra_context is not None: - context.update(extra_context) - - return TemplateResponse(request, template_name, context) - - -@login_required -def password_change_done(request, - template_name='registration/password_change_done.html', - extra_context=None): - warnings.warn("The password_change_done() view is superseded by the " - "class-based PasswordChangeDoneView().", - RemovedInDjango21Warning, stacklevel=2) - context = { - 'title': _('Password change successful'), - } - if extra_context is not None: - context.update(extra_context) - - return TemplateResponse(request, template_name, context) - - class PasswordChangeView(PasswordContextMixin, FormView): form_class = PasswordChangeForm success_url = reverse_lazy('password_change_done') diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 12859b182e..bc487cece9 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -714,7 +714,7 @@ details on these changes. remove calls to this method, and instead ensure that their auth related views are CSRF protected, which ensures that cookies are enabled. -* The version of :func:`django.contrib.auth.views.password_reset_confirm` that +* The version of ``django.contrib.auth.views.password_reset_confirm()`` that supports base36 encoded user IDs (``django.contrib.auth.views.password_reset_confirm_uidb36``) will be removed. If your site has been running Django 1.6 for more than diff --git a/docs/releases/1.10.7.txt b/docs/releases/1.10.7.txt index c5caa65143..590a15024d 100644 --- a/docs/releases/1.10.7.txt +++ b/docs/releases/1.10.7.txt @@ -10,7 +10,7 @@ CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric r ============================================================================================ Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security check for these redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric URLs (e.g. ``http:999999999``) "safe" when they shouldn't be. diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index b235afb4f5..cf59598977 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -118,19 +118,19 @@ Minor features subclassed ``django.contrib.auth.hashers.PBKDF2PasswordHasher`` to change the default value. -* The :func:`~django.contrib.auth.views.logout` view sends "no-cache" headers +* The ``django.contrib.auth.views.logout()`` view sends "no-cache" headers to prevent an issue where Safari caches redirects and prevents a user from being able to log out. -* Added the optional ``backend`` argument to :func:`~django.contrib.auth.login` +* Added the optional ``backend`` argument to :func:`django.contrib.auth.login` to allow using it without credentials. * The new :setting:`LOGOUT_REDIRECT_URL` setting controls the redirect of the - :func:`~django.contrib.auth.views.logout` view, if the view doesn't get a + ``django.contrib.auth.views.logout()`` view, if the view doesn't get a ``next_page`` argument. * The new ``redirect_authenticated_user`` parameter for the - :func:`~django.contrib.auth.views.login` view allows redirecting + ``django.contrib.auth.views.login()`` view allows redirecting authenticated users visiting the login page. * The new :class:`~django.contrib.auth.backends.AllowAllUsersModelBackend` and diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index 88c83ecd27..e1cd547b5e 100644 --- a/docs/releases/1.3.txt +++ b/docs/releases/1.3.txt @@ -636,7 +636,7 @@ message as a nonexistent account. Password reset view now accepts ``from_email`` ---------------------------------------------- -The :func:`django.contrib.auth.views.password_reset` view now accepts a +The ``django.contrib.auth.views.password_reset()`` view now accepts a ``from_email`` parameter, which is passed to the ``password_reset_form``’s ``save()`` method as a keyword argument. If you are using this view with a custom password reset form, then you will need to ensure your form's ``save()`` diff --git a/docs/releases/1.4.13.txt b/docs/releases/1.4.13.txt index 14e5af9a0d..89b4473e55 100644 --- a/docs/releases/1.4.13.txt +++ b/docs/releases/1.4.13.txt @@ -36,7 +36,7 @@ which are accepted by some browsers. This allows a user to be redirected to an unsafe URL unexpectedly. Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login`, ``django.contrib.comments``, and +``django.contrib.auth.views.login()``, ``django.contrib.comments``, and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) did not correctly validate some malformed diff --git a/docs/releases/1.4.18.txt b/docs/releases/1.4.18.txt index 124d271bc4..075e08b32a 100644 --- a/docs/releases/1.4.18.txt +++ b/docs/releases/1.4.18.txt @@ -35,7 +35,7 @@ Mitigated possible XSS attack via user-supplied redirect URLs ============================================================= Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) didn't strip leading whitespace on the tested URL and as such considered URLs like diff --git a/docs/releases/1.4.20.txt b/docs/releases/1.4.20.txt index f2ca5ac103..d40bd20edd 100644 --- a/docs/releases/1.4.20.txt +++ b/docs/releases/1.4.20.txt @@ -10,7 +10,7 @@ Mitigated possible XSS attack via user-supplied redirect URLs ============================================================= Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with leading control characters and so considered URLs like ``\x08javascript:...`` diff --git a/docs/releases/1.4.22.txt b/docs/releases/1.4.22.txt index bed56d42e7..ed5ba2ef92 100644 --- a/docs/releases/1.4.22.txt +++ b/docs/releases/1.4.22.txt @@ -14,7 +14,7 @@ Denial-of-service possibility in ``logout()`` view by filling session store =========================================================================== Previously, a session could be created when anonymously accessing the -:func:`django.contrib.auth.views.logout` view (provided it wasn't decorated +``django.contrib.auth.views.logout()`` view (provided it wasn't decorated with :func:`~django.contrib.auth.decorators.login_required` as done in the admin). This could allow an attacker to easily create many new session records by sending repeated requests, potentially filling up the session store or diff --git a/docs/releases/1.4.6.txt b/docs/releases/1.4.6.txt index 39dc6f8dca..1e9590c315 100644 --- a/docs/releases/1.4.6.txt +++ b/docs/releases/1.4.6.txt @@ -13,7 +13,7 @@ Mitigated possible XSS attack via user-supplied redirect URLs ============================================================= Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login`, ``django.contrib.comments``, and +``django.contrib.auth.views.login()``, ``django.contrib.comments``, and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) didn't check if the scheme is ``http(s)`` diff --git a/docs/releases/1.5.2.txt b/docs/releases/1.5.2.txt index 9a53ced78c..33341342a6 100644 --- a/docs/releases/1.5.2.txt +++ b/docs/releases/1.5.2.txt @@ -10,7 +10,7 @@ Mitigated possible XSS attack via user-supplied redirect URLs ============================================================= Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login`, ``django.contrib.comments``, and +``django.contrib.auth.views.login()``, ``django.contrib.comments``, and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) didn't check if the scheme is ``http(s)`` diff --git a/docs/releases/1.5.8.txt b/docs/releases/1.5.8.txt index 93ad815cec..136b953185 100644 --- a/docs/releases/1.5.8.txt +++ b/docs/releases/1.5.8.txt @@ -36,7 +36,7 @@ which are accepted by some browsers. This allows a user to be redirected to an unsafe URL unexpectedly. Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login`, ``django.contrib.comments``, and +``django.contrib.auth.views.login()``, ``django.contrib.comments``, and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) did not correctly validate some malformed diff --git a/docs/releases/1.6.10.txt b/docs/releases/1.6.10.txt index e99a8256a7..ee91dc8a3a 100644 --- a/docs/releases/1.6.10.txt +++ b/docs/releases/1.6.10.txt @@ -34,7 +34,7 @@ Mitigated possible XSS attack via user-supplied redirect URLs ============================================================= Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) didn't strip leading whitespace on the tested URL and as such considered URLs like diff --git a/docs/releases/1.6.11.txt b/docs/releases/1.6.11.txt index cf94fedbf5..8cf81f89bf 100644 --- a/docs/releases/1.6.11.txt +++ b/docs/releases/1.6.11.txt @@ -27,7 +27,7 @@ Mitigated possible XSS attack via user-supplied redirect URLs ============================================================= Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with leading control characters and so considered URLs like ``\x08javascript:...`` diff --git a/docs/releases/1.6.5.txt b/docs/releases/1.6.5.txt index cacb522af8..77e82a668f 100644 --- a/docs/releases/1.6.5.txt +++ b/docs/releases/1.6.5.txt @@ -36,7 +36,7 @@ which are accepted by some browsers. This allows a user to be redirected to an unsafe URL unexpectedly. Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login`, ``django.contrib.comments``, and +``django.contrib.auth.views.login()``, ``django.contrib.comments``, and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) did not correctly validate some malformed diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index c3dd9acf4b..640a2bd93b 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -280,10 +280,10 @@ Minor features :attr:`~django.http.HttpResponse.reason_phrase`. * When giving the URL of the next page for - :func:`~django.contrib.auth.views.logout`, - :func:`~django.contrib.auth.views.password_reset`, - :func:`~django.contrib.auth.views.password_reset_confirm`, - and :func:`~django.contrib.auth.views.password_change`, you can now pass + ``django.contrib.auth.views.logout()``, + ``django.contrib.auth.views.password_reset()``, + ``django.contrib.auth.views.password_reset_confirm()``, + and ``django.contrib.auth.views.password_change()``, you can now pass URL names and they will be resolved. * The new :option:`dumpdata --pks` option specifies the primary keys of objects @@ -742,17 +742,17 @@ can set the Past versions of Django used base 36 encoding of the ``User`` primary key in the password reset views and URLs -(:func:`django.contrib.auth.views.password_reset_confirm`). Base 36 encoding is +(``django.contrib.auth.views.password_reset_confirm()``). Base 36 encoding is sufficient if the user primary key is an integer, however, with the introduction of custom user models in Django 1.5, that assumption may no longer be true. -:func:`django.contrib.auth.views.password_reset_confirm` has been modified to +``django.contrib.auth.views.password_reset_confirm()`` has been modified to take a ``uidb64`` parameter instead of ``uidb36``. If you are reversing this view, for example in a custom ``password_reset_email.html`` template, be sure to update your code. -A temporary shim for :func:`django.contrib.auth.views.password_reset_confirm` +A temporary shim for ``django.contrib.auth.views.password_reset_confirm()`` that will allow password reset links generated prior to Django 1.6 to continue to work has been added to provide backwards compatibility; this will be removed in Django 1.7. Thus, as long as your site has been running Django 1.6 for more diff --git a/docs/releases/1.7.10.txt b/docs/releases/1.7.10.txt index 7fb8e85a55..7d4c773063 100644 --- a/docs/releases/1.7.10.txt +++ b/docs/releases/1.7.10.txt @@ -10,7 +10,7 @@ Denial-of-service possibility in ``logout()`` view by filling session store =========================================================================== Previously, a session could be created when anonymously accessing the -:func:`django.contrib.auth.views.logout` view (provided it wasn't decorated +``django.contrib.auth.views.logout()`` view (provided it wasn't decorated with :func:`~django.contrib.auth.decorators.login_required` as done in the admin). This could allow an attacker to easily create many new session records by sending repeated requests, potentially filling up the session store or diff --git a/docs/releases/1.7.3.txt b/docs/releases/1.7.3.txt index f8e0dc8b81..fb33b98883 100644 --- a/docs/releases/1.7.3.txt +++ b/docs/releases/1.7.3.txt @@ -34,7 +34,7 @@ Mitigated possible XSS attack via user-supplied redirect URLs ============================================================= Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) didn't strip leading whitespace on the tested URL and as such considered URLs like diff --git a/docs/releases/1.7.7.txt b/docs/releases/1.7.7.txt index 500a32a85e..f20ee127bc 100644 --- a/docs/releases/1.7.7.txt +++ b/docs/releases/1.7.7.txt @@ -27,7 +27,7 @@ Mitigated possible XSS attack via user-supplied redirect URLs ============================================================= Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security checks for these redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with leading control characters and so considered URLs like ``\x08javascript:...`` diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 6da941bc99..0cd9a033df 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -421,7 +421,7 @@ Minor features ` method to more easily customize the login policy. -* :func:`django.contrib.auth.views.password_reset` takes an optional +* ``django.contrib.auth.views.password_reset()`` takes an optional ``html_email_template_name`` parameter used to send a multipart HTML email for password resets. @@ -1846,7 +1846,7 @@ remove usage of these features. * The ``check_for_test_cookie`` method in :class:`~django.contrib.auth.forms.AuthenticationForm` is removed. -* The version of :func:`django.contrib.auth.views.password_reset_confirm` that +* The version of ``django.contrib.auth.views.password_reset_confirm()`` that supports base36 encoded user IDs (``django.contrib.auth.views.password_reset_confirm_uidb36``) is removed. diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt index 77d5c48ff6..b3d886960d 100644 --- a/docs/releases/1.8.10.txt +++ b/docs/releases/1.8.10.txt @@ -10,7 +10,7 @@ CVE-2016-2512: Malicious redirect and possible XSS attack via user-supplied redi =============================================================================================================== Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security check for these redirects (namely ``django.utils.http.is_safe_url()``) considered some URLs with basic authentication credentials "safe" when they shouldn't be. diff --git a/docs/releases/1.8.18.txt b/docs/releases/1.8.18.txt index f41c7d080f..4196fa1573 100644 --- a/docs/releases/1.8.18.txt +++ b/docs/releases/1.8.18.txt @@ -10,7 +10,7 @@ CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric r ============================================================================================ Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security check for these redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric URLs (e.g. ``http:999999999``) "safe" when they shouldn't be. diff --git a/docs/releases/1.8.4.txt b/docs/releases/1.8.4.txt index ee36c41fbc..486589254e 100644 --- a/docs/releases/1.8.4.txt +++ b/docs/releases/1.8.4.txt @@ -10,7 +10,7 @@ Denial-of-service possibility in ``logout()`` view by filling session store =========================================================================== Previously, a session could be created when anonymously accessing the -:func:`django.contrib.auth.views.logout` view (provided it wasn't decorated +``django.contrib.auth.views.logout()`` view (provided it wasn't decorated with :func:`~django.contrib.auth.decorators.login_required` as done in the admin). This could allow an attacker to easily create many new session records by sending repeated requests, potentially filling up the session store or diff --git a/docs/releases/1.9.13.txt b/docs/releases/1.9.13.txt index 4828096da9..41af5d36b2 100644 --- a/docs/releases/1.9.13.txt +++ b/docs/releases/1.9.13.txt @@ -11,7 +11,7 @@ CVE-2017-7233: Open redirect and possible XSS attack via user-supplied numeric r ============================================================================================ Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security check for these redirects (namely ``django.utils.http.is_safe_url()``) considered some numeric URLs (e.g. ``http:999999999``) "safe" when they shouldn't be. diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt index ad99cd863c..21949a4daa 100644 --- a/docs/releases/1.9.3.txt +++ b/docs/releases/1.9.3.txt @@ -10,7 +10,7 @@ CVE-2016-2512: Malicious redirect and possible XSS attack via user-supplied redi =============================================================================================================== Django relies on user input in some cases (e.g. -:func:`django.contrib.auth.views.login` and :doc:`i18n `) +``django.contrib.auth.views.login()`` and :doc:`i18n `) to redirect the user to an "on success" URL. The security check for these redirects (namely ``django.utils.http.is_safe_url()``) considered some URLs with basic authentication credentials "safe" when they shouldn't be. diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 2b012191cd..37c4ff1711 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -216,7 +216,7 @@ Minor features makes it possible to use ``REMOTE_USER`` for setups where the header is only populated on login pages instead of every request in the session. -* The :func:`~django.contrib.auth.views.password_reset` view accepts an +* The ``django.contrib.auth.views.password_reset()`` view accepts an ``extra_email_context`` parameter. :mod:`django.contrib.contenttypes` diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index 23a0c37e32..9f1648122a 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -223,3 +223,9 @@ Features removed in 2.1 These features have reached the end of their deprecation cycle and are removed in Django 2.1. See :ref:`deprecated-features-1.11` for details, including how to remove usage of these features. +in Django 2.1. See :ref:`deprecated-features-1.11` and for details, including +how to remove usage of these features. + +* ``contrib.auth.views.login()``, ``logout()``, ``password_change()``, + ``password_change_done()``, ``password_reset()``, ``password_reset_done()``, + ``password_reset_confirm()``, and ``password_reset_complete()`` are removed. diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 85398f428f..0d70bfc1c8 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -945,16 +945,6 @@ All authentication views This is a list with all the views ``django.contrib.auth`` provides. For implementation details see :ref:`using-the-views`. -.. function:: login(request, template_name=`registration/login.html`, redirect_field_name='next', authentication_form=AuthenticationForm, extra_context=None, redirect_authenticated_user=False) - - .. deprecated:: 1.11 - - The ``login`` function-based view should be replaced by the class-based - :class:`LoginView`. - - The optional arguments of this view are similar to the class-based - ``LoginView`` attributes. - .. class:: LoginView .. versionadded:: 1.11 @@ -1093,16 +1083,6 @@ implementation details see :ref:`using-the-views`. ``get_user()`` method which returns the authenticated user object (this method is only ever called after successful form validation). -.. function:: logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name='next', extra_context=None) - - .. deprecated:: 1.11 - - The ``logout`` function-based view should be replaced by the - class-based :class:`LogoutView`. - - The optional arguments of this view are similar to the class-based - ``LogoutView`` attributes. - .. class:: LogoutView .. versionadded:: 1.11 @@ -1165,18 +1145,6 @@ implementation details see :ref:`using-the-views`. The unused ``extra_context`` parameter is deprecated and will be removed in Django 2.1. -.. function:: password_change(request, template_name='registration/password_change_form.html', post_change_redirect=None, password_change_form=PasswordChangeForm, extra_context=None) - - .. deprecated:: 1.11 - - The ``password_change`` function-based view should be replaced by the - class-based :class:`PasswordChangeView`. - - The optional arguments of this view are similar to the class-based - ``PasswordChangeView`` attributes, except the ``post_change_redirect`` and - ``password_change_form`` arguments which map to the ``success_url`` and - ``form_class`` attributes of the class-based view. - .. class:: PasswordChangeView .. versionadded:: 1.11 @@ -1206,16 +1174,6 @@ implementation details see :ref:`using-the-views`. * ``form``: The password change form (see ``form_class`` above). -.. function:: password_change_done(request, template_name='registration/password_change_done.html', extra_context=None) - - .. deprecated:: 1.11 - - The ``password_change_done`` function-based view should be replaced by - the class-based :class:`PasswordChangeDoneView`. - - The optional arguments of this view are similar to the class-based - ``PasswordChangeDoneView`` attributes. - .. class:: PasswordChangeDoneView .. versionadded:: 1.11 @@ -1233,18 +1191,6 @@ implementation details see :ref:`using-the-views`. * ``extra_context``: A dictionary of context data that will be added to the default context data passed to the template. -.. function:: password_reset(request, template_name='registration/password_reset_form.html', email_template_name='registration/password_reset_email.html', subject_template_name='registration/password_reset_subject.txt', password_reset_form=PasswordResetForm, token_generator=default_token_generator, post_reset_redirect=None, from_email=None, extra_context=None, html_email_template_name=None, extra_email_context=None) - - .. deprecated:: 1.11 - - The ``password_reset`` function-based view should be replaced by the - class-based :class:`PasswordResetView`. - - The optional arguments of this view are similar to the class-based - ``PasswordResetView`` attributes, except the ``post_reset_redirect`` and - ``password_reset_form`` arguments which map to the ``success_url`` and - ``form_class`` attributes of the class-based view. - .. class:: PasswordResetView .. versionadded:: 1.11 @@ -1345,16 +1291,6 @@ implementation details see :ref:`using-the-views`. The same template context is used for subject template. Subject must be single line plain text string. -.. function:: password_reset_done(request, template_name='registration/password_reset_done.html', extra_context=None) - - .. deprecated:: 1.11 - - The ``password_reset_done`` function-based view should be replaced by - the class-based :class:`PasswordResetDoneView`. - - The optional arguments of this view are similar to the class-based - ``PasswordResetDoneView`` attributes. - .. class:: PasswordResetDoneView .. versionadded:: 1.11 @@ -1380,18 +1316,6 @@ implementation details see :ref:`using-the-views`. * ``extra_context``: A dictionary of context data that will be added to the default context data passed to the template. -.. function:: password_reset_confirm(request, uidb64=None, token=None, template_name='registration/password_reset_confirm.html', token_generator=default_token_generator, set_password_form=SetPasswordForm, post_reset_redirect=None, extra_context=None) - - .. deprecated:: 1.11 - - The ``password_reset_confirm`` function-based view should be replaced by - the class-based :class:`PasswordResetConfirmView`. - - The optional arguments of this view are similar to the class-based - ``PasswordResetConfirmView`` attributes, except the ``post_reset_redirect`` - and ``set_password_form`` arguments which map to the ``success_url`` and - ``form_class`` attributes of the class-based view. - .. class:: PasswordResetConfirmView .. versionadded:: 1.11 @@ -1442,16 +1366,6 @@ implementation details see :ref:`using-the-views`. * ``validlink``: Boolean, True if the link (combination of ``uidb64`` and ``token``) is valid or unused yet. -.. function:: password_reset_complete(request, template_name='registration/password_reset_complete.html', extra_context=None) - - .. deprecated:: 1.11 - - The ``password_reset_complete`` function-based view should be replaced - by the class-based :class:`PasswordResetCompleteView`. - - The optional arguments of this view are similar to the class-based - ``PasswordResetCompleteView`` attributes. - .. class:: PasswordResetCompleteView .. versionadded:: 1.11 diff --git a/tests/auth_tests/test_deprecated_views.py b/tests/auth_tests/test_deprecated_views.py deleted file mode 100644 index b53389a161..0000000000 --- a/tests/auth_tests/test_deprecated_views.py +++ /dev/null @@ -1,488 +0,0 @@ -import datetime -import itertools -import re -from urllib.parse import ParseResult, urlparse - -from django.conf import settings -from django.contrib.auth import SESSION_KEY -from django.contrib.auth.forms import ( - AuthenticationForm, PasswordChangeForm, SetPasswordForm, -) -from django.contrib.auth.models import User -from django.contrib.auth.views import login, logout -from django.core import mail -from django.http import QueryDict -from django.test import RequestFactory, TestCase, override_settings -from django.test.utils import ignore_warnings, patch_logger -from django.utils.deprecation import RemovedInDjango21Warning - -from .models import CustomUser, UUIDUser -from .settings import AUTH_TEMPLATES - - -@override_settings( - LANGUAGES=[('en', 'English')], - LANGUAGE_CODE='en', - TEMPLATES=AUTH_TEMPLATES, - ROOT_URLCONF='auth_tests.urls_deprecated', -) -class AuthViewsTestCase(TestCase): - """ - Helper base class for all the follow test cases. - """ - - @classmethod - def setUpTestData(cls): - cls.u1 = User.objects.create_user(username='testclient', password='password', email='testclient@example.com') - cls.u3 = User.objects.create_user(username='staff', password='password', email='staffmember@example.com') - - def login(self, username='testclient', password='password'): - response = self.client.post('/login/', { - 'username': username, - 'password': password, - }) - self.assertIn(SESSION_KEY, self.client.session) - return response - - def logout(self): - response = self.client.get('/admin/logout/') - self.assertEqual(response.status_code, 200) - self.assertNotIn(SESSION_KEY, self.client.session) - - def assertFormError(self, response, error): - """Assert that error is found in response.context['form'] errors""" - form_errors = list(itertools.chain(*response.context['form'].errors.values())) - self.assertIn(str(error), form_errors) - - def assertURLEqual(self, url, expected, parse_qs=False): - """ - Given two URLs, make sure all their components (the ones given by - urlparse) are equal, only comparing components that are present in both - URLs. - If `parse_qs` is True, then the querystrings are parsed with QueryDict. - This is useful if you don't want the order of parameters to matter. - Otherwise, the query strings are compared as-is. - """ - fields = ParseResult._fields - - for attr, x, y in zip(fields, urlparse(url), urlparse(expected)): - if parse_qs and attr == 'query': - x, y = QueryDict(x), QueryDict(y) - if x and y and x != y: - self.fail("%r != %r (%s doesn't match)" % (url, expected, attr)) - - -@ignore_warnings(category=RemovedInDjango21Warning) -class PasswordResetTest(AuthViewsTestCase): - - def test_email_not_found(self): - """If the provided email is not registered, don't raise any error but - also don't send any email.""" - response = self.client.get('/password_reset/') - self.assertEqual(response.status_code, 200) - response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'}) - self.assertEqual(response.status_code, 302) - self.assertEqual(len(mail.outbox), 0) - - def test_email_found(self): - "Email is sent if a valid email address is provided for password reset" - response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) - self.assertEqual(response.status_code, 302) - self.assertEqual(len(mail.outbox), 1) - self.assertIn("http://", mail.outbox[0].body) - self.assertEqual(settings.DEFAULT_FROM_EMAIL, mail.outbox[0].from_email) - # optional multipart text/html email has been added. Make sure original, - # default functionality is 100% the same - self.assertFalse(mail.outbox[0].message().is_multipart()) - - def test_extra_email_context(self): - """ - extra_email_context should be available in the email template context. - """ - response = self.client.post( - '/password_reset_extra_email_context/', - {'email': 'staffmember@example.com'}, - ) - self.assertEqual(response.status_code, 302) - self.assertEqual(len(mail.outbox), 1) - self.assertIn('Email email context: "Hello!"', mail.outbox[0].body) - - def test_html_mail_template(self): - """ - A multipart email with text/plain and text/html is sent - if the html_email_template parameter is passed to the view - """ - response = self.client.post('/password_reset/html_email_template/', {'email': 'staffmember@example.com'}) - self.assertEqual(response.status_code, 302) - self.assertEqual(len(mail.outbox), 1) - message = mail.outbox[0].message() - self.assertEqual(len(message.get_payload()), 2) - self.assertTrue(message.is_multipart()) - self.assertEqual(message.get_payload(0).get_content_type(), 'text/plain') - self.assertEqual(message.get_payload(1).get_content_type(), 'text/html') - self.assertNotIn('', message.get_payload(0).get_payload()) - self.assertIn('', message.get_payload(1).get_payload()) - - def test_email_found_custom_from(self): - "Email is sent if a valid email address is provided for password reset when a custom from_email is provided." - response = self.client.post('/password_reset_from_email/', {'email': 'staffmember@example.com'}) - self.assertEqual(response.status_code, 302) - self.assertEqual(len(mail.outbox), 1) - self.assertEqual("staffmember@example.com", mail.outbox[0].from_email) - - # Skip any 500 handler action (like sending more mail...) - @override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True) - def test_poisoned_http_host(self): - "Poisoned HTTP_HOST headers can't be used for reset emails" - # This attack is based on the way browsers handle URLs. The colon - # should be used to separate the port, but if the URL contains an @, - # the colon is interpreted as part of a username for login purposes, - # making 'evil.com' the request domain. Since HTTP_HOST is used to - # produce a meaningful reset URL, we need to be certain that the - # HTTP_HOST header isn't poisoned. This is done as a check when get_host() - # is invoked, but we check here as a practical consequence. - with patch_logger('django.security.DisallowedHost', 'error') as logger_calls: - response = self.client.post( - '/password_reset/', - {'email': 'staffmember@example.com'}, - HTTP_HOST='www.example:dr.frankenstein@evil.tld' - ) - self.assertEqual(response.status_code, 400) - self.assertEqual(len(mail.outbox), 0) - self.assertEqual(len(logger_calls), 1) - - # Skip any 500 handler action (like sending more mail...) - @override_settings(DEBUG_PROPAGATE_EXCEPTIONS=True) - def test_poisoned_http_host_admin_site(self): - "Poisoned HTTP_HOST headers can't be used for reset emails on admin views" - with patch_logger('django.security.DisallowedHost', 'error') as logger_calls: - response = self.client.post( - '/admin_password_reset/', - {'email': 'staffmember@example.com'}, - HTTP_HOST='www.example:dr.frankenstein@evil.tld' - ) - self.assertEqual(response.status_code, 400) - self.assertEqual(len(mail.outbox), 0) - self.assertEqual(len(logger_calls), 1) - - def _test_confirm_start(self): - # Start by creating the email - self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) - self.assertEqual(len(mail.outbox), 1) - return self._read_signup_email(mail.outbox[0]) - - def _read_signup_email(self, email): - urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body) - self.assertIsNotNone(urlmatch, "No URL found in sent email") - return urlmatch.group(), urlmatch.groups()[0] - - def test_confirm_valid(self): - url, path = self._test_confirm_start() - response = self.client.get(path) - # redirect to a 'complete' page: - self.assertContains(response, "Please enter your new password") - - def test_confirm_invalid(self): - url, path = self._test_confirm_start() - # Let's munge the token in the path, but keep the same length, - # in case the URLconf will reject a different length. - path = path[:-5] + ("0" * 4) + path[-1] - - response = self.client.get(path) - self.assertContains(response, "The password reset link was invalid") - - def test_confirm_invalid_user(self): - # We get a 200 response for a nonexistent user, not a 404 - response = self.client.get('/reset/123456/1-1/') - self.assertContains(response, "The password reset link was invalid") - - def test_confirm_overflow_user(self): - # We get a 200 response for a base36 user id that overflows int - response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/') - self.assertContains(response, "The password reset link was invalid") - - def test_confirm_invalid_post(self): - # Same as test_confirm_invalid, but trying - # to do a POST instead. - url, path = self._test_confirm_start() - path = path[:-5] + ("0" * 4) + path[-1] - - self.client.post(path, { - 'new_password1': 'anewpassword', - 'new_password2': ' anewpassword', - }) - # Check the password has not been changed - u = User.objects.get(email='staffmember@example.com') - self.assertTrue(not u.check_password("anewpassword")) - - def test_confirm_complete(self): - url, path = self._test_confirm_start() - response = self.client.post(path, {'new_password1': 'anewpassword', 'new_password2': 'anewpassword'}) - # Check the password has been changed - u = User.objects.get(email='staffmember@example.com') - self.assertTrue(u.check_password("anewpassword")) - - # Check we can't use the link again - response = self.client.get(path) - self.assertContains(response, "The password reset link was invalid") - - def test_confirm_different_passwords(self): - url, path = self._test_confirm_start() - response = self.client.post(path, {'new_password1': 'anewpassword', 'new_password2': 'x'}) - self.assertFormError(response, SetPasswordForm.error_messages['password_mismatch']) - - def test_reset_redirect_default(self): - response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/password_reset/done/') - - def test_reset_custom_redirect(self): - response = self.client.post('/password_reset/custom_redirect/', {'email': 'staffmember@example.com'}) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/custom/') - - def test_reset_custom_redirect_named(self): - response = self.client.post('/password_reset/custom_redirect/named/', {'email': 'staffmember@example.com'}) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/password_reset/') - - def test_confirm_redirect_default(self): - url, path = self._test_confirm_start() - response = self.client.post(path, {'new_password1': 'anewpassword', 'new_password2': 'anewpassword'}) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/reset/done/') - - def test_confirm_redirect_custom(self): - url, path = self._test_confirm_start() - path = path.replace('/reset/', '/reset/custom/') - response = self.client.post(path, {'new_password1': 'anewpassword', 'new_password2': 'anewpassword'}) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/custom/') - - def test_confirm_redirect_custom_named(self): - url, path = self._test_confirm_start() - path = path.replace('/reset/', '/reset/custom/named/') - response = self.client.post(path, {'new_password1': 'anewpassword', 'new_password2': 'anewpassword'}) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/password_reset/') - - def test_confirm_display_user_from_form(self): - url, path = self._test_confirm_start() - response = self.client.get(path) - - # #16919 -- The ``password_reset_confirm`` view should pass the user - # object to the ``SetPasswordForm``, even on GET requests. - # For this test, we render ``{{ form.user }}`` in the template - # ``registration/password_reset_confirm.html`` so that we can test this. - username = User.objects.get(email='staffmember@example.com').username - self.assertContains(response, "Hello, %s." % username) - - # However, the view should NOT pass any user object on a form if the - # password reset link was invalid. - response = self.client.get('/reset/zzzzzzzzzzzzz/1-1/') - self.assertContains(response, "Hello, .") - - -@ignore_warnings(category=RemovedInDjango21Warning) -@override_settings(AUTH_USER_MODEL='auth_tests.CustomUser') -class CustomUserPasswordResetTest(AuthViewsTestCase): - user_email = 'staffmember@example.com' - - @classmethod - def setUpTestData(cls): - cls.u1 = CustomUser.custom_objects.create( - email='staffmember@example.com', - date_of_birth=datetime.date(1976, 11, 8), - ) - cls.u1.set_password('password') - cls.u1.save() - - def _test_confirm_start(self): - # Start by creating the email - response = self.client.post('/password_reset/', {'email': self.user_email}) - self.assertEqual(response.status_code, 302) - self.assertEqual(len(mail.outbox), 1) - return self._read_signup_email(mail.outbox[0]) - - def _read_signup_email(self, email): - urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body) - self.assertIsNotNone(urlmatch, "No URL found in sent email") - return urlmatch.group(), urlmatch.groups()[0] - - def test_confirm_valid_custom_user(self): - url, path = self._test_confirm_start() - response = self.client.get(path) - # redirect to a 'complete' page: - self.assertContains(response, "Please enter your new password") - # then submit a new password - response = self.client.post(path, { - 'new_password1': 'anewpassword', - 'new_password2': 'anewpassword', - }) - self.assertRedirects(response, '/reset/done/') - - -@ignore_warnings(category=RemovedInDjango21Warning) -@override_settings(AUTH_USER_MODEL='auth_tests.UUIDUser') -class UUIDUserPasswordResetTest(CustomUserPasswordResetTest): - - def _test_confirm_start(self): - # instead of fixture - UUIDUser.objects.create_user( - email=self.user_email, - username='foo', - password='foo', - ) - return super()._test_confirm_start() - - -@ignore_warnings(category=RemovedInDjango21Warning) -class ChangePasswordTest(AuthViewsTestCase): - - def fail_login(self, password='password'): - response = self.client.post('/login/', { - 'username': 'testclient', - 'password': password, - }) - self.assertFormError(response, AuthenticationForm.error_messages['invalid_login'] % { - 'username': User._meta.get_field('username').verbose_name - }) - - def logout(self): - self.client.get('/logout/') - - def test_password_change_fails_with_invalid_old_password(self): - self.login() - response = self.client.post('/password_change/', { - 'old_password': 'donuts', - 'new_password1': 'password1', - 'new_password2': 'password1', - }) - self.assertFormError(response, PasswordChangeForm.error_messages['password_incorrect']) - - def test_password_change_fails_with_mismatched_passwords(self): - self.login() - response = self.client.post('/password_change/', { - 'old_password': 'password', - 'new_password1': 'password1', - 'new_password2': 'donuts', - }) - self.assertFormError(response, SetPasswordForm.error_messages['password_mismatch']) - - def test_password_change_succeeds(self): - self.login() - self.client.post('/password_change/', { - 'old_password': 'password', - 'new_password1': 'password1', - 'new_password2': 'password1', - }) - self.fail_login() - self.login(password='password1') - - def test_password_change_done_succeeds(self): - self.login() - response = self.client.post('/password_change/', { - 'old_password': 'password', - 'new_password1': 'password1', - 'new_password2': 'password1', - }) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/password_change/done/') - - @override_settings(LOGIN_URL='/login/') - def test_password_change_done_fails(self): - response = self.client.get('/password_change/done/') - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/login/?next=/password_change/done/') - - def test_password_change_redirect_default(self): - self.login() - response = self.client.post('/password_change/', { - 'old_password': 'password', - 'new_password1': 'password1', - 'new_password2': 'password1', - }) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/password_change/done/') - - def test_password_change_redirect_custom(self): - self.login() - response = self.client.post('/password_change/custom/', { - 'old_password': 'password', - 'new_password1': 'password1', - 'new_password2': 'password1', - }) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/custom/') - - def test_password_change_redirect_custom_named(self): - self.login() - response = self.client.post('/password_change/custom/named/', { - 'old_password': 'password', - 'new_password1': 'password1', - 'new_password2': 'password1', - }) - self.assertEqual(response.status_code, 302) - self.assertURLEqual(response.url, '/password_reset/') - - -@ignore_warnings(category=RemovedInDjango21Warning) -class SessionAuthenticationTests(AuthViewsTestCase): - def test_user_password_change_updates_session(self): - """ - #21649 - Ensure contrib.auth.views.password_change updates the user's - session auth hash after a password change so the session isn't logged out. - """ - self.login() - response = self.client.post('/password_change/', { - 'old_password': 'password', - 'new_password1': 'password1', - 'new_password2': 'password1', - }) - # if the hash isn't updated, retrieving the redirection page will fail. - self.assertRedirects(response, '/password_change/done/') - - -@ignore_warnings(category=RemovedInDjango21Warning) -class TestLogin(TestCase): - def setUp(self): - self.factory = RequestFactory() - self.request = self.factory.get('/') - - def test_template_name(self): - response = login(self.request, 'template.html') - self.assertEqual(response.template_name, ['template.html']) - - def test_form_class(self): - class NewForm(AuthenticationForm): - def confirm_login_allowed(self, user): - pass - response = login(self.request, 'template.html', None, NewForm) - self.assertEqual(response.context_data['form'].__class__.__name__, 'NewForm') - - def test_extra_context(self): - extra_context = {'fake_context': 'fake_context'} - response = login(self.request, 'template.html', None, AuthenticationForm, extra_context) - self.assertEqual(response.resolve_context('fake_context'), 'fake_context') - - -@ignore_warnings(category=RemovedInDjango21Warning) -class TestLogout(AuthViewsTestCase): - def setUp(self): - self.login() - self.factory = RequestFactory() - self.request = self.factory.post('/') - self.request.session = self.client.session - - def test_template_name(self): - response = logout(self.request, None, 'template.html') - self.assertEqual(response.template_name, ['template.html']) - - def test_next_page(self): - response = logout(self.request, 'www.next_page.com') - self.assertEqual(response.url, 'www.next_page.com') - - def test_extra_context(self): - extra_context = {'fake_context': 'fake_context'} - response = logout(self.request, None, 'template.html', None, extra_context) - self.assertEqual(response.resolve_context('fake_context'), 'fake_context') diff --git a/tests/auth_tests/urls_deprecated.py b/tests/auth_tests/urls_deprecated.py deleted file mode 100644 index 7fba46d269..0000000000 --- a/tests/auth_tests/urls_deprecated.py +++ /dev/null @@ -1,38 +0,0 @@ -from django.conf.urls import url -from django.contrib import admin -from django.contrib.auth import views -from django.contrib.auth.decorators import login_required - -# special urls for deprecated function-based views -urlpatterns = [ - url(r'^login/$', views.login, name='login'), - url(r'^logout/$', views.logout, name='logout'), - url(r'^password_change/$', views.password_change, name='password_change'), - url(r'^password_change/done/$', views.password_change_done, name='password_change_done'), - url(r'^password_reset/$', views.password_reset, name='password_reset'), - url(r'^password_reset/done/$', views.password_reset_done, name='password_reset_done'), - url(r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', - views.password_reset_confirm, name='password_reset_confirm'), - url(r'^reset/done/$', views.password_reset_complete, name='password_reset_complete'), - - url(r'^password_reset_from_email/$', views.password_reset, {'from_email': 'staffmember@example.com'}), - url(r'^password_reset_extra_email_context/$', views.password_reset, - {'extra_email_context': {'greeting': 'Hello!'}}), - url(r'^password_reset/custom_redirect/$', views.password_reset, {'post_reset_redirect': '/custom/'}), - url(r'^password_reset/custom_redirect/named/$', views.password_reset, {'post_reset_redirect': 'password_reset'}), - url(r'^password_reset/html_email_template/$', views.password_reset, - {'html_email_template_name': 'registration/html_password_reset_email.html'}), - url(r'^reset/custom/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', - views.password_reset_confirm, - {'post_reset_redirect': '/custom/'}), - url(r'^reset/custom/named/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', - views.password_reset_confirm, - {'post_reset_redirect': 'password_reset'}), - url(r'^password_change/custom/$', views.password_change, {'post_change_redirect': '/custom/'}), - url(r'^password_change/custom/named/$', views.password_change, {'post_change_redirect': 'password_reset'}), - url(r'^login_required/$', login_required(views.password_reset)), - url(r'^login_required_login_url/$', login_required(views.password_reset, login_url='/somewhere/')), - - # This line is only required to render the password reset with is_admin=True - url(r'^admin/', admin.site.urls), -]