Change: default-enable audit log

This commit is contained in:
shamoon 2024-04-01 14:51:51 -07:00
parent 00b04c2e86
commit 57709591f3
11 changed files with 57 additions and 30 deletions

View File

@ -1309,13 +1309,11 @@ assigns or creates tags if a properly formatted barcode is detected.
## Audit Trail ## Audit Trail
#### [`PAPERLESS_AUDIT_LOG_ENABLED=<bool>`](#PAPERLESS_AUDIT_LOG_ENABLED) {#PAPERLESS_AUDIT_LOG_ENABLED} #### [`PAPERLESS_AUDIT_LOG_DISABLED=<bool>`](#PAPERLESS_AUDIT_LOG_DISABLED) {#PAPERLESS_AUDIT_LOG_DISABLED}
: Enables an audit trail for documents, document types, correspondents, and tags. Log entries can be viewed in the Django backend only. : Disables the audit trail for documents, document types, correspondents, and tags.
!!! warning Defaults to false.
Once enabled cannot be disabled
## Collate Double-Sided Documents {#collate} ## Collate Double-Sided Documents {#collate}

View File

@ -15,7 +15,7 @@ from documents.models import ShareLink
from documents.models import StoragePath from documents.models import StoragePath
from documents.models import Tag from documents.models import Tag
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
from auditlog.admin import LogEntryAdmin from auditlog.admin import LogEntryAdmin
from auditlog.models import LogEntry from auditlog.models import LogEntry
@ -197,7 +197,7 @@ admin.site.register(ShareLink, ShareLinksAdmin)
admin.site.register(CustomField, CustomFieldsAdmin) admin.site.register(CustomField, CustomFieldsAdmin)
admin.site.register(CustomFieldInstance, CustomFieldInstancesAdmin) admin.site.register(CustomFieldInstance, CustomFieldInstancesAdmin)
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
class LogEntryAUDIT(LogEntryAdmin): class LogEntryAUDIT(LogEntryAdmin):
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):

View File

@ -9,6 +9,9 @@ from typing import Optional
import tqdm import tqdm
from django.conf import settings from django.conf import settings
if not settings.AUDIT_LOG_DISABLED:
from auditlog.models import LogEntry
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.contrib.auth.models import User from django.contrib.auth.models import User
@ -307,6 +310,11 @@ class Command(BaseCommand):
serializers.serialize("json", ApplicationConfiguration.objects.all()), serializers.serialize("json", ApplicationConfiguration.objects.all()),
) )
if not settings.AUDIT_LOG_DISABLED:
manifest += json.loads(
serializers.serialize("json", LogEntry.objects.all()),
)
# These are treated specially and included in the per-document manifest # These are treated specially and included in the per-document manifest
# if that setting is enabled. Otherwise, they are just exported to the bulk # if that setting is enabled. Otherwise, they are just exported to the bulk
# manifest # manifest

View File

@ -20,7 +20,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from multiselectfield import MultiSelectField from multiselectfield import MultiSelectField
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
from auditlog.registry import auditlog from auditlog.registry import auditlog
from documents.data_models import DocumentSource from documents.data_models import DocumentSource
@ -881,7 +881,7 @@ class CustomFieldInstance(models.Model):
raise NotImplementedError(self.field.data_type) raise NotImplementedError(self.field.data_type)
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
auditlog.register(Document, m2m_fields={"tags"}) auditlog.register(Document, m2m_fields={"tags"})
auditlog.register(Correspondent) auditlog.register(Correspondent)
auditlog.register(Tag) auditlog.register(Tag)

View File

