Ticket #20495: log_failed_login.diff

File log_failed_login.diff, 4.8 KB (added by boylea, 11 years ago)

Uses warning level for logging user login failures, provides extra context to logger, includes documentation

  • django/contrib/auth/__init__.py

    diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
    index 2f620a3..ca85879 100644
    a b  
    11import re
     2import logging
    23
    34from django.conf import settings
    45from django.core.exceptions import ImproperlyConfigured, PermissionDenied
    def authenticate(**credentials):  
    5960        user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
    6061        return user
    6162
    62     # The credentials supplied are invalid to all backends, fire signal
    63     user_login_failed.send(sender=__name__,
    64             credentials=_clean_credentials(credentials))
     63    # The credentials supplied are invalid to all backends, fire signal and log error
     64    credentials=_clean_credentials(credentials)
     65    user_login_failed.send(sender=__name__, credentials=credentials)
     66
     67    logger = logging.getLogger('django.security.FailedLogin')   
     68    # Make sure username is in extra context, for formatting purposes
     69    if 'username' not in credentials:
     70        credentials['username'] = None
     71    logger.warning("user login failed", extra=credentials)
    6572
    6673
    6774def login(request, user):
  • django/test/utils.py

    diff --git a/django/test/utils.py b/django/test/utils.py
    index 591c588..ee6278e 100644
    a b class IgnorePendingDeprecationWarningsMixin(IgnoreDeprecationWarningsMixin):  
    421421
    422422
    423423@contextmanager
    424 def patch_logger(logger_name, log_level):
     424def patch_logger(logger_name, log_level, formatstr=''):
    425425    """
    426426    Context manager that takes a named logger and the logging level
    427427    and provides a simple mock-like list of messages received
    428428    """
    429429    calls = []
    430     def replacement(msg):
    431         calls.append(msg)
     430    def replacement(msg, *args, **kwargs):
     431        extra = kwargs.get('extra',{})
     432        calls.append((formatstr % extra) + (msg % args))
    432433    logger = logging.getLogger(logger_name)
    433434    orig = getattr(logger, log_level)
    434435    setattr(logger, log_level, replacement)
  • docs/ref/contrib/auth.txt

    diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt
    index afbc6ec..7fc0343 100644
    a b can be used for notification when a user logs in or out.  
    409409        authentication backend. Credentials matching a set of 'sensitive' patterns,
    410410        (including password) will not be sent in the clear as part of the signal.
    411411
     412     It should be noted that the failure is also logged as a warning to ``django.
     413     security.FailedLogin``
     414
     415     .. versionadded:: 1.6
     416
    412417.. _authentication-backends-reference:
    413418
    414419Authentication backends
  • docs/topics/logging.txt

    diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt
    index a88201a..78cf32b 100644
    a b level or handlers that are installed.  
    437437``django.security.*``
    438438~~~~~~~~~~~~~~~~~~~~~~
    439439
     440.. versionadded:: 1.6
     441
    440442The security loggers will receive messages on any occurrence of
    441443:exc:`~django.core.exceptions.SuspiciousOperation`. There is a sub-logger for
    442444each sub-type of SuspiciousOperation. The level of the log event depends on
    specific logger following this example::  
    463465                'propagate': False,
    464466            },
    465467
     468Invalid username/passwords will be logged to ``django.security.FailedLogin`` as
     469a warning. This logger will have the following extra context:
     470
     471* ``username``: the provided username which failed.
     472
    466473Handlers
    467474--------
    468475
  • tests/logging_tests/tests.py

    diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
    index 0c2d269..ba87d78 100644
    a b class SettingsConfigureLogging(TestCase):  
    358358
    359359
    360360class SecurityLoggerTest(TestCase):
    361 
     361    """
     362    Test that security messages are being logged
     363    """
    362364    urls = 'logging_tests.urls'
    363365
    364366    def test_suspicious_operation_creates_log_message(self):
    class SecurityLoggerTest(TestCase):  
    374376                response = self.client.get('/suspicious_spec/')
    375377                self.assertEqual(len(calls), 1)
    376378                self.assertEqual(calls[0], 'dubious')
     379
     380    def test_user_login_failed_creates_log_message(self):
     381        with self.settings(DEBUG=True):
     382            with patch_logger('django.security.FailedLogin', 'warning', "%(username)s : ") as calls:
     383                self.client.login(username='testclient', password='bad')
     384                self.assertEqual(len(calls), 1)
     385                self.assertEqual(calls[0], "testclient : user login failed")
     386
     387    def test_no_user_login_failed_creates_log_message(self):
     388        with self.settings(DEBUG=True):
     389            with patch_logger('django.security.FailedLogin', 'warning', "%(username)s : ") as calls:
     390                self.client.login()
     391                self.assertEqual(len(calls), 1)
     392                self.assertEqual(calls[0], "None : user login failed")
Back to Top