Skip to content

Commit

Permalink
add relay support - backend part
Browse files Browse the repository at this point in the history
  • Loading branch information
alphatownsman committed Aug 31, 2023
1 parent 2a0bbf0 commit bb2eaa5
Show file tree
Hide file tree
Showing 16 changed files with 250 additions and 21 deletions.
2 changes: 1 addition & 1 deletion activities/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@


class Migration(migrations.Migration):

initial = True

dependencies = [
Expand Down Expand Up @@ -264,6 +263,7 @@ class Migration(migrations.Migration):
("identity_edited", "Identity Edited"),
("identity_deleted", "Identity Deleted"),
("identity_created", "Identity Created"),
("identity_moved", "Identity Moved"),
],
max_length=100,
),
Expand Down
6 changes: 3 additions & 3 deletions activities/models/fan_out.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def handle_new(cls, instance: "FanOut"):
instance.identity.shared_inbox_uri
or instance.identity.inbox_uri
),
body=canonicalise(post.to_create_ap()),
body=canonicalise(post.to_create_ap(), outbound_compat=True),
)
except httpx.RequestError:
return
Expand All @@ -98,7 +98,7 @@ def handle_new(cls, instance: "FanOut"):
instance.identity.shared_inbox_uri
or instance.identity.inbox_uri
),
body=canonicalise(post.to_update_ap()),
body=canonicalise(post.to_update_ap(), outbound_compat=True),
)
except httpx.RequestError:
return
Expand All @@ -124,7 +124,7 @@ def handle_new(cls, instance: "FanOut"):
instance.identity.shared_inbox_uri
or instance.identity.inbox_uri
),
body=canonicalise(post.to_delete_ap()),
body=canonicalise(post.to_delete_ap(), outbound_compat=True),
)
except httpx.RequestError:
return
Expand Down
7 changes: 7 additions & 0 deletions activities/models/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from users.models.hashtag_follow import HashtagFollow
from users.models.identity import Identity, IdentityStates
from users.models.inbox_message import InboxMessage
from users.models.relay_actor import RelayActor
from users.models.system_actor import SystemActor


Expand Down Expand Up @@ -779,6 +780,12 @@ def get_targets(self) -> Iterable[Identity]:
targets.remove(block.target)
except KeyError:
pass
# send local-created public posts to relays
if self.local and self.visibility in [
Post.Visibilities.public,
Post.Visibilities.unlisted,
]:
targets.update(set(RelayActor.get_relays()))
# Now dedupe the targets based on shared inboxes (we only keep one per
# shared inbox)
deduped_targets = set()
Expand Down
8 changes: 7 additions & 1 deletion activities/models/post_interaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from core.snowflake import Snowflake
from stator.models import State, StateField, StateGraph, StatorModel
from users.models.identity import Identity
from users.models.relay_actor import RelayActor


class PostInteractionStates(StateGraph):
Expand Down Expand Up @@ -298,6 +299,11 @@ def to_ap(self) -> dict:
"object": self.post.object_uri,
"to": "as:Public",
}
if self.identity.is_local_relay:
# if boost to relay, set "to" to remote relays instead of Public
value["to"] = list(
RelayActor.get_relays().values_list("actor_uri", flat=True)
)
elif self.type == self.Types.like:
value = {
"type": "Like",
Expand All @@ -315,7 +321,7 @@ def to_ap(self) -> dict:
"inReplyTo": self.post.object_uri,
"attributedTo": self.identity.actor_uri,
}
elif self.type == self.Types.pin:
else:
raise ValueError("Cannot turn into AP")
return value

Expand Down
4 changes: 3 additions & 1 deletion activities/views/posts.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def serve_object(self):
if not self.post_obj.local:
return redirect(self.post_obj.object_uri)
return JsonResponse(
canonicalise(self.post_obj.to_ap(), include_security=True),
canonicalise(
self.post_obj.to_ap(), include_security=True, outbound_compat=True
),
content_type="application/activity+json",
)
22 changes: 17 additions & 5 deletions core/ld.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"attachment": {"@id": "as:attachment", "@type": "@id"},
"bcc": {"@id": "as:bcc", "@type": "@id"},
"bto": {"@id": "as:bto", "@type": "@id"},
"cc": {"@id": "as:cc", "@type": "@id"},
"cc": {"@id": "as:cc", "@type": "@id", "@container": "@set"},
"context": {"@id": "as:context", "@type": "@id"},
"current": {"@id": "as:current", "@type": "@id"},
"first": {"@id": "as:first", "@type": "@id"},
Expand Down Expand Up @@ -119,7 +119,7 @@
"partOf": {"@id": "as:partOf", "@type": "@id"},
"tag": {"@id": "as:tag", "@type": "@id"},
"target": {"@id": "as:target", "@type": "@id"},
"to": {"@id": "as:to", "@type": "@id"},
"to": {"@id": "as:to", "@type": "@id", "@container": "@set"},
"url": {"@id": "as:url", "@type": "@id"},
"altitude": {"@id": "as:altitude", "@type": "xsd:float"},
"content": "as:content",
Expand Down Expand Up @@ -594,7 +594,9 @@ def builtin_document_loader(url: str, options={}):
)


