THE FOLLOWS, THEY WORK
Well, in one direction anyway
This commit is contained in:
		
							parent
							
								
									fb6c409a9a
								
							
						
					
					
						commit
						c391e7bc41
					
				
							
								
								
									
										13
									
								
								core/middleware.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								core/middleware.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
class AlwaysSecureMiddleware:
 | 
			
		||||
    """
 | 
			
		||||
    Locks the request object as always being secure, for when it's behind
 | 
			
		||||
    a HTTPS reverse proxy.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, get_response):
 | 
			
		||||
        self.get_response = get_response
 | 
			
		||||
 | 
			
		||||
    def __call__(self, request):
 | 
			
		||||
        request.__class__.scheme = "https"
 | 
			
		||||
        response = self.get_response(request)
 | 
			
		||||
        return response
 | 
			
		||||
@ -96,7 +96,7 @@ class HttpSignature:
 | 
			
		||||
        )
 | 
			
		||||
        headers["Signature"] = self.compile_signature(
 | 
			
		||||
            {
 | 
			
		||||
                "keyid": identity.urls.key.full(),  # type:ignore
 | 
			
		||||
                "keyid": identity.key_id,
 | 
			
		||||
                "headers": list(headers.keys()),
 | 
			
		||||
                "signature": identity.sign(signed_string),
 | 
			
		||||
                "algorithm": "rsa-sha256",
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ INSTALLED_APPS = [
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
    "core.middleware.AlwaysSecureMiddleware",
 | 
			
		||||
    "django.middleware.security.SecurityMiddleware",
 | 
			
		||||
    "django.contrib.sessions.middleware.SessionMiddleware",
 | 
			
		||||
    "django.middleware.common.CommonMiddleware",
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
 | 
			
		||||
from users.models import Domain, Identity, User, UserEvent
 | 
			
		||||
from users.models import Domain, Follow, Identity, User, UserEvent
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Domain)
 | 
			
		||||
@ -20,5 +20,9 @@ class UserEventAdmin(admin.ModelAdmin):
 | 
			
		||||
 | 
			
		||||
@admin.register(Identity)
 | 
			
		||||
class IdentityAdmin(admin.ModelAdmin):
 | 
			
		||||
 | 
			
		||||
    list_display = ["id", "handle", "actor_uri", "name", "local"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Follow)
 | 
			
		||||
class FollowAdmin(admin.ModelAdmin):
 | 
			
		||||
    list_display = ["id", "source", "target", "requested", "accepted"]
 | 
			
		||||
 | 
			
		||||
@ -82,11 +82,7 @@ class Identity(models.Model):
 | 
			
		||||
        view = "/@{self.username}@{self.domain_id}/"
 | 
			
		||||
        view_short = "/@{self.username}/"
 | 
			
		||||
        action = "{view}action/"
 | 
			
		||||
        actor = "{view}actor/"
 | 
			
		||||
        activate = "{view}activate/"
 | 
			
		||||
        key = "{actor}#main-key"
 | 
			
		||||
        inbox = "{actor}inbox/"
 | 
			
		||||
        outbox = "{actor}outbox/"
 | 
			
		||||
 | 
			
		||||
        def get_scheme(self, url):
 | 
			
		||||
            return "https"
 | 
			
		||||
@ -102,12 +98,9 @@ class Identity(models.Model):
 | 
			
		||||
    ### Alternate constructors/fetchers ###
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def by_handle(cls, handle, fetch=False, local=False):
 | 
			
		||||
        if handle.startswith("@"):
 | 
			
		||||
            raise ValueError("Handle must not start with @")
 | 
			
		||||
        if "@" not in handle:
 | 
			
		||||
            raise ValueError("Handle must contain domain")
 | 
			
		||||
        username, domain = handle.split("@")
 | 
			
		||||
    def by_username_and_domain(cls, username, domain, fetch=False, local=False):
 | 
			
		||||
        if username.startswith("@"):
 | 
			
		||||
            raise ValueError("Username must not start with @")
 | 
			
		||||
        try:
 | 
			
		||||
            if local:
 | 
			
		||||
                return cls.objects.get(username=username, domain_id=domain, local=True)
 | 
			
		||||
@ -115,7 +108,9 @@ class Identity(models.Model):
 | 
			
		||||
                return cls.objects.get(username=username, domain_id=domain)
 | 
			
		||||
        except cls.DoesNotExist:
 | 
			
		||||
            if fetch and not local:
 | 
			
		||||
                actor_uri, handle = async_to_sync(cls.fetch_webfinger)(handle)
 | 
			
		||||
                actor_uri, handle = async_to_sync(cls.fetch_webfinger)(
 | 
			
		||||
                    f"{username}@{domain}"
 | 
			
		||||
                )
 | 
			
		||||
                username, domain = handle.split("@")
 | 
			
		||||
                domain = Domain.get_remote_domain(domain)
 | 
			
		||||
                return cls.objects.create(
 | 
			
		||||
@ -168,6 +163,10 @@ class Identity(models.Model):
 | 
			
		||||
        # TODO: Setting
 | 
			
		||||
        return self.data_age > 60 * 24 * 24
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def key_id(self):
 | 
			
		||||
        return self.actor_uri + "#main-key"
 | 
			
		||||
 | 
			
		||||
    ### Actor/Webfinger fetching ###
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,14 @@ def by_handle_or_404(request, handle, local=True, fetch=False):
 | 
			
		||||
        domain = domain_instance.domain
 | 
			
		||||
    else:
 | 
			
		||||
        username, domain = handle.split("@", 1)
 | 
			
		||||
    identity = Identity.by_handle(handle, local=local, fetch=fetch)
 | 
			
		||||
        # Resolve the domain to the display domain
 | 
			
		||||
        domain = Domain.get_local_domain(request.META["HTTP_HOST"]).domain
 | 
			
		||||
    identity = Identity.by_username_and_domain(
 | 
			
		||||
        username,
 | 
			
		||||
        domain,
 | 
			
		||||
        local=local,
 | 
			
		||||
        fetch=fetch,
 | 
			
		||||
    )
 | 
			
		||||
    if identity is None:
 | 
			
		||||
        raise Http404(f"No identity for handle {handle}")
 | 
			
		||||
    return identity
 | 
			
		||||
 | 
			
		||||
@ -24,5 +24,6 @@ async def handle_follow_request(task_handler):
 | 
			
		||||
    response = await HttpSignature.signed_request(
 | 
			
		||||
        follow.target.inbox_uri, request, follow.source
 | 
			
		||||
    )
 | 
			
		||||
    print(response)
 | 
			
		||||
    print(response.content)
 | 
			
		||||
    if response.status_code >= 400:
 | 
			
		||||
        raise ValueError(f"Request error: {response.status_code} {response.content}")
 | 
			
		||||
    await Follow.objects.filter(pk=follow.pk).aupdate(requested=True)
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
from asgiref.sync import sync_to_async
 | 
			
		||||
 | 
			
		||||
from users.models import Follow, Identity
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,14 +7,20 @@ async def handle_inbox_item(task_handler):
 | 
			
		||||
    type = task_handler.payload["type"].lower()
 | 
			
		||||
    if type == "follow":
 | 
			
		||||
        await inbox_follow(task_handler.payload)
 | 
			
		||||
    elif type == "accept":
 | 
			
		||||
        inner_type = task_handler.payload["object"]["type"].lower()
 | 
			
		||||
        if inner_type == "follow":
 | 
			
		||||
            await sync_to_async(accept_follow)(task_handler.payload["object"])
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError(f"Cannot handle activity of type accept.{inner_type}")
 | 
			
		||||
    elif type == "undo":
 | 
			
		||||
        inner_type = task_handler.payload["object"]["type"].lower()
 | 
			
		||||
        if inner_type == "follow":
 | 
			
		||||
            await inbox_unfollow(task_handler.payload["object"])
 | 
			
		||||
        else:
 | 
			
		||||
            raise ValueError("Cannot undo activity of type {inner_type}")
 | 
			
		||||
            raise ValueError(f"Cannot handle activity of type undo.{inner_type}")
 | 
			
		||||
    else:
 | 
			
		||||
        raise ValueError("Cannot handle activity of type {inner_type}")
 | 
			
		||||
        raise ValueError(f"Cannot handle activity of type {inner_type}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def inbox_follow(payload):
 | 
			
		||||
@ -34,3 +42,15 @@ async def inbox_follow(payload):
 | 
			
		||||
 | 
			
		||||
async def inbox_unfollow(payload):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def accept_follow(payload):
 | 
			
		||||
    """
 | 
			
		||||
    Another server has acknowledged our follow request
 | 
			
		||||
    """
 | 
			
		||||
    source = Identity.by_actor_uri_with_create(payload["actor"])
 | 
			
		||||
    target = Identity.by_actor_uri(payload["object"])
 | 
			
		||||
    follow = Follow.maybe_get(source, target)
 | 
			
		||||
    if follow:
 | 
			
		||||
        follow.accepted = True
 | 
			
		||||
        follow.save()
 | 
			
		||||
 | 
			
		||||
@ -130,8 +130,9 @@ class CreateIdentity(FormView):
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        username = form.cleaned_data["username"]
 | 
			
		||||
        domain = form.cleaned_data["domain"]
 | 
			
		||||
        domain_instance = Domain.get_local_domain(domain)
 | 
			
		||||
        new_identity = Identity.objects.create(
 | 
			
		||||
            actor_uri=f"https://{domain}/@{username}/actor/",
 | 
			
		||||
            actor_uri=f"https://{domain_instance.uri_domain}/@{username}@{domain}/actor/",
 | 
			
		||||
            username=username,
 | 
			
		||||
            domain_id=domain,
 | 
			
		||||
            name=form.cleaned_data["name"],
 | 
			
		||||
@ -154,13 +155,13 @@ class Actor(View):
 | 
			
		||||
                "https://www.w3.org/ns/activitystreams",
 | 
			
		||||
                "https://w3id.org/security/v1",
 | 
			
		||||
            ],
 | 
			
		||||
            "id": identity.urls.actor.full(),
 | 
			
		||||
            "id": identity.actor_uri,
 | 
			
		||||
            "type": "Person",
 | 
			
		||||
            "inbox": identity.urls.inbox.full(),
 | 
			
		||||
            "inbox": identity.actor_uri + "inbox/",
 | 
			
		||||
            "preferredUsername": identity.username,
 | 
			
		||||
            "publicKey": {
 | 
			
		||||
                "id": identity.urls.key.full(),
 | 
			
		||||
                "owner": identity.urls.actor.full(),
 | 
			
		||||
                "id": identity.key_id,
 | 
			
		||||
                "owner": identity.actor_uri,
 | 
			
		||||
                "publicKeyPem": identity.public_key,
 | 
			
		||||
            },
 | 
			
		||||
            "published": identity.created.strftime("%Y-%m-%dT%H:%M:%SZ"),
 | 
			
		||||
@ -249,7 +250,7 @@ class Webfinger(View):
 | 
			
		||||
                    {
 | 
			
		||||
                        "rel": "self",
 | 
			
		||||
                        "type": "application/activity+json",
 | 
			
		||||
                        "href": identity.urls.actor.full(),
 | 
			
		||||
                        "href": identity.actor_uri,
 | 
			
		||||
                    },
 | 
			
		||||
                ],
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user