Fix: use PAPERLESS_URL if set for pw reset emails (#5902)

This commit is contained in:
shamoon 2024-02-26 13:41:25 -08:00 committed by GitHub
parent 86811d0733
commit 16f4552e0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 136 additions and 43 deletions

View File

@ -253,7 +253,8 @@ permissions can be granted to limit access to certain parts of the UI (and corre
### Password reset ### Password reset
In order to enable the password reset feature you will need to setup an SMTP backend, see 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 ## Workflows

View File

@ -7,4 +7,5 @@ def settings(request):
or django_settings.EMAIL_HOST_USER != "", or django_settings.EMAIL_HOST_USER != "",
"DISABLE_REGULAR_LOGIN": django_settings.DISABLE_REGULAR_LOGIN, "DISABLE_REGULAR_LOGIN": django_settings.DISABLE_REGULAR_LOGIN,
"ACCOUNT_ALLOW_SIGNUPS": django_settings.ACCOUNT_ALLOW_SIGNUPS, "ACCOUNT_ALLOW_SIGNUPS": django_settings.ACCOUNT_ALLOW_SIGNUPS,
"domain": getattr(django_settings, "PAPERLESS_URL", request.get_host()),
} }

View File

@ -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 %}

View File

@ -2,7 +2,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: paperless-ngx\n" "Project-Id-Version: paperless-ngx\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2022-02-17 04:17\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: English\n" "Language-Team: English\n"
@ -786,6 +786,18 @@ msgstr ""
msgid "Invalid variable detected." msgid "Invalid variable detected."
msgstr "" 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 #: documents/templates/account/login.html:5
msgid "Paperless-ngx sign in" msgid "Paperless-ngx sign in"
msgstr "" msgstr ""
@ -1138,131 +1150,131 @@ msgstr ""
msgid "paperless application settings" msgid "paperless application settings"
msgstr "" msgstr ""
#: paperless/settings.py:644 #: paperless/settings.py:656
msgid "English (US)" msgid "English (US)"
msgstr "" msgstr ""
#: paperless/settings.py:645 #: paperless/settings.py:657
msgid "Arabic" msgid "Arabic"
msgstr "" msgstr ""
#: paperless/settings.py:646 #: paperless/settings.py:658
msgid "Afrikaans" msgid "Afrikaans"
msgstr "" msgstr ""
#: paperless/settings.py:647 #: paperless/settings.py:659
msgid "Belarusian" msgid "Belarusian"
msgstr "" msgstr ""
#: paperless/settings.py:648 #: paperless/settings.py:660
msgid "Bulgarian" msgid "Bulgarian"
msgstr "" msgstr ""
#: paperless/settings.py:649 #: paperless/settings.py:661
msgid "Catalan" msgid "Catalan"
msgstr "" msgstr ""
#: paperless/settings.py:650 #: paperless/settings.py:662
msgid "Czech" msgid "Czech"
msgstr "" msgstr ""
#: paperless/settings.py:651 #: paperless/settings.py:663
msgid "Danish" msgid "Danish"
msgstr "" msgstr ""
#: paperless/settings.py:652 #: paperless/settings.py:664
msgid "German" msgid "German"
msgstr "" msgstr ""
#: paperless/settings.py:653 #: paperless/settings.py:665
msgid "Greek" msgid "Greek"
msgstr "" msgstr ""
#: paperless/settings.py:654 #: paperless/settings.py:666
msgid "English (GB)" msgid "English (GB)"
msgstr "" msgstr ""
#: paperless/settings.py:655 #: paperless/settings.py:667
msgid "Spanish" msgid "Spanish"
msgstr "" msgstr ""
#: paperless/settings.py:656 #: paperless/settings.py:668
msgid "Finnish" msgid "Finnish"
msgstr "" msgstr ""
#: paperless/settings.py:657 #: paperless/settings.py:669
msgid "French" msgid "French"
msgstr "" msgstr ""
#: paperless/settings.py:658 #: paperless/settings.py:670
msgid "Hungarian" msgid "Hungarian"
msgstr "" msgstr ""
#: paperless/settings.py:659 #: paperless/settings.py:671
msgid "Italian" msgid "Italian"
msgstr "" msgstr ""
#: paperless/settings.py:660 #: paperless/settings.py:672
msgid "Japanese" msgid "Japanese"
msgstr "" msgstr ""
#: paperless/settings.py:661 #: paperless/settings.py:673
msgid "Luxembourgish" msgid "Luxembourgish"
msgstr "" msgstr ""
#: paperless/settings.py:662 #: paperless/settings.py:674
msgid "Norwegian" msgid "Norwegian"
msgstr "" msgstr ""
#: paperless/settings.py:663 #: paperless/settings.py:675
msgid "Dutch" msgid "Dutch"
msgstr "" msgstr ""
#: paperless/settings.py:664 #: paperless/settings.py:676
msgid "Polish" msgid "Polish"
msgstr "" msgstr ""
#: paperless/settings.py:665 #: paperless/settings.py:677
msgid "Portuguese (Brazil)" msgid "Portuguese (Brazil)"
msgstr "" msgstr ""
#: paperless/settings.py:666 #: paperless/settings.py:678
msgid "Portuguese" msgid "Portuguese"
msgstr "" msgstr ""
#: paperless/settings.py:667 #: paperless/settings.py:679
msgid "Romanian" msgid "Romanian"
msgstr "" msgstr ""
#: paperless/settings.py:668 #: paperless/settings.py:680
msgid "Russian" msgid "Russian"
msgstr "" msgstr ""
#: paperless/settings.py:669 #: paperless/settings.py:681
msgid "Slovak" msgid "Slovak"
msgstr "" msgstr ""
#: paperless/settings.py:670 #: paperless/settings.py:682
msgid "Slovenian" msgid "Slovenian"
msgstr "" msgstr ""
#: paperless/settings.py:671 #: paperless/settings.py:683
msgid "Serbian" msgid "Serbian"
msgstr "" msgstr ""
#: paperless/settings.py:672 #: paperless/settings.py:684
msgid "Swedish" msgid "Swedish"
msgstr "" msgstr ""
#: paperless/settings.py:673 #: paperless/settings.py:685
msgid "Turkish" msgid "Turkish"
msgstr "" msgstr ""
#: paperless/settings.py:674 #: paperless/settings.py:686
msgid "Ukrainian" msgid "Ukrainian"
msgstr "" msgstr ""
#: paperless/settings.py:675 #: paperless/settings.py:687
msgid "Chinese Simplified" msgid "Chinese Simplified"
msgstr "" msgstr ""

