diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index b5556aaa11..f1b326b29a 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -12,7 +12,7 @@ from django.contrib.auth.forms import ( ) from django.contrib.auth.tokens import default_token_generator from django.contrib.sites.shortcuts import get_current_site -from django.core.exceptions import ValidationError +from django.core.exceptions import ImproperlyConfigured, ValidationError from django.http import HttpResponseRedirect, QueryDict from django.shortcuts import resolve_url from django.urls import reverse_lazy @@ -261,7 +261,10 @@ class PasswordResetConfirmView(PasswordContextMixin, FormView): @method_decorator(sensitive_post_parameters()) @method_decorator(never_cache) def dispatch(self, *args, **kwargs): - assert 'uidb64' in kwargs and 'token' in kwargs + if 'uidb64' not in kwargs or 'token' not in kwargs: + raise ImproperlyConfigured( + "The URL path must contain 'uidb64' and 'token' parameters." + ) self.validlink = False self.user = self.get_user(kwargs['uidb64']) diff --git a/django/dispatch/dispatcher.py b/django/dispatch/dispatcher.py index c9d5788fa9..d3630ae784 100644 --- a/django/dispatch/dispatcher.py +++ b/django/dispatch/dispatcher.py @@ -80,8 +80,8 @@ class Signal: # If DEBUG is on, check that we got a good receiver if settings.configured and settings.DEBUG: - assert callable(receiver), "Signal receivers must be callable." - + if not callable(receiver): + raise TypeError('Signal receivers must be callable.') # Check for **kwargs if not func_accepts_kwargs(receiver): raise ValueError("Signal receivers must accept keyword arguments (**kwargs).") diff --git a/django/views/decorators/debug.py b/django/views/decorators/debug.py index 18900ffca8..312269baba 100644 --- a/django/views/decorators/debug.py +++ b/django/views/decorators/debug.py @@ -77,11 +77,12 @@ def sensitive_post_parameters(*parameters): def decorator(view): @functools.wraps(view) def sensitive_post_parameters_wrapper(request, *args, **kwargs): - assert isinstance(request, HttpRequest), ( - "sensitive_post_parameters didn't receive an HttpRequest. " - "If you are decorating a classmethod, be sure to use " - "@method_decorator." - ) + if not isinstance(request, HttpRequest): + raise TypeError( + "sensitive_post_parameters didn't receive an HttpRequest " + "object. If you are decorating a classmethod, make sure " + "to use @method_decorator." + ) if parameters: request.sensitive_post_parameters = parameters else: diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py index 0ec3a134c2..9ae7dab252 100644 --- a/tests/auth_tests/test_views.py +++ b/tests/auth_tests/test_views.py @@ -23,6 +23,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sites.requests import RequestSite from django.core import mail +from django.core.exceptions import ImproperlyConfigured from django.db import connection from django.http import HttpRequest, HttpResponse from django.middleware.csrf import CsrfViewMiddleware, get_token @@ -386,6 +387,11 @@ class PasswordResetTest(AuthViewsTestCase): response = Client().get('/reset/%s/set-password/' % uuidb64) self.assertContains(response, 'The password reset link was invalid') + def test_missing_kwargs(self): + msg = "The URL path must contain 'uidb64' and 'token' parameters." + with self.assertRaisesMessage(ImproperlyConfigured, msg): + self.client.get('/reset/missing_parameters/') + @override_settings(AUTH_USER_MODEL='auth_tests.CustomUser') class CustomUserPasswordResetTest(AuthViewsTestCase): diff --git a/tests/auth_tests/urls.py b/tests/auth_tests/urls.py index 044f0da037..70857e866a 100644 --- a/tests/auth_tests/urls.py +++ b/tests/auth_tests/urls.py @@ -133,6 +133,7 @@ urlpatterns = auth_urlpatterns + [ post_reset_login_backend='django.contrib.auth.backends.AllowAllUsersModelBackend', ), ), + path('reset/missing_parameters/', views.PasswordResetConfirmView.as_view()), path('password_change/custom/', views.PasswordChangeView.as_view(success_url='/custom/')), path('password_change/custom/named/', diff --git a/tests/dispatch/tests.py b/tests/dispatch/tests.py index 30a9354bba..05b7bdb02f 100644 --- a/tests/dispatch/tests.py +++ b/tests/dispatch/tests.py @@ -58,7 +58,7 @@ class DispatcherTests(SimpleTestCase): @override_settings(DEBUG=True) def test_cannot_connect_non_callable(self): msg = 'Signal receivers must be callable.' - with self.assertRaisesMessage(AssertionError, msg): + with self.assertRaisesMessage(TypeError, msg): a_signal.connect(object()) self.assertTestIsClean(a_signal) diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py index c8cc4aeb1e..aa3cf4b839 100644 --- a/tests/view_tests/tests/test_debug.py +++ b/tests/view_tests/tests/test_debug.py @@ -12,7 +12,7 @@ from unittest import mock from django.core import mail from django.core.files.uploadedfile import SimpleUploadedFile from django.db import DatabaseError, connection -from django.http import Http404 +from django.http import Http404, HttpRequest, HttpResponse from django.shortcuts import render from django.template import TemplateDoesNotExist from django.test import RequestFactory, SimpleTestCase, override_settings @@ -1635,3 +1635,17 @@ class DecoratorsTests(SimpleTestCase): @sensitive_post_parameters def test_func(request): return index_page(request) + + def test_sensitive_post_parameters_http_request(self): + class MyClass: + @sensitive_post_parameters() + def a_view(self, request): + return HttpResponse() + + msg = ( + "sensitive_post_parameters didn't receive an HttpRequest object. " + "If you are decorating a classmethod, make sure to use " + "@method_decorator." + ) + with self.assertRaisesMessage(TypeError, msg): + MyClass().a_view(HttpRequest())