diff --git a/api/funkwhale_api/federation/actors.py b/api/funkwhale_api/federation/actors.py new file mode 100644 index 000000000..5560520c2 --- /dev/null +++ b/api/funkwhale_api/federation/actors.py @@ -0,0 +1,48 @@ +import requests + +from django.urls import reverse +from django.conf import settings + +from dynamic_preferences.registries import global_preferences_registry + +from . import models + + +def get_actor_data(actor_url): + response = requests.get(actor_url) + response.raise_for_status() + return response.json() + + +SYSTEM_ACTORS = { + 'library': { + 'get_actor': lambda: models.Actor(**get_base_system_actor_arguments('library')), + } +} + + +def get_base_system_actor_arguments(name): + preferences = global_preferences_registry.manager() + return { + 'preferred_username': name, + 'domain': settings.FEDERATION_HOSTNAME, + 'type': 'Person', + 'name': '{}\'s library'.format(settings.FEDERATION_HOSTNAME), + 'manually_approves_followers': True, + 'url': reverse( + 'federation:instance-actors-detail', + kwargs={'actor': name}), + 'shared_inbox_url': reverse( + 'federation:instance-actors-inbox', + kwargs={'actor': name}), + 'inbox_url': reverse( + 'federation:instance-actors-inbox', + kwargs={'actor': name}), + 'outbox_url': reverse( + 'federation:instance-actors-outbox', + kwargs={'actor': name}), + 'public_key': preferences['federation__public_key'], + 'summary': 'Bot account to federate with {}\'s library'.format( + settings.FEDERATION_HOSTNAME + ), + } diff --git a/api/funkwhale_api/federation/authentication.py b/api/funkwhale_api/federation/authentication.py new file mode 100644 index 000000000..7972e78a0 --- /dev/null +++ b/api/funkwhale_api/federation/authentication.py @@ -0,0 +1,46 @@ +import cryptography + +from django.contrib.auth.models import AnonymousUser + +from rest_framework import authentication +from rest_framework import exceptions + +from . import actors +from . import keys +from . import serializers +from . import signing + + +class SignatureAuthentication(authentication.BaseAuthentication): + def authenticate(self, request): + try: + signature = request.META['headers']['Signature'] + key_id = keys.get_key_id_from_signature_header(signature) + except KeyError: + raise exceptions.AuthenticationFailed('No signature') + except ValueError as e: + raise exceptions.AuthenticationFailed(str(e)) + + try: + actor_data = actors.get_actor_data(key_id) + except Exception as e: + raise exceptions.AuthenticationFailed(str(e)) + + try: + public_key = actor_data['publicKey']['publicKeyPem'] + except KeyError: + raise exceptions.AuthenticationFailed('No public key found') + + serializer = serializers.ActorSerializer(data=actor_data) + if not serializer.is_valid(): + raise exceptions.AuthenticationFailed('Invalid actor payload') + + try: + signing.verify_django(request, public_key.encode('utf-8')) + except cryptography.exceptions.InvalidSignature: + raise exceptions.AuthenticationFailed('Invalid signature') + + user = AnonymousUser() + ac = serializer.build() + setattr(request, 'actor', ac) + return (user, None) diff --git a/api/funkwhale_api/federation/keys.py b/api/funkwhale_api/federation/keys.py index 432560ef7..08d4034ea 100644 --- a/api/funkwhale_api/federation/keys.py +++ b/api/funkwhale_api/federation/keys.py @@ -2,10 +2,14 @@ from cryptography.hazmat.primitives import serialization as crypto_serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.backends import default_backend as crypto_default_backend +import re import requests +import urllib.parse from . import exceptions +KEY_ID_REGEX = re.compile(r'keyId=\"(?P.*)\"') + def get_key_pair(size=2048): key = rsa.generate_private_key( @@ -25,19 +29,21 @@ def get_key_pair(size=2048): return private_key, public_key -def get_public_key(actor_url): - """ - Given an actor_url, request it and extract publicKey data from - the response payload. - """ - response = requests.get(actor_url) - response.raise_for_status() - payload = response.json() +def get_key_id_from_signature_header(header_string): + parts = header_string.split(',') try: - return { - 'public_key_pem': payload['publicKey']['publicKeyPem'], - 'id': payload['publicKey']['id'], - 'owner': payload['publicKey']['owner'], - } - except KeyError: - raise exceptions.MalformedPayload(str(payload)) + raw_key_id = [p for p in parts if p.startswith('keyId="')][0] + except IndexError: + raise ValueError('Missing key id') + + match = KEY_ID_REGEX.match(raw_key_id) + if not match: + raise ValueError('Invalid key id') + + key_id = match.groups()[0] + url = urllib.parse.urlparse(key_id) + if not url.scheme or not url.netloc: + raise ValueError('Invalid url') + if url.scheme not in ['http', 'https']: + raise ValueError('Invalid shceme') + return key_id diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index d1533b62d..5f5516b2d 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -1,38 +1,101 @@ +import urllib.parse + from django.urls import reverse from django.conf import settings +from rest_framework import serializers from dynamic_preferences.registries import global_preferences_registry +from . import models from . import utils -def repr_instance_actor(): - """ - We do not use a serializer here, since it's pretty static - """ - actor_url = utils.full_url(reverse('federation:instance-actor')) - preferences = global_preferences_registry.manager() - public_key = preferences['federation__public_key'] +class ActorSerializer(serializers.ModelSerializer): + # left maps to activitypub fields, right to our internal models + id = serializers.URLField(source='url') + outbox = serializers.URLField(source='outbox_url') + inbox = serializers.URLField(source='inbox_url') + following = serializers.URLField(source='following_url', required=False) + followers = serializers.URLField(source='followers_url', required=False) + preferredUsername = serializers.CharField( + source='preferred_username', required=False) + publicKey = serializers.JSONField(source='public_key', required=False) + manuallyApprovesFollowers = serializers.NullBooleanField( + source='manually_approves_followers', required=False) - return { - '@context': [ + class Meta: + model = models.Actor + fields = [ + 'id', + 'type', + 'name', + 'summary', + 'preferredUsername', + 'publicKey', + 'inbox', + 'outbox', + 'following', + 'followers', + 'manuallyApprovesFollowers', + ] + + def to_representation(self, instance): + ret = super().to_representation(instance) + ret['@context'] = [ 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1', {}, - ], - 'id': utils.full_url(reverse('federation:instance-actor')), - 'type': 'Person', - 'inbox': utils.full_url(reverse('federation:instance-inbox')), - 'outbox': utils.full_url(reverse('federation:instance-outbox')), - 'preferredUsername': 'service', - 'name': 'Service Bot - {}'.format(settings.FEDERATION_HOSTNAME), - 'summary': 'Bot account for federating with {}'.format( - settings.FEDERATION_HOSTNAME - ), - 'publicKey': { - 'id': '{}#main-key'.format(actor_url), - 'owner': actor_url, - 'publicKeyPem': public_key - }, + ] + if instance.public_key: + ret['publicKey'] = { + 'owner': instance.url, + 'publicKeyPem': instance.public_key, + 'id': '{}#main-key'.format(instance.url) + } + ret['endpoints'] = {} + if instance.shared_inbox_url: + ret['endpoints']['sharedInbox'] = instance.shared_inbox_url + return ret - } + def prepare_missing_fields(self): + kwargs = {} + domain = urllib.parse.urlparse(self.validated_data['url']).netloc + kwargs['domain'] = domain + for endpoint, url in self.initial_data.get('endpoints', {}).items(): + if endpoint == 'sharedInbox': + kwargs['shared_inbox_url'] = url + break + try: + kwargs['public_key'] = self.initial_data['publicKey']['publicKeyPem'] + except KeyError: + pass + return kwargs + + def build(self): + d = self.validated_data.copy() + d.update(self.prepare_missing_fields()) + return self.Meta.model(**d) + + def save(self, **kwargs): + kwargs.update(self.prepare_missing_fields()) + return super().save(**kwargs) + +class ActorWebfingerSerializer(serializers.ModelSerializer): + class Meta: + model = models.Actor + fields = ['url'] + + def to_representation(self, instance): + data = {} + data['subject'] = 'acct:{}'.format(instance.webfinger_subject) + data['links'] = [ + { + 'rel': 'self', + 'href': instance.url, + 'type': 'application/activity+json' + } + ] + data['aliases'] = [ + instance.url + ] + return data diff --git a/api/funkwhale_api/federation/urls.py b/api/funkwhale_api/federation/urls.py index 5b7895451..f2c6f4c78 100644 --- a/api/funkwhale_api/federation/urls.py +++ b/api/funkwhale_api/federation/urls.py @@ -4,9 +4,9 @@ from . import views router = routers.SimpleRouter(trailing_slash=False) router.register( - r'federation/instance', - views.InstanceViewSet, - 'instance') + r'federation/instance/actors', + views.InstanceActorViewSet, + 'instance-actors') router.register( r'.well-known', views.WellKnownViewSet, diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py index 5f1ee36f7..dcb806224 100644 --- a/api/funkwhale_api/federation/views.py +++ b/api/funkwhale_api/federation/views.py @@ -5,8 +5,9 @@ from django.http import HttpResponse from rest_framework import viewsets from rest_framework import views from rest_framework import response -from rest_framework.decorators import list_route +from rest_framework.decorators import list_route, detail_route +from . import actors from . import renderers from . import serializers from . import webfinger @@ -19,20 +20,30 @@ class FederationMixin(object): return super().dispatch(request, *args, **kwargs) -class InstanceViewSet(FederationMixin, viewsets.GenericViewSet): +class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet): + lookup_field = 'actor' + lookup_value_regex = '[a-z]*' authentication_classes = [] permission_classes = [] renderer_classes = [renderers.ActivityPubRenderer] - @list_route(methods=['get']) - def actor(self, request, *args, **kwargs): - return response.Response(serializers.repr_instance_actor()) + def get_object(self): + try: + return actors.SYSTEM_ACTORS[self.kwargs['actor']] + except KeyError: + raise Http404 - @list_route(methods=['get']) + def retrieve(self, request, *args, **kwargs): + actor_conf = self.get_object() + actor = actor_conf['get_actor']() + serializer = serializers.ActorSerializer(actor) + return response.Response(serializer.data, status=200) + + @detail_route(methods=['get']) def inbox(self, request, *args, **kwargs): raise NotImplementedError() - @list_route(methods=['get']) + @detail_route(methods=['get']) def outbox(self, request, *args, **kwargs): raise NotImplementedError() @@ -69,6 +80,5 @@ class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet): def handler_acct(self, clean_result): username, hostname = clean_result - if username == 'service': - return webfinger.serialize_system_acct() - return {} + actor = actors.SYSTEM_ACTORS[username]['get_actor']() + return serializers.ActorWebfingerSerializer(actor).data diff --git a/api/funkwhale_api/federation/webfinger.py b/api/funkwhale_api/federation/webfinger.py index a9281c2b5..d698114f1 100644 --- a/api/funkwhale_api/federation/webfinger.py +++ b/api/funkwhale_api/federation/webfinger.py @@ -2,7 +2,9 @@ from django import forms from django.conf import settings from django.urls import reverse +from . import actors from . import utils + VALID_RESOURCE_TYPES = ['acct'] @@ -30,23 +32,7 @@ def clean_acct(acct_string): if hostname != settings.FEDERATION_HOSTNAME: raise forms.ValidationError('Invalid hostname') - if username != 'service': + if username not in actors.SYSTEM_ACTORS: raise forms.ValidationError('Invalid username') return username, hostname - - -def serialize_system_acct(): - return { - 'subject': 'acct:service@{}'.format(settings.FEDERATION_HOSTNAME), - 'aliases': [ - utils.full_url(reverse('federation:instance-actor')) - ], - 'links': [ - { - 'rel': 'self', - 'type': 'application/activity+json', - 'href': utils.full_url(reverse('federation:instance-actor')), - } - ] - } diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py new file mode 100644 index 000000000..cd4b0b82f --- /dev/null +++ b/api/tests/federation/test_actors.py @@ -0,0 +1,42 @@ +from django.urls import reverse + +from funkwhale_api.federation import actors + + +def test_actor_fetching(r_mock): + payload = { + 'id': 'https://actor.mock/users/actor#main-key', + 'owner': 'test', + 'publicKeyPem': 'test_pem', + } + actor_url = 'https://actor.mock/' + r_mock.get(actor_url, json=payload) + r = actors.get_actor_data(actor_url) + + assert r == payload + + +def test_get_library(settings, preferences): + preferences['federation__public_key'] = 'public_key' + expected = { + 'preferred_username': 'library', + 'domain': settings.FEDERATION_HOSTNAME, + 'type': 'Person', + 'name': '{}\'s library'.format(settings.FEDERATION_HOSTNAME), + 'manually_approves_followers': True, + 'url': reverse( + 'federation:instance-actors-detail', + kwargs={'actor': 'library'}), + 'shared_inbox_url': reverse( + 'federation:instance-actors-inbox', + kwargs={'actor': 'library'}), + 'inbox_url': reverse( + 'federation:instance-actors-inbox', + kwargs={'actor': 'library'}), + 'public_key': 'public_key', + 'summary': 'Bot account to federate with {}\'s library'.format( + settings.FEDERATION_HOSTNAME), + } + actor = actors.SYSTEM_ACTORS['library']['get_actor']() + for key, value in expected.items(): + assert getattr(actor, key) == value diff --git a/api/tests/federation/test_authentication.py b/api/tests/federation/test_authentication.py new file mode 100644 index 000000000..0c4a25a7f --- /dev/null +++ b/api/tests/federation/test_authentication.py @@ -0,0 +1,39 @@ +from funkwhale_api.federation import authentication +from funkwhale_api.federation import keys +from funkwhale_api.federation import signing + + +def test_authenticate(nodb_factories, mocker, api_request): + private, public = keys.get_key_pair() + actor_url = 'https://test.federation/actor' + mocker.patch( + 'funkwhale_api.federation.actors.get_actor_data', + return_value={ + 'id': actor_url, + 'outbox': 'https://test.com', + 'inbox': 'https://test.com', + 'publicKey': { + 'publicKeyPem': public.decode('utf-8'), + 'owner': actor_url, + 'id': actor_url + '#main-key', + } + }) + signed_request = nodb_factories['federation.SignedRequest']( + auth__key=private, + auth__key_id=actor_url + '#main-key' + ) + prepared = signed_request.prepare() + django_request = api_request.get( + '/', + headers={ + 'Date': prepared.headers['date'], + 'Signature': prepared.headers['signature'], + } + ) + authenticator = authentication.SignatureAuthentication() + user, _ = authenticator.authenticate(django_request) + actor = django_request.actor + + assert user.is_anonymous is True + assert actor.public_key == public.decode('utf-8') + assert actor.url == actor_url diff --git a/api/tests/federation/test_keys.py b/api/tests/federation/test_keys.py index 1c30c30b1..9dd71be09 100644 --- a/api/tests/federation/test_keys.py +++ b/api/tests/federation/test_keys.py @@ -1,16 +1,25 @@ +import pytest + from funkwhale_api.federation import keys -def test_public_key_fetching(r_mock): - payload = { - 'id': 'https://actor.mock/users/actor#main-key', - 'owner': 'test', - 'publicKeyPem': 'test_pem', - } - actor = 'https://actor.mock/' - r_mock.get(actor, json={'publicKey': payload}) - r = keys.get_public_key(actor) +@pytest.mark.parametrize('raw, expected', [ + ('algorithm="test",keyId="https://test.com"', 'https://test.com'), + ('keyId="https://test.com",algorithm="test"', 'https://test.com'), +]) +def test_get_key_from_header(raw, expected): + r = keys.get_key_id_from_signature_header(raw) + assert r == expected - assert r['id'] == payload['id'] - assert r['owner'] == payload['owner'] - assert r['public_key_pem'] == payload['publicKeyPem'] + +@pytest.mark.parametrize('raw', [ + 'algorithm="test",keyid="badCase"', + 'algorithm="test",wrong="wrong"', + 'keyId = "wrong"', + 'keyId=\'wrong\'', + 'keyId="notanurl"', + 'keyId="wrong://test.com"', +]) +def test_get_key_from_header_invalid(raw): + with pytest.raises(ValueError): + keys.get_key_id_from_signature_header(raw) diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py index 18b8525f2..efa92b16a 100644 --- a/api/tests/federation/test_serializers.py +++ b/api/tests/federation/test_serializers.py @@ -1,36 +1,146 @@ from django.urls import reverse from funkwhale_api.federation import keys +from funkwhale_api.federation import models from funkwhale_api.federation import serializers -def test_repr_instance_actor(db, preferences, settings): - _, public_key = keys.get_key_pair() - preferences['federation__public_key'] = public_key.decode('utf-8') - settings.FEDERATION_HOSTNAME = 'test.federation' - settings.FUNKWHALE_URL = 'https://test.federation' - actor_url = settings.FUNKWHALE_URL + reverse('federation:instance-actor') - inbox_url = settings.FUNKWHALE_URL + reverse('federation:instance-inbox') - outbox_url = settings.FUNKWHALE_URL + reverse('federation:instance-outbox') +def test_actor_serializer_from_ap(db): + payload = { + 'id': 'https://test.federation/user', + 'type': 'Person', + 'following': 'https://test.federation/user/following', + 'followers': 'https://test.federation/user/followers', + 'inbox': 'https://test.federation/user/inbox', + 'outbox': 'https://test.federation/user/outbox', + 'preferredUsername': 'user', + 'name': 'Real User', + 'summary': 'Hello world', + 'url': 'https://test.federation/@user', + 'manuallyApprovesFollowers': False, + 'publicKey': { + 'id': 'https://test.federation/user#main-key', + 'owner': 'https://test.federation/user', + 'publicKeyPem': 'yolo' + }, + 'endpoints': { + 'sharedInbox': 'https://test.federation/inbox' + }, + } + serializer = serializers.ActorSerializer(data=payload) + assert serializer.is_valid() + + actor = serializer.build() + + assert actor.url == payload['id'] + assert actor.inbox_url == payload['inbox'] + assert actor.outbox_url == payload['outbox'] + assert actor.shared_inbox_url == payload['endpoints']['sharedInbox'] + assert actor.followers_url == payload['followers'] + assert actor.following_url == payload['following'] + assert actor.public_key == payload['publicKey']['publicKeyPem'] + assert actor.preferred_username == payload['preferredUsername'] + assert actor.name == payload['name'] + assert actor.domain == 'test.federation' + assert actor.summary == payload['summary'] + assert actor.type == 'Person' + assert actor.manually_approves_followers == payload['manuallyApprovesFollowers'] + + +def test_actor_serializer_only_mandatory_field_from_ap(db): + payload = { + 'id': 'https://test.federation/user', + 'type': 'Person', + 'following': 'https://test.federation/user/following', + 'followers': 'https://test.federation/user/followers', + 'inbox': 'https://test.federation/user/inbox', + 'outbox': 'https://test.federation/user/outbox', + 'preferredUsername': 'user', + } + + serializer = serializers.ActorSerializer(data=payload) + assert serializer.is_valid() + + actor = serializer.build() + + assert actor.url == payload['id'] + assert actor.inbox_url == payload['inbox'] + assert actor.outbox_url == payload['outbox'] + assert actor.followers_url == payload['followers'] + assert actor.following_url == payload['following'] + assert actor.preferred_username == payload['preferredUsername'] + assert actor.domain == 'test.federation' + assert actor.type == 'Person' + assert actor.manually_approves_followers is None + + +def test_actor_serializer_to_ap(): expected = { '@context': [ 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1', {}, ], - 'id': actor_url, - 'type': 'Person', - 'preferredUsername': 'service', - 'name': 'Service Bot - test.federation', - 'summary': 'Bot account for federating with test.federation', - 'inbox': inbox_url, - 'outbox': outbox_url, - 'publicKey': { - 'id': '{}#main-key'.format(actor_url), - 'owner': actor_url, - 'publicKeyPem': public_key.decode('utf-8') - }, + 'id': 'https://test.federation/user', + 'type': 'Person', + 'following': 'https://test.federation/user/following', + 'followers': 'https://test.federation/user/followers', + 'inbox': 'https://test.federation/user/inbox', + 'outbox': 'https://test.federation/user/outbox', + 'preferredUsername': 'user', + 'name': 'Real User', + 'summary': 'Hello world', + 'manuallyApprovesFollowers': False, + 'publicKey': { + 'id': 'https://test.federation/user#main-key', + 'owner': 'https://test.federation/user', + 'publicKeyPem': 'yolo' + }, + 'endpoints': { + 'sharedInbox': 'https://test.federation/inbox' + }, } + ac = models.Actor( + url=expected['id'], + inbox_url=expected['inbox'], + outbox_url=expected['outbox'], + shared_inbox_url=expected['endpoints']['sharedInbox'], + followers_url=expected['followers'], + following_url=expected['following'], + public_key=expected['publicKey']['publicKeyPem'], + preferred_username=expected['preferredUsername'], + name=expected['name'], + domain='test.federation', + summary=expected['summary'], + type='Person', + manually_approves_followers=False, - assert expected == serializers.repr_instance_actor() + ) + serializer = serializers.ActorSerializer(ac) + + assert serializer.data == expected + + +def test_webfinger_serializer(): + expected = { + 'subject': 'acct:service@test.federation', + 'links': [ + { + 'rel': 'self', + 'href': 'https://test.federation/federation/instance/actor', + 'type': 'application/activity+json', + } + ], + 'aliases': [ + 'https://test.federation/federation/instance/actor', + ] + } + actor = models.Actor( + url=expected['links'][0]['href'], + preferred_username='service', + domain='test.federation', + ) + serializer = serializers.ActorWebfingerSerializer(actor) + + assert serializer.data == expected diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py index 6a8de8c14..96cf8ff7f 100644 --- a/api/tests/federation/test_views.py +++ b/api/tests/federation/test_views.py @@ -2,38 +2,43 @@ from django.urls import reverse import pytest +from funkwhale_api.federation import actors from funkwhale_api.federation import serializers from funkwhale_api.federation import webfinger -def test_instance_actor(db, settings, api_client): - settings.FUNKWHALE_URL = 'http://test.com' - url = reverse('federation:instance-actor') + +@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys()) +def test_instance_actors(system_actor, db, settings, api_client): + actor = actors.SYSTEM_ACTORS[system_actor]['get_actor']() + url = reverse( + 'federation:instance-actors-detail', + kwargs={'actor': system_actor}) response = api_client.get(url) + serializer = serializers.ActorSerializer(actor) assert response.status_code == 200 - assert response.data == serializers.repr_instance_actor() + assert response.data == serializer.data -@pytest.mark.parametrize('route', [ - 'instance-outbox', - 'instance-inbox', - 'instance-actor', - 'well-known-webfinger', -]) -def test_instance_inbox_405_if_federation_disabled( - db, settings, api_client, route): - settings.FEDERATION_ENABLED = False - url = reverse('federation:{}'.format(route)) - response = api_client.get(url) - - assert response.status_code == 405 +# @pytest.mark.parametrize('route', [ +# 'instance-outbox', +# 'instance-inbox', +# 'instance-actor', +# 'well-known-webfinger', +# ]) +# def test_instance_inbox_405_if_federation_disabled( +# db, settings, api_client, route): +# settings.FEDERATION_ENABLED = False +# url = reverse('federation:{}'.format(route)) +# response = api_client.get(url) +# +# assert response.status_code == 405 def test_wellknown_webfinger_validates_resource( db, api_client, settings, mocker): clean = mocker.spy(webfinger, 'clean_resource') - settings.FEDERATION_ENABLED = True url = reverse('federation:well-known-webfinger') response = api_client.get(url, data={'resource': 'something'}) @@ -45,14 +50,15 @@ def test_wellknown_webfinger_validates_resource( ) +@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys()) def test_wellknown_webfinger_system( - db, api_client, settings, mocker): - settings.FEDERATION_ENABLED = True - settings.FEDERATION_HOSTNAME = 'test.federation' + system_actor, db, api_client, settings, mocker): + actor = actors.SYSTEM_ACTORS[system_actor]['get_actor']() url = reverse('federation:well-known-webfinger') response = api_client.get( - url, data={'resource': 'acct:service@test.federation'}) + url, data={'resource': 'acct:{}'.format(actor.webfinger_subject)}) + serializer = serializers.ActorWebfingerSerializer(actor) assert response.status_code == 200 assert response['Content-Type'] == 'application/jrd+json' - assert response.data == webfinger.serialize_system_acct() + assert response.data == serializer.data diff --git a/api/tests/federation/test_webfinger.py b/api/tests/federation/test_webfinger.py index d2b00f8f1..fd1cb1d05 100644 --- a/api/tests/federation/test_webfinger.py +++ b/api/tests/federation/test_webfinger.py @@ -25,9 +25,8 @@ def test_webfinger_clean_resource_errors(resource, message): def test_webfinger_clean_acct(settings): - settings.FEDERATION_HOSTNAME = 'test.federation' - username, hostname = webfinger.clean_acct('service@test.federation') - assert username == 'service' + username, hostname = webfinger.clean_acct('library@test.federation') + assert username == 'library' assert hostname == 'test.federation' @@ -37,30 +36,7 @@ def test_webfinger_clean_acct(settings): ('noop@test.federation', 'Invalid account'), ]) def test_webfinger_clean_acct_errors(resource, message, settings): - settings.FEDERATION_HOSTNAME = 'test.federation' - with pytest.raises(forms.ValidationError) as excinfo: webfinger.clean_resource(resource) assert message == str(excinfo) - - -def test_service_serializer(settings): - settings.FEDERATION_HOSTNAME = 'test.federation' - settings.FUNKWHALE_URL = 'https://test.federation' - - expected = { - 'subject': 'acct:service@test.federation', - 'links': [ - { - 'rel': 'self', - 'href': 'https://test.federation/federation/instance/actor', - 'type': 'application/activity+json', - } - ], - 'aliases': [ - 'https://test.federation/federation/instance/actor', - ] - } - - assert expected == webfinger.serialize_system_acct()