summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGavin Wahl <gwahl@fusionbox.com>2016-02-18 17:58:30 -0700
committerTim Graham <timograham@gmail.com>2016-09-12 20:30:34 -0400
commitf0f3de3c96694fd3602541fa6074930667509ac8 (patch)
tree5796a4c6d4fbca35d29d3faa28c041324723d481
parent4b9330ccc04575f9e5126529ec355a450d12e77c (diff)
Fixed #23155 -- Added request argument to user_login_failed signal.
-rw-r--r--django/contrib/auth/__init__.py2
-rw-r--r--django/contrib/auth/signals.py2
-rw-r--r--docs/ref/contrib/auth.txt8
-rw-r--r--docs/releases/1.11.txt3
-rw-r--r--tests/auth_tests/test_forms.py19
-rw-r--r--tests/auth_tests/test_signals.py15
6 files changed, 42 insertions, 7 deletions
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index 77458e15f9..a490eff040 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -94,7 +94,7 @@ def authenticate(request=None, **credentials):
return user
# The credentials supplied are invalid to all backends, fire signal
- user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials))
+ user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request)
def login(request, user, backend=None):
diff --git a/django/contrib/auth/signals.py b/django/contrib/auth/signals.py
index 71ab6a11d1..6415da45fa 100644
--- a/django/contrib/auth/signals.py
+++ b/django/contrib/auth/signals.py
@@ -1,5 +1,5 @@
from django.dispatch import Signal
user_logged_in = Signal(providing_args=['request', 'user'])
-user_login_failed = Signal(providing_args=['credentials'])
+user_login_failed = Signal(providing_args=['credentials', 'request'])
user_logged_out = Signal(providing_args=['request', 'user'])
diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt
index 38f18b6dc8..69e3f40514 100644
--- a/docs/ref/contrib/auth.txt
+++ b/docs/ref/contrib/auth.txt
@@ -479,6 +479,14 @@ can be used for notification when a user logs in or out.
authentication backend. Credentials matching a set of 'sensitive' patterns,
(including password) will not be sent in the clear as part of the signal.
+ ``request``
+ The :class:`~django.http.HttpRequest` object, if one was provided to
+ :func:`~django.contrib.auth.authenticate`.
+
+ .. versionchanged:: 1.11
+
+ The ``request`` argument was added.
+
.. _authentication-backends-reference:
Authentication backends
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index a6edfcdf25..3cd56c515e 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -117,6 +117,9 @@ Minor features
which in turn passes it to the authentication backend if it accepts a
``request`` argument.
+* The :func:`~django.contrib.auth.signals.user_login_failed` signal now
+ receives a ``request`` argument.
+
:mod:`django.contrib.contenttypes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index 19f0f4521c..c0e2961424 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -12,6 +12,7 @@ from django.contrib.auth.forms import (
SetPasswordForm, UserChangeForm, UserCreationForm,
)
from django.contrib.auth.models import User
+from django.contrib.auth.signals import user_login_failed
from django.contrib.sites.models import Site
from django.core import mail
from django.core.mail import EmailMultiAlternatives
@@ -279,6 +280,24 @@ class AuthenticationFormTest(TestDataMixin, TestCase):
self.assertFalse(form.is_valid())
self.assertEqual(form.non_field_errors(), [force_text(form.error_messages['inactive'])])
+ def test_login_failed(self):
+ signal_calls = []
+
+ def signal_handler(**kwargs):
+ signal_calls.append(kwargs)
+
+ user_login_failed.connect(signal_handler)
+ fake_request = object()
+ try:
+ form = AuthenticationForm(fake_request, {
+ 'username': 'testclient',
+ 'password': 'incorrect',
+ })
+ self.assertFalse(form.is_valid())
+ self.assertIs(signal_calls[0]['request'], fake_request)
+ finally:
+ user_login_failed.disconnect(signal_handler)
+
def test_inactive_user_i18n(self):
with self.settings(USE_I18N=True), translation.override('pt-br', deactivate=True):
# The user is inactive.
diff --git a/tests/auth_tests/test_signals.py b/tests/auth_tests/test_signals.py
index 6f1ad0a7b5..c21daa3c1d 100644
--- a/tests/auth_tests/test_signals.py
+++ b/tests/auth_tests/test_signals.py
@@ -1,4 +1,4 @@
-from django.contrib.auth import signals
+from django.contrib.auth import authenticate, signals
from django.contrib.auth.models import User
from django.test import TestCase, override_settings
from django.test.client import RequestFactory
@@ -18,8 +18,8 @@ class SignalTestCase(TestCase):
def listener_logout(self, user, **kwargs):
self.logged_out.append(user)
- def listener_login_failed(self, sender, credentials, **kwargs):
- self.login_failed.append(credentials)
+ def listener_login_failed(self, sender, **kwargs):
+ self.login_failed.append(kwargs)
def setUp(self):
"""Set up the listeners and reset the logged in/logged out counters"""
@@ -41,9 +41,10 @@ class SignalTestCase(TestCase):
self.client.login(username='testclient', password='bad')
self.assertEqual(len(self.logged_in), 0)
self.assertEqual(len(self.login_failed), 1)
- self.assertEqual(self.login_failed[0]['username'], 'testclient')
+ self.assertEqual(self.login_failed[0]['credentials']['username'], 'testclient')
# verify the password is cleansed
- self.assertIn('***', self.login_failed[0]['password'])
+ self.assertIn('***', self.login_failed[0]['credentials']['password'])
+ self.assertIn('request', self.login_failed[0])
# Like this:
self.client.login(username='testclient', password='password')
@@ -77,3 +78,7 @@ class SignalTestCase(TestCase):
user.refresh_from_db()
self.assertEqual(user.username, 'staff')
self.assertNotEqual(user.last_login, old_last_login)
+
+ def test_failed_login_without_request(self):
+ authenticate(username='testclient', password='bad')
+ self.assertIsNone(self.login_failed[0]['request'])