mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Merge pull request #2359 from paperless-ngx/feature-log-failed-auth
Feature: Log failed login attempts
This commit is contained in:
15
src/paperless/apps.py
Normal file
15
src/paperless/apps.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from paperless.signals import handle_failed_login
|
||||
|
||||
|
||||
class PaperlessConfig(AppConfig):
|
||||
name = "paperless"
|
||||
|
||||
verbose_name = _("Paperless")
|
||||
|
||||
def ready(self):
|
||||
from django.contrib.auth.signals import user_login_failed
|
||||
|
||||
user_login_failed.connect(handle_failed_login)
|
||||
AppConfig.ready(self)
|
@@ -416,6 +416,13 @@ if _paperless_url:
|
||||
# always allow localhost. Necessary e.g. for healthcheck in docker.
|
||||
ALLOWED_HOSTS = [_paperless_uri.hostname] + ["localhost"]
|
||||
|
||||
# For use with trusted proxies
|
||||
_trusted_proxies = os.getenv("PAPERLESS_TRUSTED_PROXIES")
|
||||
if _trusted_proxies:
|
||||
TRUSTED_PROXIES = _trusted_proxies.split(",")
|
||||
else:
|
||||
TRUSTED_PROXIES = []
|
||||
|
||||
# The secret key has a default that should be fine so long as you're hosting
|
||||
# Paperless on a closed network. However, if you're putting this anywhere
|
||||
# public, you should change the key to something unique and verbose.
|
||||
|
32
src/paperless/signals.py
Normal file
32
src/paperless/signals.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from ipware import get_client_ip
|
||||
|
||||
logger = logging.getLogger("paperless.auth")
|
||||
|
||||
|
||||
# https://docs.djangoproject.com/en/4.1/ref/contrib/auth/#django.contrib.auth.signals.user_login_failed
|
||||
def handle_failed_login(sender, credentials, request, **kwargs):
|
||||
client_ip, is_routable = get_client_ip(
|
||||
request,
|
||||
proxy_trusted_ips=settings.TRUSTED_PROXIES,
|
||||
)
|
||||
if client_ip is None:
|
||||
logger.info(
|
||||
f"Login failed for user `{credentials['username']}`."
|
||||
+ " Unable to determine IP address.",
|
||||
)
|
||||
else:
|
||||
if is_routable:
|
||||
# We got the client's IP address
|
||||
logger.info(
|
||||
f"Login failed for user `{credentials['username']}`"
|
||||
+ f" from IP `{client_ip}.`",
|
||||
)
|
||||
else:
|
||||
# The client's IP address is private
|
||||
logger.info(
|
||||
f"Login failed for user `{credentials['username']}`"
|
||||
+ f" from private IP `{client_ip}.`",
|
||||
)
|
80
src/paperless/tests/test_signals.py
Normal file
80
src/paperless/tests/test_signals.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from django.http import HttpRequest
|
||||
from django.test import TestCase
|
||||
from paperless.signals import handle_failed_login
|
||||
|
||||
|
||||
class TestFailedLoginLogging(TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.creds = {
|
||||
"username": "john lennon",
|
||||
}
|
||||
|
||||
def test_none(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Request with no IP possible
|
||||
WHEN:
|
||||
- Request provided to signal handler
|
||||
THEN:
|
||||
- Unable to determine logged
|
||||
"""
|
||||
request = HttpRequest()
|
||||
request.META = {}
|
||||
with self.assertLogs("paperless.auth") as logs:
|
||||
handle_failed_login(None, self.creds, request)
|
||||
|
||||
self.assertEqual(
|
||||
logs.output,
|
||||
[
|
||||
"INFO:paperless.auth:Login failed for user `john lennon`. Unable to determine IP address.",
|
||||
],
|
||||
)
|
||||
|
||||
def test_public(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Request with publicly routeable IP
|
||||
WHEN:
|
||||
- Request provided to signal handler
|
||||
THEN:
|
||||
- Expected IP is logged
|
||||
"""
|
||||
request = HttpRequest()
|
||||
request.META = {
|
||||
"HTTP_X_FORWARDED_FOR": "177.139.233.139",
|
||||
}
|
||||
with self.assertLogs("paperless.auth") as logs:
|
||||
handle_failed_login(None, self.creds, request)
|
||||
|
||||
self.assertEqual(
|
||||
logs.output,
|
||||
[
|
||||
"INFO:paperless.auth:Login failed for user `john lennon` from IP `177.139.233.139.`",
|
||||
],
|
||||
)
|
||||
|
||||
def test_private(self):
|
||||
"""
|
||||
GIVEN:
|
||||
- Request with private range IP
|
||||
WHEN:
|
||||
- Request provided to signal handler
|
||||
THEN:
|
||||
- Expected IP is logged
|
||||
- IP is noted to be a private IP
|
||||
"""
|
||||
request = HttpRequest()
|
||||
request.META = {
|
||||
"HTTP_X_FORWARDED_FOR": "10.0.0.1",
|
||||
}
|
||||
with self.assertLogs("paperless.auth") as logs:
|
||||
handle_failed_login(None, self.creds, request)
|
||||
|
||||
self.assertEqual(
|
||||
logs.output,
|
||||
[
|
||||
"INFO:paperless.auth:Login failed for user `john lennon` from private IP `10.0.0.1.`",
|
||||
],
|
||||
)
|
Reference in New Issue
Block a user