diff --git a/api/funkwhale_api/federation/factories.py b/api/funkwhale_api/federation/factories.py index 16abf80dc921347a0041148c3f7b73c46e443d4b..277b9ce0c56c96a2a6e21d9cc0c6aeb2dedfb1b6 100644 --- a/api/funkwhale_api/federation/factories.py +++ b/api/funkwhale_api/federation/factories.py @@ -97,6 +97,15 @@ class FollowFactory(factory.DjangoModelFactory): ) +@registry.register +class FollowRequestFactory(factory.DjangoModelFactory): + target = factory.SubFactory(ActorFactory) + actor = factory.SubFactory(ActorFactory) + + class Meta: + model = models.FollowRequest + + @registry.register(name='federation.Note') class NoteFactory(factory.Factory): type = 'Note' diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py index 50ff9d319ca167039d3b0d30eabe21c2135fdada..833b5d8f38ae19c397ef7aeb10123ad031261d78 100644 --- a/api/funkwhale_api/federation/models.py +++ b/api/funkwhale_api/federation/models.py @@ -35,6 +35,13 @@ class Actor(models.Model): last_fetch_date = models.DateTimeField( default=timezone.now) manually_approves_followers = models.NullBooleanField(default=None) + followers = models.ManyToManyField( + to='self', + symmetrical=False, + through='Follow', + through_fields=('target', 'actor'), + related_name='following', + ) class Meta: unique_together = ['domain', 'preferred_username'] @@ -65,6 +72,10 @@ class Actor(models.Model): super().save(**kwargs) + @property + def is_local(self): + return self.domain == settings.FEDERATION_HOSTNAME + @property def is_system(self): from . import actors @@ -121,3 +132,28 @@ class FollowRequest(models.Model): last_modification_date = models.DateTimeField( default=timezone.now) approved = models.NullBooleanField(default=None) + + def approve(self): + from . import activity + from . import serializers + self.approved = True + self.save(update_fields=['approved']) + Follow.objects.get_or_create( + target=self.target, + actor=self.actor + ) + if self.target.is_local: + follow = { + '@context': serializers.AP_CONTEXT, + 'actor': self.actor.url, + 'id': self.actor.url + '#follows/{}'.format(uuid.uuid4()), + 'object': self.target.url, + 'type': 'Follow' + } + activity.accept_follow( + self.target, follow, self.actor + ) + + def refuse(self): + self.approved = False + self.save(update_fields=['approved']) diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py index 5ade9cdc8c94c5fde73f977b3ecbf47322bb199d..be24a5360440107c5106defc319001c189937d2f 100644 --- a/api/tests/federation/test_actors.py +++ b/api/tests/federation/test_actors.py @@ -351,7 +351,7 @@ def test_library_actor_handles_follow_manual_approval( def test_library_actor_handles_follow_auto_approval( settings, mocker, factories): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True + settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False actor = factories['federation.Actor']() accept_follow = mocker.patch( 'funkwhale_api.federation.activity.accept_follow') @@ -363,3 +363,8 @@ def test_library_actor_handles_follow_auto_approval( 'object': library_actor.url, } library_actor.system_conf.post_inbox(data, actor=actor) + + assert library_actor.received_follow_requests.count() == 0 + accept_follow.assert_called_once_with( + library_actor, data, actor + ) diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py index 18daf87887f94abb12a43a35a06ef3aabd7485d6..86e4f4a847bac8350a1fcc8d49c47a2e503eabc3 100644 --- a/api/tests/federation/test_models.py +++ b/api/tests/federation/test_models.py @@ -1,8 +1,10 @@ import pytest +import uuid from django import db from funkwhale_api.federation import models +from funkwhale_api.federation import serializers def test_cannot_duplicate_actor(factories): @@ -30,3 +32,47 @@ def test_follow_federation_url(factories): follow.actor.url, follow.uuid) assert follow.get_federation_url() == expected + + +def test_follow_request_approve(mocker, factories): + uid = uuid.uuid4() + mocker.patch('uuid.uuid4', return_value=uid) + accept_follow = mocker.patch( + 'funkwhale_api.federation.activity.accept_follow') + fr = factories['federation.FollowRequest'](target__local=True) + fr.approve() + + follow = { + '@context': serializers.AP_CONTEXT, + 'actor': fr.actor.url, + 'id': fr.actor.url + '#follows/{}'.format(uid), + 'object': fr.target.url, + 'type': 'Follow' + } + + assert fr.approved is True + assert list(fr.target.followers.all()) == [fr.actor] + accept_follow.assert_called_once_with( + fr.target, follow, fr.actor + ) + + +def test_follow_request_approve_non_local(mocker, factories): + uid = uuid.uuid4() + mocker.patch('uuid.uuid4', return_value=uid) + accept_follow = mocker.patch( + 'funkwhale_api.federation.activity.accept_follow') + fr = factories['federation.FollowRequest']() + fr.approve() + + assert fr.approved is True + assert list(fr.target.followers.all()) == [fr.actor] + accept_follow.assert_not_called() + + +def test_follow_request_refused(mocker, factories): + fr = factories['federation.FollowRequest']() + fr.refuse() + + assert fr.approved is False + assert fr.target.followers.count() == 0