Construct absolute URLs with the correct protocol and port

This commit is contained in:
Bart Schuurmans 2024-04-07 17:22:29 +02:00
parent 3aefbb548e
commit c42db40a63
14 changed files with 55 additions and 73 deletions

View file

@ -4,7 +4,7 @@ from django.template.loader import get_template
from bookwyrm import models, settings from bookwyrm import models, settings
from bookwyrm.tasks import app, EMAIL from bookwyrm.tasks import app, EMAIL
from bookwyrm.settings import DOMAIN from bookwyrm.settings import DOMAIN, BASE_URL
def email_data(): def email_data():
@ -14,6 +14,7 @@ def email_data():
"site_name": site.name, "site_name": site.name,
"logo": site.logo_small_url, "logo": site.logo_small_url,
"domain": DOMAIN, "domain": DOMAIN,
"base_url": BASE_URL,
"user": None, "user": None,
} }

View file

@ -12,7 +12,7 @@ from model_utils import FieldTracker
from bookwyrm.connectors.abstract_connector import get_data from bookwyrm.connectors.abstract_connector import get_data
from bookwyrm.preview_images import generate_site_preview_image_task from bookwyrm.preview_images import generate_site_preview_image_task
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, STATIC_FULL_URL from bookwyrm.settings import BASE_URL, ENABLE_PREVIEW_IMAGES, STATIC_FULL_URL
from bookwyrm.settings import RELEASE_API from bookwyrm.settings import RELEASE_API
from bookwyrm.tasks import app, MISC from bookwyrm.tasks import app, MISC
from .base_model import BookWyrmModel, new_access_code from .base_model import BookWyrmModel, new_access_code
@ -188,7 +188,7 @@ class SiteInvite(models.Model):
@property @property
def link(self): def link(self):
"""formats the invite link""" """formats the invite link"""
return f"https://{DOMAIN}/invite/{self.code}" return f"{BASE_URL}/invite/{self.code}"
class InviteRequest(BookWyrmModel): class InviteRequest(BookWyrmModel):
@ -235,7 +235,7 @@ class PasswordReset(models.Model):
@property @property
def link(self): def link(self):
"""formats the invite link""" """formats the invite link"""
return f"https://{DOMAIN}/password-reset/{self.code}" return f"{BASE_URL}/password-reset/{self.code}"
# pylint: disable=unused-argument # pylint: disable=unused-argument

View file

@ -19,7 +19,7 @@ from bookwyrm.connectors import get_data, ConnectorException
from bookwyrm.models.shelf import Shelf from bookwyrm.models.shelf import Shelf
from bookwyrm.models.status import Status from bookwyrm.models.status import Status
from bookwyrm.preview_images import generate_user_preview_image_task from bookwyrm.preview_images import generate_user_preview_image_task
from bookwyrm.settings import DOMAIN, ENABLE_PREVIEW_IMAGES, USE_HTTPS, LANGUAGES from bookwyrm.settings import BASE_URL, DOMAIN, ENABLE_PREVIEW_IMAGES, USE_HTTPS, LANGUAGES
from bookwyrm.signatures import create_key_pair from bookwyrm.signatures import create_key_pair
from bookwyrm.tasks import app, MISC from bookwyrm.tasks import app, MISC
from bookwyrm.utils import regex from bookwyrm.utils import regex
@ -214,8 +214,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
@property @property
def confirmation_link(self): def confirmation_link(self):
"""helper for generating confirmation links""" """helper for generating confirmation links"""
link = site_link() return f"{BASE_URL}/confirm-email/{self.confirmation_code}"
return f"{link}/confirm-email/{self.confirmation_code}"
@property @property
def following_link(self): def following_link(self):

View file

