Starting testing
This commit is contained in:
parent
281347aa4d
commit
bccd1d11d3
@ -31,7 +31,7 @@ if settings.AUDIT_LOG_ENABLED:
|
|||||||
|
|
||||||
from documents.file_handling import delete_empty_directories
|
from documents.file_handling import delete_empty_directories
|
||||||
from documents.file_handling import generate_filename
|
from documents.file_handling import generate_filename
|
||||||
from documents.management.commands.mixins import SecurityMixin
|
from documents.management.commands.mixins import CryptMixin
|
||||||
from documents.models import Correspondent
|
from documents.models import Correspondent
|
||||||
from documents.models import CustomField
|
from documents.models import CustomField
|
||||||
from documents.models import CustomFieldInstance
|
from documents.models import CustomFieldInstance
|
||||||
@ -47,11 +47,6 @@ from documents.models import Workflow
|
|||||||
from documents.models import WorkflowAction
|
from documents.models import WorkflowAction
|
||||||
from documents.models import WorkflowTrigger
|
from documents.models import WorkflowTrigger
|
||||||
from documents.settings import EXPORTER_ARCHIVE_NAME
|
from documents.settings import EXPORTER_ARCHIVE_NAME
|
||||||
from documents.settings import EXPORTER_CRYPTO_ALGO_NAME
|
|
||||||
from documents.settings import EXPORTER_CRYPTO_KEY_ITERATIONS_NAME
|
|
||||||
from documents.settings import EXPORTER_CRYPTO_KEY_SIZE_NAME
|
|
||||||
from documents.settings import EXPORTER_CRYPTO_SALT_NAME
|
|
||||||
from documents.settings import EXPORTER_CRYPTO_SETTINGS_NAME
|
|
||||||
from documents.settings import EXPORTER_FILE_NAME
|
from documents.settings import EXPORTER_FILE_NAME
|
||||||
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
||||||
from documents.utils import copy_file_with_basic_stats
|
from documents.utils import copy_file_with_basic_stats
|
||||||
@ -62,7 +57,7 @@ from paperless_mail.models import MailAccount
|
|||||||
from paperless_mail.models import MailRule
|
from paperless_mail.models import MailRule
|
||||||
|
|
||||||
|
|
||||||
class Command(SecurityMixin, BaseCommand):
|
class Command(CryptMixin, BaseCommand):
|
||||||
help = (
|
help = (
|
||||||
"Decrypt and rename all files in our collection into a given target "
|
"Decrypt and rename all files in our collection into a given target "
|
||||||
"directory. And include a manifest file containing document data for "
|
"directory. And include a manifest file containing document data for "
|
||||||
@ -375,12 +370,7 @@ class Command(SecurityMixin, BaseCommand):
|
|||||||
# 4.2.1 If needed, write the crypto values into the metadata
|
# 4.2.1 If needed, write the crypto values into the metadata
|
||||||
# Django stores most of these in the field itself, we store them once here
|
# Django stores most of these in the field itself, we store them once here
|
||||||
if self.passphrase:
|
if self.passphrase:
|
||||||
metadata[EXPORTER_CRYPTO_SETTINGS_NAME] = {
|
metadata.update(self.get_crypt_params())
|
||||||
EXPORTER_CRYPTO_ALGO_NAME: self.kdf_algorithm,
|
|
||||||
EXPORTER_CRYPTO_KEY_ITERATIONS_NAME: self.key_iterations,
|
|
||||||
EXPORTER_CRYPTO_KEY_SIZE_NAME: self.key_size,
|
|
||||||
EXPORTER_CRYPTO_SALT_NAME: self.salt,
|
|
||||||
}
|
|
||||||
extra_metadata_path.write_text(
|
extra_metadata_path.write_text(
|
||||||
json.dumps(
|
json.dumps(
|
||||||
metadata,
|
metadata,
|
||||||
|
@ -22,7 +22,7 @@ from django.db.models.signals import post_save
|
|||||||
from filelock import FileLock
|
from filelock import FileLock
|
||||||
|
|
||||||
from documents.file_handling import create_source_path_directory
|
from documents.file_handling import create_source_path_directory
|
||||||
from documents.management.commands.mixins import SecurityMixin
|
from documents.management.commands.mixins import CryptMixin
|
||||||
from documents.models import Correspondent
|
from documents.models import Correspondent
|
||||||
from documents.models import CustomField
|
from documents.models import CustomField
|
||||||
from documents.models import CustomFieldInstance
|
from documents.models import CustomFieldInstance
|
||||||
@ -32,10 +32,6 @@ from documents.models import Note
|
|||||||
from documents.models import Tag
|
from documents.models import Tag
|
||||||
from documents.parsers import run_convert
|
from documents.parsers import run_convert
|
||||||
from documents.settings import EXPORTER_ARCHIVE_NAME
|
from documents.settings import EXPORTER_ARCHIVE_NAME
|
||||||
from documents.settings import EXPORTER_CRYPTO_ALGO_NAME
|
|
||||||
from documents.settings import EXPORTER_CRYPTO_KEY_ITERATIONS_NAME
|
|
||||||
from documents.settings import EXPORTER_CRYPTO_KEY_SIZE_NAME
|
|
||||||
from documents.settings import EXPORTER_CRYPTO_SALT_NAME
|
|
||||||
from documents.settings import EXPORTER_CRYPTO_SETTINGS_NAME
|
from documents.settings import EXPORTER_CRYPTO_SETTINGS_NAME
|
||||||
from documents.settings import EXPORTER_FILE_NAME
|
from documents.settings import EXPORTER_FILE_NAME
|
||||||
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
from documents.settings import EXPORTER_THUMBNAIL_NAME
|
||||||
@ -56,7 +52,7 @@ def disable_signal(sig, receiver, sender):
|
|||||||
sig.connect(receiver=receiver, sender=sender)
|
sig.connect(receiver=receiver, sender=sender)
|
||||||
|
|
||||||
|
|
||||||
class Command(SecurityMixin, BaseCommand):
|
class Command(CryptMixin, BaseCommand):
|
||||||
help = (
|
help = (
|
||||||
"Using a manifest.json file, load the data from there, and import the "
|
"Using a manifest.json file, load the data from there, and import the "
|
||||||
"documents it refers to."
|
"documents it refers to."
|
||||||
@ -182,19 +178,7 @@ class Command(SecurityMixin, BaseCommand):
|
|||||||
"No passphrase was given, but this export contains encrypted fields",
|
"No passphrase was given, but this export contains encrypted fields",
|
||||||
)
|
)
|
||||||
elif EXPORTER_CRYPTO_SETTINGS_NAME in data:
|
elif EXPORTER_CRYPTO_SETTINGS_NAME in data:
|
||||||
# Load up the values for setting up decryption
|
self.load_crypt_params(data)
|
||||||
self.kdf_algorithm: str = data[EXPORTER_CRYPTO_SETTINGS_NAME][
|
|
||||||
EXPORTER_CRYPTO_ALGO_NAME
|
|
||||||
]
|
|
||||||
self.key_iterations: int = data[EXPORTER_CRYPTO_SETTINGS_NAME][
|
|
||||||
EXPORTER_CRYPTO_KEY_ITERATIONS_NAME
|
|
||||||
]
|
|
||||||
self.key_size: int = data[EXPORTER_CRYPTO_SETTINGS_NAME][
|
|
||||||
EXPORTER_CRYPTO_KEY_SIZE_NAME
|
|
||||||
]
|
|
||||||
self.salt: str = data[EXPORTER_CRYPTO_SETTINGS_NAME][
|
|
||||||
EXPORTER_CRYPTO_SALT_NAME
|
|
||||||
]
|
|
||||||
|
|
||||||
if self.version and self.version != version.__full_version_str__:
|
if self.version and self.version != version.__full_version_str__:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
|
@ -2,12 +2,19 @@ import base64
|
|||||||
import os
|
import os
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
from cryptography.hazmat.primitives import hashes
|
from cryptography.hazmat.primitives import hashes
|
||||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||||
from django.core.management import CommandError
|
from django.core.management import CommandError
|
||||||
|
|
||||||
|
from documents.settings import EXPORTER_CRYPTO_ALGO_NAME
|
||||||
|
from documents.settings import EXPORTER_CRYPTO_KEY_ITERATIONS_NAME
|
||||||
|
from documents.settings import EXPORTER_CRYPTO_KEY_SIZE_NAME
|
||||||
|
from documents.settings import EXPORTER_CRYPTO_SALT_NAME
|
||||||
|
from documents.settings import EXPORTER_CRYPTO_SETTINGS_NAME
|
||||||
|
|
||||||
|
|
||||||
class MultiProcessMixin:
|
class MultiProcessMixin:
|
||||||
"""
|
"""
|
||||||
@ -48,7 +55,7 @@ class ProgressBarMixin:
|
|||||||
self.use_progress_bar = not self.no_progress_bar
|
self.use_progress_bar = not self.no_progress_bar
|
||||||
|
|
||||||
|
|
||||||
class SecurityMixin:
|
class CryptMixin:
|
||||||
"""
|
"""
|
||||||
Fully based on:
|
Fully based on:
|
||||||
https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet
|
https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet
|
||||||
@ -79,6 +86,31 @@ class SecurityMixin:
|
|||||||
key_size = 32
|
key_size = 32
|
||||||
kdf_algorithm = "pbkdf2_sha256"
|
kdf_algorithm = "pbkdf2_sha256"
|
||||||
|
|
||||||
|
def get_crypt_params(self) -> dict[str, dict[str, Union[str, int]]]:
|
||||||
|
return {
|
||||||
|
EXPORTER_CRYPTO_SETTINGS_NAME: {
|
||||||
|
EXPORTER_CRYPTO_ALGO_NAME: self.kdf_algorithm,
|
||||||
|
EXPORTER_CRYPTO_KEY_ITERATIONS_NAME: self.key_iterations,
|
||||||
|
EXPORTER_CRYPTO_KEY_SIZE_NAME: self.key_size,
|
||||||
|
EXPORTER_CRYPTO_SALT_NAME: self.salt,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def load_crypt_params(self, metadata: dict):
|
||||||
|
# Load up the values for setting up decryption
|
||||||
|
self.kdf_algorithm: str = metadata[EXPORTER_CRYPTO_SETTINGS_NAME][
|
||||||
|
EXPORTER_CRYPTO_ALGO_NAME
|
||||||
|
]
|
||||||
|
self.key_iterations: int = metadata[EXPORTER_CRYPTO_SETTINGS_NAME][
|
||||||
|
EXPORTER_CRYPTO_KEY_ITERATIONS_NAME
|
||||||
|
]
|
||||||
|
self.key_size: int = metadata[EXPORTER_CRYPTO_SETTINGS_NAME][
|
||||||
|
EXPORTER_CRYPTO_KEY_SIZE_NAME
|
||||||
|
]
|
||||||
|
self.salt: str = metadata[EXPORTER_CRYPTO_SETTINGS_NAME][
|
||||||
|
EXPORTER_CRYPTO_SALT_NAME
|
||||||
|
]
|
||||||
|
|
||||||
def setup_crypto(self, *, passphrase: str, salt: Optional[str] = None):
|
def setup_crypto(self, *, passphrase: str, salt: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
Constructs a class for encryption or decryption using the specified passphrase and salt
|
Constructs a class for encryption or decryption using the specified passphrase and salt
|
||||||
|
@ -39,6 +39,7 @@ from documents.tests.utils import DirectoriesMixin
|
|||||||
from documents.tests.utils import FileSystemAssertsMixin
|
from documents.tests.utils import FileSystemAssertsMixin
|
||||||
from documents.tests.utils import SampleDirMixin
|
from documents.tests.utils import SampleDirMixin
|
||||||
from documents.tests.utils import paperless_environment
|
from documents.tests.utils import paperless_environment
|
||||||
|
from paperless_mail.models import MailAccount
|
||||||
|
|
||||||
|
|
||||||
class TestExportImport(
|
class TestExportImport(
|
||||||
@ -841,5 +842,47 @@ class TestExportImport(
|
|||||||
|
|
||||||
self.assertEqual(Document.objects.all().count(), 4)
|
self.assertEqual(Document.objects.all().count(), 4)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCryptExportImport(
|
||||||
|
DirectoriesMixin,
|
||||||
|
FileSystemAssertsMixin,
|
||||||
|
TestCase,
|
||||||
|
):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.target = Path(tempfile.mkdtemp())
|
||||||
|
return super().setUp()
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
shutil.rmtree(self.target, ignore_errors=True)
|
||||||
|
return super().tearDown()
|
||||||
|
|
||||||
def test_export_passphrase(self):
|
def test_export_passphrase(self):
|
||||||
pass
|
MailAccount.objects.create(
|
||||||
|
name="Test Account",
|
||||||
|
imap_server="test.imap.com",
|
||||||
|
username="myusername",
|
||||||
|
password="mypassword",
|
||||||
|
)
|
||||||
|
|
||||||
|
call_command(
|
||||||
|
"document_exporter",
|
||||||
|
"--no-progress-bar",
|
||||||
|
"--passphrase",
|
||||||
|
"securepassword",
|
||||||
|
self.target,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsFile(self.target / "metadata.json")
|
||||||
|
self.assertIsFile(self.target / "manifest.json")
|
||||||
|
|
||||||
|
data = json.loads((self.target / "manifest.json").read_text())
|
||||||
|
|
||||||
|
mail_accounts = list(
|
||||||
|
filter(lambda r: r["model"] == "paperless_mail.mailaccount", data),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(mail_accounts), 1)
|
||||||
|
|
||||||
|
mail_account_data = mail_accounts[0]
|
||||||
|
|
||||||
|
self.assertNotEqual(mail_account_data["fields"]["password"], "mypassword")
|
||||||
|
@ -4,6 +4,7 @@ addopts = --pythonwarnings=all --cov --cov-report=html --cov-report=xml --numpro
|
|||||||
env =
|
env =
|
||||||
PAPERLESS_DISABLE_DBHANDLER=true
|
PAPERLESS_DISABLE_DBHANDLER=true
|
||||||
PAPERLESS_CACHE_BACKEND=django.core.cache.backends.locmem.LocMemCache
|
PAPERLESS_CACHE_BACKEND=django.core.cache.backends.locmem.LocMemCache
|
||||||
|
norecursedirs = locale/*
|
||||||
|
|
||||||
[coverage:run]
|
[coverage:run]
|
||||||
source =
|
source =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user