diff --git a/Dockerfile b/Dockerfile index 6122cab8f..2803e74c1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -222,7 +222,7 @@ RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache \ && rm --recursive --force --verbose /var/tmp/* \ && rm --recursive --force --verbose /var/cache/apt/archives/* \ && truncate --size 0 /var/log/*log - +RUN --mount=type=cache,target=/root/.cache/pip/,id=pip-cache python3 -m pip install --default-timeout=1000 django-auditlog # copy backend COPY --chown=1000:1000 ./src ./ diff --git a/src/documents/admin.py b/src/documents/admin.py index a190f8d1e..16f707306 100644 --- a/src/documents/admin.py +++ b/src/documents/admin.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.contrib import admin from guardian.admin import GuardedModelAdmin @@ -12,6 +13,10 @@ from documents.models import ShareLink from documents.models import StoragePath from documents.models import Tag +if settings.AUDIT_ENABLED: + from auditlog.admin import LogEntryAdmin + from auditlog.models import LogEntry + class CorrespondentAdmin(GuardedModelAdmin): list_display = ("name", "match", "matching_algorithm") @@ -148,3 +153,12 @@ admin.site.register(StoragePath, StoragePathAdmin) admin.site.register(PaperlessTask, TaskAdmin) admin.site.register(Note, NotesAdmin) admin.site.register(ShareLink, ShareLinksAdmin) + +if settings.AUDIT_ENABLED: + + class LogEntryAUDIT(LogEntryAdmin): + def has_delete_permission(self, request, obj=None): + return False + + admin.site.unregister(LogEntry) + admin.site.register(LogEntry, LogEntryAUDIT) diff --git a/src/documents/models.py b/src/documents/models.py index a1f7d7dd6..767168a51 100644 --- a/src/documents/models.py +++ b/src/documents/models.py @@ -20,6 +20,9 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from multiselectfield import MultiSelectField +if settings.AUDIT_ENABLED: + from auditlog.registry import auditlog + from documents.data_models import DocumentSource from documents.parsers import get_default_file_extension @@ -872,3 +875,10 @@ class ConsumptionTemplate(models.Model): def __str__(self): return f"{self.name}" + + +if settings.AUDIT_ENABLED: + auditlog.register(Document, m2m_fields={"tags"}) + auditlog.register(Correspondent) + auditlog.register(Tag) + auditlog.register(DocumentType) diff --git a/src/paperless/settings.py b/src/paperless/settings.py index 3cbfb4bff..60cddc4f8 100644 --- a/src/paperless/settings.py +++ b/src/paperless/settings.py @@ -15,6 +15,8 @@ from urllib.parse import urlparse from celery.schedules import crontab from concurrent_log_handler.queue import setup_logging_queues +from django.core.exceptions import ImproperlyConfigured +from django.db import connections from django.utils.translation import gettext_lazy as _ from dotenv import load_dotenv @@ -933,6 +935,19 @@ TIKA_GOTENBERG_ENDPOINT = os.getenv( if TIKA_ENABLED: INSTALLED_APPS.append("paperless_tika.apps.PaperlessTikaConfig") +AUDIT_ENABLED = __get_boolean("PAPERLESS_AUDIT_ENABLED", "NO") +if AUDIT_ENABLED: + INSTALLED_APPS.append("auditlog") + MIDDLEWARE.append("auditlog.middleware.AuditlogMiddleware") +db_conn = connections["default"] + +all_tables = db_conn.introspection.table_names() + +if ("auditlog_logentry" in all_tables) and not (AUDIT_ENABLED): + raise ImproperlyConfigured( + "auditlog table was found but PAPERLESS_AUDIT_ENABLED is not active", + ) + def _parse_ignore_dates( env_ignore: str,