@ -2,10 +2,10 @@
<div style="font-family: BlinkMacSystemFont,-apple-system,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Fira Sans','Droid Sans','Helvetica Neue',Helvetica,Arial,sans-serif; border-radius: 6px; background-color: #efefef; max-width: 632px;"> <div style="font-family: BlinkMacSystemFont,-apple-system,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Fira Sans','Droid Sans','Helvetica Neue',Helvetica,Arial,sans-serif; border-radius: 6px; background-color: #efefef; max-width: 632px;">
<div style="padding: 1rem; overflow: auto;"> <div style="padding: 1rem; overflow: auto;">
<div style="float: left; margin-right: 1rem;"> <div style="float: left; margin-right: 1rem;">
<a style="color: #3273dc;" href="https://{{ domain }}" style="text-decoration: none;"><img src="{{ logo }}" alt="logo" loading="lazy" decoding="async"></a> <a style="color: #3273dc;" href="{{ base_url }}" style="text-decoration: none;"><img src="{{ logo }}" alt="logo" loading="lazy" decoding="async"></a>
</div> </div>
<div> <div>
<a style="color: black; text-decoration: none" href="https://{{ domain }}" style="text-decoration: none;"><strong>{{ site_name }}</strong><br> <a style="color: black; text-decoration: none" href="{{ base_url }}" style="text-decoration: none;"><strong>{{ site_name }}</strong><br>
{{ domain }}</a> {{ domain }}</a>
</div> </div>
</div> </div>
@ -18,9 +18,9 @@
</div> </div>
<div style="padding: 1rem; font-size: 0.8rem;"> <div style="padding: 1rem; font-size: 0.8rem;">
<p style="margin: 0; color: #333;">{% blocktrans %}BookWyrm hosted on <a style="color: #3273dc;" href="https://{{ domain }}">{{ site_name }}</a>{% endblocktrans %}</p> <p style="margin: 0; color: #333;">{% blocktrans %}BookWyrm hosted on <a style="color: #3273dc;" href="{{ base_url }}">{{ site_name }}</a>{% endblocktrans %}</p>
{% if user %} {% if user %}
<p style="margin: 0; color: #333;"><a style="color: #3273dc;" href="https://{{ domain }}{% url 'prefs-profile' %}">{% trans "Email preference" %}</a></p> <p style="margin: 0; color: #333;"><a style="color: #3273dc;" href="{{ base_url }}{% url 'prefs-profile' %}">{% trans "Email preference" %}</a></p>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View file

@ -12,6 +12,6 @@
<p> <p>
{% url 'code-of-conduct' as coc_path %} {% url 'code-of-conduct' as coc_path %}
{% url 'about' as about_path %} {% url 'about' as about_path %}
{% blocktrans %}Learn more <a href="https://{{ domain }}{{ about_path }}">about {{ site_name }}</a>.{% endblocktrans %} {% blocktrans %}Learn more <a href="{{ base_url }}{{ about_path }}">about {{ site_name }}</a>.{% endblocktrans %}
</p> </p>
{% endblock %} {% endblock %}

View file

@ -5,6 +5,6 @@
{{ invite_link }} {{ invite_link }}
{% blocktrans %}Learn more about {{ site_name }}:{% endblocktrans %} https://{{ domain }}{% url 'about' %} {% blocktrans %}Learn more about {{ site_name }}:{% endblocktrans %} {{ base_url }}{% url 'about' %}
{% endblock %} {% endblock %}

View file

@ -10,6 +10,6 @@
<Image width="16" height="16" type="image/x-icon">{{ image }}</Image> <Image width="16" height="16" type="image/x-icon">{{ image }}</Image>
<Url <Url
type="text/html" type="text/html"
template="https://{{ DOMAIN }}{% url 'search' %}?q={searchTerms}" template="{{ BASE_URL }}{% url 'search' %}?q={searchTerms}"
/> />
</OpenSearchDescription> </OpenSearchDescription>

View file

