Snapshot: alpha-2026-9

This commit is contained in:
j3d1 2026-09-02 21:40:28 +02:00
parent 9acf5a97e2
commit d00b5c7961
241 changed files with 85546 additions and 2409 deletions

View file

@ -2,7 +2,7 @@ from nacl.exceptions import BadSignatureError
from nacl.signing import VerifyKey
from rest_framework import authentication
from authentication.models import KnownIdentity, ToolshedUser
from authentication.models import Group, KnownIdentity, ToolshedUser
def split_userhandle_or_throw(userhandle):
@ -16,6 +16,27 @@ def split_userhandle_or_throw(userhandle):
return username, domain
def split_grouphandle_or_throw(grouphandle):
if not grouphandle.startswith('#'):
raise ValueError('Group handle must be in the format #name@domain')
return split_userhandle_or_throw(grouphandle[1:])
def resolve_owner_handle(handle):
"""Resolves a "user@domain" or "+name@domain" handle to (owner_user, owner_group), exactly one set. Raises ValueError if the handle doesn't parse at all (no '@'); returns (None, None) if it parses but names nothing that exists."""
is_group = handle.startswith('+')
name, domain = split_userhandle_or_throw(handle[1:] if is_group else handle)
if is_group:
try:
return None, Group.objects.get(name=name, domain=domain)
except Group.DoesNotExist:
return None, None
try:
return ToolshedUser.objects.get(username=name, domain=domain), None
except ToolshedUser.DoesNotExist:
return None, None
def verify_request(request, raw_request_body):
authentication_header = request.META.get('HTTP_AUTHORIZATION')
@ -74,6 +95,32 @@ def verify_incoming_friend_request(request, raw_request_body):
return False
def verify_incoming_group_invite(request, raw_request_body, handle_field, key_field):
"""Self-certifying verifier for the group invite/accept dance. See
docs/implementation.md#group-invite-and-accept-self-certifying-verification."""
try:
username, domain, signed_data, signature_bytes_hex = verify_request(request, raw_request_body)
except ValueError:
return False
try:
claimed_handle = request.data[handle_field]
claimed_key = request.data[key_field]
except KeyError:
return False
if not claimed_handle or not claimed_key:
return False
if username + "@" + domain != claimed_handle:
return False
if len(claimed_key) != 64:
return False
verify_key = VerifyKey(bytes.fromhex(claimed_key))
try:
verify_key.verify(signed_data.encode('utf-8'), bytes.fromhex(signature_bytes_hex))
return True
except BadSignatureError:
return False
def authenticate_request_against_known_identities(request, raw_request_body):
try:
username, domain, signed_data, signature_bytes_hex = verify_request(request, raw_request_body)
@ -106,11 +153,17 @@ def authenticate_request_against_local_users(request, raw_request_body):
class SignatureAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
return authenticate_request_against_known_identities(
request, request.body.decode('utf-8')), None
identity = authenticate_request_against_known_identities(request, request.body.decode('utf-8'))
# Bare None (not a (None, None) tuple) tells DRF to try the next authenticator, instead
# of treating the request as authenticated with an empty user.
if identity is None:
return None
return identity, None
class SignatureAuthenticationLocal(authentication.BaseAuthentication):
def authenticate(self, request):
return authenticate_request_against_local_users(
request, request.body.decode('utf-8')), None
user = authenticate_request_against_local_users(request, request.body.decode('utf-8'))
if user is None:
return None
return user, None