Loading django/contrib/auth/__init__.py +22 −1 Original line number Diff line number Diff line 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' Loading Loading @@ -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. Loading @@ -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): """ Loading django/contrib/auth/signals.py +1 −0 Original line number Diff line number Diff line 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']) django/contrib/auth/tests/signals.py +15 −1 Original line number Diff line number Diff line Loading @@ -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. Loading docs/releases/1.5.txt +4 −0 Original line number Diff line number Diff line Loading @@ -191,6 +191,10 @@ Django 1.5 also includes several smaller improvements worth noting: recommended as good practice to provide those templates in order to present pretty error pages to the user. * :mod:`django.contrib.auth` provides a new signal that is emitted whenever a user fails to login successfully. See :data:`~django.contrib.auth.signals.user_login_failed` Backwards incompatible changes in 1.5 ===================================== Loading docs/topics/auth.txt +20 −1 Original line number Diff line number Diff line Loading @@ -876,13 +876,15 @@ The auth framework uses two :doc:`signals </topics/signals>` that can be used for notification when a user logs in or out. .. data:: django.contrib.auth.signals.user_logged_in :module: .. versionadded:: 1.3 Sent when a user logs in successfully. Arguments sent with this signal: ``sender`` As above: the class of the user that just logged in. The class of the user that just logged in. ``request`` The current :class:`~django.http.HttpRequest` instance. Loading @@ -891,6 +893,8 @@ Arguments sent with this signal: The user instance that just logged in. .. data:: django.contrib.auth.signals.user_logged_out :module: .. versionadded:: 1.3 Sent when the logout method is called. Loading @@ -905,6 +909,21 @@ Sent when the logout method is called. The user instance that just logged out or ``None`` if the user was not authenticated. .. data:: django.contrib.auth.signals.user_login_failed :module: .. versionadded:: 1.5 Sent when the user failed to login successfully ``sender`` The name of the module used for authentication. ``credentials`` A dictonary of keyword arguments containing the user credentials that were passed to :func:`~django.contrib.auth.authenticate()` or your own custom authentication backend. Credentials matching a set of 'sensitive' patterns, (including password) will not be sent in the clear as part of the signal. Limiting access to logged-in users ---------------------------------- Loading Loading
django/contrib/auth/__init__.py +22 −1 Original line number Diff line number Diff line 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' Loading Loading @@ -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. Loading @@ -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): """ Loading
django/contrib/auth/signals.py +1 −0 Original line number Diff line number Diff line 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'])
django/contrib/auth/tests/signals.py +15 −1 Original line number Diff line number Diff line Loading @@ -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. Loading
docs/releases/1.5.txt +4 −0 Original line number Diff line number Diff line Loading @@ -191,6 +191,10 @@ Django 1.5 also includes several smaller improvements worth noting: recommended as good practice to provide those templates in order to present pretty error pages to the user. * :mod:`django.contrib.auth` provides a new signal that is emitted whenever a user fails to login successfully. See :data:`~django.contrib.auth.signals.user_login_failed` Backwards incompatible changes in 1.5 ===================================== Loading
docs/topics/auth.txt +20 −1 Original line number Diff line number Diff line Loading @@ -876,13 +876,15 @@ The auth framework uses two :doc:`signals </topics/signals>` that can be used for notification when a user logs in or out. .. data:: django.contrib.auth.signals.user_logged_in :module: .. versionadded:: 1.3 Sent when a user logs in successfully. Arguments sent with this signal: ``sender`` As above: the class of the user that just logged in. The class of the user that just logged in. ``request`` The current :class:`~django.http.HttpRequest` instance. Loading @@ -891,6 +893,8 @@ Arguments sent with this signal: The user instance that just logged in. .. data:: django.contrib.auth.signals.user_logged_out :module: .. versionadded:: 1.3 Sent when the logout method is called. Loading @@ -905,6 +909,21 @@ Sent when the logout method is called. The user instance that just logged out or ``None`` if the user was not authenticated. .. data:: django.contrib.auth.signals.user_login_failed :module: .. versionadded:: 1.5 Sent when the user failed to login successfully ``sender`` The name of the module used for authentication. ``credentials`` A dictonary of keyword arguments containing the user credentials that were passed to :func:`~django.contrib.auth.authenticate()` or your own custom authentication backend. Credentials matching a set of 'sensitive' patterns, (including password) will not be sent in the clear as part of the signal. Limiting access to logged-in users ---------------------------------- Loading