diff --git a/.env.example b/.env.example index 6a217df0c..c61ceba1e 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,11 @@ DEFAULT_LANGUAGE="English" ## Leave unset to allow all hosts # ALLOWED_HOSTS="localhost,127.0.0.1,[::1]" +# Specify when the site is served from a port that is not the default +# for the protocol (80 for HTTP or 443 for HTTPS). +# Probably only necessary in development. +# PORT=1333 + MEDIA_ROOT=images/ # Database configuration @@ -139,9 +144,9 @@ HTTP_X_FORWARDED_PROTO=false TWO_FACTOR_LOGIN_VALIDITY_WINDOW=2 TWO_FACTOR_LOGIN_MAX_SECONDS=60 -# Additional hosts to allow in the Content-Security-Policy, "self" (should be DOMAIN) -# and AWS_S3_CUSTOM_DOMAIN (if used) are added by default. -# Value should be a comma-separated list of host names. +# Additional hosts to allow in the Content-Security-Policy, "self" (should be +# DOMAIN with optionally ":" + PORT) and AWS_S3_CUSTOM_DOMAIN (if used) are +# added by default. Value should be a comma-separated list of host names. CSP_ADDITIONAL_HOSTS= # Time before being logged out (in seconds) diff --git a/bookwyrm/connectors/connector_manager.py b/bookwyrm/connectors/connector_manager.py index 444a626ba..1e1b3b554 100644 --- a/bookwyrm/connectors/connector_manager.py +++ b/bookwyrm/connectors/connector_manager.py @@ -118,9 +118,11 @@ def get_connectors() -> Iterator[abstract_connector.AbstractConnector]: def get_or_create_connector(remote_id: str) -> abstract_connector.AbstractConnector: """get the connector related to the object's server""" url = urlparse(remote_id) - identifier = url.netloc + identifier = url.hostname if not identifier: - raise ValueError("Invalid remote id") + raise ValueError(f"Invalid remote id: {remote_id}") + + base_url = f"{url.scheme}://{url.netloc}" try: connector_info = models.Connector.objects.get(identifier=identifier) @@ -128,10 +130,10 @@ def get_or_create_connector(remote_id: str) -> abstract_connector.AbstractConnec connector_info = models.Connector.objects.create( identifier=identifier, connector_file="bookwyrm_connector", - base_url=f"https://{identifier}", - books_url=f"https://{identifier}/book", - covers_url=f"https://{identifier}/images/covers", - search_url=f"https://{identifier}/search?q=", + base_url=base_url, + books_url=f"{base_url}/book", + covers_url=f"{base_url}/images/covers", + search_url=f"{base_url}/search?q=", priority=2, ) @@ -188,8 +190,11 @@ def raise_not_valid_url(url: str) -> None: if not parsed.scheme in ["http", "https"]: raise ConnectorException("Invalid scheme: ", url) + if not parsed.hostname: + raise ConnectorException("Hostname missing: ", url) + try: - ipaddress.ip_address(parsed.netloc) + ipaddress.ip_address(parsed.hostname) raise ConnectorException("Provided url is an IP address: ", url) except ValueError: # it's not an IP address, which is good diff --git a/bookwyrm/emailing.py b/bookwyrm/emailing.py index 5e08ebba1..ccc0aea61 100644 --- a/bookwyrm/emailing.py +++ b/bookwyrm/emailing.py @@ -4,7 +4,7 @@ from django.template.loader import get_template from bookwyrm import models, settings from bookwyrm.tasks import app, EMAIL -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import DOMAIN, BASE_URL def email_data(): @@ -14,6 +14,7 @@ def email_data(): "site_name": site.name, "logo": site.logo_small_url, "domain": DOMAIN, + "base_url": BASE_URL, "user": None, } diff --git a/bookwyrm/forms/links.py b/bookwyrm/forms/links.py index 345c5c1d4..5156d2578 100644 --- a/bookwyrm/forms/links.py +++ b/bookwyrm/forms/links.py @@ -26,7 +26,7 @@ class FileLinkForm(CustomForm): url = cleaned_data.get("url") filetype = cleaned_data.get("filetype") book = cleaned_data.get("book") - domain = urlparse(url).netloc + domain = urlparse(url).hostname if models.LinkDomain.objects.filter(domain=domain).exists(): status = models.LinkDomain.objects.get(domain=domain).status if status == "blocked": diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index 70f85b3c8..abe78dafb 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -8,7 +8,7 @@ from django.contrib.postgres.indexes import GinIndex import pgtrigger from bookwyrm import activitypub -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL from bookwyrm.utils.db import format_trigger from .book import BookDataModel, MergedAuthor @@ -70,7 +70,7 @@ class Author(BookDataModel): def get_remote_id(self): """editions and works both use "book" instead of model_name""" - return f"https://{DOMAIN}/author/{self.id}" + return f"{BASE_URL}/author/{self.id}" class Meta: """sets up indexes and triggers""" diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 2d39e2a6f..ca13d9553 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -10,7 +10,7 @@ from django.http import Http404 from django.utils.translation import gettext_lazy as _ from django.utils.text import slugify -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL from .fields import RemoteIdField @@ -38,7 +38,7 @@ class BookWyrmModel(models.Model): def get_remote_id(self): """generate the url that resolves to the local object, without a slug""" - base_path = f"https://{DOMAIN}" + base_path = BASE_URL if hasattr(self, "user"): base_path = f"{base_path}{self.user.local_path}" @@ -53,7 +53,7 @@ class BookWyrmModel(models.Model): @property def local_path(self): """how to link to this object in the local app, with a slug""" - local = self.get_remote_id().replace(f"https://{DOMAIN}", "") + local = self.get_remote_id().replace(BASE_URL, "") name = None if hasattr(self, "name_field"): diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 6075d2c92..8e957b717 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -21,7 +21,7 @@ from bookwyrm import activitypub from bookwyrm.isbn.isbn import hyphenator_singleton as hyphenator from bookwyrm.preview_images import generate_edition_preview_image_task from bookwyrm.settings import ( - DOMAIN, + BASE_URL, DEFAULT_LANGUAGE, LANGUAGE_ARTICLES, ENABLE_PREVIEW_IMAGES, @@ -327,7 +327,7 @@ class Book(BookDataModel): def get_remote_id(self): """editions and works both use "book" instead of model_name""" - return f"https://{DOMAIN}/book/{self.id}" + return f"{BASE_URL}/book/{self.id}" def guess_sort_title(self): """Get a best-guess sort title for the current book""" diff --git a/bookwyrm/models/connector.py b/bookwyrm/models/connector.py index 99e73ab37..f4b5be04c 100644 --- a/bookwyrm/models/connector.py +++ b/bookwyrm/models/connector.py @@ -11,7 +11,7 @@ ConnectorFiles = models.TextChoices("ConnectorFiles", CONNECTORS) class Connector(BookWyrmModel): """book data source connectors""" - identifier = models.CharField(max_length=255, unique=True) + identifier = models.CharField(max_length=255, unique=True) # domain priority = models.IntegerField(default=2) name = models.CharField(max_length=255, null=True, blank=True) connector_file = models.CharField(max_length=255, choices=ConnectorFiles.choices) diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index e1081ed45..5e08fc11d 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -16,7 +16,7 @@ FederationStatus = [ class FederatedServer(BookWyrmModel): """store which servers we federate with""" - server_name = models.CharField(max_length=255, unique=True) + server_name = models.CharField(max_length=255, unique=True) # domain status = models.CharField( max_length=255, default="federated", choices=FederationStatus ) @@ -64,5 +64,4 @@ class FederatedServer(BookWyrmModel): def is_blocked(cls, url: str) -> bool: """look up if a domain is blocked""" url = urlparse(url) - domain = url.netloc - return cls.objects.filter(server_name=domain, status="blocked").exists() + return cls.objects.filter(server_name=url.hostname, status="blocked").exists() diff --git a/bookwyrm/models/group.py b/bookwyrm/models/group.py index d02b56ab1..40a32b5dc 100644 --- a/bookwyrm/models/group.py +++ b/bookwyrm/models/group.py @@ -1,7 +1,7 @@ """ do book related things with other users """ from django.db import models, IntegrityError, transaction from django.db.models import Q -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL from .base_model import BookWyrmModel from . import fields from .relationship import UserBlocks @@ -17,7 +17,7 @@ class Group(BookWyrmModel): def get_remote_id(self): """don't want the user to be in there in this case""" - return f"https://{DOMAIN}/group/{self.id}" + return f"{BASE_URL}/group/{self.id}" @classmethod def followers_filter(cls, queryset, viewer): diff --git a/bookwyrm/models/link.py b/bookwyrm/models/link.py index d334a9d29..67bf9c4d4 100644 --- a/bookwyrm/models/link.py +++ b/bookwyrm/models/link.py @@ -38,7 +38,7 @@ class Link(ActivitypubMixin, BookWyrmModel): """create a link""" # get or create the associated domain if not self.domain: - domain = urlparse(self.url).netloc + domain = urlparse(self.url).hostname self.domain, _ = LinkDomain.objects.get_or_create(domain=domain) # this is never broadcast, the owning model broadcasts an update diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index 63dd5b23f..d32a8da95 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -7,7 +7,7 @@ from django.db.models import Q from django.utils import timezone from bookwyrm import activitypub -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin from .base_model import BookWyrmModel @@ -50,7 +50,7 @@ class List(OrderedCollectionMixin, BookWyrmModel): def get_remote_id(self): """don't want the user to be in there in this case""" - return f"https://{DOMAIN}/list/{self.id}" + return f"{BASE_URL}/list/{self.id}" @property def collection_queryset(self): diff --git a/bookwyrm/models/report.py b/bookwyrm/models/report.py index 74a9bbe41..64ade3a40 100644 --- a/bookwyrm/models/report.py +++ b/bookwyrm/models/report.py @@ -3,7 +3,7 @@ from django.core.exceptions import PermissionDenied from django.db import models from django.utils.translation import gettext_lazy as _ -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL from .base_model import BookWyrmModel @@ -46,7 +46,7 @@ class Report(BookWyrmModel): raise PermissionDenied() def get_remote_id(self): - return f"https://{DOMAIN}/settings/reports/{self.id}" + return f"{BASE_URL}/settings/reports/{self.id}" def comment(self, user, note): """comment on a report""" diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 3d92f8d43..4b4e3cd8d 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -6,7 +6,7 @@ from django.db import models from django.utils import timezone from bookwyrm import activitypub -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL from bookwyrm.tasks import BROADCAST from .activitypub_mixin import CollectionItemMixin, OrderedCollectionMixin from .base_model import BookWyrmModel @@ -71,7 +71,7 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel): @property def local_path(self): """No slugs""" - return self.get_remote_id().replace(f"https://{DOMAIN}", "") + return self.get_remote_id().replace(BASE_URL, "") def raise_not_deletable(self, viewer): """don't let anyone delete a default shelf""" diff --git a/bookwyrm/models/site.py b/bookwyrm/models/site.py index 201a499e5..36e6bb128 100644 --- a/bookwyrm/models/site.py +++ b/bookwyrm/models/site.py @@ -12,7 +12,7 @@ from model_utils import FieldTracker from bookwyrm.connectors.abstract_connector import get_data 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.tasks import app, MISC from .base_model import BookWyrmModel, new_access_code @@ -188,7 +188,7 @@ class SiteInvite(models.Model): @property def link(self): """formats the invite link""" - return f"https://{DOMAIN}/invite/{self.code}" + return f"{BASE_URL}/invite/{self.code}" class InviteRequest(BookWyrmModel): @@ -235,7 +235,7 @@ class PasswordReset(models.Model): @property def link(self): """formats the invite link""" - return f"https://{DOMAIN}/password-reset/{self.code}" + return f"{BASE_URL}/password-reset/{self.code}" # pylint: disable=unused-argument diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 0ec2c6529..e2671b07f 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -19,7 +19,7 @@ from bookwyrm.connectors import get_data, ConnectorException from bookwyrm.models.shelf import Shelf from bookwyrm.models.status import Status 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, ENABLE_PREVIEW_IMAGES, LANGUAGES from bookwyrm.signatures import create_key_pair from bookwyrm.tasks import app, MISC from bookwyrm.utils import regex @@ -42,12 +42,6 @@ def get_feed_filter_choices(): return [f[0] for f in FeedFilterChoices] -def site_link(): - """helper for generating links to the site""" - protocol = "https" if USE_HTTPS else "http" - return f"{protocol}://{DOMAIN}" - - # pylint: disable=too-many-public-methods class User(OrderedCollectionPageMixin, AbstractUser): """a user who wants to read books""" @@ -214,8 +208,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): @property def confirmation_link(self): """helper for generating confirmation links""" - link = site_link() - return f"{link}/confirm-email/{self.confirmation_code}" + return f"{BASE_URL}/confirm-email/{self.confirmation_code}" @property def following_link(self): @@ -349,7 +342,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): if not self.local and not re.match(regex.FULL_USERNAME, self.username): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) - self.username = f"{self.username}@{actor_parts.netloc}" + self.username = f"{self.username}@{actor_parts.hostname}" # this user already exists, no need to populate fields if not created: @@ -369,11 +362,10 @@ class User(OrderedCollectionPageMixin, AbstractUser): with transaction.atomic(): # populate fields for local users - link = site_link() - self.remote_id = f"{link}/user/{self.localname}" + self.remote_id = f"{BASE_URL}/user/{self.localname}" self.followers_url = f"{self.remote_id}/followers" self.inbox = f"{self.remote_id}/inbox" - self.shared_inbox = f"{link}/inbox" + self.shared_inbox = f"{BASE_URL}/inbox" self.outbox = f"{self.remote_id}/outbox" # an id needs to be set before we can proceed with related models @@ -558,7 +550,7 @@ def set_remote_server(user_id, allow_external_connections=False): user = User.objects.get(id=user_id) actor_parts = urlparse(user.remote_id) federated_server = get_or_create_remote_server( - actor_parts.netloc, allow_external_connections=allow_external_connections + actor_parts.hostname, allow_external_connections=allow_external_connections ) # if we were unable to find the server, we need to create a new entry for it if not federated_server: diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 27c86a22a..72c5221a4 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -350,28 +350,31 @@ USE_L10N = True USE_TZ = True - -USER_AGENT = f"BookWyrm (BookWyrm/{VERSION}; +https://{DOMAIN}/)" - # Imagekit generated thumbnails ENABLE_THUMBNAIL_GENERATION = env.bool("ENABLE_THUMBNAIL_GENERATION", False) IMAGEKIT_CACHEFILE_DIR = "thumbnails" IMAGEKIT_DEFAULT_CACHEFILE_STRATEGY = "bookwyrm.thumbnail_generation.Strategy" -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.2/howto/static-files/ - PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) CSP_ADDITIONAL_HOSTS = env.list("CSP_ADDITIONAL_HOSTS", []) -# Storage - PROTOCOL = "http" if USE_HTTPS: PROTOCOL = "https" SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True +PORT = env.int("PORT", 443 if USE_HTTPS else 80) +if (USE_HTTPS and PORT == 443) or (not USE_HTTPS and PORT == 80): + NETLOC = DOMAIN +else: + NETLOC = f"{DOMAIN}:{PORT}" +BASE_URL = f"{PROTOCOL}://{NETLOC}" + +USER_AGENT = f"BookWyrm (BookWyrm/{VERSION}; +{BASE_URL})" + +# Storage + USE_S3 = env.bool("USE_S3", False) USE_AZURE = env.bool("USE_AZURE", False) S3_SIGNED_URL_EXPIRY = env.int("S3_SIGNED_URL_EXPIRY", 900) @@ -440,11 +443,11 @@ elif USE_AZURE: else: # Static settings STATIC_URL = "/static/" - STATIC_FULL_URL = f"{PROTOCOL}://{DOMAIN}{STATIC_URL}" + STATIC_FULL_URL = BASE_URL + STATIC_URL STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" # Media settings MEDIA_URL = "/images/" - MEDIA_FULL_URL = f"{PROTOCOL}://{DOMAIN}{MEDIA_URL}" + MEDIA_FULL_URL = BASE_URL + MEDIA_URL DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage" # Exports settings EXPORTS_STORAGE = "bookwyrm.storage_backends.ExportsFileStorage" diff --git a/bookwyrm/templates/email/html_layout.html b/bookwyrm/templates/email/html_layout.html index b9f88732f..467d6d6e5 100644 --- a/bookwyrm/templates/email/html_layout.html +++ b/bookwyrm/templates/email/html_layout.html @@ -2,10 +2,10 @@
- logo + logo
- {{ site_name }}
+
{{ site_name }}
{{ domain }}
@@ -18,9 +18,9 @@
-

