diff --git a/api/funkwhale_api/federation/actors.py b/api/funkwhale_api/federation/actors.py index a0e23d12f864c9ef29fc1b39e3bd1819e0cb9c62..7a209b1ff4cca48028a094685bff8eb258652f89 100644 --- a/api/funkwhale_api/federation/actors.py +++ b/api/funkwhale_api/federation/actors.py @@ -12,6 +12,7 @@ from rest_framework.exceptions import PermissionDenied from dynamic_preferences.registries import global_preferences_registry +from funkwhale_api.common import preferences from funkwhale_api.common import session from funkwhale_api.common import utils as funkwhale_utils from funkwhale_api.music import models as music_models @@ -55,7 +56,7 @@ def get_actor(actor_url): except models.Actor.DoesNotExist: actor = None fetch_delta = datetime.timedelta( - minutes=settings.FEDERATION_ACTOR_FETCH_DELAY) + minutes=preferences.get('federation__actor_fetch_delay')) if actor and actor.last_fetch_date > timezone.now() - fetch_delta: # cache is hot, we can return as is return actor @@ -225,7 +226,7 @@ class LibraryActor(SystemActor): @property def manually_approves_followers(self): - return settings.FEDERATION_MUSIC_NEEDS_APPROVAL + return preferences.get('federation__music_needs_approval') @transaction.atomic def handle_create(self, ac, sender): diff --git a/api/funkwhale_api/federation/dynamic_preferences_registry.py b/api/funkwhale_api/federation/dynamic_preferences_registry.py index 43877c75c9717eda3c46c0f039366803aadd77c9..e86b9f6f2b8b30da1c82145ef288e591449aa9f3 100644 --- a/api/funkwhale_api/federation/dynamic_preferences_registry.py +++ b/api/funkwhale_api/federation/dynamic_preferences_registry.py @@ -3,6 +3,7 @@ from django.forms import widgets from dynamic_preferences import types from dynamic_preferences.registries import global_preferences_registry +from funkwhale_api.common import preferences federation = types.Section('federation') @@ -18,3 +19,53 @@ class MusicCacheDuration(types.IntPreference): 'locally? Federated files that were not listened in this interval ' 'will be erased and refetched from the remote on the next listening.' ) + + +@global_preferences_registry.register +class Enabled(preferences.DefaultFromSettingMixin, types.BooleanPreference): + section = federation + name = 'enabled' + setting = 'FEDERATION_ENABLED' + verbose_name = 'Federation enabled' + help_text = ( + 'Use this setting to enable or disable federation logic and API' + ' globally' + ) + + +@global_preferences_registry.register +class CollectionPageSize( + preferences.DefaultFromSettingMixin, types.IntPreference): + section = federation + name = 'collection_page_size' + setting = 'FEDERATION_COLLECTION_PAGE_SIZE' + verbose_name = 'Federation collection page size' + help_text = ( + 'How much items to display in ActivityPub collections' + ) + + +@global_preferences_registry.register +class ActorFetchDelay( + preferences.DefaultFromSettingMixin, types.IntPreference): + section = federation + name = 'actor_fetch_delay' + setting = 'FEDERATION_ACTOR_FETCH_DELAY' + verbose_name = 'Federation actor fetch delay' + help_text = ( + 'How much minutes to wait before refetching actors on ' + 'request authentication' + ) + + +@global_preferences_registry.register +class MusicNeedsApproval( + preferences.DefaultFromSettingMixin, types.BooleanPreference): + section = federation + name = 'music_needs_approval' + setting = 'FEDERATION_MUSIC_NEEDS_APPROVAL' + verbose_name = 'Federation music needs approval' + help_text = ( + 'When true, other federation actors will require your approval' + ' before being able to browse your library.' + ) diff --git a/api/funkwhale_api/federation/permissions.py b/api/funkwhale_api/federation/permissions.py index c6f0660b198e88037e0480a0e88fa4cbadb7ad9d..438b675cb300a6ba1607e24c639189224488cced 100644 --- a/api/funkwhale_api/federation/permissions.py +++ b/api/funkwhale_api/federation/permissions.py @@ -2,13 +2,14 @@ from django.conf import settings from rest_framework.permissions import BasePermission +from funkwhale_api.common import preferences from . import actors class LibraryFollower(BasePermission): def has_permission(self, request, view): - if not settings.FEDERATION_MUSIC_NEEDS_APPROVAL: + if not preferences.get('federation__music_needs_approval'): return True actor = getattr(request, 'actor', None) diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py index 381f87eff2e90f4a64feade856756044393d4f4d..9b51a534df506169be39b66d75becca94bb5d90c 100644 --- a/api/funkwhale_api/federation/views.py +++ b/api/funkwhale_api/federation/views.py @@ -13,6 +13,7 @@ from rest_framework import viewsets from rest_framework.decorators import list_route, detail_route from rest_framework.serializers import ValidationError +from funkwhale_api.common import preferences from funkwhale_api.common import utils as funkwhale_utils from funkwhale_api.common.permissions import HasModelPermission from funkwhale_api.music.models import TrackFile @@ -33,7 +34,7 @@ from . import webfinger class FederationMixin(object): def dispatch(self, request, *args, **kwargs): - if not settings.FEDERATION_ENABLED: + if not preferences.get('federation__enabled'): return HttpResponse(status=405) return super().dispatch(request, *args, **kwargs) @@ -136,7 +137,8 @@ class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet): if page is None: conf = { 'id': utils.full_url(reverse('federation:music:files-list')), - 'page_size': settings.FEDERATION_COLLECTION_PAGE_SIZE, + 'page_size': preferences.get( + 'federation__collection_page_size'), 'items': qs, 'item_serializer': serializers.AudioSerializer, 'actor': library, @@ -150,7 +152,7 @@ class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet): return response.Response( {'page': ['Invalid page number']}, status=400) p = paginator.Paginator( - qs, settings.FEDERATION_COLLECTION_PAGE_SIZE) + qs, preferences.get('federation__collection_page_size')) try: page = p.page(page_number) conf = { diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py index aaaa2cdca12539808b6bf1554a946d4dfbb9ae30..4509c9a57e6faf11b63d21c98af189a940c30677 100644 --- a/api/funkwhale_api/music/tasks.py +++ b/api/funkwhale_api/music/tasks.py @@ -2,8 +2,7 @@ import os from django.core.files.base import ContentFile -from dynamic_preferences.registries import global_preferences_registry - +from funkwhale_api.common import preferences from funkwhale_api.federation import activity from funkwhale_api.federation import actors from funkwhale_api.federation import models as federation_models @@ -80,8 +79,7 @@ def _do_import(import_job, replace=False, use_acoustid=True): acoustid_track_id = None duration = None track = None - manager = global_preferences_registry.manager() - use_acoustid = use_acoustid and manager['providers_acoustid__api_key'] + use_acoustid = use_acoustid and preferences.get('providers_acoustid__api_key') if not mbid and use_acoustid and from_file: # we try to deduce mbid from acoustid client = get_acoustid_client() @@ -185,7 +183,7 @@ def fetch_content(lyrics): @celery.require_instance( models.ImportBatch.objects.filter(status='finished'), 'import_batch') def import_batch_notify_followers(import_batch): - if not settings.FEDERATION_ENABLED: + if not preferences.get('federation__enabled'): return if import_batch.source == 'federation': diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py index f566e7a72aa4fd5bf7f1417856903d39535e4f66..6f73a9b9b2fc2fae47933898ba6c518158fcafbd 100644 --- a/api/tests/federation/test_actors.py +++ b/api/tests/federation/test_actors.py @@ -39,8 +39,8 @@ def test_get_actor(factories, r_mock): assert serializers.ActorSerializer(new_actor).data == payload -def test_get_actor_use_existing(factories, settings, mocker): - settings.FEDERATION_ACTOR_FETCH_DELAY = 60 +def test_get_actor_use_existing(factories, preferences, mocker): + preferences['federation__actor_fetch_delay'] = 60 actor = factories['federation.Actor']() get_data = mocker.patch('funkwhale_api.federation.actors.get_actor_data') new_actor = actors.get_actor(actor.url) @@ -49,8 +49,8 @@ def test_get_actor_use_existing(factories, settings, mocker): get_data.assert_not_called() -def test_get_actor_refresh(factories, settings, mocker): - settings.FEDERATION_ACTOR_FETCH_DELAY = 0 +def test_get_actor_refresh(factories, preferences, mocker): + preferences['federation__actor_fetch_delay'] = 0 actor = factories['federation.Actor']() payload = serializers.ActorSerializer(actor).data # actor changed their username in the meantime @@ -274,9 +274,9 @@ def test_actor_is_system( @pytest.mark.parametrize('value', [False, True]) -def test_library_actor_manually_approves_based_on_setting( - value, settings): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = value +def test_library_actor_manually_approves_based_on_preference( + value, preferences): + preferences['federation__music_needs_approval'] = value library_conf = actors.SYSTEM_ACTORS['library'] assert library_conf.manually_approves_followers is value @@ -356,8 +356,8 @@ def test_test_actor_handles_undo_follow( def test_library_actor_handles_follow_manual_approval( - settings, mocker, factories): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True + preferences, mocker, factories): + preferences['federation__music_needs_approval'] = True actor = factories['federation.Actor']() now = timezone.now() mocker.patch('django.utils.timezone.now', return_value=now) @@ -377,8 +377,8 @@ def test_library_actor_handles_follow_manual_approval( def test_library_actor_handles_follow_auto_approval( - settings, mocker, factories): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False + preferences, mocker, factories): + preferences['federation__music_needs_approval'] = False actor = factories['federation.Actor']() accept_follow = mocker.patch( 'funkwhale_api.federation.activity.accept_follow') diff --git a/api/tests/federation/test_permissions.py b/api/tests/federation/test_permissions.py index 9b86832108fdc36a708c22ea104af4561cf0c1d9..a87f26f1b910b77b63ba65061f319d9f8ab4ba50 100644 --- a/api/tests/federation/test_permissions.py +++ b/api/tests/federation/test_permissions.py @@ -5,8 +5,8 @@ from funkwhale_api.federation import permissions def test_library_follower( - factories, api_request, anonymous_user, settings): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True + factories, api_request, anonymous_user, preferences): + preferences['federation__music_needs_approval'] = True view = APIView.as_view() permission = permissions.LibraryFollower() request = api_request.get('/') @@ -17,8 +17,8 @@ def test_library_follower( def test_library_follower_actor_non_follower( - factories, api_request, anonymous_user, settings): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True + factories, api_request, anonymous_user, preferences): + preferences['federation__music_needs_approval'] = True actor = factories['federation.Actor']() view = APIView.as_view() permission = permissions.LibraryFollower() @@ -31,8 +31,8 @@ def test_library_follower_actor_non_follower( def test_library_follower_actor_follower_not_approved( - factories, api_request, anonymous_user, settings): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True + factories, api_request, anonymous_user, preferences): + preferences['federation__music_needs_approval'] = True library = actors.SYSTEM_ACTORS['library'].get_actor_instance() follow = factories['federation.Follow'](target=library, approved=False) view = APIView.as_view() @@ -46,8 +46,8 @@ def test_library_follower_actor_follower_not_approved( def test_library_follower_actor_follower( - factories, api_request, anonymous_user, settings): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True + factories, api_request, anonymous_user, preferences): + preferences['federation__music_needs_approval'] = True library = actors.SYSTEM_ACTORS['library'].get_actor_instance() follow = factories['federation.Follow'](target=library, approved=True) view = APIView.as_view() diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py index ae94bcdc02ab2e23704eb3622ec311128713e9ce..09ecfc8ff7f6d192808890b78eb9d3226ad8cc7e 100644 --- a/api/tests/federation/test_views.py +++ b/api/tests/federation/test_views.py @@ -13,7 +13,7 @@ from funkwhale_api.federation import webfinger @pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys()) -def test_instance_actors(system_actor, db, settings, api_client): +def test_instance_actors(system_actor, db, api_client): actor = actors.SYSTEM_ACTORS[system_actor].get_actor_instance() url = reverse( 'federation:instance-actors-detail', @@ -34,8 +34,8 @@ def test_instance_actors(system_actor, db, settings, api_client): ('well-known-webfinger', {}), ]) def test_instance_endpoints_405_if_federation_disabled( - authenticated_actor, db, settings, api_client, route, kwargs): - settings.FEDERATION_ENABLED = False + authenticated_actor, db, preferences, api_client, route, kwargs): + preferences['federation__enabled'] = False url = reverse('federation:{}'.format(route), kwargs=kwargs) response = api_client.get(url) @@ -71,8 +71,8 @@ def test_wellknown_webfinger_system( def test_audio_file_list_requires_authenticated_actor( - db, settings, api_client): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = True + db, preferences, api_client): + preferences['federation__music_needs_approval'] = True url = reverse('federation:music:files-list') response = api_client.get(url) @@ -80,9 +80,9 @@ def test_audio_file_list_requires_authenticated_actor( def test_audio_file_list_actor_no_page( - db, settings, api_client, factories): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False - settings.FEDERATION_COLLECTION_PAGE_SIZE = 2 + db, preferences, api_client, factories): + preferences['federation__music_needs_approval'] = False + preferences['federation__collection_page_size'] = 2 library = actors.SYSTEM_ACTORS['library'].get_actor_instance() tfs = factories['music.TrackFile'].create_batch(size=5) conf = { @@ -101,9 +101,9 @@ def test_audio_file_list_actor_no_page( def test_audio_file_list_actor_page( - db, settings, api_client, factories): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False - settings.FEDERATION_COLLECTION_PAGE_SIZE = 2 + db, preferences, api_client, factories): + preferences['federation__music_needs_approval'] = False + preferences['federation__collection_page_size'] = 2 library = actors.SYSTEM_ACTORS['library'].get_actor_instance() tfs = factories['music.TrackFile'].create_batch(size=5) conf = { @@ -121,8 +121,8 @@ def test_audio_file_list_actor_page( def test_audio_file_list_actor_page_exclude_federated_files( - db, settings, api_client, factories): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False + db, preferences, api_client, factories): + preferences['federation__music_needs_approval'] = False library = actors.SYSTEM_ACTORS['library'].get_actor_instance() tfs = factories['music.TrackFile'].create_batch(size=5, federation=True) @@ -134,8 +134,8 @@ def test_audio_file_list_actor_page_exclude_federated_files( def test_audio_file_list_actor_page_error( - db, settings, api_client, factories): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False + db, preferences, api_client, factories): + preferences['federation__music_needs_approval'] = False url = reverse('federation:music:files-list') response = api_client.get(url, data={'page': 'nope'}) @@ -143,15 +143,15 @@ def test_audio_file_list_actor_page_error( def test_audio_file_list_actor_page_error_too_far( - db, settings, api_client, factories): - settings.FEDERATION_MUSIC_NEEDS_APPROVAL = False + db, preferences, api_client, factories): + preferences['federation__music_needs_approval'] = False url = reverse('federation:music:files-list') response = api_client.get(url, data={'page': 5000}) assert response.status_code == 404 -def test_library_actor_includes_library_link(db, settings, api_client): +def test_library_actor_includes_library_link(db, preferences, api_client): actor = actors.SYSTEM_ACTORS['library'].get_actor_instance() url = reverse( 'federation:instance-actors-detail', diff --git a/api/tests/music/test_import.py b/api/tests/music/test_import.py index fa1c98eb4ca78c7e9fab5861989852687d764f03..000e6a8b6265eca2178c98ff52b0807956314830 100644 --- a/api/tests/music/test_import.py +++ b/api/tests/music/test_import.py @@ -169,10 +169,10 @@ def test_import_job_run_triggers_notifies_followers( def test_import_batch_notifies_followers_skip_on_disabled_federation( - settings, factories, mocker): + preferences, factories, mocker): mocked_deliver = mocker.patch('funkwhale_api.federation.activity.deliver') batch = factories['music.ImportBatch'](finished=True) - settings.FEDERATION_ENABLED = False + preferences['federation__enabled'] = False tasks.import_batch_notify_followers(import_batch_id=batch.pk) mocked_deliver.assert_not_called() diff --git a/deploy/env.prod.sample b/deploy/env.prod.sample index 54f2e1ef08192d5a181d04bb69ad69b0848f2faa..e357d08d3389202ac28920b7332448099705223e 100644 --- a/deploy/env.prod.sample +++ b/deploy/env.prod.sample @@ -98,15 +98,6 @@ API_AUTHENTICATION_REQUIRED=True RAVEN_ENABLED=false RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5 -# This settings enable/disable federation on the instance level -FEDERATION_ENABLED=True -# This setting decide wether music library is shared automatically -# to followers or if it requires manual approval before. -# FEDERATION_MUSIC_NEEDS_APPROVAL=False -# means anyone can subscribe to your library and import your file, -# use with caution. -FEDERATION_MUSIC_NEEDS_APPROVAL=True - # In-place import settings # You can safely leave those settings uncommented if you don't plan to use # in place imports.