@ -43,7 +43,7 @@ from documents.plugins.helpers import ProgressStatusOptions
from documents.sanity_checker import SanityCheckFailedException from documents.sanity_checker import SanityCheckFailedException
from documents.signals import document_updated from documents.signals import document_updated
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
import json import json
from auditlog.models import LogEntry from auditlog.models import LogEntry
@ -275,7 +275,7 @@ def update_document_archive_file(document_id):
archive_filename=document.archive_filename, archive_filename=document.archive_filename,
) )
newDocument = Document.objects.get(pk=document.pk) newDocument = Document.objects.get(pk=document.pk)
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
LogEntry.objects.log_create( LogEntry.objects.log_create(
instance=oldDocument, instance=oldDocument,
changes=json.dumps( changes=json.dumps(

View File

@ -7,6 +7,7 @@ from pathlib import Path
from unittest import mock from unittest import mock
from zipfile import ZipFile from zipfile import ZipFile
from auditlog.models import LogEntry
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -122,6 +123,19 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.workflow.actions.add(self.action) self.workflow.actions.add(self.action)
self.workflow.save() self.workflow.save()
LogEntry.objects.log_create(
instance=self.dt1,
changes=json.dumps(
{
"name": [
self.dt1.name,
"New name",
],
},
),
action=LogEntry.Action.UPDATE,
)
super().setUp() super().setUp()
def _get_document_from_manifest(self, manifest, id): def _get_document_from_manifest(self, manifest, id):
@ -780,3 +794,17 @@ class TestExportImport(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
self.assertEqual(ContentType.objects.count(), num_content_type_objects) self.assertEqual(ContentType.objects.count(), num_content_type_objects)
self.assertEqual(Permission.objects.count(), num_permission_objects + 1) self.assertEqual(Permission.objects.count(), num_permission_objects + 1)
def test_exporter_with_auditlog_disabled(self):
shutil.rmtree(os.path.join(self.dirs.media_dir, "documents"))
shutil.copytree(
os.path.join(os.path.dirname(__file__), "samples", "documents"),
os.path.join(self.dirs.media_dir, "documents"),
)
with override_settings(
AUDIT_LOG_DISABLED=True,
):
manifest = self._do_export(use_filename_format=True)
for obj in manifest:
self.assertNotEqual(obj["model"], "auditlog.logentry")

View File

@ -153,7 +153,7 @@ from paperless.config import GeneralConfig
from paperless.db import GnuPG from paperless.db import GnuPG
from paperless.views import StandardPagination from paperless.views import StandardPagination
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
from auditlog.models import LogEntry from auditlog.models import LogEntry
logger = logging.getLogger("paperless.api") logger = logging.getLogger("paperless.api")
@ -636,7 +636,7 @@ class DocumentViewSet(
c.save() c.save()
# If audit log is enabled make an entry in the log # If audit log is enabled make an entry in the log
# about this note change # about this note change
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
LogEntry.objects.log_create( LogEntry.objects.log_create(
instance=doc, instance=doc,
changes=json.dumps( changes=json.dumps(
@ -671,7 +671,7 @@ class DocumentViewSet(
return HttpResponseForbidden("Insufficient permissions to delete notes") return HttpResponseForbidden("Insufficient permissions to delete notes")
note = Note.objects.get(id=int(request.GET.get("id"))) note = Note.objects.get(id=int(request.GET.get("id")))
if settings.AUDIT_LOG_ENABLED: if not settings.AUDIT_LOG_DISABLED:
LogEntry.objects.log_create( LogEntry.objects.log_create(
instance=doc, instance=doc,
changes=json.dumps( changes=json.dumps(

View File

@ -5,7 +5,6 @@ import shutil
import stat import stat
from django.conf import settings from django.conf import settings
from django.core.checks import Critical
from django.core.checks import Error from django.core.checks import Error
from django.core.checks import Warning from django.core.checks import Warning
from django.core.checks import register from django.core.checks import register
@ -205,13 +204,10 @@ def audit_log_check(app_configs, **kwargs):
all_tables = db_conn.introspection.table_names() all_tables = db_conn.introspection.table_names()
result = [] result = []
if ("auditlog_logentry" in all_tables) and not (settings.AUDIT_LOG_ENABLED): if ("auditlog_logentry" in all_tables) or (settings.AUDIT_LOG_DISABLED):
result.append( result.append(
Critical( Warning(
( ("auditlog table was found but AUDIT_LOG_DISABLED is active."),
"auditlog table was found but PAPERLESS_AUDIT_LOG_ENABLED"
" is not active. This setting cannot be disabled after enabling"
),
), ),
) )

View File

@ -1045,8 +1045,8 @@ TIKA_GOTENBERG_ENDPOINT = os.getenv(
if TIKA_ENABLED: if TIKA_ENABLED:
INSTALLED_APPS.append("paperless_tika.apps.PaperlessTikaConfig") INSTALLED_APPS.append("paperless_tika.apps.PaperlessTikaConfig")
AUDIT_LOG_ENABLED = __get_boolean("PAPERLESS_AUDIT_LOG_ENABLED", "NO") AUDIT_LOG_DISABLED = __get_boolean("PAPERLESS_AUDIT_LOG_DISABLED", "NO")
if AUDIT_LOG_ENABLED: if not AUDIT_LOG_DISABLED:
INSTALLED_APPS.append("auditlog") INSTALLED_APPS.append("auditlog")
MIDDLEWARE.append("auditlog.middleware.AuditlogMiddleware") MIDDLEWARE.append("auditlog.middleware.AuditlogMiddleware")

View File

@ -247,7 +247,7 @@ class TestAuditLogChecks(TestCase):
""" """
introspect_mock = mock.MagicMock() introspect_mock = mock.MagicMock()
introspect_mock.introspection.table_names.return_value = ["auditlog_logentry"] introspect_mock.introspection.table_names.return_value = ["auditlog_logentry"]
with override_settings(AUDIT_LOG_ENABLED=False): with override_settings(AUDIT_LOG_DISABLED=True):
with mock.patch.dict( with mock.patch.dict(
"paperless.checks.connections", "paperless.checks.connections",
{"default": introspect_mock}, {"default": introspect_mock},
@ -259,9 +259,6 @@ class TestAuditLogChecks(TestCase):
msg = msgs[0] msg = msgs[0]
self.assertIn( self.assertIn(
( ("auditlog table was found but AUDIT_LOG_DISABLED is active."),
"auditlog table was found but PAPERLESS_AUDIT_LOG_ENABLED"
" is not active."
),
msg.msg, msg.msg,
) )

View File

@ -17,8 +17,8 @@ omit =
[coverage:report] [coverage:report]
exclude_also = exclude_also =
if settings.AUDIT_LOG_ENABLED: if settings.AUDIT_LOG_DISABLED:
if AUDIT_LOG_ENABLED: if AUDIT_LOG_DISABLED:
if TYPE_CHECKING: if TYPE_CHECKING:
[mypy] [mypy]