@ -4,7 +4,7 @@ from dataclasses import dataclass
import datetime import datetime
import json import json
import pathlib import pathlib
import re from urllib.parse import urlparse
from typing import List from typing import List
from unittest import expectedFailure from unittest import expectedFailure
from unittest.mock import patch from unittest.mock import patch
@ -22,7 +22,7 @@ from bookwyrm.activitypub.base_activity import ActivityObject
from bookwyrm.models import fields, User, Status, Edition from bookwyrm.models import fields, User, Status, Edition
from bookwyrm.models.base_model import BookWyrmModel from bookwyrm.models.base_model import BookWyrmModel
from bookwyrm.models.activitypub_mixin import ActivitypubMixin from bookwyrm.models.activitypub_mixin import ActivitypubMixin
from bookwyrm.settings import DOMAIN from bookwyrm.settings import PROTOCOL, NETLOC
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@ -427,12 +427,10 @@ class ModelFields(TestCase):
instance = fields.ImageField() instance = fields.ImageField()
output = instance.field_to_activity(user.avatar) output = instance.field_to_activity(user.avatar)
self.assertIsNotNone( parsed_url = urlparse(output.url)
re.match( self.assertEqual(parsed_url.scheme, PROTOCOL)
rf"https:\/\/{DOMAIN}\/.*\.jpg", self.assertEqual(parsed_url.netloc, NETLOC)
output.url, self.assertRegex(parsed_url.path, r"\.jpg$")
)
)
self.assertEqual(output.name, "") self.assertEqual(output.name, "")
self.assertEqual(output.type, "Image") self.assertEqual(output.type, "Image")

View file

@ -79,7 +79,7 @@ class SiteModels(TestCase):
def test_site_invite_link(self): def test_site_invite_link(self):
"""invite link generator""" """invite link generator"""
invite = models.SiteInvite.objects.create(user=self.local_user, code="hello") invite = models.SiteInvite.objects.create(user=self.local_user, code="hello")
self.assertEqual(invite.link, f"https://{settings.DOMAIN}/invite/hello") self.assertEqual(invite.link, f"{settings.BASE_URL}/invite/hello")
def test_invite_request(self): def test_invite_request(self):
"""someone wants an invite""" """someone wants an invite"""
@ -95,7 +95,7 @@ class SiteModels(TestCase):
"""password reset token""" """password reset token"""
token = models.PasswordReset.objects.create(user=self.local_user, code="hello") token = models.PasswordReset.objects.create(user=self.local_user, code="hello")
self.assertTrue(token.valid()) self.assertTrue(token.valid())
self.assertEqual(token.link, f"https://{settings.DOMAIN}/password-reset/hello") self.assertEqual(token.link, f"{settings.BASE_URL}/password-reset/hello")
@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay")
@patch("bookwyrm.suggested_users.remove_user_task.delay") @patch("bookwyrm.suggested_users.remove_user_task.delay")

View file

