From 097d86454a137495b1462e42ad11098507f4a08c Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Tue, 29 Dec 2020 20:51:05 +0100 Subject: [PATCH 01/14] Add signatures to requests to mastodon to support authorized fetch mode When mastodon is in authorized fetch mode any request has to be signed or it fails with 401. This adds the needed signature to the requests made to discover the actor when receiving something from mastodon (such as a follow request) --- bookwyrm/activitypub/base_activity.py | 40 +++++++++++++++++++++++++++ bookwyrm/models/activitypub_mixin.py | 2 +- bookwyrm/signatures.py | 12 +++++--- bookwyrm/tests/test_signing.py | 2 +- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 24d383ac79..c3287d45d6 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -8,6 +8,10 @@ from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.tasks import app +import requests +from django.utils.http import http_date +from bookwyrm import models +from bookwyrm.signatures import make_signature class ActivitySerializerError(ValueError): """routine problems serializing activitypub json""" @@ -284,6 +288,12 @@ def resolve_remote_id( # load the data and create the object try: data = get_data(remote_id) + except requests.HTTPError as e: + if e.response.status_code == 401: + ''' This most likely means it's a mastodon with secure fetch enabled. Need to be specific ''' + data = get_activitypub_data(remote_id) + else: + raise e except ConnectorException: raise ActivitySerializerError( f"Could not connect to host for remote_id: {remote_id}" @@ -304,3 +314,33 @@ def resolve_remote_id( # if we're refreshing, "result" will be set and we'll update it return item.to_model(model=model, instance=result, save=save) + +def get_activitypub_data(url): + ''' wrapper for request.get ''' + now = http_date() + + # XXX TEMP!! + sender = models.User.objects.get(id=1) + if not sender.key_pair.private_key: + # this shouldn't happen. it would be bad if it happened. + raise ValueError('No private key found for sender') + + try: + resp = requests.get( + url, + headers={ + 'Accept': 'application/json; charset=utf-8', + 'Date': now, + 'Signature': make_signature('get', sender, url, now), + }, + ) + except RequestError: + raise ConnectorException() + if not resp.ok: + resp.raise_for_status() + try: + data = resp.json() + except ValueError: + raise ConnectorException() + + return data diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 402cb040be..ee0b2c40dc 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -533,7 +533,7 @@ def sign_and_send(sender, data, destination): headers={ "Date": now, "Digest": digest, - "Signature": make_signature(sender, destination, now, digest), + "Signature": make_signature("post", sender, destination, now, digest), "Content-Type": "application/activity+json; charset=utf-8", "User-Agent": USER_AGENT, }, diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index 61cafe71f3..27c6357f67 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -22,27 +22,31 @@ def create_key_pair(): return private_key, public_key -def make_signature(sender, destination, date, digest): +def make_signature(method, sender, destination, date, digest): """uses a private key to sign an outgoing message""" inbox_parts = urlparse(destination) signature_headers = [ - f"(request-target): post {inbox_parts.path}", + f"(request-target): {method} {inbox_parts.path}", f"host: {inbox_parts.netloc}", f"date: {date}", f"digest: {digest}", ] + headers = "(request-target) host date" + if digest is not None: + signature_headers.append("digest: %s" % digest) + headers = "(request-target) host date digest" + message_to_sign = "\n".join(signature_headers) signer = pkcs1_15.new(RSA.import_key(sender.key_pair.private_key)) signed_message = signer.sign(SHA256.new(message_to_sign.encode("utf8"))) signature = { "keyId": f"{sender.remote_id}#main-key", "algorithm": "rsa-sha256", - "headers": "(request-target) host date digest", + "headers": headers, "signature": b64encode(signed_message).decode("utf8"), } return ",".join(f'{k}="{v}"' for (k, v) in signature.items()) - def make_digest(data): """creates a message digest for signing""" return "SHA-256=" + b64encode(hashlib.sha256(data.encode("utf-8")).digest()).decode( diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index d33687a59e..afcfb69072 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -85,7 +85,7 @@ def send_test_request( # pylint: disable=too-many-arguments now = date or http_date() data = json.dumps(get_follow_activity(sender, self.rat)) digest = digest or make_digest(data) - signature = make_signature(signer or sender, self.rat.inbox, now, digest) + signature = make_signature("post", signer or sender, self.rat.inbox, now, digest) with patch("bookwyrm.views.inbox.activity_task.delay"): with patch("bookwyrm.models.user.set_remote_server.delay"): return self.send(signature, now, send_data or data, digest) From e2ee3d27a7e40b877d8ddba90c056aa3f7418d25 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Wed, 5 Jan 2022 15:42:54 +0100 Subject: [PATCH 02/14] WIP --- bookwyrm/activitypub/base_activity.py | 12 ++++++++++-- bookwyrm/tests/activitypub/test_base_activity.py | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index c3287d45d6..f58b0bde9e 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -12,6 +12,7 @@ from django.utils.http import http_date from bookwyrm import models from bookwyrm.signatures import make_signature +from bookwyrm.settings import DOMAIN class ActivitySerializerError(ValueError): """routine problems serializing activitypub json""" @@ -315,12 +316,19 @@ def resolve_remote_id( # if we're refreshing, "result" will be set and we'll update it return item.to_model(model=model, instance=result, save=save) +def get_representative(): + try: + models.User.objects.get(id=-99) + except models.User.DoesNotExist: + username = "%s@%s" % (DOMAIN, DOMAIN) + email = "representative@%s" % (DOMAIN) + models.User.objects.create_user(id=-99, username=username, email=email, local=True, localname=DOMAIN) + def get_activitypub_data(url): ''' wrapper for request.get ''' now = http_date() - # XXX TEMP!! - sender = models.User.objects.get(id=1) + sender = get_representative() if not sender.key_pair.private_key: # this shouldn't happen. it would be bad if it happened. raise ValueError('No private key found for sender') diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index b951c7ab43..0eca7f7aac 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -14,6 +14,7 @@ ActivityObject, resolve_remote_id, set_related_field, + get_representative ) from bookwyrm.activitypub import ActivitySerializerError from bookwyrm import models @@ -51,6 +52,10 @@ def setUp(self): image.save(output, format=image.format) self.image_data = output.getvalue() + def test_get_representative_not_existing(self, _): + representative = get_representative() + self.assertIsInstance(representative, models.User) + def test_init(self, *_): """simple successfuly init""" instance = ActivityObject(id="a", type="b") From 9a0f8f9c2a6882f88de26736cafb80bcdeb13060 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Thu, 19 Jan 2023 16:40:13 +1100 Subject: [PATCH 03/14] fix test_get_representative_not_existing params --- bookwyrm/tests/activitypub/test_base_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index e3ba19e6d9..6ae446ff7b 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -53,7 +53,7 @@ def setUp(self): image.save(output, format=image.format) self.image_data = output.getvalue() - def test_get_representative_not_existing(self, _): + def test_get_representative_not_existing(self, *_): representative = get_representative() self.assertIsInstance(representative, models.User) From 0c614e828fee016814d1e31f963aa23fba3d8dc6 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Fri, 20 Jan 2023 08:24:46 +1100 Subject: [PATCH 04/14] deal with missing digests in signatures If no digest value is passed to make_signature and Exception was thrown. Since digest is added to the signature headers if it is not None anyway, there is no need to assign the digest value before that check. When signing a request _as the server_ for Mastodon's AUTHORIZED_FETCH there is no need to include a digest. --- bookwyrm/signatures.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index dc094d9884..ff634232d0 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -22,14 +22,13 @@ def create_key_pair(): return private_key, public_key -def make_signature(method, sender, destination, date, digest): +def make_signature(method, sender, destination, date, digest=None): """uses a private key to sign an outgoing message""" inbox_parts = urlparse(destination) signature_headers = [ f"(request-target): {method} {inbox_parts.path}", f"host: {inbox_parts.netloc}", - f"date: {date}", - f"digest: {digest}", + f"date: {date}" ] headers = "(request-target) host date" if digest is not None: From 0da5473b0c2beb6e4bc567d6761bb9c9d8113c42 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Fri, 20 Jan 2023 16:31:27 +1100 Subject: [PATCH 05/14] black formatting --- bookwyrm/signatures.py | 3 ++- bookwyrm/tests/activitypub/test_base_activity.py | 2 +- bookwyrm/tests/test_signing.py | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index ff634232d0..fb0ad49c10 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -28,7 +28,7 @@ def make_signature(method, sender, destination, date, digest=None): signature_headers = [ f"(request-target): {method} {inbox_parts.path}", f"host: {inbox_parts.netloc}", - f"date: {date}" + f"date: {date}", ] headers = "(request-target) host date" if digest is not None: @@ -46,6 +46,7 @@ def make_signature(method, sender, destination, date, digest=None): } return ",".join(f'{k}="{v}"' for (k, v) in signature.items()) + def make_digest(data): """creates a message digest for signing""" return "SHA-256=" + b64encode(hashlib.sha256(data.encode("utf-8")).digest()).decode( diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index 6ae446ff7b..56bc142bb9 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -14,7 +14,7 @@ ActivityObject, resolve_remote_id, set_related_field, - get_representative + get_representative, ) from bookwyrm.activitypub import ActivitySerializerError from bookwyrm import models diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index 54675d9d50..ec50a3da0f 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -85,7 +85,9 @@ def send_test_request( # pylint: disable=too-many-arguments now = date or http_date() data = json.dumps(get_follow_activity(sender, self.rat)) digest = digest or make_digest(data) - signature = make_signature("post", signer or sender, self.rat.inbox, now, digest) + signature = make_signature( + "post", signer or sender, self.rat.inbox, now, digest + ) with patch("bookwyrm.views.inbox.activity_task.delay"): with patch("bookwyrm.models.user.set_remote_server.delay"): return self.send(signature, now, send_data or data, digest) From 41082387160341dd12951a31a4449520dc77b640 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Fri, 20 Jan 2023 16:32:17 +1100 Subject: [PATCH 06/14] resolve SECURE_FETCH bugs ERROR HANDLING FIXES - use raise_for_status() to pass through response code - handle exceptions where no response object is passed through INSTANCE ACTOR - models.User.objects.create_user function cannot take an ID - allow instance admins to determine username and email for instance actor in settings.py --- bookwyrm/activitypub/base_activity.py | 38 +++++++++++------------ bookwyrm/connectors/abstract_connector.py | 2 +- bookwyrm/settings.py | 5 +++ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 3390c9cf50..e7061a4561 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -11,7 +11,7 @@ from bookwyrm import models from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.signatures import make_signature -from bookwyrm.settings import DOMAIN +from bookwyrm.settings import DOMAIN, INSTANCE_ACTOR_USERNAME, INSTANCE_ACTOR_EMAIL from bookwyrm.tasks import app, MEDIUM logger = logging.getLogger(__name__) @@ -281,15 +281,12 @@ def resolve_remote_id( try: data = get_data(remote_id) except requests.HTTPError as e: - if e.response.status_code == 401: - ''' This most likely means it's a mastodon with secure fetch enabled. Need to be specific ''' + if (e.response is not None) and e.response.status_code == 401: + """This most likely means it's a mastodon with secure fetch enabled.""" data = get_activitypub_data(remote_id) else: - raise e - except ConnectorException: - logger.info("Could not connect to host for remote_id: %s", remote_id) - return None - + logger.info("Could not connect to host for remote_id: %s", remote_id) + return None # determine the model implicitly, if not provided # or if it's a model with subclasses like Status, check again if not model or hasattr(model.objects, "select_subclasses"): @@ -309,33 +306,34 @@ def resolve_remote_id( def get_representative(): + username = "%s@%s" % (INSTANCE_ACTOR_USERNAME, DOMAIN) try: - models.User.objects.get(id=-99) + user = models.User.objects.get(username=username) except models.User.DoesNotExist: - username = "%s@%s" % (DOMAIN, DOMAIN) - email = "representative@%s" % (DOMAIN) - models.User.objects.create_user(id=-99, username=username, email=email, local=True, localname=DOMAIN) + email = INSTANCE_ACTOR_EMAIL + user = models.User.objects.create_user( + username=username, email=email, local=True, localname=DOMAIN + ) + return user def get_activitypub_data(url): - ''' wrapper for request.get ''' + """wrapper for request.get""" now = http_date() - sender = get_representative() if not sender.key_pair.private_key: # this shouldn't happen. it would be bad if it happened. - raise ValueError('No private key found for sender') - + raise ValueError("No private key found for sender") try: resp = requests.get( url, headers={ - 'Accept': 'application/json; charset=utf-8', - 'Date': now, - 'Signature': make_signature('get', sender, url, now), + "Accept": "application/json; charset=utf-8", + "Date": now, + "Signature": make_signature("get", sender, url, now), }, ) - except RequestError: + except requests.RequestException: raise ConnectorException() if not resp.ok: resp.raise_for_status() diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 8ae93926a4..dbfb2668d1 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -244,7 +244,7 @@ def get_data(url, params=None, timeout=settings.QUERY_TIMEOUT): raise ConnectorException(err) if not resp.ok: - raise ConnectorException() + resp.raise_for_status() try: data = resp.json() except ValueError as err: diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 74ef7d3136..528bf68e29 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -370,3 +370,8 @@ HTTP_X_FORWARDED_PROTO = env.bool("SECURE_PROXY_SSL_HEADER", False) if HTTP_X_FORWARDED_PROTO: SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + +# AUTHORIZED_FETCH Instance Actor +# WARNING this must both be unique - not used by any other user +INSTANCE_ACTOR_USERNAME = DOMAIN +INSTANCE_ACTOR_EMAIL = f"representative@{DOMAIN}" From f8c9df4aff68e92b47db14dbcebe2ba8980f99c1 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Fri, 20 Jan 2023 18:20:18 +1100 Subject: [PATCH 07/14] pylint fixes --- bookwyrm/activitypub/base_activity.py | 13 +++++++------ bookwyrm/signatures.py | 2 +- bookwyrm/tests/activitypub/test_base_activity.py | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index e7061a4561..6fc600b77e 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -1,8 +1,8 @@ """ basics for an activitypub serializer """ from dataclasses import dataclass, fields, MISSING from json import JSONEncoder -import requests import logging +import requests from django.apps import apps from django.db import IntegrityError, transaction @@ -251,10 +251,10 @@ def set_related_field( def get_model_from_type(activity_type): """given the activity, what type of model""" - models = apps.get_models() + activity_models = apps.get_models() model = [ m - for m in models + for m in activity_models if hasattr(m, "activity_serializer") and hasattr(m.activity_serializer, "type") and m.activity_serializer.type == activity_type @@ -280,9 +280,9 @@ def resolve_remote_id( # load the data and create the object try: data = get_data(remote_id) - except requests.HTTPError as e: + except ConnectorException as e: if (e.response is not None) and e.response.status_code == 401: - """This most likely means it's a mastodon with secure fetch enabled.""" + # This most likely means it's a mastodon with secure fetch enabled. data = get_activitypub_data(remote_id) else: logger.info("Could not connect to host for remote_id: %s", remote_id) @@ -306,7 +306,8 @@ def resolve_remote_id( def get_representative(): - username = "%s@%s" % (INSTANCE_ACTOR_USERNAME, DOMAIN) + """Get or create an actor representing the entire instance to sign requests to 'secure mastodon' servers""" + username = f"{INSTANCE_ACTOR_USERNAME}@{DOMAIN}" try: user = models.User.objects.get(username=username) except models.User.DoesNotExist: diff --git a/bookwyrm/signatures.py b/bookwyrm/signatures.py index fb0ad49c10..772d39cce7 100644 --- a/bookwyrm/signatures.py +++ b/bookwyrm/signatures.py @@ -32,7 +32,7 @@ def make_signature(method, sender, destination, date, digest=None): ] headers = "(request-target) host date" if digest is not None: - signature_headers.append("digest: %s" % digest) + signature_headers.append(f"digest: {digest}") headers = "(request-target) host date digest" message_to_sign = "\n".join(signature_headers) diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index 56bc142bb9..120cd2c91a 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -54,6 +54,7 @@ def setUp(self): self.image_data = output.getvalue() def test_get_representative_not_existing(self, *_): + """test that an instance representative actor is created if it does not exist""" representative = get_representative() self.assertIsInstance(representative, models.User) From e8452011f76c3c026e016669b7c717285c7281b0 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Fri, 20 Jan 2023 19:55:38 +1100 Subject: [PATCH 08/14] handle get_data exceptions better Makes exception handling more precise, only raising status for 401s. Also fixes a string pylint was complaining about. --- bookwyrm/activitypub/base_activity.py | 3 ++- bookwyrm/connectors/abstract_connector.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 6fc600b77e..a4affa172d 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -306,7 +306,8 @@ def resolve_remote_id( def get_representative(): - """Get or create an actor representing the entire instance to sign requests to 'secure mastodon' servers""" + """Get or create an actor representing the instance + to sign requests to 'secure mastodon' servers""" username = f"{INSTANCE_ACTOR_USERNAME}@{DOMAIN}" try: user = models.User.objects.get(username=username) diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index dbfb2668d1..6dd8a3081c 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -244,7 +244,11 @@ def get_data(url, params=None, timeout=settings.QUERY_TIMEOUT): raise ConnectorException(err) if not resp.ok: - resp.raise_for_status() + if resp.status_code == 401: + # this is probably an AUTHORIZED_FETCH issue + resp.raise_for_status() + else: + raise ConnectorException() try: data = resp.json() except ValueError as err: From 317fa5cdfd63d49c34a6210a13674a596ee97bd7 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Fri, 20 Jan 2023 20:05:14 +1100 Subject: [PATCH 09/14] black --- bookwyrm/activitypub/base_activity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index a4affa172d..7befc5bc6b 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -307,7 +307,7 @@ def resolve_remote_id( def get_representative(): """Get or create an actor representing the instance - to sign requests to 'secure mastodon' servers""" + to sign requests to 'secure mastodon' servers""" username = f"{INSTANCE_ACTOR_USERNAME}@{DOMAIN}" try: user = models.User.objects.get(username=username) From 803bba71a6da58b51caacd3210db279c13faba9f Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 22 Jan 2023 15:59:19 +1100 Subject: [PATCH 10/14] fix error handling - when using raise_for_status we need to catch an HTTPError, not a ConnectionError - simplify instance actor - use internal email address since it will never be used anyway, and make default username less likely to already be in use. --- bookwyrm/activitypub/base_activity.py | 11 +++++++---- bookwyrm/settings.py | 4 +--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 7befc5bc6b..3adddfeb9c 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -11,7 +11,7 @@ from bookwyrm import models from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.signatures import make_signature -from bookwyrm.settings import DOMAIN, INSTANCE_ACTOR_USERNAME, INSTANCE_ACTOR_EMAIL +from bookwyrm.settings import DOMAIN, INSTANCE_ACTOR_USERNAME from bookwyrm.tasks import app, MEDIUM logger = logging.getLogger(__name__) @@ -280,7 +280,10 @@ def resolve_remote_id( # load the data and create the object try: data = get_data(remote_id) - except ConnectorException as e: + except ConnectionError: + logger.info("Could not connect to host for remote_id: %s", remote_id) + return None + except requests.HTTPError as e: if (e.response is not None) and e.response.status_code == 401: # This most likely means it's a mastodon with secure fetch enabled. data = get_activitypub_data(remote_id) @@ -309,12 +312,12 @@ def get_representative(): """Get or create an actor representing the instance to sign requests to 'secure mastodon' servers""" username = f"{INSTANCE_ACTOR_USERNAME}@{DOMAIN}" + email = "bookwyrm@localhost" try: user = models.User.objects.get(username=username) except models.User.DoesNotExist: - email = INSTANCE_ACTOR_EMAIL user = models.User.objects.create_user( - username=username, email=email, local=True, localname=DOMAIN + username=username, email=email, local=True, localname=INSTANCE_ACTOR_USERNAME ) return user diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index 528bf68e29..ed1447b05d 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -372,6 +372,4 @@ SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # AUTHORIZED_FETCH Instance Actor -# WARNING this must both be unique - not used by any other user -INSTANCE_ACTOR_USERNAME = DOMAIN -INSTANCE_ACTOR_EMAIL = f"representative@{DOMAIN}" +INSTANCE_ACTOR_USERNAME = "bookwyrm.instance.actor" From f0e1767bc96e9924cf66c80d8909b3ab4ed8facf Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 22 Jan 2023 16:10:30 +1100 Subject: [PATCH 11/14] black code --- bookwyrm/activitypub/base_activity.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 3adddfeb9c..02e5395fc4 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -317,7 +317,10 @@ def get_representative(): user = models.User.objects.get(username=username) except models.User.DoesNotExist: user = models.User.objects.create_user( - username=username, email=email, local=True, localname=INSTANCE_ACTOR_USERNAME + username=username, + email=email, + local=True, + localname=INSTANCE_ACTOR_USERNAME, ) return user From 821169251ceada45de45d4cf31d0fbf9f99ea805 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Thu, 26 Jan 2023 17:19:44 +1100 Subject: [PATCH 12/14] add more verbose comment to settings.py --- bookwyrm/settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index ed1447b05d..cd63ebb0df 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -371,5 +371,8 @@ if HTTP_X_FORWARDED_PROTO: SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") -# AUTHORIZED_FETCH Instance Actor +# Instance Actor for signing GET requests to "secure mode" +# Mastodon servers. +# Do not change this setting unless you already have an existing +# user with the same username - in which case you should change it! INSTANCE_ACTOR_USERNAME = "bookwyrm.instance.actor" From 63dafd54d30108ce95a2bfebbc3045344aabbcfc Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Thu, 26 Jan 2023 17:24:51 +1100 Subject: [PATCH 13/14] black I can't even tell what it thinks it did, but Black likes to complain. --- bookwyrm/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/settings.py b/bookwyrm/settings.py index cd63ebb0df..5cbb4b1e4b 100644 --- a/bookwyrm/settings.py +++ b/bookwyrm/settings.py @@ -371,7 +371,7 @@ if HTTP_X_FORWARDED_PROTO: SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") -# Instance Actor for signing GET requests to "secure mode" +# Instance Actor for signing GET requests to "secure mode" # Mastodon servers. # Do not change this setting unless you already have an existing # user with the same username - in which case you should change it! From 9be2f00064c0242c055432f36d1222548f291af0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 26 Jan 2023 07:19:53 -0800 Subject: [PATCH 14/14] Update test_signing.py --- bookwyrm/tests/test_signing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index 9c50980187..8a7f65249f 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -89,6 +89,7 @@ def send_test_request( # pylint: disable=too-many-arguments signature = make_signature( "post", signer or sender, self.rat.inbox, now, digest ) + with patch("bookwyrm.views.inbox.activity_task.apply_async"): with patch("bookwyrm.models.user.set_remote_server.delay"): return self.send(signature, now, send_data or data, digest)