kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
Factorized follow logic between system actors, Library can now accept follows
rodzic
b833a11fb6
commit
d8f86c4fce
|
@ -2,7 +2,9 @@ import logging
|
||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
import requests_http_signature
|
import requests_http_signature
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from . import models
|
||||||
from . import signing
|
from . import signing
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -117,3 +119,20 @@ def get_accept_follow(accept_id, accept_actor, follow, follow_actor):
|
||||||
"object": accept_actor.url
|
"object": accept_actor.url
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def accept_follow(target, follow, actor):
|
||||||
|
accept_uuid = uuid.uuid4()
|
||||||
|
accept = get_accept_follow(
|
||||||
|
accept_id=accept_uuid,
|
||||||
|
accept_actor=target,
|
||||||
|
follow=follow,
|
||||||
|
follow_actor=actor)
|
||||||
|
deliver(
|
||||||
|
accept,
|
||||||
|
to=[actor.url],
|
||||||
|
on_behalf_of=target)
|
||||||
|
return models.Follow.objects.get_or_create(
|
||||||
|
actor=actor,
|
||||||
|
target=target,
|
||||||
|
)
|
||||||
|
|
|
@ -132,6 +132,20 @@ class SystemActor(object):
|
||||||
|
|
||||||
return handler(data, actor)
|
return handler(data, actor)
|
||||||
|
|
||||||
|
def handle_follow(self, ac, sender):
|
||||||
|
system_actor = self.get_actor_instance()
|
||||||
|
if self.manually_approves_followers:
|
||||||
|
fr, created = models.FollowRequest.objects.get_or_create(
|
||||||
|
actor=sender,
|
||||||
|
target=system_actor,
|
||||||
|
approved=None,
|
||||||
|
)
|
||||||
|
return fr
|
||||||
|
|
||||||
|
return activity.accept_follow(
|
||||||
|
system_actor, ac, sender
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LibraryActor(SystemActor):
|
class LibraryActor(SystemActor):
|
||||||
id = 'library'
|
id = 'library'
|
||||||
|
@ -140,6 +154,7 @@ class LibraryActor(SystemActor):
|
||||||
additional_attributes = {
|
additional_attributes = {
|
||||||
'manually_approves_followers': True
|
'manually_approves_followers': True
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def manually_approves_followers(self):
|
def manually_approves_followers(self):
|
||||||
return settings.FEDERATION_MUSIC_NEEDS_APPROVAL
|
return settings.FEDERATION_MUSIC_NEEDS_APPROVAL
|
||||||
|
@ -159,18 +174,18 @@ class TestActor(SystemActor):
|
||||||
|
|
||||||
def get_outbox(self, data, actor=None):
|
def get_outbox(self, data, actor=None):
|
||||||
return {
|
return {
|
||||||
"@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": utils.full_url(
|
"id": utils.full_url(
|
||||||
reverse(
|
reverse(
|
||||||
'federation:instance-actors-outbox',
|
'federation:instance-actors-outbox',
|
||||||
kwargs={'actor': self.id})),
|
kwargs={'actor': self.id})),
|
||||||
"type": "OrderedCollection",
|
"type": "OrderedCollection",
|
||||||
"totalItems": 0,
|
"totalItems": 0,
|
||||||
"orderedItems": []
|
"orderedItems": []
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse_command(self, message):
|
def parse_command(self, message):
|
||||||
|
@ -204,10 +219,10 @@ class TestActor(SystemActor):
|
||||||
)
|
)
|
||||||
reply_activity = {
|
reply_activity = {
|
||||||
"@context": [
|
"@context": [
|
||||||
"https://www.w3.org/ns/activitystreams",
|
"https://www.w3.org/ns/activitystreams",
|
||||||
"https://w3id.org/security/v1",
|
"https://w3id.org/security/v1",
|
||||||
{}
|
{}
|
||||||
],
|
],
|
||||||
'type': 'Create',
|
'type': 'Create',
|
||||||
'actor': test_actor.url,
|
'actor': test_actor.url,
|
||||||
'id': '{}/activity'.format(reply_url),
|
'id': '{}/activity'.format(reply_url),
|
||||||
|
@ -240,25 +255,9 @@ class TestActor(SystemActor):
|
||||||
on_behalf_of=test_actor)
|
on_behalf_of=test_actor)
|
||||||
|
|
||||||
def handle_follow(self, ac, sender):
|
def handle_follow(self, ac, sender):
|
||||||
# on a follow we:
|
super().handle_follow(ac, sender)
|
||||||
# 1. send the accept answer
|
# also, we follow back
|
||||||
# 2. follow back
|
|
||||||
#
|
|
||||||
test_actor = self.get_actor_instance()
|
test_actor = self.get_actor_instance()
|
||||||
accept_uuid = uuid.uuid4()
|
|
||||||
accept = activity.get_accept_follow(
|
|
||||||
accept_id=accept_uuid,
|
|
||||||
accept_actor=test_actor,
|
|
||||||
follow=ac,
|
|
||||||
follow_actor=sender)
|
|
||||||
activity.deliver(
|
|
||||||
accept,
|
|
||||||
to=[ac['actor']],
|
|
||||||
on_behalf_of=test_actor)
|
|
||||||
models.Follow.objects.get_or_create(
|
|
||||||
actor=sender,
|
|
||||||
target=test_actor,
|
|
||||||
)
|
|
||||||
follow_uuid = uuid.uuid4()
|
follow_uuid = uuid.uuid4()
|
||||||
follow = activity.get_follow(
|
follow = activity.get_follow(
|
||||||
follow_id=follow_uuid,
|
follow_id=follow_uuid,
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
import uuid
|
||||||
|
|
||||||
from funkwhale_api.federation import activity
|
from funkwhale_api.federation import activity
|
||||||
|
|
||||||
|
|
||||||
def test_deliver(nodb_factories, r_mock, mocker):
|
def test_deliver(nodb_factories, r_mock, mocker):
|
||||||
to = nodb_factories['federation.Actor']()
|
to = nodb_factories['federation.Actor']()
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
|
@ -30,3 +33,42 @@ def test_deliver(nodb_factories, r_mock, mocker):
|
||||||
assert r_mock.call_count == 1
|
assert r_mock.call_count == 1
|
||||||
assert request.url == to.inbox_url
|
assert request.url == to.inbox_url
|
||||||
assert request.headers['content-type'] == 'application/activity+json'
|
assert request.headers['content-type'] == 'application/activity+json'
|
||||||
|
|
||||||
|
|
||||||
|
def test_accept_follow(mocker, factories):
|
||||||
|
deliver = mocker.patch(
|
||||||
|
'funkwhale_api.federation.activity.deliver')
|
||||||
|
actor = factories['federation.Actor']()
|
||||||
|
target = factories['federation.Actor'](local=True)
|
||||||
|
follow = {
|
||||||
|
'actor': actor.url,
|
||||||
|
'type': 'Follow',
|
||||||
|
'id': 'http://test.federation/user#follows/267',
|
||||||
|
'object': target.url,
|
||||||
|
}
|
||||||
|
uid = uuid.uuid4()
|
||||||
|
mocker.patch('uuid.uuid4', return_value=uid)
|
||||||
|
expected_accept = {
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://w3id.org/security/v1",
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
"id": target.url + '#accepts/follows/{}'.format(uid),
|
||||||
|
"type": "Accept",
|
||||||
|
"actor": target.url,
|
||||||
|
"object": {
|
||||||
|
"id": follow['id'],
|
||||||
|
"type": "Follow",
|
||||||
|
"actor": actor.url,
|
||||||
|
"object": target.url
|
||||||
|
},
|
||||||
|
}
|
||||||
|
activity.accept_follow(
|
||||||
|
target, follow, actor
|
||||||
|
)
|
||||||
|
deliver.assert_called_once_with(
|
||||||
|
expected_accept, to=[actor.url], on_behalf_of=target
|
||||||
|
)
|
||||||
|
follow_instance = actor.emitted_follows.first()
|
||||||
|
assert follow_instance.target == target
|
||||||
|
|
|
@ -93,18 +93,18 @@ def test_get_test(settings, preferences):
|
||||||
|
|
||||||
def test_test_get_outbox():
|
def test_test_get_outbox():
|
||||||
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": utils.full_url(
|
"id": utils.full_url(
|
||||||
reverse(
|
reverse(
|
||||||
'federation:instance-actors-outbox',
|
'federation:instance-actors-outbox',
|
||||||
kwargs={'actor': 'test'})),
|
kwargs={'actor': 'test'})),
|
||||||
"type": "OrderedCollection",
|
"type": "OrderedCollection",
|
||||||
"totalItems": 0,
|
"totalItems": 0,
|
||||||
"orderedItems": []
|
"orderedItems": []
|
||||||
}
|
}
|
||||||
|
|
||||||
data = actors.SYSTEM_ACTORS['test'].get_outbox({}, actor=None)
|
data = actors.SYSTEM_ACTORS['test'].get_outbox({}, actor=None)
|
||||||
|
@ -248,7 +248,7 @@ def test_system_actor_handle(mocker, nodb_factories):
|
||||||
)
|
)
|
||||||
assert serializer.is_valid()
|
assert serializer.is_valid()
|
||||||
actors.SYSTEM_ACTORS['test'].handle(activity, actor)
|
actors.SYSTEM_ACTORS['test'].handle(activity, actor)
|
||||||
handler.assert_called_once_with(serializer.data, actor)
|
handler.assert_called_once_with(activity, actor)
|
||||||
|
|
||||||
|
|
||||||
def test_test_actor_handles_follow(
|
def test_test_actor_handles_follow(
|
||||||
|
@ -258,6 +258,8 @@ def test_test_actor_handles_follow(
|
||||||
actor = factories['federation.Actor']()
|
actor = factories['federation.Actor']()
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
mocker.patch('django.utils.timezone.now', return_value=now)
|
mocker.patch('django.utils.timezone.now', return_value=now)
|
||||||
|
accept_follow = mocker.patch(
|
||||||
|
'funkwhale_api.federation.activity.accept_follow')
|
||||||
test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance()
|
test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance()
|
||||||
data = {
|
data = {
|
||||||
'actor': actor.url,
|
'actor': actor.url,
|
||||||
|
@ -267,22 +269,6 @@ def test_test_actor_handles_follow(
|
||||||
}
|
}
|
||||||
uid = uuid.uuid4()
|
uid = uuid.uuid4()
|
||||||
mocker.patch('uuid.uuid4', return_value=uid)
|
mocker.patch('uuid.uuid4', return_value=uid)
|
||||||
expected_accept = {
|
|
||||||
"@context": [
|
|
||||||
"https://www.w3.org/ns/activitystreams",
|
|
||||||
"https://w3id.org/security/v1",
|
|
||||||
{}
|
|
||||||
],
|
|
||||||
"id": test_actor.url + '#accepts/follows/{}'.format(uid),
|
|
||||||
"type": "Accept",
|
|
||||||
"actor": test_actor.url,
|
|
||||||
"object": {
|
|
||||||
"id": data['id'],
|
|
||||||
"type": "Follow",
|
|
||||||
"actor": actor.url,
|
|
||||||
"object": test_actor.url
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expected_follow = {
|
expected_follow = {
|
||||||
'@context': serializers.AP_CONTEXT,
|
'@context': serializers.AP_CONTEXT,
|
||||||
'actor': test_actor.url,
|
'actor': test_actor.url,
|
||||||
|
@ -292,12 +278,10 @@ def test_test_actor_handles_follow(
|
||||||
}
|
}
|
||||||
|
|
||||||
actors.SYSTEM_ACTORS['test'].post_inbox(data, actor=actor)
|
actors.SYSTEM_ACTORS['test'].post_inbox(data, actor=actor)
|
||||||
|
accept_follow.assert_called_once_with(
|
||||||
|
test_actor, data, actor
|
||||||
|
)
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
mocker.call(
|
|
||||||
expected_accept,
|
|
||||||
to=[actor.url],
|
|
||||||
on_behalf_of=test_actor,
|
|
||||||
),
|
|
||||||
mocker.call(
|
mocker.call(
|
||||||
expected_follow,
|
expected_follow,
|
||||||
to=[actor.url],
|
to=[actor.url],
|
||||||
|
@ -306,10 +290,6 @@ def test_test_actor_handles_follow(
|
||||||
]
|
]
|
||||||
deliver.assert_has_calls(expected_calls)
|
deliver.assert_has_calls(expected_calls)
|
||||||
|
|
||||||
follow = test_actor.received_follows.first()
|
|
||||||
assert follow.actor == actor
|
|
||||||
assert follow.target == test_actor
|
|
||||||
|
|
||||||
|
|
||||||
def test_test_actor_handles_undo_follow(
|
def test_test_actor_handles_undo_follow(
|
||||||
settings, mocker, factories):
|
settings, mocker, factories):
|
||||||
|
@ -344,3 +324,42 @@ def test_test_actor_handles_undo_follow(
|
||||||
on_behalf_of=test_actor,)
|
on_behalf_of=test_actor,)
|
||||||
|
|
||||||
assert models.Follow.objects.count() == 0
|
assert models.Follow.objects.count() == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_actor_handles_follow_manual_approval(
|
||||||
|
settings, mocker, factories):
|
||||||
|
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True
|
||||||
|
actor = factories['federation.Actor']()
|
||||||
|
now = timezone.now()
|
||||||
|
mocker.patch('django.utils.timezone.now', return_value=now)
|
||||||
|
library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
|
||||||
|
data = {
|
||||||
|
'actor': actor.url,
|
||||||
|
'type': 'Follow',
|
||||||
|
'id': 'http://test.federation/user#follows/267',
|
||||||
|
'object': library_actor.url,
|
||||||
|
}
|
||||||
|
|
||||||
|
library_actor.system_conf.post_inbox(data, actor=actor)
|
||||||
|
fr = library_actor.received_follow_requests.first()
|
||||||
|
|
||||||
|
assert library_actor.received_follow_requests.count() == 1
|
||||||
|
assert fr.target == library_actor
|
||||||
|
assert fr.actor == actor
|
||||||
|
assert fr.approved is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_library_actor_handles_follow_auto_approval(
|
||||||
|
settings, mocker, factories):
|
||||||
|
settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True
|
||||||
|
actor = factories['federation.Actor']()
|
||||||
|
accept_follow = mocker.patch(
|
||||||
|
'funkwhale_api.federation.activity.accept_follow')
|
||||||
|
library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
|
||||||
|
data = {
|
||||||
|
'actor': actor.url,
|
||||||
|
'type': 'Follow',
|
||||||
|
'id': 'http://test.federation/user#follows/267',
|
||||||
|
'object': library_actor.url,
|
||||||
|
}
|
||||||
|
library_actor.system_conf.post_inbox(data, actor=actor)
|
||||||
|
|
Ładowanie…
Reference in New Issue