parent
716b74404f
commit
b03d9f0e12
|
@ -1,6 +1,7 @@
|
|||
import re
|
||||
from collections.abc import Iterable
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
import urlman
|
||||
|
@ -660,6 +661,9 @@ class Post(StatorModel):
|
|||
Raises DoesNotExist if it's not found and create is False,
|
||||
or it's from a blocked domain.
|
||||
"""
|
||||
# Ensure the domain of the object's actor and ID match to prevent injection
|
||||
if urlparse(data["id"]).hostname != urlparse(data["attributedTo"]).hostname:
|
||||
raise ValueError("Object's ID domain is different to its author")
|
||||
# Do we have one with the right ID?
|
||||
created = False
|
||||
try:
|
||||
|
@ -828,9 +832,14 @@ class Post(StatorModel):
|
|||
Handles an incoming delete request
|
||||
"""
|
||||
with transaction.atomic():
|
||||
# Is this an embedded object or plain ID?
|
||||
if isinstance(data["object"], str):
|
||||
object_uri = data["object"]
|
||||
else:
|
||||
object_uri = data["object"]["id"]
|
||||
# Find our post by ID if we have one
|
||||
try:
|
||||
post = cls.by_object_uri(data["object"]["id"])
|
||||
post = cls.by_object_uri(object_uri)
|
||||
except cls.DoesNotExist:
|
||||
# It's already been deleted
|
||||
return
|
||||
|
|
|
@ -179,13 +179,11 @@ class StatorRunner:
|
|||
|
||||
async def run_single_cycle(self):
|
||||
"""
|
||||
Testing entrypoint to advance things just one cycle
|
||||
Testing entrypoint to advance things just one cycle, and allow errors
|
||||
to propagate out.
|
||||
"""
|
||||
await asyncio.wait_for(self.fetch_and_process_tasks(), timeout=1)
|
||||
for _ in range(100):
|
||||
if not self.tasks:
|
||||
break
|
||||
self.remove_completed_tasks()
|
||||
await asyncio.sleep(0.05)
|
||||
for task in self.tasks:
|
||||
await task
|
||||
|
||||
run_single_cycle_sync = async_to_sync(run_single_cycle)
|
||||
|
|
|
@ -2,6 +2,7 @@ import pytest
|
|||
from pytest_httpx import HTTPXMock
|
||||
|
||||
from activities.models import Post, PostStates
|
||||
from users.models import Identity, InboxMessage
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
@ -237,3 +238,97 @@ def test_content_map(remote_identity):
|
|||
create=True,
|
||||
)
|
||||
assert post3.content == "Hello World"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("delete_type", ["note", "tombstone", "ref"])
|
||||
def test_inbound_posts(
|
||||
remote_identity: Identity,
|
||||
stator,
|
||||
delete_type: bool,
|
||||
):
|
||||
"""
|
||||
Ensures that a remote post can arrive via inbox message, be edited, and be
|
||||
deleted.
|
||||
"""
|
||||
# Create an inbound new post message
|
||||
message = {
|
||||
"id": "test",
|
||||
"type": "Create",
|
||||
"actor": remote_identity.actor_uri,
|
||||
"object": {
|
||||
"id": "https://remote.test/test-post",
|
||||
"type": "Note",
|
||||
"published": "2022-11-13T23:20:16Z",
|
||||
"attributedTo": remote_identity.actor_uri,
|
||||
"content": "post version one",
|
||||
},
|
||||
}
|
||||
InboxMessage.objects.create(message=message)
|
||||
|
||||
# Run stator and ensure that made the post
|
||||
stator.run_single_cycle_sync()
|
||||
post = Post.objects.get(object_uri="https://remote.test/test-post")
|
||||
assert post.content == "post version one"
|
||||
assert post.published.day == 13
|
||||
assert post.url == "https://remote.test/test-post"
|
||||
|
||||
# Create an inbound post edited message
|
||||
message = {
|
||||
"id": "test",
|
||||
"type": "Update",
|
||||
"actor": remote_identity.actor_uri,
|
||||
"object": {
|
||||
"id": "https://remote.test/test-post",
|
||||
"type": "Note",
|
||||
"published": "2022-11-13T23:20:16Z",
|
||||
"updated": "2022-11-14T23:20:16Z",
|
||||
"url": "https://remote.test/test-post/display",
|
||||
"attributedTo": remote_identity.actor_uri,
|
||||
"content": "post version two",
|
||||
},
|
||||
}
|
||||
InboxMessage.objects.create(message=message)
|
||||
|
||||
# Run stator and ensure that edited the post
|
||||
stator.run_single_cycle_sync()
|
||||
post = Post.objects.get(object_uri="https://remote.test/test-post")
|
||||
assert post.content == "post version two"
|
||||
assert post.edited.day == 14
|
||||
assert post.url == "https://remote.test/test-post/display"
|
||||
|
||||
# Create an inbound post deleted message
|
||||
if delete_type == "ref":
|
||||
message = {
|
||||
"id": "test",
|
||||
"type": "Delete",
|
||||
"actor": remote_identity.actor_uri,
|
||||
"object": "https://remote.test/test-post",
|
||||
}
|
||||
elif delete_type == "tombstone":
|
||||
message = {
|
||||
"id": "test",
|
||||
"type": "Delete",
|
||||
"actor": remote_identity.actor_uri,
|
||||
"object": {
|
||||
"id": "https://remote.test/test-post",
|
||||
"type": "Tombstone",
|
||||
},
|
||||
}
|
||||
else:
|
||||
message = {
|
||||
"id": "test",
|
||||
"type": "Delete",
|
||||
"actor": remote_identity.actor_uri,
|
||||
"object": {
|
||||
"id": "https://remote.test/test-post",
|
||||
"type": "Note",
|
||||
"published": "2022-11-13T23:20:16Z",
|
||||
"attributedTo": remote_identity.actor_uri,
|
||||
},
|
||||
}
|
||||
InboxMessage.objects.create(message=message)
|
||||
|
||||
# Run stator and ensure that deleted the post
|
||||
stator.run_single_cycle_sync()
|
||||
assert not Post.objects.filter(object_uri="https://remote.test/test-post").exists()
|
||||
|
|
|
@ -98,9 +98,20 @@ class InboxMessageStates(StateGraph):
|
|||
f"Cannot handle activity of type undo.{unknown}"
|
||||
)
|
||||
case "delete":
|
||||
# If there is no object type, it's probably a profile
|
||||
# If there is no object type, we need to see if it's a profile or a post
|
||||
if not isinstance(instance.message["object"], dict):
|
||||
if await Identity.objects.filter(
|
||||
actor_uri=instance.message["object"]
|
||||
).aexists():
|
||||
await sync_to_async(Identity.handle_delete_ap)(instance.message)
|
||||
elif await Post.objects.filter(
|
||||
object_uri=instance.message["object"]
|
||||
).aexists():
|
||||
await sync_to_async(Post.handle_delete_ap)(instance.message)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Cannot handle activity of type delete on URI {instance.message['object']}"
|
||||
)
|
||||
else:
|
||||
match instance.message_object_type:
|
||||
case "tombstone":
|
||||
|
|
Loading…
Reference in New Issue