summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorMichael Farrell <micolous+git@gmail.com>2012-07-12 11:13:15 +0930
committerPreston Holmes <preston@ptone.com>2012-09-30 22:34:50 -0700
commit7cc4068c4470876c526830778cbdac2fdfd6dc26 (patch)
treeee837a8ccbbece0e83e2846f1ad5343f5e3d6b93 /django
parent8bd7b598b6de1be1e3f72f3a1ee62803b1c02010 (diff)
Fixed #18616 -- added user_login_fail signal to contrib.auth
Thanks to Brad Pitcher for documentation
Diffstat (limited to 'django')
-rw-r--r--django/contrib/auth/__init__.py23
-rw-r--r--django/contrib/auth/signals.py1
-rw-r--r--django/contrib/auth/tests/signals.py16
3 files changed, 38 insertions, 2 deletions
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index 1050d1d1bb..dd4a8484f5 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -1,6 +1,8 @@
+import re
+
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module
-from django.contrib.auth.signals import user_logged_in, user_logged_out
+from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
@@ -33,6 +35,21 @@ def get_backends():
return backends
+def _clean_credentials(credentials):
+ """
+ Cleans a dictionary of credentials of potentially sensitive info before
+ sending to less secure functions.
+
+ Not comprehensive - intended for user_login_failed signal
+ """
+ SENSITIVE_CREDENTIALS = re.compile('api|token|key|secret|password|signature', re.I)
+ CLEANSED_SUBSTITUTE = '********************'
+ for key in credentials:
+ if SENSITIVE_CREDENTIALS.search(key):
+ credentials[key] = CLEANSED_SUBSTITUTE
+ return credentials
+
+
def authenticate(**credentials):
"""
If the given credentials are valid, return a User object.
@@ -49,6 +66,10 @@ def authenticate(**credentials):
user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
return user
+ # The credentials supplied are invalid to all backends, fire signal
+ user_login_failed.send(sender=__name__,
+ credentials=_clean_credentials(credentials))
+
def login(request, user):
"""
diff --git a/django/contrib/auth/signals.py b/django/contrib/auth/signals.py
index 4f0b2c235c..71ab6a11d1 100644
--- a/django/contrib/auth/signals.py
+++ b/django/contrib/auth/signals.py
@@ -1,4 +1,5 @@
from django.dispatch import Signal
user_logged_in = Signal(providing_args=['request', 'user'])
+user_login_failed = Signal(providing_args=['credentials'])
user_logged_out = Signal(providing_args=['request', 'user'])
diff --git a/django/contrib/auth/tests/signals.py b/django/contrib/auth/tests/signals.py
index c597aa9ed0..024f44f547 100644
--- a/django/contrib/auth/tests/signals.py
+++ b/django/contrib/auth/tests/signals.py
@@ -18,27 +18,41 @@ 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 setUp(self):
"""Set up the listeners and reset the logged in/logged out counters"""
self.logged_in = []
self.logged_out = []
+ self.login_failed = []
signals.user_logged_in.connect(self.listener_login)
signals.user_logged_out.connect(self.listener_logout)
+ signals.user_login_failed.connect(self.listener_login_failed)
def tearDown(self):
"""Disconnect the listeners"""
signals.user_logged_in.disconnect(self.listener_login)
signals.user_logged_out.disconnect(self.listener_logout)
+ signals.user_login_failed.disconnect(self.listener_login_failed)
def test_login(self):
- # Only a successful login will trigger the signal.
+ # Only a successful login will trigger the success signal.
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')
+ # verify the password is cleansed
+ self.assertTrue('***' in self.login_failed[0]['password'])
+
# Like this:
self.client.login(username='testclient', password='password')
self.assertEqual(len(self.logged_in), 1)
self.assertEqual(self.logged_in[0].username, 'testclient')
+ # Ensure there were no more failures.
+ self.assertEqual(len(self.login_failed), 1)
+
def test_logout_anonymous(self):
# The log_out function will still trigger the signal for anonymous
# users.