summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2017-09-02 19:24:18 -0400
committerTim Graham <timograham@gmail.com>2017-09-22 12:51:17 -0400
commit4f313e284e03a675da5fb1d25122ac9b04af5950 (patch)
tree8dcc91d0e8d6a16a8788160e351131904d6f2617
parentdeb592b3e3d883ae32655f574c42af90079de8fa (diff)
Refs #17209 -- Removed login/logout and password reset/change function-based views.
Per deprecation timeline.
-rw-r--r--django/contrib/auth/views.py224
-rw-r--r--docs/internals/deprecation.txt2
-rw-r--r--docs/releases/1.10.7.txt2
-rw-r--r--docs/releases/1.10.txt8
-rw-r--r--docs/releases/1.3.txt2
-rw-r--r--docs/releases/1.4.13.txt2
-rw-r--r--docs/releases/1.4.18.txt2
-rw-r--r--docs/releases/1.4.20.txt2
-rw-r--r--docs/releases/1.4.22.txt2
-rw-r--r--docs/releases/1.4.6.txt2
-rw-r--r--docs/releases/1.5.2.txt2
-rw-r--r--docs/releases/1.5.8.txt2
-rw-r--r--docs/releases/1.6.10.txt2
-rw-r--r--docs/releases/1.6.11.txt2
-rw-r--r--docs/releases/1.6.5.txt2
-rw-r--r--docs/releases/1.6.txt14
-rw-r--r--docs/releases/1.7.10.txt2
-rw-r--r--docs/releases/1.7.3.txt2
-rw-r--r--docs/releases/1.7.7.txt2
-rw-r--r--docs/releases/1.7.txt4
-rw-r--r--docs/releases/1.8.10.txt2
-rw-r--r--docs/releases/1.8.18.txt2
-rw-r--r--docs/releases/1.8.4.txt2
-rw-r--r--docs/releases/1.9.13.txt2
-rw-r--r--docs/releases/1.9.3.txt2
-rw-r--r--docs/releases/1.9.txt2
-rw-r--r--docs/releases/2.1.txt6
-rw-r--r--docs/topics/auth/default.txt86
-rw-r--r--tests/auth_tests/test_deprecated_views.py488
-rw-r--r--tests/auth_tests/urls_deprecated.py38
30 files changed, 42 insertions, 870 deletions
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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`) 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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`) 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 </topics/i18n/index>`) 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 </topics/i18n/index>`) 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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`) 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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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
<django.contrib.auth.forms.AuthenticationForm.confirm_login_allowed>` 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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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 </topics/i18n/index>`)
+``django.contrib.auth.views.login()`` and :doc:`i18n </topics/i18n/index>`)
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('<html>', message.get_payload(0).get_payload())
- self.assertIn('<html>', 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<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[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<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[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<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[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),
-]