summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/ld.py59
-rw-r--r--core/signatures.py67
2 files changed, 117 insertions, 9 deletions
diff --git a/core/ld.py b/core/ld.py
index 28ff65a..2211ba9 100644
--- a/core/ld.py
+++ b/core/ld.py
@@ -227,6 +227,49 @@ schemas = {
}
},
},
+ "*/schemas/litepub-0.1.jsonld": {
+ "contentType": "application/ld+json",
+ "documentUrl": "http://w3id.org/security/v1",
+ "contextUrl": None,
+ "document": {
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "Emoji": "toot:Emoji",
+ "Hashtag": "as:Hashtag",
+ "PropertyValue": "schema:PropertyValue",
+ "atomUri": "ostatus:atomUri",
+ "conversation": {"@id": "ostatus:conversation", "@type": "@id"},
+ "discoverable": "toot:discoverable",
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "capabilities": "litepub:capabilities",
+ "ostatus": "http://ostatus.org#",
+ "schema": "http://schema.org#",
+ "toot": "http://joinmastodon.org/ns#",
+ "misskey": "https://misskey-hub.net/ns#",
+ "fedibird": "http://fedibird.com/ns#",
+ "value": "schema:value",
+ "sensitive": "as:sensitive",
+ "litepub": "http://litepub.social/ns#",
+ "invisible": "litepub:invisible",
+ "directMessage": "litepub:directMessage",
+ "listMessage": {"@id": "litepub:listMessage", "@type": "@id"},
+ "quoteUrl": "as:quoteUrl",
+ "quoteUri": "fedibird:quoteUri",
+ "oauthRegistrationEndpoint": {
+ "@id": "litepub:oauthRegistrationEndpoint",
+ "@type": "@id",
+ },
+ "EmojiReact": "litepub:EmojiReact",
+ "ChatMessage": "litepub:ChatMessage",
+ "alsoKnownAs": {"@id": "as:alsoKnownAs", "@type": "@id"},
+ "vcard": "http://www.w3.org/2006/vcard/ns#",
+ "formerRepresentations": "litepub:formerRepresentations",
+ },
+ ]
+ },
+ },
}
@@ -244,12 +287,16 @@ def builtin_document_loader(url: str, options={}):
try:
return schemas[key]
except KeyError:
- raise JsonLdError(
- f"No schema built-in for {key!r}",
- "jsonld.LoadDocumentError",
- code="loading document failed",
- cause="KeyError",
- )
+ try:
+ key = "*" + pieces.path.rstrip("/")
+ return schemas[key]
+ except KeyError:
+ raise JsonLdError(
+ f"No schema built-in for {key!r}",
+ "jsonld.LoadDocumentError",
+ code="loading document failed",
+ cause="KeyError",
+ )
def canonicalise(json_data, include_security=False):
diff --git a/core/signatures.py b/core/signatures.py
index a5e4fed..6f4d9ef 100644
--- a/core/signatures.py
+++ b/core/signatures.py
@@ -1,8 +1,14 @@
import base64
-from typing import List, TypedDict
+import json
+from typing import Dict, List, Literal, TypedDict
+from urllib.parse import urlparse
+import httpx
from cryptography.hazmat.primitives import hashes
from django.http import HttpRequest
+from django.utils.http import http_date
+
+from users.models import Identity
class HttpSignature:
@@ -25,7 +31,8 @@ class HttpSignature:
@classmethod
def headers_from_request(cls, request: HttpRequest, header_names: List[str]) -> str:
"""
- Creates the to-be-signed header payload from a Django request"""
+ Creates the to-be-signed header payload from a Django request
+ """
headers = {}
for header_name in header_names:
if header_name == "(request-target)":
@@ -38,7 +45,7 @@ class HttpSignature:
return "\n".join(f"{name.lower()}: {value}" for name, value in headers.items())
@classmethod
- def parse_signature(cls, signature) -> "SignatureDetails":
+ def parse_signature(cls, signature: str) -> "SignatureDetails":
bits = {}
for item in signature.split(","):
name, value = item.split("=", 1)
@@ -52,6 +59,60 @@ class HttpSignature:
}
return signature_details
+ @classmethod
+ def compile_signature(cls, details: "SignatureDetails") -> str:
+ value = f'keyId="{details["keyid"]}",headers="'
+ value += " ".join(h.lower() for h in details["headers"])
+ value += '",signature="'
+ value += base64.b64encode(details["signature"]).decode("ascii")
+ value += f'",algorithm="{details["algorithm"]}"'
+ return value
+
+ @classmethod
+ async def signed_request(
+ self,
+ uri: str,
+ body: Dict,
+ identity: Identity,
+ content_type: str = "application/json",
+ method: Literal["post"] = "post",
+ ):
+ """
+ Performs an async request to the given path, with a document, signed
+ as an identity.
+ """
+ uri_parts = urlparse(uri)
+ date_string = http_date()
+ body_bytes = json.dumps(body).encode("utf8")
+ headers = {
+ "(request-target)": f"{method} {uri_parts.path}",
+ "Host": uri_parts.hostname,
+ "Date": date_string,
+ "Digest": self.calculate_digest(body_bytes),
+ "Content-Type": content_type,
+ }
+ signed_string = "\n".join(
+ f"{name.lower()}: {value}" for name, value in headers.items()
+ )
+ headers["Signature"] = self.compile_signature(
+ {
+ "keyid": identity.urls.key.full(), # type:ignore
+ "headers": list(headers.keys()),
+ "signature": identity.sign(signed_string),
+ "algorithm": "rsa-sha256",
+ }
+ )
+ del headers["(request-target)"]
+ async with httpx.AsyncClient() as client:
+ print(f"Calling {method} {uri}")
+ print(body)
+ return await client.request(
+ method,
+ uri,
+ headers=headers,
+ content=body_bytes,
+ )
+
class SignatureDetails(TypedDict):
algorithm: str