]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Breaking: Remove support for document and thumbnail encryption (#11850)
authorTrenton H <797416+stumpylog@users.noreply.github.com>
Sun, 25 Jan 2026 03:29:54 +0000 (19:29 -0800)
committerGitHub <noreply@github.com>
Sun, 25 Jan 2026 03:29:54 +0000 (19:29 -0800)
27 files changed:
docker/install_management_commands.sh
docker/rootfs/usr/local/bin/decrypt_documents [deleted file]
docs/administration.md
docs/migration.md
src/documents/__init__.py
src/documents/admin.py
src/documents/checks.py
src/documents/conditionals.py
src/documents/consumer.py
src/documents/file_handling.py
src/documents/management/commands/decrypt_documents.py [deleted file]
src/documents/management/commands/document_exporter.py
src/documents/management/commands/document_importer.py
src/documents/migrations/0004_remove_document_storage_type.py [new file with mode: 0644]
src/documents/models.py
src/documents/templating/filepath.py
src/documents/tests/samples/documents/originals/0000004.pdf [new file with mode: 0644]
src/documents/tests/samples/documents/originals/0000004.pdf.gpg [deleted file]
src/documents/tests/samples/documents/thumbnails/0000004.webp [new file with mode: 0644]
src/documents/tests/samples/documents/thumbnails/0000004.webp.gpg [deleted file]
src/documents/tests/test_checks.py
src/documents/tests/test_file_handling.py
src/documents/tests/test_management.py
src/documents/tests/test_management_exporter.py
src/documents/views.py
src/paperless/db.py [deleted file]
src/paperless/settings.py

index be972d605f800b4ea97fe201d8f560a63a0e424b..f7a175e9ecc07efae64d197f0782b684d9db5896 100755 (executable)
@@ -4,8 +4,7 @@
 
 set -eu
 
-for command in decrypt_documents \
-       document_archiver \
+for command in document_archiver \
        document_exporter \
        document_importer \
        mail_fetcher \
diff --git a/docker/rootfs/usr/local/bin/decrypt_documents b/docker/rootfs/usr/local/bin/decrypt_documents
deleted file mode 100755 (executable)
index 4da1549..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/command/with-contenv /usr/bin/bash
-# shellcheck shell=bash
-
-set -e
-
-cd "${PAPERLESS_SRC_DIR}"
-
-if [[ $(id -u) == 0 ]]; then
-       s6-setuidgid paperless python3 manage.py decrypt_documents "$@"
-elif [[ $(id -un) == "paperless" ]]; then
-       python3 manage.py decrypt_documents "$@"
-else
-       echo "Unknown user."
-fi
index ddf51bf9a611283b6c1ff9cdd60edc19000ff8ae..2fb70a8062ab4502986bc73760429aee15a62c63 100644 (file)
@@ -580,36 +580,6 @@ document.
     documents, such as encrypted PDF documents. The archiver will skip over
     these documents each time it sees them.
 
-### Managing encryption {#encryption}
-
-!!! warning
-
-    Encryption was removed in [paperless-ng 0.9](changelog.md#paperless-ng-090)
-    because it did not really provide any additional security, the passphrase
-    was stored in a configuration file on the same system as the documents.
-    Furthermore, the entire text content of the documents is stored plain in
-    the database, even if your documents are encrypted. Filenames are not
-    encrypted as well. Finally, the web server provides transparent access to
-    your encrypted documents.
-
-    Consider running paperless on an encrypted filesystem instead, which
-    will then at least provide security against physical hardware theft.
-
-#### Enabling encryption
-
-Enabling encryption is no longer supported.
-
-#### Disabling encryption
-
-Basic usage to disable encryption of your document store:
-
-(Note: If `PAPERLESS_PASSPHRASE` isn't set already, you need to specify
-it here)
-
-```
-decrypt_documents [--passphrase SECR3TP4SSPHRA$E]
-```
-
 ### Detecting duplicates {#fuzzy_duplicate}
 
 Paperless already catches and prevents upload of exactly matching documents,
index 2ef850cbe01b53cfc944d18f26ecbaa409b08067..1c934e6df4f00afd613c67a97eb9220cca2342a9 100644 (file)
@@ -17,3 +17,9 @@ separating the directory ignore from the file ignore.
 | `CONSUMER_POLLING_RETRY_COUNT` | _Removed_                                                                           | Automatic with stability tracking                                                    |
 | `CONSUMER_IGNORE_PATTERNS`     | [`CONSUMER_IGNORE_PATTERNS`](configuration.md#PAPERLESS_CONSUMER_IGNORE_PATTERNS)   | **Now regex, not fnmatch**; user patterns are added to (not replacing) default ones  |
 | _New_                          | [`CONSUMER_IGNORE_DIRS`](configuration.md#PAPERLESS_CONSUMER_IGNORE_DIRS)           | Additional directories to ignore; user entries are added to (not replacing) defaults |
+
+## Encryption Support
+
+Document and thumbnail encryption is no longer supported. This was previously deprecated in [paperless-ng 0.9.3](https://github.com/paperless-ngx/paperless-ngx/blob/dev/docs/changelog.md#paperless-ng-093)
+
+Users must decrypt their document using the `decrypt_documents` command before upgrading.
index dd8c76d19419757ad059ac8155619e37005fd849..861c451852f265b61b0c4afa6088cf3a2e790c48 100644 (file)
@@ -1,5 +1,4 @@
 # this is here so that django finds the checks.
-from documents.checks import changed_password_check
 from documents.checks import parser_check
 
-__all__ = ["changed_password_check", "parser_check"]
+__all__ = ["parser_check"]
index c6f179e2aa3d09389e8dc462d0280514c472480d..1ebbdc9ceaf88787be9c99818e3cee271da49d7a 100644 (file)
@@ -60,7 +60,6 @@ class DocumentAdmin(GuardedModelAdmin):
         "added",
         "modified",
         "mime_type",
-        "storage_type",
         "filename",
         "checksum",
         "archive_filename",
index 8f8fbf4f9f2857cfcb286f20c85e855bbdec6b30..b6e9e90fcb0cd335a4befb51807f55702216d0ca 100644 (file)
@@ -1,60 +1,12 @@
-import textwrap
-
 from django.conf import settings
 from django.core.checks import Error
 from django.core.checks import Warning
 from django.core.checks import register
-from django.core.exceptions import FieldError
-from django.db.utils import OperationalError
-from django.db.utils import ProgrammingError
 
 from documents.signals import document_consumer_declaration
 from documents.templating.utils import convert_format_str_to_template_format
 
 
-@register()
-def changed_password_check(app_configs, **kwargs):
-    from documents.models import Document
-    from paperless.db import GnuPG
-
-    try:
-        encrypted_doc = (
-            Document.objects.filter(
-                storage_type=Document.STORAGE_TYPE_GPG,
-            )
-            .only("pk", "storage_type")
-            .first()
-        )
-    except (OperationalError, ProgrammingError, FieldError):
-        return []  # No documents table yet
-
-    if encrypted_doc:
-        if not settings.PASSPHRASE:
-            return [
-                Error(
-                    "The database contains encrypted documents but no password is set.",
-                ),
-            ]
-
-        if not GnuPG.decrypted(encrypted_doc.source_file):
-            return [
-                Error(
-                    textwrap.dedent(
-                        """
-                The current password doesn't match the password of the
-                existing documents.
-
-                If you intend to change your password, you must first export
-                all of the old documents, start fresh with the new password
-                and then re-import them."
-                """,
-                    ),
-                ),
-            ]
-
-    return []
-
-
 @register()
 def parser_check(app_configs, **kwargs):
     parsers = []
index 47d9bfe4b184f68d47ba576231c13f4e62e5fad2..b93cabf6281b8a5fa87cce687531980d84d7da63 100644 (file)
@@ -128,7 +128,7 @@ def thumbnail_last_modified(request, pk: int) -> datetime | None:
     Cache should be (slightly?) faster than filesystem
     """
     try:
-        doc = Document.objects.only("storage_type").get(pk=pk)
+        doc = Document.objects.only("pk").get(pk=pk)
         if not doc.thumbnail_path.exists():
             return None
         doc_key = get_thumbnail_modified_key(pk)
index 2c1cf025b304a0e5c2214ef4a0b32b23941e5255..4c8c4dd28fa2fbdaf689b7de0e6a80533f67284f 100644 (file)
@@ -497,7 +497,6 @@ class ConsumerPlugin(
                     create_source_path_directory(document.source_path)
 
                     self._write(
-                        document.storage_type,
                         self.unmodified_original
                         if self.unmodified_original is not None
                         else self.working_copy,
@@ -505,7 +504,6 @@ class ConsumerPlugin(
                     )
 
                     self._write(
-                        document.storage_type,
                         thumbnail,
                         document.thumbnail_path,
                     )
@@ -517,7 +515,6 @@ class ConsumerPlugin(
                         )
                         create_source_path_directory(document.archive_path)
                         self._write(
-                            document.storage_type,
                             archive_path,
                             document.archive_path,
                         )
@@ -637,8 +634,6 @@ class ConsumerPlugin(
             )
             self.log.debug(f"Creation date from st_mtime: {create_date}")
 
-        storage_type = Document.STORAGE_TYPE_UNENCRYPTED
-
         if self.metadata.filename:
             title = Path(self.metadata.filename).stem
         else:
@@ -665,7 +660,6 @@ class ConsumerPlugin(
             checksum=hashlib.md5(file_for_checksum.read_bytes()).hexdigest(),
             created=create_date,
             modified=create_date,
-            storage_type=storage_type,
             page_count=page_count,
             original_filename=self.filename,
         )
@@ -736,7 +730,7 @@ class ConsumerPlugin(
                 }
                 CustomFieldInstance.objects.create(**args)  # adds to document
 
-    def _write(self, storage_type, source, target):
+    def _write(self, source, target):
         with (
             Path(source).open("rb") as read_file,
             Path(target).open("wb") as write_file,
index 48cd573117b4c1deb0e199d7e87bd38ee62b37c9..39831016d164be006dccba18c05896f52ce2eee2 100644 (file)
@@ -126,7 +126,6 @@ def generate_filename(
     doc: Document,
     *,
     counter=0,
-    append_gpg=True,
     archive_filename=False,
 ) -> Path:
     base_path: Path | None = None
@@ -170,8 +169,4 @@ def generate_filename(
         final_filename = f"{doc.pk:07}{counter_str}{filetype_str}"
         full_path = Path(final_filename)
 
-    # Add GPG extension if needed
-    if append_gpg and doc.storage_type == doc.STORAGE_TYPE_GPG:
-        full_path = full_path.with_suffix(full_path.suffix + ".gpg")
-
     return full_path
diff --git a/src/documents/management/commands/decrypt_documents.py b/src/documents/management/commands/decrypt_documents.py
deleted file mode 100644 (file)
index 793cac4..0000000
+++ /dev/null
@@ -1,93 +0,0 @@
-from pathlib import Path
-
-from django.conf import settings
-from django.core.management.base import BaseCommand
-from django.core.management.base import CommandError
-
-from documents.models import Document
-from paperless.db import GnuPG
-
-
-class Command(BaseCommand):
-    help = (
-        "This is how you migrate your stored documents from an encrypted "
-        "state to an unencrypted one (or vice-versa)"
-    )
-
-    def add_arguments(self, parser) -> None:
-        parser.add_argument(
-            "--passphrase",
-            help=(
-                "If PAPERLESS_PASSPHRASE isn't set already, you need to specify it here"
-            ),
-        )
-
-    def handle(self, *args, **options) -> None:
-        try:
-            self.stdout.write(
-                self.style.WARNING(
-                    "\n\n"
-                    "WARNING: This script is going to work directly on your "
-                    "document originals, so\n"
-                    "WARNING: you probably shouldn't run "
-                    "this unless you've got a recent backup\n"
-                    "WARNING: handy.  It "
-                    "*should* work without a hitch, but be safe and backup your\n"
-                    "WARNING: stuff first.\n\n"
-                    "Hit Ctrl+C to exit now, or Enter to "
-                    "continue.\n\n",
-                ),
-            )
-            _ = input()
-        except KeyboardInterrupt:
-            return
-
-        passphrase = options["passphrase"] or settings.PASSPHRASE
-        if not passphrase:
-            raise CommandError(
-                "Passphrase not defined.  Please set it with --passphrase or "
-                "by declaring it in your environment or your config.",
-            )
-
-        self.__gpg_to_unencrypted(passphrase)
-
-    def __gpg_to_unencrypted(self, passphrase: str) -> None:
-        encrypted_files = Document.objects.filter(
-            storage_type=Document.STORAGE_TYPE_GPG,
-        )
-
-        for document in encrypted_files:
-            self.stdout.write(f"Decrypting {document}")
-
-            old_paths = [document.source_path, document.thumbnail_path]
-
-            with document.source_file as file_handle:
-                raw_document = GnuPG.decrypted(file_handle, passphrase)
-            with document.thumbnail_file as file_handle:
-                raw_thumb = GnuPG.decrypted(file_handle, passphrase)
-
-            document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
-
-            ext: str = Path(document.filename).suffix
-
-            if not ext == ".gpg":
-                raise CommandError(
-                    f"Abort: encrypted file {document.source_path} does not "
-                    f"end with .gpg",
-                )
-
-            document.filename = Path(document.filename).stem
-
-            with document.source_path.open("wb") as f:
-                f.write(raw_document)
-
-            with document.thumbnail_path.open("wb") as f:
-                f.write(raw_thumb)
-
-            Document.objects.filter(id=document.id).update(
-                storage_type=document.storage_type,
-                filename=document.filename,
-            )
-
-            for path in old_paths:
-                path.unlink()
index 88daeddf594d52d92c9edd092c0c28abf29f954c..77b3b6416fbb66ca1426dfb1a30d69787979949e 100644 (file)
@@ -3,7 +3,6 @@ import json
 import os
 import shutil
 import tempfile
-import time
 from pathlib import Path
 from typing import TYPE_CHECKING
 
@@ -56,7 +55,6 @@ from documents.settings import EXPORTER_FILE_NAME
 from documents.settings import EXPORTER_THUMBNAIL_NAME
 from documents.utils import copy_file_with_basic_stats
 from paperless import version
-from paperless.db import GnuPG
 from paperless.models import ApplicationConfiguration
 from paperless_mail.models import MailAccount
 from paperless_mail.models import MailRule
@@ -316,20 +314,17 @@ class Command(CryptMixin, BaseCommand):
             total=len(document_manifest),
             disable=self.no_progress_bar,
         ):
-            # 3.1. store files unencrypted
-            document_dict["fields"]["storage_type"] = Document.STORAGE_TYPE_UNENCRYPTED
-
             document = document_map[document_dict["pk"]]
 
-            # 3.2. generate a unique filename
+            # 3.1. generate a unique filename
             base_name = self.generate_base_name(document)
 
-            # 3.3. write filenames into manifest
+            # 3.2. write filenames into manifest
             original_target, thumbnail_target, archive_target = (
                 self.generate_document_targets(document, base_name, document_dict)
             )
 
-            # 3.4. write files to target folder
+            # 3.3. write files to target folder
             if not self.data_only:
                 self.copy_document_files(
                     document,
@@ -423,7 +418,6 @@ class Command(CryptMixin, BaseCommand):
                 base_name = generate_filename(
                     document,
                     counter=filename_counter,
-                    append_gpg=False,
                 )
             else:
                 base_name = document.get_public_filename(counter=filename_counter)
@@ -482,45 +476,23 @@ class Command(CryptMixin, BaseCommand):
 
         If the document is encrypted, the files are decrypted before copying them to the target location.
         """
-        if document.storage_type == Document.STORAGE_TYPE_GPG:
-            t = int(time.mktime(document.created.timetuple()))
-
-            original_target.parent.mkdir(parents=True, exist_ok=True)
-            with document.source_file as out_file:
-                original_target.write_bytes(GnuPG.decrypted(out_file))
-                os.utime(original_target, times=(t, t))
-
-            if thumbnail_target:
-                thumbnail_target.parent.mkdir(parents=True, exist_ok=True)
-                with document.thumbnail_file as out_file:
-                    thumbnail_target.write_bytes(GnuPG.decrypted(out_file))
-                    os.utime(thumbnail_target, times=(t, t))
-
-            if archive_target:
-                archive_target.parent.mkdir(parents=True, exist_ok=True)
-                if TYPE_CHECKING:
-                    assert isinstance(document.archive_path, Path)
-                with document.archive_path as out_file:
-                    archive_target.write_bytes(GnuPG.decrypted(out_file))
-                    os.utime(archive_target, times=(t, t))
-        else:
-            self.check_and_copy(
-                document.source_path,
-                document.checksum,
-                original_target,
-            )
+        self.check_and_copy(
+            document.source_path,
+            document.checksum,
+            original_target,
+        )
 
-            if thumbnail_target:
-                self.check_and_copy(document.thumbnail_path, None, thumbnail_target)
+        if thumbnail_target:
+            self.check_and_copy(document.thumbnail_path, None, thumbnail_target)
 
-            if archive_target:
-                if TYPE_CHECKING:
-                    assert isinstance(document.archive_path, Path)
-                self.check_and_copy(
-                    document.archive_path,
-                    document.archive_checksum,
-                    archive_target,
-                )
+        if archive_target:
+            if TYPE_CHECKING:
+                assert isinstance(document.archive_path, Path)
+            self.check_and_copy(
+                document.archive_path,
+                document.archive_checksum,
+                archive_target,
+            )
 
     def check_and_write_json(
         self,
index 3e614c6a6c31283dbb55a25d8aaf35b5d0b5480f..ba3d793b37e62030d82d114be499104804b2ba7c 100644 (file)
@@ -383,8 +383,6 @@ class Command(CryptMixin, BaseCommand):
             else:
                 archive_path = None
 
-            document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
-
             with FileLock(settings.MEDIA_LOCK):
                 if Path(document.source_path).is_file():
                     raise FileExistsError(document.source_path)
diff --git a/src/documents/migrations/0004_remove_document_storage_type.py b/src/documents/migrations/0004_remove_document_storage_type.py
new file mode 100644 (file)
index 0000000..e138d5d
--- /dev/null
@@ -0,0 +1,16 @@
+# Generated by Django 5.2.9 on 2026-01-24 23:05
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("documents", "0003_workflowaction_order"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="document",
+            name="storage_type",
+        ),
+    ]
index 372fafaf2a5243acc78658b01d307c097f75c10e..88d33f1fedb87c556696b150e1143ad1f0e78c50 100644 (file)
@@ -154,13 +154,6 @@ class StoragePath(MatchingModel):
 
 
 class Document(SoftDeleteModel, ModelWithOwner):
-    STORAGE_TYPE_UNENCRYPTED = "unencrypted"
-    STORAGE_TYPE_GPG = "gpg"
-    STORAGE_TYPES = (
-        (STORAGE_TYPE_UNENCRYPTED, _("Unencrypted")),
-        (STORAGE_TYPE_GPG, _("Encrypted with GNU Privacy Guard")),
-    )
-
     correspondent = models.ForeignKey(
         Correspondent,
         blank=True,
@@ -250,14 +243,6 @@ class Document(SoftDeleteModel, ModelWithOwner):
         db_index=True,
     )
 
-    storage_type = models.CharField(
-        _("storage type"),
-        max_length=11,
-        choices=STORAGE_TYPES,
-        default=STORAGE_TYPE_UNENCRYPTED,
-        editable=False,
-    )
-
     added = models.DateTimeField(
         _("added"),
         default=timezone.now,
@@ -353,12 +338,7 @@ class Document(SoftDeleteModel, ModelWithOwner):
 
     @property
     def source_path(self) -> Path:
-        if self.filename:
-            fname = str(self.filename)
-        else:
-            fname = f"{self.pk:07}{self.file_type}"
-            if self.storage_type == self.STORAGE_TYPE_GPG:
-                fname += ".gpg"  # pragma: no cover
+        fname = str(self.filename) if self.filename else f"{self.pk:07}{self.file_type}"
 
         return (settings.ORIGINALS_DIR / Path(fname)).resolve()
 
@@ -407,8 +387,6 @@ class Document(SoftDeleteModel, ModelWithOwner):
     @property
     def thumbnail_path(self) -> Path:
         webp_file_name = f"{self.pk:07}.webp"
-        if self.storage_type == self.STORAGE_TYPE_GPG:
-            webp_file_name += ".gpg"
 
         webp_file_path = settings.THUMBNAIL_DIR / Path(webp_file_name)
 
index 805cefbdb0a5c7e5bfd4d6e3d7ba762633601aff..3647948ea4e88f39a2aa06aca7840dd8df4c9281 100644 (file)
@@ -108,7 +108,6 @@ def create_dummy_document():
         page_count=5,
         created=timezone.now(),
         modified=timezone.now(),
-        storage_type=Document.STORAGE_TYPE_UNENCRYPTED,
         added=timezone.now(),
         filename="/dummy/filename.pdf",
         archive_filename="/dummy/archive_filename.pdf",
diff --git a/src/documents/tests/samples/documents/originals/0000004.pdf b/src/documents/tests/samples/documents/originals/0000004.pdf
new file mode 100644 (file)
index 0000000..953bb88
Binary files /dev/null and b/src/documents/tests/samples/documents/originals/0000004.pdf differ
diff --git a/src/documents/tests/samples/documents/originals/0000004.pdf.gpg b/src/documents/tests/samples/documents/originals/0000004.pdf.gpg
deleted file mode 100644 (file)
index 754efcb..0000000
Binary files a/src/documents/tests/samples/documents/originals/0000004.pdf.gpg and /dev/null differ
diff --git a/src/documents/tests/samples/documents/thumbnails/0000004.webp b/src/documents/tests/samples/documents/thumbnails/0000004.webp
new file mode 100644 (file)
index 0000000..a7ff623
Binary files /dev/null and b/src/documents/tests/samples/documents/thumbnails/0000004.webp differ
diff --git a/src/documents/tests/samples/documents/thumbnails/0000004.webp.gpg b/src/documents/tests/samples/documents/thumbnails/0000004.webp.gpg
deleted file mode 100644 (file)
index 3abc69d..0000000
Binary files a/src/documents/tests/samples/documents/thumbnails/0000004.webp.gpg and /dev/null differ
index 4af05746fde4a332da3203c78a251f21190b7449..304074e37c3788502d64effd754ade998c71250e 100644 (file)
@@ -1,4 +1,3 @@
-import textwrap
 from unittest import mock
 
 from django.core.checks import Error
@@ -6,60 +5,11 @@ from django.core.checks import Warning
 from django.test import TestCase
 from django.test import override_settings
 
-from documents.checks import changed_password_check
 from documents.checks import filename_format_check
 from documents.checks import parser_check
-from documents.models import Document
-from documents.tests.factories import DocumentFactory
 
 
 class TestDocumentChecks(TestCase):
-    def test_changed_password_check_empty_db(self):
-        self.assertListEqual(changed_password_check(None), [])
-
-    def test_changed_password_check_no_encryption(self):
-        DocumentFactory.create(storage_type=Document.STORAGE_TYPE_UNENCRYPTED)
-        self.assertListEqual(changed_password_check(None), [])
-
-    def test_encrypted_missing_passphrase(self):
-        DocumentFactory.create(storage_type=Document.STORAGE_TYPE_GPG)
-        msgs = changed_password_check(None)
-        self.assertEqual(len(msgs), 1)
-        msg_text = msgs[0].msg
-        self.assertEqual(
-            msg_text,
-            "The database contains encrypted documents but no password is set.",
-        )
-
-    @override_settings(
-        PASSPHRASE="test",
-    )
-    @mock.patch("paperless.db.GnuPG.decrypted")
-    @mock.patch("documents.models.Document.source_file")
-    def test_encrypted_decrypt_fails(self, mock_decrypted, mock_source_file):
-        mock_decrypted.return_value = None
-        mock_source_file.return_value = b""
-
-        DocumentFactory.create(storage_type=Document.STORAGE_TYPE_GPG)
-
-        msgs = changed_password_check(None)
-
-        self.assertEqual(len(msgs), 1)
-        msg_text = msgs[0].msg
-        self.assertEqual(
-            msg_text,
-            textwrap.dedent(
-                """
-                The current password doesn't match the password of the
-                existing documents.
-
-                If you intend to change your password, you must first export
-                all of the old documents, start fresh with the new password
-                and then re-import them."
-                """,
-            ),
-        )
-
     def test_parser_check(self):
         self.assertEqual(parser_check(None), [])
 
index befc7050fddb7a269b527cca02a9c185119c1a97..f6764d3f87299839947d9d04fc0d844f23b2f23b 100644 (file)
@@ -34,22 +34,14 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
     def test_generate_source_filename(self):
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
         document.save()
 
         self.assertEqual(generate_filename(document), Path(f"{document.pk:07d}.pdf"))
 
-        document.storage_type = Document.STORAGE_TYPE_GPG
-        self.assertEqual(
-            generate_filename(document),
-            Path(f"{document.pk:07d}.pdf.gpg"),
-        )
-
     @override_settings(FILENAME_FORMAT="{correspondent}/{correspondent}")
     def test_file_renaming(self):
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
         document.save()
 
         # Test default source_path
@@ -63,11 +55,6 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
         # Ensure that filename is properly generated
         self.assertEqual(document.filename, Path("none/none.pdf"))
 
-        # Enable encryption and check again
-        document.storage_type = Document.STORAGE_TYPE_GPG
-        document.filename = generate_filename(document)
-        self.assertEqual(document.filename, Path("none/none.pdf.gpg"))
-
         document.save()
 
         # test that creating dirs for the source_path creates the correct directory
@@ -87,14 +74,14 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
             settings.ORIGINALS_DIR / "none",
         )
         self.assertIsFile(
-            settings.ORIGINALS_DIR / "test" / "test.pdf.gpg",
+            settings.ORIGINALS_DIR / "test" / "test.pdf",
         )
 
     @override_settings(FILENAME_FORMAT="{correspondent}/{correspondent}")
     def test_file_renaming_missing_permissions(self):
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
 
         # Ensure that filename is properly generated
@@ -128,14 +115,13 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
     def test_file_renaming_database_error(self):
         Document.objects.create(
             mime_type="application/pdf",
-            storage_type=Document.STORAGE_TYPE_UNENCRYPTED,
             checksum="AAAAA",
         )
 
         document = Document()
         document.mime_type = "application/pdf"
         document.checksum = "BBBBB"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
 
         # Ensure that filename is properly generated
@@ -170,7 +156,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
     def test_document_delete(self):
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
 
         # Ensure that filename is properly generated
@@ -196,7 +182,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
     def test_document_delete_trash_dir(self):
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
 
         # Ensure that filename is properly generated
@@ -221,7 +207,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
         # Create an identical document and ensure it is trashed under a new name
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
         document.filename = generate_filename(document)
         document.save()
@@ -235,7 +221,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
     def test_document_delete_nofile(self):
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
 
         document.delete()
@@ -245,7 +231,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
     def test_directory_not_empty(self):
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
 
         # Ensure that filename is properly generated
@@ -362,7 +348,7 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
     def test_nested_directory_cleanup(self):
         document = Document()
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
 
         # Ensure that filename is properly generated
@@ -390,7 +376,6 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
         document = Document()
         document.pk = 1
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
 
         self.assertEqual(generate_filename(document), Path("0000001.pdf"))
 
@@ -403,7 +388,6 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
         document = Document()
         document.pk = 1
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
 
         self.assertEqual(generate_filename(document), Path("0000001.pdf"))
 
@@ -429,7 +413,6 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
         document = Document()
         document.pk = 1
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
 
         self.assertEqual(generate_filename(document), Path("0000001.pdf"))
 
@@ -438,7 +421,6 @@ class TestFileHandling(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
         document = Document()
         document.pk = 1
         document.mime_type = "application/pdf"
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
 
         self.assertEqual(generate_filename(document), Path("0000001.pdf"))
 
@@ -1258,7 +1240,7 @@ class TestFilenameGeneration(DirectoriesMixin, TestCase):
             title="doc1",
             mime_type="application/pdf",
         )
-        document.storage_type = Document.STORAGE_TYPE_UNENCRYPTED
+
         document.save()
 
         # Ensure that filename is properly generated
@@ -1732,7 +1714,6 @@ class TestPathDateLocalization:
         document = DocumentFactory.create(
             title="My Document",
             mime_type="application/pdf",
-            storage_type=Document.STORAGE_TYPE_UNENCRYPTED,
             created=self.TEST_DATE,  # 2023-10-26 (which is a Thursday)
         )
         with override_settings(FILENAME_FORMAT=filename_format):
index 014f5d6730f8274a399beab5b7e9f173047b0c57..e1b88633c52ae8d58b4597d606d0dc495c7c119f 100644 (file)
@@ -1,7 +1,5 @@
 import filecmp
-import hashlib
 import shutil
-import tempfile
 from io import StringIO
 from pathlib import Path
 from unittest import mock
@@ -96,66 +94,6 @@ class TestArchiver(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
         self.assertEqual(doc2.archive_filename, "document_01.pdf")
 
 
-class TestDecryptDocuments(FileSystemAssertsMixin, TestCase):
-    @mock.patch("documents.management.commands.decrypt_documents.input")
-    def test_decrypt(self, m):
-        media_dir = tempfile.mkdtemp()
-        originals_dir = Path(media_dir) / "documents" / "originals"
-        thumb_dir = Path(media_dir) / "documents" / "thumbnails"
-        originals_dir.mkdir(parents=True, exist_ok=True)
-        thumb_dir.mkdir(parents=True, exist_ok=True)
-
-        with override_settings(
-            ORIGINALS_DIR=originals_dir,
-            THUMBNAIL_DIR=thumb_dir,
-            PASSPHRASE="test",
-            FILENAME_FORMAT=None,
-        ):
-            doc = Document.objects.create(
-                checksum="82186aaa94f0b98697d704b90fd1c072",
-                title="wow",
-                filename="0000004.pdf.gpg",
-                mime_type="application/pdf",
-                storage_type=Document.STORAGE_TYPE_GPG,
-            )
-
-            shutil.copy(
-                (
-                    Path(__file__).parent
-                    / "samples"
-                    / "documents"
-                    / "originals"
-                    / "0000004.pdf.gpg"
-                ),
-                originals_dir / "0000004.pdf.gpg",
-            )
-            shutil.copy(
-                (
-                    Path(__file__).parent
-                    / "samples"
-                    / "documents"
-                    / "thumbnails"
-                    / "0000004.webp.gpg"
-                ),
-                thumb_dir / f"{doc.id:07}.webp.gpg",
-            )
-
-            call_command("decrypt_documents")
-
-            doc.refresh_from_db()
-
-            self.assertEqual(doc.storage_type, Document.STORAGE_TYPE_UNENCRYPTED)
-            self.assertEqual(doc.filename, "0000004.pdf")
-            self.assertIsFile(Path(originals_dir) / "0000004.pdf")
-            self.assertIsFile(doc.source_path)
-            self.assertIsFile(Path(thumb_dir) / f"{doc.id:07}.webp")
-            self.assertIsFile(doc.thumbnail_path)
-
-            with doc.source_file as f:
-                checksum: str = hashlib.md5(f.read()).hexdigest()
-                self.assertEqual(checksum, doc.checksum)
-
-
 class TestMakeIndex(TestCase):
     @mock.patch("documents.management.commands.document_index.index_reindex")
     def test_reindex(self, m):
index b01b8d47e22cd52ffa09dce5c033431afc264653..81262779a6bb1088d7fad2daf1e5b11de2b863ac 100644 (file)
@@ -86,9 +86,8 @@ class TestExportImport(
             content="Content",
             checksum="82186aaa94f0b98697d704b90fd1c072",
             title="wow_dec",
-            filename="0000004.pdf.gpg",
+            filename="0000004.pdf",
             mime_type="application/pdf",
-            storage_type=Document.STORAGE_TYPE_GPG,
         )
 
         self.note = Note.objects.create(
@@ -242,11 +241,6 @@ class TestExportImport(
                     checksum = hashlib.md5(f.read()).hexdigest()
                 self.assertEqual(checksum, element["fields"]["checksum"])
 
-                self.assertEqual(
-                    element["fields"]["storage_type"],
-                    Document.STORAGE_TYPE_UNENCRYPTED,
-                )
-
                 if document_exporter.EXPORTER_ARCHIVE_NAME in element:
                     fname = (
                         self.target / element[document_exporter.EXPORTER_ARCHIVE_NAME]
@@ -436,7 +430,7 @@ class TestExportImport(
         Document.objects.create(
             checksum="AAAAAAAAAAAAAAAAA",
             title="wow",
-            filename="0000004.pdf",
+            filename="0000010.pdf",
             mime_type="application/pdf",
         )
         self.assertRaises(FileNotFoundError, call_command, "document_exporter", target)
index 730a6dc1aacb1994b155733403cccd022b0f589b..96b1f50b08966e8c69479c3fa31ab2288575cfbc 100644 (file)
@@ -195,7 +195,6 @@ from paperless import version
 from paperless.celery import app as celery_app
 from paperless.config import AIConfig
 from paperless.config import GeneralConfig
-from paperless.db import GnuPG
 from paperless.models import ApplicationConfiguration
 from paperless.serialisers import GroupSerializer
 from paperless.serialisers import UserSerializer
@@ -1071,10 +1070,8 @@ class DocumentViewSet(
                 doc,
             ):
                 return HttpResponseForbidden("Insufficient permissions")
-            if doc.storage_type == Document.STORAGE_TYPE_GPG:
-                handle = GnuPG.decrypted(doc.thumbnail_file)
-            else:
-                handle = doc.thumbnail_file
+
+            handle = doc.thumbnail_file
 
             return HttpResponse(handle, content_type="image/webp")
         except (FileNotFoundError, Document.DoesNotExist):
@@ -2824,9 +2821,6 @@ def serve_file(*, doc: Document, use_archive: bool, disposition: str):
         if mime_type in {"application/csv", "text/csv"} and disposition == "inline":
             mime_type = "text/plain"
 
-    if doc.storage_type == Document.STORAGE_TYPE_GPG:
-        file_handle = GnuPG.decrypted(file_handle)
-
     response = HttpResponse(file_handle, content_type=mime_type)
     # Firefox is not able to handle unicode characters in filename field
     # RFC 5987 addresses this issue
diff --git a/src/paperless/db.py b/src/paperless/db.py
deleted file mode 100644 (file)
index 286ccb0..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-import gnupg
-from django.conf import settings
-
-
-class GnuPG:
-    """
-    A handy singleton to use when handling encrypted files.
-    """
-
-    gpg = gnupg.GPG(gnupghome=settings.GNUPG_HOME)
-
-    @classmethod
-    def decrypted(cls, file_handle, passphrase=None):
-        if not passphrase:
-            passphrase = settings.PASSPHRASE
-
-        return cls.gpg.decrypt_file(file_handle, passphrase=passphrase).data
index 024c7f0769273f91e6f0e5f890a65627d029add1..30ee213d1ed54f89b25b7231a14c0705fe4d4239 100644 (file)
@@ -1203,19 +1203,6 @@ EMAIL_PARSE_DEFAULT_LAYOUT = __get_int(
     1,  # MailRule.PdfLayout.TEXT_HTML but that can't be imported here
 )
 
-# Pre-2.x versions of Paperless stored your documents locally with GPG
-# encryption, but that is no longer the default.  This behaviour is still
-# available, but it must be explicitly enabled by setting
-# `PAPERLESS_PASSPHRASE` in your environment or config file.  The default is to
-# store these files unencrypted.
-#
-# Translation:
-# * If you're a new user, you can safely ignore this setting.
-# * If you're upgrading from 1.x, this must be set, OR you can run
-#   `./manage.py change_storage_type gpg unencrypted` to decrypt your files,
-#   after which you can unset this value.
-PASSPHRASE = os.getenv("PAPERLESS_PASSPHRASE")
-
 # Trigger a script after every successful document consumption?
 PRE_CONSUME_SCRIPT = os.getenv("PAPERLESS_PRE_CONSUME_SCRIPT")
 POST_CONSUME_SCRIPT = os.getenv("PAPERLESS_POST_CONSUME_SCRIPT")