From dbe57075d386d7474bafc208b654507d9a2d769e Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Sun, 6 Nov 2022 13:48:04 -0700 Subject: [PATCH] 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 | 29 ++++-- users/views/identity.py | 153 +++++++++++++++------------- 24 files changed, 518 insertions(+), 169 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 @@

    {{ status.identity }} - {{ status.identity.short_handle }} + {{ status.identity.handle }}