def canonicalise(json_data: dict, include_security: bool = False) -> dict:
def canonicalise(
json_data: dict, include_security: bool = False, outbound_compat: bool = False
) -> dict:
"""
Given an ActivityPub JSON-LD document, round-trips it through the LD
systems to end up in a canonicalised, compacted format.
Expand Down Expand Up @@ -632,8 +634,18 @@ def canonicalise(json_data: dict, include_security: bool = False) -> dict:
context.append("https://w3id.org/security/v1")

json_data["@context"] = context

return jsonld.compact(jsonld.expand(json_data), context)
j = jsonld.compact(jsonld.expand(json_data), context)
if outbound_compat:
# patch outbound json to make it compatible with various implementations
for k in ["to", "cc"]:
if j.get(k):
j[k] = [
x
if x != "as:Public"
else "https://www.w3.org/ns/activitystreams#Public"
for x in j[k]
]
return j


def get_list(container, key) -> list:
Expand Down
2 changes: 1 addition & 1 deletion core/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ class SystemOptions(pydantic.BaseModel):
cache_timeout_page_post: int = 60 * 2
cache_timeout_identity_feed: int = 60 * 5

restricted_usernames: str = "admin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"
restricted_usernames: str = "__system__\n__relay__\nadmin\nadmins\nadministrator\nadministrators\nsystem\nroot\nannounce\nannouncement\nannouncements"

custom_head: str | None

Expand Down
1 change: 1 addition & 0 deletions takahe/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@
path("actor/inbox/", activitypub.Inbox.as_view()),
path("actor/outbox/", activitypub.EmptyOutbox.as_view()),
path("inbox/", activitypub.Inbox.as_view(), name="shared_inbox"),
path("relay", activitypub.RelayActorView.as_view()),
# API/Oauth
path("api/", include("api.urls")),
path("oauth/authorize", oauth.AuthorizationView.as_view()),
Expand Down
3 changes: 2 additions & 1 deletion users/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ def data_init(self, **kwargs):
boot (or post upgrade).
"""
# Generate the server actor keypair if needed
from users.models import SystemActor
from users.models import RelayActor, SystemActor

SystemActor.generate_keys_if_needed()
RelayActor.initialize_if_needed()

def ready(self) -> None:
post_migrate.connect(self.data_init, sender=self)
1 change: 1 addition & 0 deletions users/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .inbox_message import InboxMessage, InboxMessageStates # noqa
from .invite import Invite # noqa
from .password_reset import PasswordReset # noqa
from .relay_actor import RelayActor # noqa
from .report import Report # noqa
from .system_actor import SystemActor # noqa
from .user import User # noqa
Expand Down
8 changes: 6 additions & 2 deletions users/models/follow.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def handle_unrequested(cls, instance: "Follow"):
instance.source.signed_request(
method="post",
uri=instance.target.inbox_uri,
body=canonicalise(instance.to_ap()),
body=canonicalise(instance.to_ap(), outbound_compat=True),
)
except httpx.RequestError:
return
Expand Down Expand Up @@ -242,7 +242,11 @@ def create_local(cls, source, target, boosts=True):
uri="",
state=FollowStates.unrequested,
)
follow.uri = source.actor_uri + f"follow/{follow.pk}/"
follow.uri = (
source.actor_uri
+ ("/" if source.actor_uri[-1] != "/" else "")
+ f"follow/{follow.pk}/"
)
follow.save()
return follow

Expand Down
5 changes: 5 additions & 0 deletions users/models/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
)
from stator.models import State, StateField, StateGraph, StatorModel
from users.models.domain import Domain
from users.models.relay_actor import RelayActor
from users.models.system_actor import SystemActor


Expand Down Expand Up @@ -337,6 +338,10 @@ def safe_metadata(self):
for data in self.metadata
]

@property
def is_local_relay(self):
return self.local and self.actor_uri == RelayActor.actor_uri

def ensure_uris(self):
"""
Ensures that local identities have all the URIs populated on their fields
Expand Down
5 changes: 5 additions & 0 deletions users/models/inbox_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from core.exceptions import ActivityPubError
from stator.models import State, StateField, StateGraph, StatorModel
from users.models.relay_actor import RelayActor


class InboxMessageStates(StateGraph):
Expand Down Expand Up @@ -141,6 +142,10 @@ def handle_received(cls, instance: "InboxMessage"):
IdentityService.handle_internal_add_follow(
instance.message["object"]
)
case "unfollowrelay":
RelayActor.handle_internal_unfollow(
instance.message["object"]
)
case unknown:
return cls.errored
case unknown:
Expand Down
Loading

0 comments on commit bb2eaa5

Please sign in to comment.