{% blocktrans %}BookWyrm hosted on {{ site_name }}{% endblocktrans %}

+

{% blocktrans %}BookWyrm hosted on {{ site_name }}{% endblocktrans %}

{% if user %} -

{% trans "Email preference" %}

+

{% trans "Email preference" %}

{% endif %}
diff --git a/bookwyrm/templates/email/invite/html_content.html b/bookwyrm/templates/email/invite/html_content.html index adc993b7b..9d2cda364 100644 --- a/bookwyrm/templates/email/invite/html_content.html +++ b/bookwyrm/templates/email/invite/html_content.html @@ -12,6 +12,6 @@

{% url 'code-of-conduct' as coc_path %} {% url 'about' as about_path %} - {% blocktrans %}Learn more about {{ site_name }}.{% endblocktrans %} + {% blocktrans %}Learn more about {{ site_name }}.{% endblocktrans %}

{% endblock %} diff --git a/bookwyrm/templates/email/invite/text_content.html b/bookwyrm/templates/email/invite/text_content.html index 26dcd1720..05fe91456 100644 --- a/bookwyrm/templates/email/invite/text_content.html +++ b/bookwyrm/templates/email/invite/text_content.html @@ -5,6 +5,6 @@ {{ 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 %} diff --git a/bookwyrm/templates/opensearch.xml b/bookwyrm/templates/opensearch.xml index fd5c8f231..980ca5604 100644 --- a/bookwyrm/templates/opensearch.xml +++ b/bookwyrm/templates/opensearch.xml @@ -10,6 +10,6 @@ {{ image }} diff --git a/bookwyrm/templatetags/utilities.py b/bookwyrm/templatetags/utilities.py index fb2113de4..bc87a60d7 100644 --- a/bookwyrm/templatetags/utilities.py +++ b/bookwyrm/templatetags/utilities.py @@ -120,7 +120,7 @@ def id_to_username(user_id): """given an arbitrary remote id, return the username""" if user_id: url = urlparse(user_id) - domain = url.netloc + domain = url.hostname parts = url.path.split("/") name = parts[-1] value = f"{name}@{domain}" diff --git a/bookwyrm/tests/connectors/test_abstract_connector.py b/bookwyrm/tests/connectors/test_abstract_connector.py index 994286994..1a8421c12 100644 --- a/bookwyrm/tests/connectors/test_abstract_connector.py +++ b/bookwyrm/tests/connectors/test_abstract_connector.py @@ -6,7 +6,7 @@ import responses from bookwyrm import models from bookwyrm.connectors import abstract_connector, ConnectorException from bookwyrm.connectors.abstract_connector import Mapping, get_data -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL class AbstractConnector(TestCase): @@ -86,7 +86,7 @@ class AbstractConnector(TestCase): def test_get_or_create_book_existing(self): """find an existing book by remote/origin id""" self.assertEqual(models.Book.objects.count(), 1) - self.assertEqual(self.book.remote_id, f"https://{DOMAIN}/book/{self.book.id}") + self.assertEqual(self.book.remote_id, f"{BASE_URL}/book/{self.book.id}") self.assertEqual(self.book.origin_id, "https://example.com/book/1234") # dedupe by origin id @@ -95,9 +95,7 @@ class AbstractConnector(TestCase): self.assertEqual(result, self.book) # dedupe by remote id - result = self.connector.get_or_create_book( - f"https://{DOMAIN}/book/{self.book.id}" - ) + result = self.connector.get_or_create_book(f"{BASE_URL}/book/{self.book.id}") self.assertEqual(models.Book.objects.count(), 1) self.assertEqual(result, self.book) diff --git a/bookwyrm/tests/data/ap_user_move.json b/bookwyrm/tests/data/ap_user_move.json index 52de40a68..11b10ded1 100644 --- a/bookwyrm/tests/data/ap_user_move.json +++ b/bookwyrm/tests/data/ap_user_move.json @@ -29,7 +29,9 @@ "bookwyrmUser": true, "manuallyApprovesFollowers": false, "discoverable": false, - "alsoKnownAs": ["https://your.domain.here/user/rat"], + "alsoKnownAs": [ + "https://your.domain.here:4242/user/rat" + ], "devices": "https://friend.camp/users/tripofmice/collections/devices", "tag": [], "icon": { @@ -37,4 +39,4 @@ "mediaType": "image/png", "url": "https://example.com/images/avatars/AL-2-crop-50.png" } -} +} \ No newline at end of file diff --git a/bookwyrm/tests/data/bookwyrm_account_export.tar.gz b/bookwyrm/tests/data/bookwyrm_account_export.tar.gz index 34cee6bc0..d7bc5634b 100644 Binary files a/bookwyrm/tests/data/bookwyrm_account_export.tar.gz and b/bookwyrm/tests/data/bookwyrm_account_export.tar.gz differ diff --git a/bookwyrm/tests/data/user_import.json b/bookwyrm/tests/data/user_import.json index ffe2e7d9e..5c0e22ea0 100644 --- a/bookwyrm/tests/data/user_import.json +++ b/bookwyrm/tests/data/user_import.json @@ -214,7 +214,7 @@ "attributedTo": "https://www.example.com//user/rat", "content": "

I like it

", "to": [ - "https://your.domain.here/user/rat/followers" + "https://your.domain.here:4242/user/rat/followers" ], "cc": [], "replies": { @@ -395,7 +395,7 @@ "https://local.lists/9999" ], "follows": [ - "https://your.domain.here/user/rat" + "https://your.domain.here:4242/user/rat" ], - "blocks": ["https://your.domain.here/user/badger"] + "blocks": ["https://your.domain.here:4242/user/badger"] } diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index d666b0e46..f16bdfa78 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -5,7 +5,7 @@ from django.test import TestCase from bookwyrm import models from bookwyrm.models import base_model -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL # pylint: disable=attribute-defined-outside-init @@ -44,14 +44,14 @@ class BaseModel(TestCase): """these should be generated""" self.test_model.id = 1 # pylint: disable=invalid-name expected = self.test_model.get_remote_id() - self.assertEqual(expected, f"https://{DOMAIN}/bookwyrmtestmodel/1") + self.assertEqual(expected, f"{BASE_URL}/bookwyrmtestmodel/1") def test_remote_id_with_user(self): """format of remote id when there's a user object""" self.test_model.user = self.local_user self.test_model.id = 1 expected = self.test_model.get_remote_id() - self.assertEqual(expected, f"https://{DOMAIN}/user/mouse/bookwyrmtestmodel/1") + self.assertEqual(expected, f"{BASE_URL}/user/mouse/bookwyrmtestmodel/1") def test_set_remote_id(self): """this function sets remote ids after creation""" @@ -60,7 +60,7 @@ class BaseModel(TestCase): instance = models.Work.objects.create(title="work title") instance.remote_id = None base_model.set_remote_id(None, instance, True) - self.assertEqual(instance.remote_id, f"https://{DOMAIN}/book/{instance.id}") + self.assertEqual(instance.remote_id, f"{BASE_URL}/book/{instance.id}") # shouldn't set remote_id if it's not created instance.remote_id = None diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index 5b2b71ba9..ebb57061d 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -31,7 +31,7 @@ class Book(TestCase): def test_remote_id(self): """fanciness with remote/origin ids""" - remote_id = f"https://{settings.DOMAIN}/book/{self.work.id}" + remote_id = f"{settings.BASE_URL}/book/{self.work.id}" self.assertEqual(self.work.get_remote_id(), remote_id) self.assertEqual(self.work.remote_id, remote_id) diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 2917c8908..7c1dcadc9 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -4,7 +4,7 @@ from dataclasses import dataclass import datetime import json import pathlib -import re +from urllib.parse import urlparse from typing import List from unittest import expectedFailure 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.base_model import BookWyrmModel from bookwyrm.models.activitypub_mixin import ActivitypubMixin -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import PROTOCOL, NETLOC # pylint: disable=too-many-public-methods @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") @@ -427,12 +427,10 @@ class ModelFields(TestCase): instance = fields.ImageField() output = instance.field_to_activity(user.avatar) - self.assertIsNotNone( - re.match( - rf"https:\/\/{DOMAIN}\/.*\.jpg", - output.url, - ) - ) + parsed_url = urlparse(output.url) + self.assertEqual(parsed_url.scheme, PROTOCOL) + self.assertEqual(parsed_url.netloc, NETLOC) + self.assertRegex(parsed_url.path, r"\.jpg$") self.assertEqual(output.name, "") self.assertEqual(output.type, "Image") diff --git a/bookwyrm/tests/models/test_list.py b/bookwyrm/tests/models/test_list.py index b9148853b..a902f6cca 100644 --- a/bookwyrm/tests/models/test_list.py +++ b/bookwyrm/tests/models/test_list.py @@ -28,7 +28,7 @@ class List(TestCase): def test_remote_id(self, *_): """shelves use custom remote ids""" book_list = models.List.objects.create(name="Test List", user=self.local_user) - expected_id = f"https://{settings.DOMAIN}/list/{book_list.id}" + expected_id = f"{settings.BASE_URL}/list/{book_list.id}" self.assertEqual(book_list.get_remote_id(), expected_id) def test_to_activity(self, *_): diff --git a/bookwyrm/tests/models/test_shelf_model.py b/bookwyrm/tests/models/test_shelf_model.py index 022cb5c61..d510952ea 100644 --- a/bookwyrm/tests/models/test_shelf_model.py +++ b/bookwyrm/tests/models/test_shelf_model.py @@ -35,7 +35,7 @@ class Shelf(TestCase): shelf = models.Shelf.objects.create( name="Test Shelf", identifier="test-shelf", user=self.local_user ) - expected_id = f"https://{settings.DOMAIN}/user/mouse/books/test-shelf" + expected_id = f"{settings.BASE_URL}/user/mouse/books/test-shelf" self.assertEqual(shelf.get_remote_id(), expected_id) def test_to_activity(self, *_): diff --git a/bookwyrm/tests/models/test_site.py b/bookwyrm/tests/models/test_site.py index 0933dac0c..c9f9a1315 100644 --- a/bookwyrm/tests/models/test_site.py +++ b/bookwyrm/tests/models/test_site.py @@ -79,7 +79,7 @@ class SiteModels(TestCase): def test_site_invite_link(self): """invite link generator""" 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): """someone wants an invite""" @@ -95,7 +95,7 @@ class SiteModels(TestCase): """password reset token""" token = models.PasswordReset.objects.create(user=self.local_user, code="hello") 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.remove_user_task.delay") diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 6323eeca3..5837b4188 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -60,7 +60,7 @@ class Status(TestCase): def test_status_generated_fields(self, *_): """setting remote id""" status = models.Status.objects.create(content="bleh", user=self.local_user) - expected_id = f"https://{settings.DOMAIN}/user/mouse/status/{status.id}" + expected_id = f"{settings.BASE_URL}/user/mouse/status/{status.id}" self.assertEqual(status.remote_id, expected_id) self.assertEqual(status.privacy, "public") @@ -151,7 +151,7 @@ class Status(TestCase): self.assertEqual(activity["tag"][0]["type"], "Hashtag") self.assertEqual(activity["tag"][0]["name"], "#content") self.assertEqual( - activity["tag"][0]["href"], f"https://{settings.DOMAIN}/hashtag/{tag.id}" + activity["tag"][0]["href"], f"{settings.BASE_URL}/hashtag/{tag.id}" ) def test_status_with_mention_to_activity(self, *_): @@ -227,11 +227,9 @@ class Status(TestCase): self.assertEqual(activity["sensitive"], False) self.assertIsInstance(activity["attachment"], list) self.assertEqual(activity["attachment"][0]["type"], "Document") - self.assertTrue( - re.match( - r"https:\/\/your.domain.here\/images\/covers\/test(_[A-z0-9]+)?.jpg", - activity["attachment"][0]["url"], - ) + self.assertRegex( + activity["attachment"][0]["url"], + rf"^{settings.BASE_URL}/images/covers/test(_[A-z0-9]+)?.jpg$", ) self.assertEqual(activity["attachment"][0]["name"], "Test Edition") @@ -263,12 +261,10 @@ class Status(TestCase): ), ) self.assertEqual(activity["attachment"][0]["type"], "Document") - # self.assertTrue( - # re.match( - # r"https:\/\/your.domain.here\/images\/covers\/test_[A-z0-9]+.jpg", - # activity["attachment"][0].url, - # ) - # ) + self.assertRegex( + activity["attachment"][0]["url"], + rf"^{settings.BASE_URL}/images/covers/test_[A-z0-9]+.jpg$", + ) self.assertEqual(activity["attachment"][0]["name"], "Test Edition") def test_quotation_to_activity(self, *_): @@ -306,11 +302,9 @@ class Status(TestCase): ), ) self.assertEqual(activity["attachment"][0]["type"], "Document") - self.assertTrue( - re.match( - r"https:\/\/your.domain.here\/images\/covers\/test(_[A-z0-9]+)?.jpg", - activity["attachment"][0]["url"], - ) + self.assertRegex( + activity["attachment"][0]["url"], + rf"^{settings.BASE_URL}/images/covers/test(_[A-z0-9]+)?.jpg$", ) self.assertEqual(activity["attachment"][0]["name"], "Test Edition") @@ -400,11 +394,9 @@ class Status(TestCase): ) self.assertEqual(activity["content"], "test content") self.assertEqual(activity["attachment"][0]["type"], "Document") - self.assertTrue( - re.match( - r"https:\/\/your.domain.here\/images\/covers\/test_[A-z0-9]+.jpg", - activity["attachment"][0]["url"], - ) + self.assertRegex( + activity["attachment"][0]["url"], + rf"^{settings.BASE_URL}/images/covers/test_[A-z0-9]+.jpg$", ) self.assertEqual(activity["attachment"][0]["name"], "Test Edition") @@ -425,11 +417,9 @@ class Status(TestCase): ) self.assertEqual(activity["content"], "test content") self.assertEqual(activity["attachment"][0]["type"], "Document") - self.assertTrue( - re.match( - r"https:\/\/your.domain.here\/images\/covers\/test_[A-z0-9]+.jpg", - activity["attachment"][0]["url"], - ) + self.assertRegex( + activity["attachment"][0]["url"], + rf"^{settings.BASE_URL}/images/covers/test_[A-z0-9]+.jpg$", ) self.assertEqual(activity["attachment"][0]["name"], "Test Edition") @@ -448,11 +438,9 @@ class Status(TestCase): f'rated {self.book.title}: 3 stars', ) self.assertEqual(activity["attachment"][0]["type"], "Document") - self.assertTrue( - re.match( - r"https:\/\/your.domain.here\/images\/covers\/test_[A-z0-9]+.jpg", - activity["attachment"][0]["url"], - ) + self.assertRegex( + activity["attachment"][0]["url"], + rf"^{settings.BASE_URL}/images/covers/test_[A-z0-9]+.jpg$", ) self.assertEqual(activity["attachment"][0]["name"], "Test Edition") diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index 22c7a171b..3147f95e3 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -9,15 +9,12 @@ import responses from bookwyrm import models from bookwyrm.management.commands import initdb -from bookwyrm.settings import USE_HTTPS, DOMAIN +from bookwyrm.settings import DOMAIN, BASE_URL # pylint: disable=missing-class-docstring # pylint: disable=missing-function-docstring class User(TestCase): - - protocol = "https://" if USE_HTTPS else "http://" - @classmethod def setUpTestData(cls): with ( @@ -49,11 +46,11 @@ class User(TestCase): def test_computed_fields(self): """username instead of id here""" - expected_id = f"{self.protocol}{DOMAIN}/user/mouse" + expected_id = f"{BASE_URL}/user/mouse" self.assertEqual(self.user.remote_id, expected_id) self.assertEqual(self.user.username, f"mouse@{DOMAIN}") self.assertEqual(self.user.localname, "mouse") - self.assertEqual(self.user.shared_inbox, f"{self.protocol}{DOMAIN}/inbox") + self.assertEqual(self.user.shared_inbox, f"{BASE_URL}/inbox") self.assertEqual(self.user.inbox, f"{expected_id}/inbox") self.assertEqual(self.user.outbox, f"{expected_id}/outbox") self.assertEqual(self.user.followers_url, f"{expected_id}/followers") @@ -130,7 +127,7 @@ class User(TestCase): patch("bookwyrm.lists_stream.populate_lists_task.delay"), ): user = models.User.objects.create_user( - f"test2{DOMAIN}", + "test2", "test2@bookwyrm.test", localname="test2", **user_attrs, @@ -145,7 +142,7 @@ class User(TestCase): patch("bookwyrm.lists_stream.populate_lists_task.delay"), ): user = models.User.objects.create_user( - f"test1{DOMAIN}", + "test1", "test1@bookwyrm.test", localname="test1", **user_attrs, diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index e41548bcf..79370844a 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -15,7 +15,7 @@ from django.utils.http import http_date from bookwyrm import models from bookwyrm.activitypub import Follow -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import DOMAIN, NETLOC from bookwyrm.signatures import create_key_pair, make_signature, make_digest @@ -77,7 +77,7 @@ class Signature(TestCase): "HTTP_SIGNATURE": signature, "HTTP_DIGEST": digest, "HTTP_CONTENT_TYPE": "application/activity+json; charset=utf-8", - "HTTP_HOST": DOMAIN, + "HTTP_HOST": NETLOC, }, ) diff --git a/bookwyrm/tests/test_utils.py b/bookwyrm/tests/test_utils.py index 61ed2262c..438eb1dd3 100644 --- a/bookwyrm/tests/test_utils.py +++ b/bookwyrm/tests/test_utils.py @@ -2,6 +2,7 @@ import re from django.test import TestCase +from bookwyrm.settings import BASE_URL from bookwyrm.utils import regex from bookwyrm.utils.validate import validate_url_domain @@ -15,17 +16,11 @@ class TestUtils(TestCase): def test_valid_url_domain(self): """Check with a valid URL""" - self.assertEqual( - validate_url_domain("https://your.domain.here/legit-book-url/"), - "https://your.domain.here/legit-book-url/", - ) + legit = f"{BASE_URL}/legit-book-url/" + self.assertEqual(validate_url_domain(legit), legit) def test_invalid_url_domain(self): """Check with an invalid URL""" self.assertIsNone( validate_url_domain("https://up-to-no-good.tld/bad-actor.exe") ) - - def test_default_url_domain(self): - """Check with a default URL""" - self.assertEqual(validate_url_domain("/"), "/") diff --git a/bookwyrm/tests/views/preferences/test_move.py b/bookwyrm/tests/views/preferences/test_move.py index acd2f4a2d..15edf3638 100644 --- a/bookwyrm/tests/views/preferences/test_move.py +++ b/bookwyrm/tests/views/preferences/test_move.py @@ -27,7 +27,6 @@ class ViewsHelpers(TestCase): patch("bookwyrm.lists_stream.populate_lists_task.delay"), patch("bookwyrm.suggested_users.rerank_user_task.delay"), ): - cls.local_user = models.User.objects.create_user( "rat", "rat@rat.com", @@ -35,7 +34,6 @@ class ViewsHelpers(TestCase): local=True, discoverable=True, localname="rat", - remote_id="https://your.domain.here/user/rat", ) with ( diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 818647db1..a1c06bede 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -8,7 +8,7 @@ from django.test.client import RequestFactory import responses 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") @@ -288,13 +288,13 @@ class ViewsHelpers(TestCase): # pylint: disable=too-many-public-methods def test_redirect_to_referer_valid_domain(self, *_): """redirect to within the app""" 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) - 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, *_): """if the path has get params (like sort) they are preserved""" 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) - self.assertEqual(result.url, f"https://{DOMAIN}/and/a/path?sort=hello") + self.assertEqual(result.url, f"{BASE_URL}/and/a/path?sort=hello") diff --git a/bookwyrm/tests/views/test_isbn.py b/bookwyrm/tests/views/test_isbn.py index 632a831b0..60a81f3a2 100644 --- a/bookwyrm/tests/views/test_isbn.py +++ b/bookwyrm/tests/views/test_isbn.py @@ -8,7 +8,7 @@ from django.test.client import RequestFactory from bookwyrm import models, views from bookwyrm.tests.validate_html import validate_html -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL class IsbnViews(TestCase): @@ -55,7 +55,7 @@ class IsbnViews(TestCase): data = json.loads(response.content) self.assertEqual(len(data), 1) self.assertEqual(data[0]["title"], "Test Book") - self.assertEqual(data[0]["key"], f"https://{DOMAIN}/book/{self.book.id}") + self.assertEqual(data[0]["key"], f"{BASE_URL}/book/{self.book.id}") def test_isbn_html_response(self): """searches local data only and returns book data in json format""" diff --git a/bookwyrm/tests/views/test_search.py b/bookwyrm/tests/views/test_search.py index 64ff68ba8..6c7e41cf3 100644 --- a/bookwyrm/tests/views/test_search.py +++ b/bookwyrm/tests/views/test_search.py @@ -10,7 +10,7 @@ from django.test.client import RequestFactory from bookwyrm import models, views from bookwyrm.book_search import SearchResult -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import BASE_URL from bookwyrm.tests.validate_html import validate_html @@ -57,7 +57,7 @@ class Views(TestCase): data = json.loads(response.content) self.assertEqual(len(data), 1) self.assertEqual(data[0]["title"], "Test Book") - self.assertEqual(data[0]["key"], f"https://{DOMAIN}/book/{self.book.id}") + self.assertEqual(data[0]["key"], f"{BASE_URL}/book/{self.book.id}") def test_search_no_query(self): """just the search page""" diff --git a/bookwyrm/utils/validate.py b/bookwyrm/utils/validate.py index ed1b00b0e..962d51a4e 100644 --- a/bookwyrm/utils/validate.py +++ b/bookwyrm/utils/validate.py @@ -1,21 +1,15 @@ """Validations""" from typing import Optional -from bookwyrm.settings import DOMAIN, USE_HTTPS +from bookwyrm.settings import BASE_URL -def validate_url_domain(url: str) -> Optional[str]: +def validate_url_domain(url: Optional[str]) -> Optional[str]: """Basic check that the URL starts with the instance domain name""" - if not url: + if url is None: return None - if url == "/": - return url + if not url.startswith(BASE_URL): + return None - protocol = "https://" if USE_HTTPS else "http://" - origin = f"{protocol}{DOMAIN}" - - if url.startswith(origin): - return url - - return None + return url diff --git a/bookwyrm/views/wellknown.py b/bookwyrm/views/wellknown.py index 0f2805ff2..e640c1c72 100644 --- a/bookwyrm/views/wellknown.py +++ b/bookwyrm/views/wellknown.py @@ -9,7 +9,7 @@ from django.utils import timezone from django.views.decorators.http import require_GET from bookwyrm import models -from bookwyrm.settings import DOMAIN, VERSION, LANGUAGE_CODE +from bookwyrm.settings import BASE_URL, DOMAIN, VERSION, LANGUAGE_CODE @require_GET @@ -34,7 +34,7 @@ def webfinger(request): }, { "rel": "http://ostatus.org/schema/1.0/subscribe", - "template": f"https://{DOMAIN}/ostatus_subscribe?acct={{uri}}", + "template": f"{BASE_URL}/ostatus_subscribe?acct={{uri}}", }, ], } diff --git a/pytest.ini b/pytest.ini index 18c955032..b963fb316 100644 --- a/pytest.ini +++ b/pytest.ini @@ -11,19 +11,20 @@ env = DEBUG = false USE_HTTPS = true DOMAIN = your.domain.here + PORT = 4242 ALLOWED_HOSTS = your.domain.here BOOKWYRM_DATABASE_BACKEND = postgres MEDIA_ROOT = images/ - CELERY_BROKER = "" + CELERY_BROKER = REDIS_BROKER_PORT = 6379 REDIS_BROKER_PASSWORD = beep REDIS_ACTIVITY_PORT = 6379 REDIS_ACTIVITY_PASSWORD = beep USE_DUMMY_CACHE = true FLOWER_PORT = 8888 - EMAIL_HOST = "smtp.mailgun.org" + EMAIL_HOST = smtp.mailgun.org EMAIL_PORT = 587 - EMAIL_HOST_USER = "" - EMAIL_HOST_PASSWORD = "" + EMAIL_HOST_USER = + EMAIL_HOST_PASSWORD = EMAIL_USE_TLS = true ENABLE_PREVIEW_IMAGES = false