Settings this value has security implications. Read the Django documentation
and be sure you understand its usage before setting it.
+`PAPERLESS_EMAIL_CERTIFICATE_FILE=<path>`
+
+: Configures an additional SSL certificate file containing a [combined key and certificate](https://docs.python.org/3/library/ssl.html#combined-key-and-certificate) file
+for validating SSL connections against mail providers. This is for use with self-signed certificates against
+local IMAP servers.
+
+ Defaults to None.
+
+!!! warning
+
+ Settings this value has security implications for the security of your email.
+ Understand what it does and be sure you need to before setting.
+
## OCR settings {#ocr}
Paperless uses [OCRmyPDF](https://ocrmypdf.readthedocs.io/en/latest/)
)
return msgs
+ def _email_certificate_validate():
+ msgs = []
+ # Existence checks
+ if (
+ settings.EMAIL_CERTIFICATE_FILE is not None
+ and not settings.EMAIL_CERTIFICATE_FILE.is_file()
+ ):
+ msgs.append(
+ Error(
+ f"Email cert {settings.EMAIL_CERTIFICATE_FILE} is not a file",
+ ),
+ )
+ return msgs
+
return (
- _ocrmypdf_settings_check() + _timezone_validate() + _barcode_scanner_validate()
+ _ocrmypdf_settings_check()
+ + _timezone_validate()
+ + _barcode_scanner_validate()
+ + _email_certificate_validate()
)
return float(os.getenv(key, default))
-def __get_path(key: str, default: Union[PathLike, str]) -> Path:
+def __get_path(
+ key: str,
+ default: Optional[Union[PathLike, str]] = None,
+) -> Optional[Path]:
"""
- Return a normalized, absolute path based on the environment variable or a default
+ Return a normalized, absolute path based on the environment variable or a default,
+ if provided. If not set and no default, returns None
"""
- return Path(os.environ.get(key, default)).resolve()
+ if key in os.environ:
+ return Path(os.environ[key]).resolve()
+ elif default is not None:
+ return Path(default).resolve()
+ else:
+ return None
def __get_list(
SESSION_COOKIE_NAME = f"{COOKIE_PREFIX}sessionid"
LANGUAGE_COOKIE_NAME = f"{COOKIE_PREFIX}django_language"
+EMAIL_CERTIFICATE_FILE = __get_path("PAPERLESS_EMAIL_CERTIFICATE_FILE")
+
###############################################################################
# Database #
import os
+from pathlib import Path
from django.test import TestCase
from django.test import override_settings
from documents.tests.utils import DirectoriesMixin
+from documents.tests.utils import FileSystemAssertsMixin
from paperless.checks import binaries_check
from paperless.checks import debug_mode_check
from paperless.checks import paths_check
self.assertEqual(len(debug_mode_check(None)), 1)
-class TestSettingsChecks(DirectoriesMixin, TestCase):
+class TestSettingsChecksAgainstDefaults(DirectoriesMixin, TestCase):
def test_all_valid(self):
"""
GIVEN:
msgs = settings_values_check(None)
self.assertEqual(len(msgs), 0)
+
+class TestOcrSettingsChecks(DirectoriesMixin, TestCase):
@override_settings(OCR_OUTPUT_TYPE="notapdf")
def test_invalid_output_type(self):
"""
self.assertIn('OCR clean mode "cleanme"', msg.msg)
+
+class TestTimezoneSettingsChecks(DirectoriesMixin, TestCase):
@override_settings(TIME_ZONE="TheMoon\\MyCrater")
def test_invalid_timezone(self):
"""
self.assertIn('Timezone "TheMoon\\MyCrater"', msg.msg)
+
+class TestBarcodeSettingsChecks(DirectoriesMixin, TestCase):
@override_settings(CONSUMER_BARCODE_SCANNER="Invalid")
def test_barcode_scanner_invalid(self):
msgs = settings_values_check(None)
def test_barcode_scanner_valid(self):
msgs = settings_values_check(None)
self.assertEqual(len(msgs), 0)
+
+
+class TestEmailCertSettingsChecks(DirectoriesMixin, FileSystemAssertsMixin, TestCase):
+ @override_settings(EMAIL_CERTIFICATE_FILE=Path("/tmp/not_actually_here.pem"))
+ def test_not_valid_file(self):
+ """
+ GIVEN:
+ - Default settings
+ - Email certificate is set
+ WHEN:
+ - Email certificate file doesn't exist
+ THEN:
+ - system check error reported for email certificate
+ """
+ self.assertIsNotFile("/tmp/not_actually_here.pem")
+
+ msgs = settings_values_check(None)
+
+ self.assertEqual(len(msgs), 1)
+
+ msg = msgs[0]
+
+ self.assertIn("Email cert /tmp/not_actually_here.pem is not a file", msg.msg)
"""
Returns the correct MailBox instance for the given configuration.
"""
+ ssl_context = ssl.create_default_context()
+ if settings.EMAIL_CERTIFICATE_FILE is not None: # pragma: nocover
+ ssl_context.load_cert_chain(certfile=settings.EMAIL_CERTIFICATE_FILE)
+
if security == MailAccount.ImapSecurity.NONE:
mailbox = MailBoxUnencrypted(server, port)
elif security == MailAccount.ImapSecurity.STARTTLS:
- mailbox = MailBoxTls(server, port, ssl_context=ssl.create_default_context())
+ mailbox = MailBoxTls(server, port, ssl_context=ssl_context)
elif security == MailAccount.ImapSecurity.SSL:
- mailbox = MailBox(server, port, ssl_context=ssl.create_default_context())
+ mailbox = MailBox(server, port, ssl_context=ssl_context)
else:
raise NotImplementedError("Unknown IMAP security") # pragma: nocover
return mailbox