Skip to content
Snippets Groups Projects
Verified Commit 0c8faf83 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Can now have multiple system actors

We also handle webfinger/activity serialization properly
parent 6c3b7ce1
No related branches found
No related tags found
No related merge requests found
Showing with 495 additions and 154 deletions
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
),
}
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)
...@@ -2,10 +2,14 @@ from cryptography.hazmat.primitives import serialization as crypto_serialization ...@@ -2,10 +2,14 @@ from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend as crypto_default_backend from cryptography.hazmat.backends import default_backend as crypto_default_backend
import re
import requests import requests
import urllib.parse
from . import exceptions from . import exceptions
KEY_ID_REGEX = re.compile(r'keyId=\"(?P<id>.*)\"')
def get_key_pair(size=2048): def get_key_pair(size=2048):
key = rsa.generate_private_key( key = rsa.generate_private_key(
...@@ -25,19 +29,21 @@ def get_key_pair(size=2048): ...@@ -25,19 +29,21 @@ def get_key_pair(size=2048):
return private_key, public_key return private_key, public_key
def get_public_key(actor_url): def get_key_id_from_signature_header(header_string):
""" parts = header_string.split(',')
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()
try: try:
return { raw_key_id = [p for p in parts if p.startswith('keyId="')][0]
'public_key_pem': payload['publicKey']['publicKeyPem'], except IndexError:
'id': payload['publicKey']['id'], raise ValueError('Missing key id')
'owner': payload['publicKey']['owner'],
} match = KEY_ID_REGEX.match(raw_key_id)
except KeyError: if not match:
raise exceptions.MalformedPayload(str(payload)) 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
import urllib.parse
from django.urls import reverse from django.urls import reverse
from django.conf import settings from django.conf import settings
from rest_framework import serializers
from dynamic_preferences.registries import global_preferences_registry from dynamic_preferences.registries import global_preferences_registry
from . import models
from . import utils from . import utils
def repr_instance_actor(): class ActorSerializer(serializers.ModelSerializer):
""" # left maps to activitypub fields, right to our internal models
We do not use a serializer here, since it's pretty static id = serializers.URLField(source='url')
""" outbox = serializers.URLField(source='outbox_url')
actor_url = utils.full_url(reverse('federation:instance-actor')) inbox = serializers.URLField(source='inbox_url')
preferences = global_preferences_registry.manager() following = serializers.URLField(source='following_url', required=False)
public_key = preferences['federation__public_key'] 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)
class Meta:
model = models.Actor
fields = [
'id',
'type',
'name',
'summary',
'preferredUsername',
'publicKey',
'inbox',
'outbox',
'following',
'followers',
'manuallyApprovesFollowers',
]
return { def to_representation(self, instance):
'@context': [ ret = super().to_representation(instance)
ret['@context'] = [
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1', 'https://w3id.org/security/v1',
{}, {},
], ]
'id': utils.full_url(reverse('federation:instance-actor')), if instance.public_key:
'type': 'Person', ret['publicKey'] = {
'inbox': utils.full_url(reverse('federation:instance-inbox')), 'owner': instance.url,
'outbox': utils.full_url(reverse('federation:instance-outbox')), 'publicKeyPem': instance.public_key,
'preferredUsername': 'service', 'id': '{}#main-key'.format(instance.url)
'name': 'Service Bot - {}'.format(settings.FEDERATION_HOSTNAME), }
'summary': 'Bot account for federating with {}'.format( ret['endpoints'] = {}
settings.FEDERATION_HOSTNAME if instance.shared_inbox_url:
), ret['endpoints']['sharedInbox'] = instance.shared_inbox_url
'publicKey': { return ret
'id': '{}#main-key'.format(actor_url),
'owner': actor_url, def prepare_missing_fields(self):
'publicKeyPem': public_key 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
...@@ -4,9 +4,9 @@ from . import views ...@@ -4,9 +4,9 @@ from . import views
router = routers.SimpleRouter(trailing_slash=False) router = routers.SimpleRouter(trailing_slash=False)
router.register( router.register(
r'federation/instance', r'federation/instance/actors',
views.InstanceViewSet, views.InstanceActorViewSet,
'instance') 'instance-actors')
router.register( router.register(
r'.well-known', r'.well-known',
views.WellKnownViewSet, views.WellKnownViewSet,
......
...@@ -5,8 +5,9 @@ from django.http import HttpResponse ...@@ -5,8 +5,9 @@ from django.http import HttpResponse
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework import views from rest_framework import views
from rest_framework import response 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 renderers
from . import serializers from . import serializers
from . import webfinger from . import webfinger
...@@ -19,20 +20,30 @@ class FederationMixin(object): ...@@ -19,20 +20,30 @@ class FederationMixin(object):
return super().dispatch(request, *args, **kwargs) 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 = [] authentication_classes = []
permission_classes = [] permission_classes = []
renderer_classes = [renderers.ActivityPubRenderer] renderer_classes = [renderers.ActivityPubRenderer]
@list_route(methods=['get']) def get_object(self):
def actor(self, request, *args, **kwargs): try:
return response.Response(serializers.repr_instance_actor()) 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): def inbox(self, request, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
@list_route(methods=['get']) @detail_route(methods=['get'])
def outbox(self, request, *args, **kwargs): def outbox(self, request, *args, **kwargs):
raise NotImplementedError() raise NotImplementedError()
...@@ -69,6 +80,5 @@ class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet): ...@@ -69,6 +80,5 @@ class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet):
def handler_acct(self, clean_result): def handler_acct(self, clean_result):
username, hostname = clean_result username, hostname = clean_result
if username == 'service': actor = actors.SYSTEM_ACTORS[username]['get_actor']()
return webfinger.serialize_system_acct() return serializers.ActorWebfingerSerializer(actor).data
return {}
...@@ -2,7 +2,9 @@ from django import forms ...@@ -2,7 +2,9 @@ from django import forms
from django.conf import settings from django.conf import settings
from django.urls import reverse from django.urls import reverse
from . import actors
from . import utils from . import utils
VALID_RESOURCE_TYPES = ['acct'] VALID_RESOURCE_TYPES = ['acct']
...@@ -30,23 +32,7 @@ def clean_acct(acct_string): ...@@ -30,23 +32,7 @@ def clean_acct(acct_string):
if hostname != settings.FEDERATION_HOSTNAME: if hostname != settings.FEDERATION_HOSTNAME:
raise forms.ValidationError('Invalid hostname') raise forms.ValidationError('Invalid hostname')
if username != 'service': if username not in actors.SYSTEM_ACTORS:
raise forms.ValidationError('Invalid username') raise forms.ValidationError('Invalid username')
return username, hostname 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')),
}
]
}
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
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
import pytest
from funkwhale_api.federation import keys from funkwhale_api.federation import keys
def test_public_key_fetching(r_mock): @pytest.mark.parametrize('raw, expected', [
payload = { ('algorithm="test",keyId="https://test.com"', 'https://test.com'),
'id': 'https://actor.mock/users/actor#main-key', ('keyId="https://test.com",algorithm="test"', 'https://test.com'),
'owner': 'test', ])
'publicKeyPem': 'test_pem', def test_get_key_from_header(raw, expected):
} r = keys.get_key_id_from_signature_header(raw)
actor = 'https://actor.mock/' assert r == expected
r_mock.get(actor, json={'publicKey': payload})
r = keys.get_public_key(actor)
assert r['id'] == payload['id'] @pytest.mark.parametrize('raw', [
assert r['owner'] == payload['owner'] 'algorithm="test",keyid="badCase"',
assert r['public_key_pem'] == payload['publicKeyPem'] '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)
from django.urls import reverse from django.urls import reverse
from funkwhale_api.federation import keys from funkwhale_api.federation import keys
from funkwhale_api.federation import models
from funkwhale_api.federation import serializers from funkwhale_api.federation import serializers
def test_repr_instance_actor(db, preferences, settings): def test_actor_serializer_from_ap(db):
_, public_key = keys.get_key_pair() payload = {
preferences['federation__public_key'] = public_key.decode('utf-8') 'id': 'https://test.federation/user',
settings.FEDERATION_HOSTNAME = 'test.federation' 'type': 'Person',
settings.FUNKWHALE_URL = 'https://test.federation' 'following': 'https://test.federation/user/following',
actor_url = settings.FUNKWHALE_URL + reverse('federation:instance-actor') 'followers': 'https://test.federation/user/followers',
inbox_url = settings.FUNKWHALE_URL + reverse('federation:instance-inbox') 'inbox': 'https://test.federation/user/inbox',
outbox_url = settings.FUNKWHALE_URL + reverse('federation:instance-outbox') '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 = { expected = {
'@context': [ '@context': [
'https://www.w3.org/ns/activitystreams', 'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1', 'https://w3id.org/security/v1',
{}, {},
], ],
'id': actor_url, 'id': 'https://test.federation/user',
'type': 'Person', 'type': 'Person',
'preferredUsername': 'service', 'following': 'https://test.federation/user/following',
'name': 'Service Bot - test.federation', 'followers': 'https://test.federation/user/followers',
'summary': 'Bot account for federating with test.federation', 'inbox': 'https://test.federation/user/inbox',
'inbox': inbox_url, 'outbox': 'https://test.federation/user/outbox',
'outbox': outbox_url, 'preferredUsername': 'user',
'name': 'Real User',
'summary': 'Hello world',
'manuallyApprovesFollowers': False,
'publicKey': { 'publicKey': {
'id': '{}#main-key'.format(actor_url), 'id': 'https://test.federation/user#main-key',
'owner': actor_url, 'owner': 'https://test.federation/user',
'publicKeyPem': public_key.decode('utf-8') '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,
)
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 expected == serializers.repr_instance_actor() assert serializer.data == expected
...@@ -2,38 +2,43 @@ from django.urls import reverse ...@@ -2,38 +2,43 @@ from django.urls import reverse
import pytest import pytest
from funkwhale_api.federation import actors
from funkwhale_api.federation import serializers from funkwhale_api.federation import serializers
from funkwhale_api.federation import webfinger from funkwhale_api.federation import webfinger
def test_instance_actor(db, settings, api_client):
settings.FUNKWHALE_URL = 'http://test.com' @pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
url = reverse('federation:instance-actor') 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) response = api_client.get(url)
serializer = serializers.ActorSerializer(actor)
assert response.status_code == 200 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( def test_wellknown_webfinger_validates_resource(
db, api_client, settings, mocker): db, api_client, settings, mocker):
clean = mocker.spy(webfinger, 'clean_resource') clean = mocker.spy(webfinger, 'clean_resource')
settings.FEDERATION_ENABLED = True
url = reverse('federation:well-known-webfinger') url = reverse('federation:well-known-webfinger')
response = api_client.get(url, data={'resource': 'something'}) response = api_client.get(url, data={'resource': 'something'})
...@@ -45,14 +50,15 @@ def test_wellknown_webfinger_validates_resource( ...@@ -45,14 +50,15 @@ def test_wellknown_webfinger_validates_resource(
) )
@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
def test_wellknown_webfinger_system( def test_wellknown_webfinger_system(
db, api_client, settings, mocker): system_actor, db, api_client, settings, mocker):
settings.FEDERATION_ENABLED = True actor = actors.SYSTEM_ACTORS[system_actor]['get_actor']()
settings.FEDERATION_HOSTNAME = 'test.federation'
url = reverse('federation:well-known-webfinger') url = reverse('federation:well-known-webfinger')
response = api_client.get( 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.status_code == 200
assert response['Content-Type'] == 'application/jrd+json' assert response['Content-Type'] == 'application/jrd+json'
assert response.data == webfinger.serialize_system_acct() assert response.data == serializer.data
...@@ -25,9 +25,8 @@ def test_webfinger_clean_resource_errors(resource, message): ...@@ -25,9 +25,8 @@ def test_webfinger_clean_resource_errors(resource, message):
def test_webfinger_clean_acct(settings): def test_webfinger_clean_acct(settings):
settings.FEDERATION_HOSTNAME = 'test.federation' username, hostname = webfinger.clean_acct('library@test.federation')
username, hostname = webfinger.clean_acct('service@test.federation') assert username == 'library'
assert username == 'service'
assert hostname == 'test.federation' assert hostname == 'test.federation'
...@@ -37,30 +36,7 @@ def test_webfinger_clean_acct(settings): ...@@ -37,30 +36,7 @@ def test_webfinger_clean_acct(settings):
('noop@test.federation', 'Invalid account'), ('noop@test.federation', 'Invalid account'),
]) ])
def test_webfinger_clean_acct_errors(resource, message, settings): def test_webfinger_clean_acct_errors(resource, message, settings):
settings.FEDERATION_HOSTNAME = 'test.federation'
with pytest.raises(forms.ValidationError) as excinfo: with pytest.raises(forms.ValidationError) as excinfo:
webfinger.clean_resource(resource) webfinger.clean_resource(resource)
assert message == str(excinfo) 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()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment