summaryrefslogtreecommitdiffstats
path: root/users
diff options
context:
space:
mode:
authorAndrew Godwin2022-11-12 21:14:21 -0700
committerAndrew Godwin2022-11-12 21:14:21 -0700
commit878f56b411279cd9865a7ec05f1d14c9f70f6187 (patch)
tree93f3c65e109a014041e4380a854bdf8b4dd7fe6d /users
parentdd4328ae523bb375dd871e85d1bacd9311e87a89 (diff)
downloadtakahe-878f56b411279cd9865a7ec05f1d14c9f70f6187.tar.gz
takahe-878f56b411279cd9865a7ec05f1d14c9f70f6187.tar.bz2
takahe-878f56b411279cd9865a7ec05f1d14c9f70f6187.zip
Post URIs and host-meta
Diffstat (limited to 'users')
-rw-r--r--users/admin.py2
-rw-r--r--users/models/follow.py6
-rw-r--r--users/models/inbox_message.py4
-rw-r--r--users/views/activitypub.py148
-rw-r--r--users/views/identity.py129
5 files changed, 158 insertions, 131 deletions
diff --git a/users/admin.py b/users/admin.py
index 61a2bc8..e52e41c 100644
--- a/users/admin.py
+++ b/users/admin.py
@@ -38,7 +38,7 @@ class FollowAdmin(admin.ModelAdmin):
@admin.register(InboxMessage)
class InboxMessageAdmin(admin.ModelAdmin):
- list_display = ["id", "state", "state_attempted", "message_type"]
+ list_display = ["id", "state", "state_attempted", "message_type", "message_actor"]
actions = ["reset_state"]
@admin.action(description="Reset State")
diff --git a/users/models/follow.py b/users/models/follow.py
index 238081e..0236d19 100644
--- a/users/models/follow.py
+++ b/users/models/follow.py
@@ -37,7 +37,7 @@ class FollowStates(StateGraph):
await HttpSignature.signed_request(
uri=follow.target.inbox_uri,
body=canonicalise(follow.to_ap()),
- private_key=follow.source.public_key,
+ private_key=follow.source.private_key,
key_id=follow.source.public_key_id,
)
return cls.local_requested
@@ -57,7 +57,7 @@ class FollowStates(StateGraph):
await HttpSignature.signed_request(
uri=follow.source.inbox_uri,
body=canonicalise(follow.to_accept_ap()),
- private_key=follow.target.public_key,
+ private_key=follow.target.private_key,
key_id=follow.target.public_key_id,
)
return cls.accepted
@@ -71,7 +71,7 @@ class FollowStates(StateGraph):
await HttpSignature.signed_request(
uri=follow.target.inbox_uri,
body=canonicalise(follow.to_undo_ap()),
- private_key=follow.source.public_key,
+ private_key=follow.source.private_key,
key_id=follow.source.public_key_id,
)
return cls.undone_remotely
diff --git a/users/models/inbox_message.py b/users/models/inbox_message.py
index ea55b17..43424c9 100644
--- a/users/models/inbox_message.py
+++ b/users/models/inbox_message.py
@@ -66,3 +66,7 @@ class InboxMessage(StatorModel):
@property
def message_object_type(self):
return self.message["object"]["type"].lower()
+
+ @property
+ def message_actor(self):
+ return self.message.get("actor")
diff --git a/users/views/activitypub.py b/users/views/activitypub.py
new file mode 100644
index 0000000..54f04bc
--- /dev/null
+++ b/users/views/activitypub.py
@@ -0,0 +1,148 @@
+import json
+
+from asgiref.sync import async_to_sync
+from django.http import Http404, HttpResponse, HttpResponseBadRequest, JsonResponse
+from django.utils.decorators import method_decorator
+from django.views.decorators.csrf import csrf_exempt
+from django.views.generic import View
+
+from core.ld import canonicalise
+from core.signatures import (
+ HttpSignature,
+ LDSignature,
+ VerificationError,
+ VerificationFormatError,
+)
+from users.models import Identity, InboxMessage
+from users.shortcuts import by_handle_or_404
+
+
+class HttpResponseUnauthorized(HttpResponse):
+ status_code = 401
+
+
+class HostMeta(View):
+ """
+ Returns a canned host-meta response
+ """
+
+ def get(self, request):
+ return HttpResponse(
+ """<?xml version="1.0" encoding="UTF-8"?>
+ <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
+ <Link rel="lrdd" template="https://%s/.well-known/webfinger?resource={uri}"/>
+ </XRD>"""
+ % request.META["HTTP_HOST"],
+ content_type="application/xml",
+ )
+
+
+class Webfinger(View):
+ """
+ Services webfinger requests
+ """
+
+ def get(self, request):
+ resource = request.GET.get("resource")
+ if not resource.startswith("acct:"):
+ raise Http404("Not an account resource")
+ handle = resource[5:].replace("testfedi", "feditest")
+ identity = by_handle_or_404(request, handle)
+ return JsonResponse(
+ {
+ "subject": f"acct:{identity.handle}",
+ "aliases": [
+ identity.urls.view_short.full(),
+ ],
+ "links": [
+ {
+ "rel": "http://webfinger.net/rel/profile-page",
+ "type": "text/html",
+ "href": identity.urls.view_short.full(),
+ },
+ {
+ "rel": "self",
+ "type": "application/activity+json",
+ "href": identity.actor_uri,
+ },
+ ],
+ }
+ )
+
+
+class Actor(View):
+ """
+ Returns the AP Actor object
+ """
+
+ def get(self, request, handle):
+ identity = by_handle_or_404(self.request, handle)
+ response = {
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ ],
+ "id": identity.actor_uri,
+ "type": "Person",
+ "inbox": identity.actor_uri + "inbox/",
+ "preferredUsername": identity.username,
+ "publicKey": {
+ "id": identity.public_key_id,
+ "owner": identity.actor_uri,
+ "publicKeyPem": identity.public_key,
+ },
+ "published": identity.created.strftime("%Y-%m-%dT%H:%M:%SZ"),
+ "url": identity.urls.view_short.full(),
+ }
+ if identity.name:
+ response["name"] = identity.name
+ if identity.summary:
+ response["summary"] = identity.summary
+ return JsonResponse(canonicalise(response, include_security=True))
+
+
+@method_decorator(csrf_exempt, name="dispatch")
+class Inbox(View):
+ """
+ AP Inbox endpoint
+ """
+
+ def post(self, request, handle):
+ # Load the LD
+ document = canonicalise(json.loads(request.body), include_security=True)
+ # Find the Identity by the actor on the incoming item
+ # This ensures that the signature used for the headers matches the actor
+ # described in the payload.
+ identity = Identity.by_actor_uri(document["actor"], create=True)
+ if not identity.public_key:
+ # See if we can fetch it right now
+ async_to_sync(identity.fetch_actor)()
+ if not identity.public_key:
+ print("Cannot get actor")
+ return HttpResponseBadRequest("Cannot retrieve actor")
+ # If there's a "signature" payload, verify against that
+ if "signature" in document:
+ try:
+ LDSignature.verify_signature(document, identity.public_key)
+ except VerificationFormatError as e:
+ print("Bad LD signature format:", e.args[0])
+ return HttpResponseBadRequest(e.args[0])
+ except VerificationError:
+ print("Bad LD signature")
+ return HttpResponseUnauthorized("Bad signature")
+ # Otherwise, verify against the header (assuming it's the same actor)
+ else:
+ try:
+ HttpSignature.verify_request(
+ request,
+ identity.public_key,
+ )
+ except VerificationFormatError as e:
+ print("Bad HTTP signature format:", e.args[0])
+ return HttpResponseBadRequest(e.args[0])
+ except VerificationError:
+ print("Bad HTTP signature")
+ return HttpResponseUnauthorized("Bad signature")
+ # Hand off the item to be processed by the queue
+ InboxMessage.objects.create(message=document)
+ return HttpResponse(status=202)
diff --git a/users/views/identity.py b/users/views/identity.py
index d4e1155..5d11d63 100644
--- a/users/views/identity.py
+++ b/users/views/identity.py
@@ -1,33 +1,19 @@
-import json
import string
-from asgiref.sync import async_to_sync
from django import forms
from django.conf import settings
from django.contrib.auth.decorators import login_required
-from django.http import Http404, HttpResponse, HttpResponseBadRequest, JsonResponse
+from django.http import Http404
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
-from django.views.decorators.csrf import csrf_exempt
from django.views.generic import FormView, TemplateView, View
from core.forms import FormHelper
-from core.ld import canonicalise
-from core.signatures import (
- HttpSignature,
- LDSignature,
- VerificationError,
- VerificationFormatError,
-)
from users.decorators import identity_required
-from users.models import Domain, Follow, Identity, IdentityStates, InboxMessage
+from users.models import Domain, Follow, Identity, IdentityStates
from users.shortcuts import by_handle_or_404
-class HttpResponseUnauthorized(HttpResponse):
- status_code = 401
-
-
class ViewIdentity(TemplateView):
template_name = "identity/view.html"
@@ -151,114 +137,3 @@ class CreateIdentity(FormView):
new_identity.users.add(self.request.user)
new_identity.generate_keypair()
return redirect(new_identity.urls.view)
-
-
-class Actor(View):
- """
- Returns the AP Actor object
- """
-
- def get(self, request, handle):
- identity = by_handle_or_404(self.request, handle)
- response = {
- "@context": [
- "https://www.w3.org/ns/activitystreams",
- "https://w3id.org/security/v1",
- ],
- "id": identity.actor_uri,
- "type": "Person",
- "inbox": identity.actor_uri + "inbox/",
- "preferredUsername": identity.username,
- "publicKey": {
- "id": identity.public_key_id,
- "owner": identity.actor_uri,
- "publicKeyPem": identity.public_key,
- },
- "published": identity.created.strftime("%Y-%m-%dT%H:%M:%SZ"),
- "url": identity.urls.view_short.full(),
- }
- if identity.name:
- response["name"] = identity.name
- if identity.summary:
- response["summary"] = identity.summary
- return JsonResponse(canonicalise(response, include_security=True))
-
-
-@method_decorator(csrf_exempt, name="dispatch")
-class Inbox(View):
- """
- AP Inbox endpoint
- """
-
- def post(self, request, handle):
- # Load the LD
- document = canonicalise(json.loads(request.body), include_security=True)
- # Find the Identity by the actor on the incoming item
- # This ensures that the signature used for the headers matches the actor
- # described in the payload.
- identity = Identity.by_actor_uri(document["actor"], create=True)
- if not identity.public_key:
- # See if we can fetch it right now
- async_to_sync(identity.fetch_actor)()
- if not identity.public_key:
- print("Cannot get actor")
- return HttpResponseBadRequest("Cannot retrieve actor")
- # If there's a "signature" payload, verify against that
- if "signature" in document:
- try:
- LDSignature.verify_signature(document, identity.public_key)
- except VerificationFormatError as e:
- print("Bad LD signature format:", e.args[0])
- return HttpResponseBadRequest(e.args[0])
- except VerificationError:
- print("Bad LD signature")
- return HttpResponseUnauthorized("Bad signature")
- # Otherwise, verify against the header (assuming it's the same actor)
- else:
- try:
- HttpSignature.verify_request(
- request,
- identity.public_key,
- )
- except VerificationFormatError as e:
- print("Bad HTTP signature format:", e.args[0])
- return HttpResponseBadRequest(e.args[0])
- except VerificationError:
- print("Bad HTTP signature")
- return HttpResponseUnauthorized("Bad signature")
- # Hand off the item to be processed by the queue
- InboxMessage.objects.create(message=document)
- return HttpResponse(status=202)
-
-
-class Webfinger(View):
- """
- Services webfinger requests
- """
-
- def get(self, request):
- resource = request.GET.get("resource")
- if not resource.startswith("acct:"):
- raise Http404("Not an account resource")
- handle = resource[5:].replace("testfedi", "feditest")
- identity = by_handle_or_404(request, handle)
- return JsonResponse(
- {
- "subject": f"acct:{identity.handle}",
- "aliases": [
- identity.urls.view_short.full(),
- ],
- "links": [
- {
- "rel": "http://webfinger.net/rel/profile-page",
- "type": "text/html",
- "href": identity.urls.view_short.full(),
- },
- {
- "rel": "self",
- "type": "application/activity+json",
- "href": identity.actor_uri,
- },
- ],
- }
- )