From dbe57075d386d7474bafc208b654507d9a2d769e Mon Sep 17 00:00:00 2001
From: Andrew Godwin
Date: Sun, 6 Nov 2022 13:48:04 -0700
Subject: Rework to a domains model for better vhosting
---
core/context.py | 4 +-
core/ld.py | 11 ++-
core/signatures.py | 48 +++++++++++
miniq/migrations/0001_initial.py | 9 ++-
miniq/views.py | 7 +-
static/css/style.css | 3 +-
statuses/migrations/0001_initial.py | 2 +-
statuses/models/status.py | 2 +-
takahe/settings.py | 3 +-
templates/base.html | 8 +-
templates/identity/select.html | 2 +-
templates/identity/view.html | 2 +-
templates/statuses/_status.html | 2 +-
users/admin.py | 7 +-
users/decorators.py | 17 +---
users/middleware.py | 24 ++++++
users/migrations/0001_initial.py | 93 ++++++++++++++++++++--
users/models/__init__.py | 2 +
users/models/block.py | 30 +++++++
users/models/domain.py | 83 +++++++++++++++++++
users/models/follow.py | 2 +-
users/models/identity.py | 144 +++++++++++++++++++++------------
users/shortcuts.py | 27 +++++--
users/views/identity.py | 153 ++++++++++++++++++++----------------
24 files changed, 517 insertions(+), 168 deletions(-)
create mode 100644 core/signatures.py
create mode 100644 users/middleware.py
create mode 100644 users/models/block.py
create mode 100644 users/models/domain.py
diff --git a/core/context.py b/core/context.py
index 38a268c..026ac11 100644
--- a/core/context.py
+++ b/core/context.py
@@ -2,4 +2,6 @@ from django.conf import settings
def config_context(request):
- return {"config": {"site_name": settings.SITE_NAME}}
+ return {
+ "config": {"site_name": settings.SITE_NAME},
+ }
diff --git a/core/ld.py b/core/ld.py
index 38e436a..28ff65a 100644
--- a/core/ld.py
+++ b/core/ld.py
@@ -252,7 +252,7 @@ def builtin_document_loader(url: str, options={}):
)
-def canonicalise(json_data):
+def canonicalise(json_data, include_security=False):
"""
Given an ActivityPub JSON-LD document, round-trips it through the LD
systems to end up in a canonicalised, compacted format.
@@ -264,5 +264,12 @@ def canonicalise(json_data):
raise ValueError("Pass decoded JSON data into LDDocument")
return jsonld.compact(
jsonld.expand(json_data),
- ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
+ (
+ [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ ]
+ if include_security
+ else "https://www.w3.org/ns/activitystreams"
+ ),
)
diff --git a/core/signatures.py b/core/signatures.py
new file mode 100644
index 0000000..bcacb68
--- /dev/null
+++ b/core/signatures.py
@@ -0,0 +1,48 @@
+import base64
+from typing import Any, Dict, List
+
+from cryptography.hazmat.primitives import hashes
+from django.http import HttpRequest
+
+
+class HttpSignature:
+ """
+ Allows for calculation and verification of HTTP signatures
+ """
+
+ @classmethod
+ def calculate_digest(cls, data, algorithm="sha-256") -> str:
+ """
+ Calculates the digest header value for a given HTTP body
+ """
+ if algorithm == "sha-256":
+ digest = hashes.Hash(hashes.SHA256())
+ digest.update(data)
+ return "SHA-256=" + base64.b64encode(digest.finalize()).decode("ascii")
+ else:
+ raise ValueError(f"Unknown digest algorithm {algorithm}")
+
+ @classmethod
+ def headers_from_request(cls, request: HttpRequest, header_names: List[str]) -> str:
+ """
+ Creates the to-be-signed header payload from a Django request"""
+ headers = {}
+ for header_name in header_names:
+ if header_name == "(request-target)":
+ value = f"post {request.path}"
+ elif header_name == "content-type":
+ value = request.META["CONTENT_TYPE"]
+ else:
+ value = request.META[f"HTTP_{header_name.upper()}"]
+ headers[header_name] = value
+ return "\n".join(f"{name.lower()}: {value}" for name, value in headers.items())
+
+ @classmethod
+ def parse_signature(cls, signature) -> Dict[str, Any]:
+ signature_details = {}
+ for item in signature.split(","):
+ name, value = item.split("=", 1)
+ value = value.strip('"')
+ signature_details[name.lower()] = value
+ signature_details["headers"] = signature_details["headers"].split()
+ return signature_details
diff --git a/miniq/migrations/0001_initial.py b/miniq/migrations/0001_initial.py
index 6775ff3..32c5d53 100644
--- a/miniq/migrations/0001_initial.py
+++ b/miniq/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.1.3 on 2022-11-06 03:59
+# Generated by Django 4.1.3 on 2022-11-06 19:58
from django.db import migrations, models
@@ -22,7 +22,12 @@ class Migration(migrations.Migration):
verbose_name="ID",
),
),
- ("type", models.CharField(max_length=500)),
+ (
+ "type",
+ models.CharField(
+ choices=[("identity_fetch", "Identity Fetch")], max_length=500
+ ),
+ ),
("priority", models.IntegerField(default=0)),
("subject", models.TextField()),
("payload", models.JSONField(blank=True, null=True)),
diff --git a/miniq/views.py b/miniq/views.py
index 12da7cd..21275f8 100644
--- a/miniq/views.py
+++ b/miniq/views.py
@@ -64,5 +64,8 @@ class QueueProcessor(View):
await task.fail(f"{e}\n\n" + traceback.format_exc())
async def handle_identity_fetch(self, subject, payload):
- identity = await sync_to_async(Identity.by_handle)(subject)
- await identity.fetch_details()
+ # Get the actor URI via webfinger
+ actor_uri, handle = await Identity.fetch_webfinger(subject)
+ # Get or create the identity, then fetch
+ identity = await sync_to_async(Identity.by_actor_uri)(actor_uri, create=True)
+ await identity.fetch_actor()
diff --git a/static/css/style.css b/static/css/style.css
index 7a3b20a..511d301 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -229,7 +229,8 @@ form .help-block {
padding: 4px 0 0 0;
}
-form input {
+form input,
+form select {
width: 100%;
padding: 4px 6px;
background: var(--color-bg1);
diff --git a/statuses/migrations/0001_initial.py b/statuses/migrations/0001_initial.py
index 58a7d29..933c526 100644
--- a/statuses/migrations/0001_initial.py
+++ b/statuses/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.1.3 on 2022-11-05 23:50
+# Generated by Django 4.1.3 on 2022-11-06 19:58
import django.db.models.deletion
from django.db import migrations, models
diff --git a/statuses/models/status.py b/statuses/models/status.py
index ac40806..bfc8eb9 100644
--- a/statuses/models/status.py
+++ b/statuses/models/status.py
@@ -36,4 +36,4 @@ class Status(models.Model):
)
class urls(urlman.Urls):
- view = "{self.identity.urls.view}{self.id}/"
+ view = "{self.identity.urls.view}statuses/{self.id}/"
diff --git a/takahe/settings.py b/takahe/settings.py
index 26fd705..c3c8d38 100644
--- a/takahe/settings.py
+++ b/takahe/settings.py
@@ -10,7 +10,7 @@ SECRET_KEY = "insecure_secret"
DEBUG = True
ALLOWED_HOSTS = ["*"]
-
+CSRF_TRUSTED_ORIGINS = ["http://*", "https://*"]
# Application definition
@@ -36,6 +36,7 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
+ "users.middleware.IdentityMiddleware",
]
ROOT_URLCONF = "takahe.urls"
diff --git a/templates/base.html b/templates/base.html
index af2887f..2ff0f15 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -26,10 +26,12 @@
{% if user.is_authenticated %}
- {% if user.icon_uri %}
-
+ {% if not request.identity %}
+
+ {% elif request.identity.icon_uri %}
+
{% else %}
-
+
{% endif %}
{% else %}
diff --git a/templates/identity/select.html b/templates/identity/select.html
index dae1ca1..ea4065c 100644
--- a/templates/identity/select.html
+++ b/templates/identity/select.html
@@ -14,7 +14,7 @@
{% endif %}
{{ identity }}
- @{{ identity.short_handle }}
+ @{{ identity.handle }}
{% empty %}
You have no identities.
diff --git a/templates/identity/view.html b/templates/identity/view.html
index 2a82478..ffb76db 100644
--- a/templates/identity/view.html
+++ b/templates/identity/view.html
@@ -10,7 +10,7 @@
{% else %}
{% endif %}
- {{ identity }} {{ identity.handle }}
+ {{ identity }} @{{ identity.handle }}
{% if not identity.local %}
diff --git a/templates/statuses/_status.html b/templates/statuses/_status.html
index b89909a..b501abc 100644
--- a/templates/statuses/_status.html
+++ b/templates/statuses/_status.html
@@ -2,7 +2,7 @@