@ -227,11 +227,9 @@ class Status(TestCase):
self.assertEqual(activity["sensitive"], False) self.assertEqual(activity["sensitive"], False)
self.assertIsInstance(activity["attachment"], list) self.assertIsInstance(activity["attachment"], list)
self.assertEqual(activity["attachment"][0]["type"], "Document") self.assertEqual(activity["attachment"][0]["type"], "Document")
self.assertTrue( self.assertRegex(
re.match(
r"https:\/\/your.domain.here\/images\/covers\/test(_[A-z0-9]+)?.jpg",
activity["attachment"][0]["url"], activity["attachment"][0]["url"],
) rf"^{settings.BASE_URL}/images/covers/test(_[A-z0-9]+)?.jpg$",
) )
self.assertEqual(activity["attachment"][0]["name"], "Test Edition") self.assertEqual(activity["attachment"][0]["name"], "Test Edition")
@ -263,12 +261,10 @@ class Status(TestCase):
), ),
) )
self.assertEqual(activity["attachment"][0]["type"], "Document") self.assertEqual(activity["attachment"][0]["type"], "Document")
# self.assertTrue( self.assertRegex(
# re.match( activity["attachment"][0]["url"],
# r"https:\/\/your.domain.here\/images\/covers\/test_[A-z0-9]+.jpg", rf"^{settings.BASE_URL}/images/covers/test_[A-z0-9]+.jpg$",
# activity["attachment"][0].url, )
# )
# )
self.assertEqual(activity["attachment"][0]["name"], "Test Edition") self.assertEqual(activity["attachment"][0]["name"], "Test Edition")
def test_quotation_to_activity(self, *_): def test_quotation_to_activity(self, *_):
@ -306,11 +302,9 @@ class Status(TestCase):
), ),
) )
self.assertEqual(activity["attachment"][0]["type"], "Document") self.assertEqual(activity["attachment"][0]["type"], "Document")
self.assertTrue( self.assertRegex(
re.match(
r"https:\/\/your.domain.here\/images\/covers\/test(_[A-z0-9]+)?.jpg",
activity["attachment"][0]["url"], activity["attachment"][0]["url"],
) rf"^{settings.BASE_URL}/images/covers/test(_[A-z0-9]+)?.jpg$",
) )
self.assertEqual(activity["attachment"][0]["name"], "Test Edition") self.assertEqual(activity["attachment"][0]["name"], "Test Edition")
@ -400,11 +394,9 @@ class Status(TestCase):
) )
self.assertEqual(activity["content"], "test content") self.assertEqual(activity["content"], "test content")
self.assertEqual(activity["attachment"][0]["type"], "Document") self.assertEqual(activity["attachment"][0]["type"], "Document")
self.assertTrue( self.assertRegex(
re.match(
r"https:\/\/your.domain.here\/images\/covers\/test_[A-z0-9]+.jpg",
activity["attachment"][0]["url"], activity["attachment"][0]["url"],
) rf"^{settings.BASE_URL}/images/covers/test_[A-z0-9]+.jpg$",
) )
self.assertEqual(activity["attachment"][0]["name"], "Test Edition") self.assertEqual(activity["attachment"][0]["name"], "Test Edition")
@ -425,11 +417,9 @@ class Status(TestCase):
) )
self.assertEqual(activity["content"], "test content") self.assertEqual(activity["content"], "test content")
self.assertEqual(activity["attachment"][0]["type"], "Document") self.assertEqual(activity["attachment"][0]["type"], "Document")
self.assertTrue( self.assertRegex(
re.match(
r"https:\/\/your.domain.here\/images\/covers\/test_[A-z0-9]+.jpg",
activity["attachment"][0]["url"], activity["attachment"][0]["url"],
) rf"^{settings.BASE_URL}/images/covers/test_[A-z0-9]+.jpg$",
) )
self.assertEqual(activity["attachment"][0]["name"], "Test Edition") self.assertEqual(activity["attachment"][0]["name"], "Test Edition")
@ -448,11 +438,9 @@ class Status(TestCase):
f'rated <em><a href="{self.book.remote_id}">{self.book.title}</a></em>: 3 stars', f'rated <em><a href="{self.book.remote_id}">{self.book.title}</a></em>: 3 stars',
) )
self.assertEqual(activity["attachment"][0]["type"], "Document") self.assertEqual(activity["attachment"][0]["type"], "Document")
self.assertTrue( self.assertRegex(
re.match(
r"https:\/\/your.domain.here\/images\/covers\/test_[A-z0-9]+.jpg",
activity["attachment"][0]["url"], activity["attachment"][0]["url"],
) rf"^{settings.BASE_URL}/images/covers/test_[A-z0-9]+.jpg$",
) )
self.assertEqual(activity["attachment"][0]["name"], "Test Edition") self.assertEqual(activity["attachment"][0]["name"], "Test Edition")

View file

@ -2,6 +2,7 @@
import re import re
from django.test import TestCase from django.test import TestCase
from bookwyrm.settings import BASE_URL
from bookwyrm.utils import regex from bookwyrm.utils import regex
from bookwyrm.utils.validate import validate_url_domain from bookwyrm.utils.validate import validate_url_domain
@ -15,10 +16,8 @@ class TestUtils(TestCase):
def test_valid_url_domain(self): def test_valid_url_domain(self):
"""Check with a valid URL""" """Check with a valid URL"""
self.assertEqual( legit = f"{BASE_URL}/legit-book-url/"
validate_url_domain("https://your.domain.here/legit-book-url/"), self.assertEqual(validate_url_domain(legit), legit)
"https://your.domain.here/legit-book-url/",
)
def test_invalid_url_domain(self): def test_invalid_url_domain(self):
"""Check with an invalid URL""" """Check with an invalid URL"""

