]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Fix: use PAPERLESS_URL if set for pw reset emails (#5902)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Mon, 26 Feb 2024 21:41:25 +0000 (13:41 -0800)
committerGitHub <noreply@github.com>
Mon, 26 Feb 2024 21:41:25 +0000 (21:41 +0000)
docs/usage.md
src/documents/context_processors.py
src/documents/templates/account/email/base_message.txt [new file with mode: 0644]
src/locale/en_US/LC_MESSAGES/django.po
src/paperless/adapter.py
src/paperless/settings.py
src/paperless/tests/test_adapter.py
src/paperless/tests/test_settings.py

index 11b1f710ae13d44f9a29d6208f4f916ec55a385f..b396168449a1e1c78bdfc52d5f3faf7822956413 100644 (file)
@@ -253,7 +253,8 @@ permissions can be granted to limit access to certain parts of the UI (and corre
 ### Password reset
 
 In order to enable the password reset feature you will need to setup an SMTP backend, see
-[`PAPERLESS_EMAIL_HOST`](configuration.md#PAPERLESS_EMAIL_HOST)
+[`PAPERLESS_EMAIL_HOST`](configuration.md#PAPERLESS_EMAIL_HOST). If your installation does not have
+[`PAPERLESS_URL`](configuration.md#PAPERLESS_URL) set, the reset link included in emails will use the server host.
 
 ## Workflows
 
index 0eaaa8e46c9dc88249dab8e4935c166db55c1a85..9f8dacfb3f965749a40f1e9dc7f09260e9229ad5 100644 (file)
@@ -7,4 +7,5 @@ def settings(request):
         or django_settings.EMAIL_HOST_USER != "",
         "DISABLE_REGULAR_LOGIN": django_settings.DISABLE_REGULAR_LOGIN,
         "ACCOUNT_ALLOW_SIGNUPS": django_settings.ACCOUNT_ALLOW_SIGNUPS,
+        "domain": getattr(django_settings, "PAPERLESS_URL", request.get_host()),
     }
diff --git a/src/documents/templates/account/email/base_message.txt b/src/documents/templates/account/email/base_message.txt
new file mode 100644 (file)
index 0000000..033ba01
--- /dev/null
@@ -0,0 +1,7 @@
+{% load i18n %}{% autoescape off %}{% blocktrans with site_name="Paperless-ngx" %}Hello from {{ site_name }}!{% endblocktrans %}
+
+{% block content %}{% endblock content %}
+
+{% blocktrans with site_name="Paperless-ngx" site_domain=settings.domain %}Thank you for using {{ site_name }}!
+{{ site_domain }}{% endblocktrans %}
+{% endautoescape %}
index 523ca8f227ed902120fb8196377c9244c1008e51..109940a59e2ce35cb790373272458028c416912a 100644 (file)
@@ -2,7 +2,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: paperless-ngx\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-02-26 00:10-0800\n"
+"POT-Creation-Date: 2024-02-26 13:34-0800\n"
 "PO-Revision-Date: 2022-02-17 04:17\n"
 "Last-Translator: \n"
 "Language-Team: English\n"
@@ -786,6 +786,18 @@ msgstr ""
 msgid "Invalid variable detected."
 msgstr ""
 
+#: documents/templates/account/email/base_message.txt:1
+#, python-format
+msgid "Hello from %(site_name)s!"
+msgstr ""
+
+#: documents/templates/account/email/base_message.txt:5
+#, python-format
+msgid ""
+"Thank you for using %(site_name)s!\n"
+"%(site_domain)s"
+msgstr ""
+
 #: documents/templates/account/login.html:5
 msgid "Paperless-ngx sign in"
 msgstr ""
@@ -1138,131 +1150,131 @@ msgstr ""
 msgid "paperless application settings"
 msgstr ""
 
-#: paperless/settings.py:644
+#: paperless/settings.py:656
 msgid "English (US)"
 msgstr ""
 
-#: paperless/settings.py:645
+#: paperless/settings.py:657
 msgid "Arabic"
 msgstr ""
 
-#: paperless/settings.py:646
+#: paperless/settings.py:658
 msgid "Afrikaans"
 msgstr ""
 
-#: paperless/settings.py:647
+#: paperless/settings.py:659
 msgid "Belarusian"
 msgstr ""
 
-#: paperless/settings.py:648
+#: paperless/settings.py:660
 msgid "Bulgarian"
 msgstr ""
 
-#: paperless/settings.py:649
+#: paperless/settings.py:661
 msgid "Catalan"
 msgstr ""
 
-#: paperless/settings.py:650
+#: paperless/settings.py:662
 msgid "Czech"
 msgstr ""
 
-#: paperless/settings.py:651
+#: paperless/settings.py:663
 msgid "Danish"
 msgstr ""
 
-#: paperless/settings.py:652
+#: paperless/settings.py:664
 msgid "German"
 msgstr ""
 
-#: paperless/settings.py:653
+#: paperless/settings.py:665
 msgid "Greek"
 msgstr ""
 
-#: paperless/settings.py:654
+#: paperless/settings.py:666
 msgid "English (GB)"
 msgstr ""
 
-#: paperless/settings.py:655
+#: paperless/settings.py:667
 msgid "Spanish"
 msgstr ""
 
-#: paperless/settings.py:656
+#: paperless/settings.py:668
 msgid "Finnish"
 msgstr ""
 
-#: paperless/settings.py:657
+#: paperless/settings.py:669
 msgid "French"
 msgstr ""
 
-#: paperless/settings.py:658
+#: paperless/settings.py:670
 msgid "Hungarian"
 msgstr ""
 
-#: paperless/settings.py:659
+#: paperless/settings.py:671
 msgid "Italian"
 msgstr ""
 
-#: paperless/settings.py:660
+#: paperless/settings.py:672
 msgid "Japanese"
 msgstr ""
 
-#: paperless/settings.py:661
+#: paperless/settings.py:673
 msgid "Luxembourgish"
 msgstr ""
 
-#: paperless/settings.py:662
+#: paperless/settings.py:674
 msgid "Norwegian"
 msgstr ""
 
-#: paperless/settings.py:663
+#: paperless/settings.py:675
 msgid "Dutch"
 msgstr ""
 
-#: paperless/settings.py:664
+#: paperless/settings.py:676
 msgid "Polish"
 msgstr ""
 
-#: paperless/settings.py:665
+#: paperless/settings.py:677
 msgid "Portuguese (Brazil)"
 msgstr ""
 
-#: paperless/settings.py:666
+#: paperless/settings.py:678
 msgid "Portuguese"
 msgstr ""
 
-#: paperless/settings.py:667
+#: paperless/settings.py:679
 msgid "Romanian"
 msgstr ""
 
-#: paperless/settings.py:668
+#: paperless/settings.py:680
 msgid "Russian"
 msgstr ""
 
-#: paperless/settings.py:669
+#: paperless/settings.py:681
 msgid "Slovak"
 msgstr ""
 
-#: paperless/settings.py:670
+#: paperless/settings.py:682
 msgid "Slovenian"
 msgstr ""
 
-#: paperless/settings.py:671
+#: paperless/settings.py:683
 msgid "Serbian"
 msgstr ""
 
-#: paperless/settings.py:672
+#: paperless/settings.py:684
 msgid "Swedish"
 msgstr ""
 
-#: paperless/settings.py:673
+#: paperless/settings.py:685
 msgid "Turkish"
 msgstr ""
 
-#: paperless/settings.py:674
+#: paperless/settings.py:686
 msgid "Ukrainian"
 msgstr ""
 
-#: paperless/settings.py:675
+#: paperless/settings.py:687
 msgid "Chinese Simplified"
 msgstr ""
 
index 3d521bd66810085137a78f7cee1c7727c3b5f072..add2bf45d028a71aa2379ff6b6b2e22d17f7aca6 100644 (file)
@@ -1,3 +1,5 @@
+from urllib.parse import quote
+
 from allauth.account.adapter import DefaultAccountAdapter
 from allauth.core import context
 from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
@@ -45,6 +47,20 @@ class CustomAccountAdapter(DefaultAccountAdapter):
 
         return url_has_allowed_host_and_scheme(url, allowed_hosts=allowed_hosts)
 
+    def get_reset_password_from_key_url(self, key):
+        """
+        Return the URL to reset a password e.g. in reset email.
+        """
+        if settings.PAPERLESS_URL is None:
+            return super().get_reset_password_from_key_url(key)
+        else:
+            path = reverse(
+                "account_reset_password_from_key",
+                kwargs={"uidb36": "UID", "key": "KEY"},
+            )
+            path = path.replace("UID-KEY", quote(key))
+            return settings.PAPERLESS_URL + path
+
 
 class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
     def is_open_for_signup(self, request, sociallogin):
index 24b3ae32fe7e5000e4048bb55332a5a84f9b2710..d2ee531fc3e7758c2cec73e1cf4ac3da55eaf905 100644 (file)
@@ -437,6 +437,8 @@ SOCIALACCOUNT_PROVIDERS = json.loads(
     os.getenv("PAPERLESS_SOCIALACCOUNT_PROVIDERS", "{}"),
 )
 
+ACCOUNT_EMAIL_SUBJECT_PREFIX = "[Paperless-ngx] "
+
 DISABLE_REGULAR_LOGIN = __get_boolean("PAPERLESS_DISABLE_REGULAR_LOGIN")
 
 AUTO_LOGIN_USERNAME = os.getenv("PAPERLESS_AUTO_LOGIN_USERNAME")
@@ -498,18 +500,23 @@ if DEBUG:
     CORS_ALLOWED_ORIGINS.append("http://localhost:4200")
 
 ALLOWED_HOSTS = __get_list("PAPERLESS_ALLOWED_HOSTS", ["*"])
-
-_paperless_url = os.getenv("PAPERLESS_URL")
-if _paperless_url:
-    _paperless_uri = urlparse(_paperless_url)
-    CSRF_TRUSTED_ORIGINS.append(_paperless_url)
-    CORS_ALLOWED_ORIGINS.append(_paperless_url)
-
 if ["*"] != ALLOWED_HOSTS:
     # always allow localhost. Necessary e.g. for healthcheck in docker.
     ALLOWED_HOSTS.append("localhost")
-    if _paperless_url:
-        ALLOWED_HOSTS.append(_paperless_uri.hostname)
+
+
+def _parse_paperless_url():
+    global CSRF_TRUSTED_ORIGINS, CORS_ALLOWED_ORIGINS, ALLOWED_HOSTS
+    url = os.getenv("PAPERLESS_URL")
+    if url:
+        CSRF_TRUSTED_ORIGINS.append(url)
+        CORS_ALLOWED_ORIGINS.append(url)
+        ALLOWED_HOSTS.append(urlparse(url).hostname)
+
+    return url
+
+
+PAPERLESS_URL = _parse_paperless_url()
 
 # For use with trusted proxies
 TRUSTED_PROXIES = __get_list("PAPERLESS_TRUSTED_PROXIES")
@@ -1126,3 +1133,6 @@ DEFAULT_FROM_EMAIL: Final[str] = os.getenv("PAPERLESS_EMAIL_FROM", EMAIL_HOST_US
 EMAIL_USE_TLS: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_TLS")
 EMAIL_USE_SSL: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_SSL")
 EMAIL_SUBJECT_PREFIX: Final[str] = "[Paperless-ngx] "
+if DEBUG:  # pragma: no cover
+    EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
+    EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
index a77c55f2302c27564b8b4f7f03e3b3948055412f..8e73cafea64ac623d9b92c7948a93499db71863c 100644 (file)
@@ -61,6 +61,27 @@ class TestCustomAccountAdapter(TestCase):
         with self.assertRaises(ValidationError):
             adapter.pre_authenticate(request)
 
+    def test_get_reset_password_from_key_url(self):
+        request = HttpRequest()
+        request.get_host = mock.Mock(return_value="foo.org")
+        with context.request_context(request):
+            adapter = get_adapter()
+
+            # Test when PAPERLESS_URL is None
+            expected_url = f"https://foo.org{reverse('account_reset_password_from_key', kwargs={'uidb36': 'UID', 'key': 'KEY'})}"
+            self.assertEqual(
+                adapter.get_reset_password_from_key_url("UID-KEY"),
+                expected_url,
+            )
+
+            # Test when PAPERLESS_URL is not None
+            with override_settings(PAPERLESS_URL="https://bar.com"):
+                expected_url = f"https://bar.com{reverse('account_reset_password_from_key', kwargs={'uidb36': 'UID', 'key': 'KEY'})}"
+                self.assertEqual(
+                    adapter.get_reset_password_from_key_url("UID-KEY"),
+                    expected_url,
+                )
+
 
 class TestCustomSocialAccountAdapter(TestCase):
     def test_is_open_for_signup(self):
index 41abb0a500c8da37665bbf1104d9197199517c9b..5b6335fd2fe921d2022b402490477af7c34ca1a3 100644 (file)
@@ -8,6 +8,7 @@ from celery.schedules import crontab
 from paperless.settings import _parse_beat_schedule
 from paperless.settings import _parse_db_settings
 from paperless.settings import _parse_ignore_dates
+from paperless.settings import _parse_paperless_url
 from paperless.settings import _parse_redis_url
 from paperless.settings import default_threads_per_worker
 
@@ -349,3 +350,27 @@ class TestDBSettings(TestCase):
                 },
                 databases["sqlite"]["OPTIONS"],
             )
+
+
+class TestPaperlessURLSettings(TestCase):
+    def test_paperless_url(self):
+        """
+        GIVEN:
+            - PAPERLESS_URL is set
+        WHEN:
+            - The URL is parsed
+        THEN:
+            - The URL is returned and present in related settings
+        """
+        with mock.patch.dict(
+            os.environ,
+            {
+                "PAPERLESS_URL": "https://example.com",
+            },
+        ):
+            url = _parse_paperless_url()
+            self.assertEqual("https://example.com", url)
+            from django.conf import settings
+
+            self.assertIn(url, settings.CSRF_TRUSTED_ORIGINS)
+            self.assertIn(url, settings.CORS_ALLOWED_ORIGINS)