From c611ff4d896ef58e7ca493d997abaed3dc25e60d Mon Sep 17 00:00:00 2001 From: Struan Donald Date: Mon, 18 Nov 2024 14:34:36 +0000 Subject: [PATCH] add optional logging of failed login attempts If LOG_FAILED_LOGINS is True in .env then this will use the user_login_failed signal to send some basic details about failed logins if logging is configured. Hopefully helpful to try and work out why users are unable to log in. Note that this does not include the reason shown to the user but does say if the account is present, active and has a marker object. --- ceuk-marking/settings.py | 2 ++ crowdsourcer/views/base.py | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/ceuk-marking/settings.py b/ceuk-marking/settings.py index 758f552..b69329e 100644 --- a/ceuk-marking/settings.py +++ b/ceuk-marking/settings.py @@ -33,6 +33,7 @@ HIDE_DEBUG_TOOLBAR=(bool, False), LOG_LEVEL=(str, "WARNING"), BRAND=(str, "default"), + LOG_FAILED_LOGINS=(bool, False), ) environ.Env.read_env(BASE_DIR / ".env") @@ -45,6 +46,7 @@ MAPIT_API_KEY = env("MAPIT_API_KEY") LOG_LEVEL = env("LOG_LEVEL") BRAND = env("BRAND") +LOG_FAILED_LOGINS = env("LOG_FAILED_LOGINS") BRAND_TEMPLATES = BASE_DIR / "cobrands" / BRAND diff --git a/crowdsourcer/views/base.py b/crowdsourcer/views/base.py index 17ac170..010f349 100644 --- a/crowdsourcer/views/base.py +++ b/crowdsourcer/views/base.py @@ -2,10 +2,13 @@ from django.conf import settings from django.contrib.auth.mixins import UserPassesTestMixin +from django.contrib.auth.models import User +from django.contrib.auth.signals import user_login_failed from django.core.exceptions import PermissionDenied from django.core.mail import mail_admins from django.db.models import Count, F, FloatField, OuterRef, Subquery from django.db.models.functions import Cast +from django.dispatch import receiver from django.http import JsonResponse from django.views.generic import ListView, TemplateView @@ -639,3 +642,25 @@ def get_context_data(self, **kwargs): context["do_not_mark_only"] = do_not_mark_only return context + + +@receiver(user_login_failed) +def log_failed_login(sender, credentials, **kwargs): + print(f"LOG_FAILED_LOGINS is {settings.LOG_FAILED_LOGINS}") + if not settings.LOG_FAILED_LOGINS: + return + + username = credentials.get("username", None) + msg = "User exists" + try: + u = User.objects.get(username=username) + if not u.is_active: + msg = "User exists, is not active" + elif not hasattr(u, "marker"): + msg = "User exists, has no marker" + except User.DoesNotExist: + msg = "User does not exist" + + logger.warning( + f"Failed login attempt: Username '{username}' failed to authenticate. {msg}" + )