View file

@ -8,7 +8,7 @@ from django.test.client import RequestFactory
import responses import responses
from bookwyrm import models, views from bookwyrm import models, views
from bookwyrm.settings import USER_AGENT, DOMAIN from bookwyrm.settings import USER_AGENT, BASE_URL
@patch("bookwyrm.activitystreams.add_status_task.delay") @patch("bookwyrm.activitystreams.add_status_task.delay")
@ -288,13 +288,13 @@ class ViewsHelpers(TestCase): # pylint: disable=too-many-public-methods
def test_redirect_to_referer_valid_domain(self, *_): def test_redirect_to_referer_valid_domain(self, *_):
"""redirect to within the app""" """redirect to within the app"""
request = self.factory.get("/path") request = self.factory.get("/path")
request.META = {"HTTP_REFERER": f"https://{DOMAIN}/and/a/path"} request.META = {"HTTP_REFERER": f"{BASE_URL}/and/a/path"}
result = views.helpers.redirect_to_referer(request) result = views.helpers.redirect_to_referer(request)
self.assertEqual(result.url, f"https://{DOMAIN}/and/a/path") self.assertEqual(result.url, f"{BASE_URL}/and/a/path")
def test_redirect_to_referer_with_get_args(self, *_): def test_redirect_to_referer_with_get_args(self, *_):
"""if the path has get params (like sort) they are preserved""" """if the path has get params (like sort) they are preserved"""
request = self.factory.get("/path") request = self.factory.get("/path")
request.META = {"HTTP_REFERER": f"https://{DOMAIN}/and/a/path?sort=hello"} request.META = {"HTTP_REFERER": f"{BASE_URL}/and/a/path?sort=hello"}
result = views.helpers.redirect_to_referer(request) result = views.helpers.redirect_to_referer(request)
self.assertEqual(result.url, f"https://{DOMAIN}/and/a/path?sort=hello") self.assertEqual(result.url, f"{BASE_URL}/and/a/path?sort=hello")

View file

@ -1,7 +1,7 @@
"""Validations""" """Validations"""
from typing import Optional from typing import Optional
from bookwyrm.settings import DOMAIN, USE_HTTPS from bookwyrm.settings import BASE_URL
def validate_url_domain(url: Optional[str]) -> Optional[str]: def validate_url_domain(url: Optional[str]) -> Optional[str]:
@ -9,10 +9,7 @@ def validate_url_domain(url: Optional[str]) -> Optional[str]:
if url is None: if url is None:
return None return None
protocol = "https://" if USE_HTTPS else "http://" if not url.startswith(BASE_URL):
origin = f"{protocol}{DOMAIN}"
if url.startswith(origin):
return url
return None return None
return url

View file

@ -9,7 +9,7 @@ from django.utils import timezone
from django.views.decorators.http import require_GET from django.views.decorators.http import require_GET
from bookwyrm import models from bookwyrm import models
from bookwyrm.settings import DOMAIN, VERSION, LANGUAGE_CODE from bookwyrm.settings import BASE_URL, DOMAIN, VERSION, LANGUAGE_CODE
@require_GET @require_GET
@ -34,7 +34,7 @@ def webfinger(request):
}, },
{ {
"rel": "http://ostatus.org/schema/1.0/subscribe", "rel": "http://ostatus.org/schema/1.0/subscribe",
"template": f"https://{DOMAIN}/ostatus_subscribe?acct={{uri}}", "template": f"{BASE_URL}/ostatus_subscribe?acct={{uri}}",
}, },
], ],
} }