View File

@ -1,3 +1,5 @@
from urllib.parse import quote
from allauth.account.adapter import DefaultAccountAdapter from allauth.account.adapter import DefaultAccountAdapter
from allauth.core import context from allauth.core import context
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter 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) 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): class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request, sociallogin): def is_open_for_signup(self, request, sociallogin):

View File

@ -437,6 +437,8 @@ SOCIALACCOUNT_PROVIDERS = json.loads(
os.getenv("PAPERLESS_SOCIALACCOUNT_PROVIDERS", "{}"), os.getenv("PAPERLESS_SOCIALACCOUNT_PROVIDERS", "{}"),
) )
ACCOUNT_EMAIL_SUBJECT_PREFIX = "[Paperless-ngx] "
DISABLE_REGULAR_LOGIN = __get_boolean("PAPERLESS_DISABLE_REGULAR_LOGIN") DISABLE_REGULAR_LOGIN = __get_boolean("PAPERLESS_DISABLE_REGULAR_LOGIN")
AUTO_LOGIN_USERNAME = os.getenv("PAPERLESS_AUTO_LOGIN_USERNAME") AUTO_LOGIN_USERNAME = os.getenv("PAPERLESS_AUTO_LOGIN_USERNAME")
@ -498,18 +500,23 @@ if DEBUG:
CORS_ALLOWED_ORIGINS.append("http://localhost:4200") CORS_ALLOWED_ORIGINS.append("http://localhost:4200")
ALLOWED_HOSTS = __get_list("PAPERLESS_ALLOWED_HOSTS", ["*"]) 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: if ["*"] != ALLOWED_HOSTS:
# always allow localhost. Necessary e.g. for healthcheck in docker. # always allow localhost. Necessary e.g. for healthcheck in docker.
ALLOWED_HOSTS.append("localhost") 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 # For use with trusted proxies
TRUSTED_PROXIES = __get_list("PAPERLESS_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_TLS: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_TLS")
EMAIL_USE_SSL: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_SSL") EMAIL_USE_SSL: Final[bool] = __get_boolean("PAPERLESS_EMAIL_USE_SSL")
EMAIL_SUBJECT_PREFIX: Final[str] = "[Paperless-ngx] " 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"

View File

@ -61,6 +61,27 @@ class TestCustomAccountAdapter(TestCase):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
adapter.pre_authenticate(request) 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): class TestCustomSocialAccountAdapter(TestCase):
def test_is_open_for_signup(self): def test_is_open_for_signup(self):

View File

@ -8,6 +8,7 @@ from celery.schedules import crontab
from paperless.settings import _parse_beat_schedule from paperless.settings import _parse_beat_schedule
from paperless.settings import _parse_db_settings from paperless.settings import _parse_db_settings
from paperless.settings import _parse_ignore_dates 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 _parse_redis_url
from paperless.settings import default_threads_per_worker from paperless.settings import default_threads_per_worker
@ -349,3 +350,27 @@ class TestDBSettings(TestCase):
}, },
databases["sqlite"]["OPTIONS"], 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)