From c600f3f514aabc4b40d991b6ec38dd3c47c51259 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Thu, 3 Feb 2022 16:44:16 +0100 Subject: [PATCH] Add request response logging --- config/settings/base.py | 5 +- config/urls.py | 4 +- vbv_lernwelt/core/apps.py | 2 +- vbv_lernwelt/core/middleware/__init__.py | 0 .../{middleware.py => middleware/auth.py} | 3 + vbv_lernwelt/core/middleware/security.py | 72 +++++++++++++++++++ vbv_lernwelt/core/models.py | 14 +++- 7 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 vbv_lernwelt/core/middleware/__init__.py rename vbv_lernwelt/core/{middleware.py => middleware/auth.py} (95%) create mode 100644 vbv_lernwelt/core/middleware/security.py diff --git a/config/settings/base.py b/config/settings/base.py index a1c5565c..f95aec55 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -78,8 +78,8 @@ THIRD_PARTY_APPS = [ ] LOCAL_APPS = [ - "vbv_lernwelt.core", "vbv_lernwelt.users", + "vbv_lernwelt.core", # Your stuff: custom apps go here ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps @@ -138,7 +138,8 @@ MIDDLEWARE = [ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.common.BrokenLinkEmailsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - "vbv_lernwelt.core.middleware.AuthenticationRequiredMiddleware", + "vbv_lernwelt.core.middleware.auth.AuthenticationRequiredMiddleware", + "vbv_lernwelt.core.middleware.security.SecurityRequestResponseLoggingMiddleware", ] # STATIC diff --git a/config/urls.py b/config/urls.py index fe70a6f4..b389857e 100644 --- a/config/urls.py +++ b/config/urls.py @@ -9,10 +9,10 @@ from django.views.generic import TemplateView from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView from rest_framework.authtoken.views import obtain_auth_token -from vbv_lernwelt.core.middleware import django_view_authentication_exempt +from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt urlpatterns = [ - path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), + path("", django_view_authentication_exempt(TemplateView.as_view(template_name="pages/home.html")), name="home"), path("about/", TemplateView.as_view(template_name="pages/about.html"), name="about"), # Django Admin, use {% url 'admin:index' %} path(settings.ADMIN_URL, admin.site.urls), diff --git a/vbv_lernwelt/core/apps.py b/vbv_lernwelt/core/apps.py index 8115ae60..df80bc98 100644 --- a/vbv_lernwelt/core/apps.py +++ b/vbv_lernwelt/core/apps.py @@ -3,4 +3,4 @@ from django.apps import AppConfig class CoreConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'core' + name = 'vbv_lernwelt.core' diff --git a/vbv_lernwelt/core/middleware/__init__.py b/vbv_lernwelt/core/middleware/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/vbv_lernwelt/core/middleware.py b/vbv_lernwelt/core/middleware/auth.py similarity index 95% rename from vbv_lernwelt/core/middleware.py rename to vbv_lernwelt/core/middleware/auth.py index eae2b0ce..628078cc 100644 --- a/vbv_lernwelt/core/middleware.py +++ b/vbv_lernwelt/core/middleware/auth.py @@ -1,9 +1,12 @@ from functools import wraps +import structlog from django.conf import settings from django.contrib.auth.views import redirect_to_login from django.utils.deprecation import MiddlewareMixin +logger = structlog.get_logger(__name__) + class AuthenticationRequiredMiddleware(MiddlewareMixin): def process_view(self, request, callback, callback_args, callback_kwargs): diff --git a/vbv_lernwelt/core/middleware/security.py b/vbv_lernwelt/core/middleware/security.py new file mode 100644 index 00000000..dc159327 --- /dev/null +++ b/vbv_lernwelt/core/middleware/security.py @@ -0,0 +1,72 @@ +import uuid + +import structlog +from structlog.threadlocal import bind_threadlocal, clear_threadlocal + +from vbv_lernwelt.core.models import SecurityRequestResponseLog + +logger = structlog.get_logger(__name__) + + +class SecurityRequestResponseLoggingMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def create_logging_threadlocalbind(self, request): + request_username = request.user.username if hasattr(request, 'user') else '' + + bind_threadlocal( + request_method=request.method, + request_full_path=request.get_full_path(), + request_username=request_username, + request_client_ip=request.META.get('REMOTE_ADDR'), + request_trace_id=uuid.uuid4().hex, + ) + + def create_database_security_request_response_log(self, request, response): + try: + entry = SecurityRequestResponseLog() + entry.label = getattr(request, 'security_request_logging', '') + entry.request_method = request.method + entry.request_full_path = request.get_full_path()[:255] + entry.request_username = request.user.username if hasattr(request, 'user') else '' + entry.request_client_ip = request.META.get('REMOTE_ADDR') + entry.request_scn = getattr(request, 'scn', '') + entry.response_status_code = response.status_code + entry.additional_json_data = getattr(request, 'log_additional_json_data', {}) + + entry.save() + + # pylint: disable=broad-except + except Exception: + logger.warn('could not create db entry', label='security', exc_info=True) + + def log_request_response(self, request): + clear_threadlocal() + self.create_logging_threadlocalbind(request) + + logger.info( + 'url access initialized', + label='security', + ) + + response = self.get_response(request) + + security_request_logging = getattr(request, 'security_request_logging', None) + if security_request_logging: + self.create_database_security_request_response_log(request, response) + + logger.info( + 'url access finished', + label='security', + response_status_code=response.status_code, + request_ratelimited=getattr(request, 'limited', False), + request_finished=True + ) + + clear_threadlocal() + + return response + + def __call__(self, request): + return self.log_request_response(request) diff --git a/vbv_lernwelt/core/models.py b/vbv_lernwelt/core/models.py index 71a83623..28dbc96a 100644 --- a/vbv_lernwelt/core/models.py +++ b/vbv_lernwelt/core/models.py @@ -1,3 +1,15 @@ from django.db import models +from django.db.models import JSONField -# Create your models here. + +class SecurityRequestResponseLog(models.Model): + label = models.CharField(max_length=255, blank=True, default='') + + request_method = models.CharField(max_length=255, blank=True, default='') + request_full_path = models.CharField(max_length=255, blank=True, default='') + request_username = models.CharField(max_length=255, blank=True, default='') + request_client_ip = models.CharField(max_length=255, blank=True, default='') + + response_status_code = models.CharField(max_length=255, blank=True, default='') + + additional_json_data = JSONField(default=dict, blank=True)