diff --git a/core/exceptions.py b/core/exceptions.py new file mode 100644 index 0000000..4475538 --- /dev/null +++ b/core/exceptions.py @@ -0,0 +1,10 @@ +class ActivityPubError(BaseException): + """ + A problem with an ActivityPub message + """ + + +class ActorMismatchError(ActivityPubError): + """ + The actor is not authorised to do the action we saw + """ diff --git a/stator/models.py b/stator/models.py index a84f8c2..426803a 100644 --- a/stator/models.py +++ b/stator/models.py @@ -158,7 +158,7 @@ class StatorModel(models.Model): if settings.SENTRY_ENABLED: from sentry_sdk import capture_exception - capture_exception(e) + await sync_to_async(capture_exception, thread_sensitive=False)(e) traceback.print_exc() else: if next_state: diff --git a/stator/runner.py b/stator/runner.py index ed459be..7daf921 100644 --- a/stator/runner.py +++ b/stator/runner.py @@ -5,6 +5,7 @@ import traceback import uuid from typing import List, Optional, Type +from asgiref.sync import sync_to_async from django.conf import settings from django.utils import timezone @@ -46,12 +47,12 @@ class StatorRunner: if (time.monotonic() - self.last_clean) >= self.schedule_interval: print(f"{self.handled} tasks processed so far") print("Running cleaning and scheduling") - self.remove_completed_tasks() for model in self.models: asyncio.create_task(model.atransition_clean_locks()) asyncio.create_task(model.atransition_schedule_due()) self.last_clean = time.monotonic() # Calculate space left for tasks + self.remove_completed_tasks() space_remaining = self.concurrency - len(self.tasks) # Fetch new tasks for model in self.models: @@ -95,7 +96,7 @@ class StatorRunner: if settings.SENTRY_ENABLED: from sentry_sdk import capture_exception - capture_exception(e) + await sync_to_async(capture_exception, thread_sensitive=False)(e) traceback.print_exc() def remove_completed_tasks(self): diff --git a/users/models/identity.py b/users/models/identity.py index a78a451..c26762d 100644 --- a/users/models/identity.py +++ b/users/models/identity.py @@ -12,6 +12,7 @@ from django.template.defaultfilters import linebreaks_filter from django.templatetags.static import static from django.utils import timezone +from core.exceptions import ActorMismatchError from core.html import sanitize_post from core.ld import canonicalise, media_type_from_filename from core.uploads import upload_namer @@ -261,6 +262,24 @@ class Identity(StatorModel): except cls.DoesNotExist: pass + @classmethod + def handle_delete_ap(cls, data): + """ + Takes an incoming update.person message and just forces us to add it + to our fetch queue (don't want to bother with two load paths right now) + """ + # Assert that the actor matches the object + if data["actor"] != data["object"]: + raise ActorMismatchError( + f"Actor {data['actor']} trying to delete identity {data['object']}" + ) + # Find by actor + try: + actor = cls.by_actor_uri(data["actor"]) + actor.delete() + except cls.DoesNotExist: + pass + ### Actor/Webfinger fetching ### @classmethod diff --git a/users/models/inbox_message.py b/users/models/inbox_message.py index fc81d71..a73166a 100644 --- a/users/models/inbox_message.py +++ b/users/models/inbox_message.py @@ -67,7 +67,7 @@ class InboxMessageStates(StateGraph): case "delete": # If there is no object type, it's probably a profile if not isinstance(instance.message["object"], dict): - raise ValueError("Cannot handle activity of type delete") + await sync_to_async(Identity.handle_delete_ap)(instance.message) match instance.message_object_type: case "tombstone": await sync_to_async(Post.handle_delete_ap)(instance.message)