summaryrefslogtreecommitdiffstats
path: root/users/models
diff options
context:
space:
mode:
authorAndrew Godwin2022-11-10 23:42:43 -0700
committerAndrew Godwin2022-11-10 23:42:43 -0700
commitfbfad9fbf5e061cb7c658dada3c4014c9796021c (patch)
tree41cb9c3685e347d506876e18c8e535e3d126f1d9 /users/models
parent2c3a1299709f2612e96c37e4e121c83ad4df7a56 (diff)
downloadtakahe-fbfad9fbf5e061cb7c658dada3c4014c9796021c.tar.gz
takahe-fbfad9fbf5e061cb7c658dada3c4014c9796021c.tar.bz2
takahe-fbfad9fbf5e061cb7c658dada3c4014c9796021c.zip
Inbound and outbound follows basic working
Diffstat (limited to 'users/models')
-rw-r--r--users/models/domain.py12
-rw-r--r--users/models/follow.py114
-rw-r--r--users/models/inbox_message.py18
3 files changed, 128 insertions, 16 deletions
diff --git a/users/models/domain.py b/users/models/domain.py
index 4ac6ee9..a3815ee 100644
--- a/users/models/domain.py
+++ b/users/models/domain.py
@@ -81,3 +81,15 @@ class Domain(models.Model):
def __str__(self):
return self.domain
+
+ def save(self, *args, **kwargs):
+ # Ensure that we are not conflicting with other domains
+ if Domain.objects.filter(service_domain=self.domain).exists():
+ raise ValueError(
+ f"Domain {self.domain} is already a service domain elsewhere!"
+ )
+ if self.service_domain:
+ if Domain.objects.filter(domain=self.service_domain).exists():
+ raise ValueError(
+ f"Service domain {self.service_domain} is already a domain elsewhere!"
+ )
diff --git a/users/models/follow.py b/users/models/follow.py
index 6f62481..94ad40f 100644
--- a/users/models/follow.py
+++ b/users/models/follow.py
@@ -2,24 +2,110 @@ from typing import Optional
from django.db import models
+from core.ld import canonicalise
+from core.signatures import HttpSignature
from stator.models import State, StateField, StateGraph, StatorModel
class FollowStates(StateGraph):
unrequested = State(try_interval=30)
- requested = State(try_interval=24 * 60 * 60)
- accepted = State()
-
- unrequested.transitions_to(requested)
- requested.transitions_to(accepted)
+ local_requested = State(try_interval=24 * 60 * 60)
+ remote_requested = State(try_interval=24 * 60 * 60)
+ accepted = State(externally_progressed=True)
+ undone_locally = State(try_interval=60 * 60)
+ undone_remotely = State()
+
+ unrequested.transitions_to(local_requested)
+ unrequested.transitions_to(remote_requested)
+ local_requested.transitions_to(accepted)
+ remote_requested.transitions_to(accepted)
+ accepted.transitions_to(undone_locally)
+ undone_locally.transitions_to(undone_remotely)
@classmethod
async def handle_unrequested(cls, instance: "Follow"):
- print("Would have tried to follow on", instance)
+ # Re-retrieve the follow with more things linked
+ follow = await Follow.objects.select_related(
+ "source", "source__domain", "target"
+ ).aget(pk=instance.pk)
+ # Remote follows should not be here
+ if not follow.source.local:
+ return cls.remote_requested
+ # Construct the request
+ request = canonicalise(
+ {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": follow.uri,
+ "type": "Follow",
+ "actor": follow.source.actor_uri,
+ "object": follow.target.actor_uri,
+ }
+ )
+ # Sign it and send it
+ await HttpSignature.signed_request(
+ follow.target.inbox_uri, request, follow.source
+ )
+ return cls.local_requested
+
+ @classmethod
+ async def handle_local_requested(cls, instance: "Follow"):
+ # TODO: Resend follow requests occasionally
+ pass
+
+ @classmethod
+ async def handle_remote_requested(cls, instance: "Follow"):
+ # Re-retrieve the follow with more things linked
+ follow = await Follow.objects.select_related(
+ "source", "source__domain", "target"
+ ).aget(pk=instance.pk)
+ # Send an accept
+ request = canonicalise(
+ {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": follow.target.actor_uri + f"follow/{follow.pk}/#accept",
+ "type": "Follow",
+ "actor": follow.source.actor_uri,
+ "object": {
+ "id": follow.uri,
+ "type": "Follow",
+ "actor": follow.source.actor_uri,
+ "object": follow.target.actor_uri,
+ },
+ }
+ )
+ # Sign it and send it
+ await HttpSignature.signed_request(
+ follow.source.inbox_uri,
+ request,
+ identity=follow.target,
+ )
+ return cls.accepted
@classmethod
- async def handle_requested(cls, instance: "Follow"):
- print("Would have tried to requested on", instance)
+ async def handle_undone_locally(cls, instance: "Follow"):
+ follow = Follow.objects.select_related(
+ "source", "source__domain", "target"
+ ).get(pk=instance.pk)
+ # Construct the request
+ request = canonicalise(
+ {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ "id": follow.uri + "#undo",
+ "type": "Undo",
+ "actor": follow.source.actor_uri,
+ "object": {
+ "id": follow.uri,
+ "type": "Follow",
+ "actor": follow.source.actor_uri,
+ "object": follow.target.actor_uri,
+ },
+ }
+ )
+ # Sign it and send it
+ await HttpSignature.signed_request(
+ follow.target.inbox_uri, request, follow.source
+ )
+ return cls.undone_remotely
class Follow(StatorModel):
@@ -83,11 +169,17 @@ class Follow(StatorModel):
follow = cls.maybe_get(source=source, target=target)
if follow is None:
follow = Follow.objects.create(source=source, target=target, uri=uri)
- if follow.state == FollowStates.fresh:
- follow.transition_perform(FollowStates.requested)
+ if follow.state == FollowStates.unrequested:
+ follow.transition_perform(FollowStates.remote_requested)
@classmethod
def remote_accepted(cls, source, target):
+ print(f"accepted follow source {source} target {target}")
follow = cls.maybe_get(source=source, target=target)
- if follow and follow.state == FollowStates.requested:
+ print(f"accepting follow {follow}")
+ if follow and follow.state in [
+ FollowStates.unrequested,
+ FollowStates.local_requested,
+ ]:
follow.transition_perform(FollowStates.accepted)
+ print("accepted")
diff --git a/users/models/inbox_message.py b/users/models/inbox_message.py
index 0dbdc3a..54b05e9 100644
--- a/users/models/inbox_message.py
+++ b/users/models/inbox_message.py
@@ -13,7 +13,7 @@ class InboxMessageStates(StateGraph):
@classmethod
async def handle_received(cls, instance: "InboxMessage"):
- type = instance.message["type"].lower()
+ type = instance.message_type
if type == "follow":
await instance.follow_request()
elif type == "accept":
@@ -30,6 +30,7 @@ class InboxMessageStates(StateGraph):
raise ValueError(f"Cannot handle activity of type undo.{inner_type}")
else:
raise ValueError(f"Cannot handle activity of type {type}")
+ return cls.processed
class InboxMessage(StatorModel):
@@ -60,10 +61,17 @@ class InboxMessage(StatorModel):
"""
Handles an incoming acceptance of one of our follow requests
"""
- Follow.remote_accepted(
- source=Identity.by_actor_uri_with_create(self.message["actor"]),
- target=Identity.by_actor_uri(self.message["object"]),
- )
+ target = Identity.by_actor_uri_with_create(self.message["actor"])
+ source = Identity.by_actor_uri(self.message["object"]["actor"])
+ if source is None:
+ raise ValueError(
+ f"Follow-Accept has invalid source {self.message['object']['actor']}"
+ )
+ Follow.remote_accepted(source=source, target=target)
+
+ @property
+ def message_type(self):
+ return self.message["type"].lower()
async def follow_undo(self):
"""