Commit 34ce18ff authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch '186-settings-preferences' into 'develop'

Resolve "Migrate federation settings to preferences"

Closes #186

See merge request funkwhale/funkwhale!173
parents e226d60c 7ad21b7d
API_AUTHENTICATION_REQUIRED=True
RAVEN_ENABLED=false
RAVEN_DSN=https://44332e9fdd3d42879c7d35bf8562c6a4:0062dc16a22b41679cd5765e5342f716@sentry.eliotberriot.com/5
DJANGO_ALLOWED_HOSTS=.funkwhale.test,localhost,nginx,0.0.0.0,127.0.0.1
......
......@@ -48,16 +48,18 @@ else:
FUNKWHALE_URL = '{}://{}'.format(FUNKWHALE_PROTOCOL, FUNKWHALE_HOSTNAME)
# XXX: deprecated, see #186
FEDERATION_ENABLED = env.bool('FEDERATION_ENABLED', default=True)
FEDERATION_HOSTNAME = env('FEDERATION_HOSTNAME', default=FUNKWHALE_HOSTNAME)
# XXX: deprecated, see #186
FEDERATION_COLLECTION_PAGE_SIZE = env.int(
'FEDERATION_COLLECTION_PAGE_SIZE', default=50
)
# XXX: deprecated, see #186
FEDERATION_MUSIC_NEEDS_APPROVAL = env.bool(
'FEDERATION_MUSIC_NEEDS_APPROVAL', default=True
)
# how much minutes to wait before refetching actor data
# when authenticating
# XXX: deprecated, see #186
FEDERATION_ACTOR_FETCH_DELAY = env.int(
'FEDERATION_ACTOR_FETCH_DELAY', default=60 * 12)
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
......@@ -366,7 +368,6 @@ CORS_ORIGIN_ALLOW_ALL = True
# 'funkwhale.localhost',
# )
CORS_ALLOW_CREDENTIALS = True
API_AUTHENTICATION_REQUIRED = env.bool("API_AUTHENTICATION_REQUIRED", True)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
......@@ -433,6 +434,7 @@ ADMIN_URL = env('DJANGO_ADMIN_URL', default='^api/admin/')
CSRF_USE_SESSIONS = True
# Playlist settings
# XXX: deprecated, see #186
PLAYLISTS_MAX_TRACKS = env.int('PLAYLISTS_MAX_TRACKS', default=250)
ACCOUNT_USERNAME_BLACKLIST = [
......@@ -452,6 +454,8 @@ EXTERNAL_REQUESTS_VERIFY_SSL = env.bool(
'EXTERNAL_REQUESTS_VERIFY_SSL',
default=True
)
# XXX: deprecated, see #186
API_AUTHENTICATION_REQUIRED = env.bool("API_AUTHENTICATION_REQUIRED", True)
MUSIC_DIRECTORY_PATH = env('MUSIC_DIRECTORY_PATH', default=None)
# on Docker setup, the music directory may not match the host path,
......
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry
from funkwhale_api.common import preferences
common = types.Section('common')
@global_preferences_registry.register
class APIAutenticationRequired(
preferences.DefaultFromSettingMixin, types.BooleanPreference):
section = common
name = 'api_authentication_required'
verbose_name = 'API Requires authentication'
setting = 'API_AUTHENTICATION_REQUIRED'
help_text = (
'If disabled, anonymous users will be able to query the API'
'and access music data (as well as other data exposed in the API '
'without specific permissions)'
)
......@@ -5,11 +5,13 @@ from django.http import Http404
from rest_framework.permissions import BasePermission, DjangoModelPermissions
from funkwhale_api.common import preferences
class ConditionalAuthentication(BasePermission):
def has_permission(self, request, view):
if settings.API_AUTHENTICATION_REQUIRED:
if preferences.get('common__api_authentication_required'):
return request.user and request.user.is_authenticated
return True
......
from django.conf import settings
from dynamic_preferences.registries import global_preferences_registry
class DefaultFromSettingMixin(object):
def get_default(self):
return getattr(settings, self.setting)
def get(pref):
manager = global_preferences_registry.manager()
return manager[pref]
......@@ -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):
......
......@@ -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.'
)
......@@ -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)
......
......@@ -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 = {
......
......@@ -2,6 +2,7 @@ from django.conf import settings
from rest_framework.permissions import BasePermission
from funkwhale_api.common import preferences
from funkwhale_api.federation import actors
from funkwhale_api.federation import models
......@@ -12,6 +13,9 @@ class Listen(BasePermission):
if not settings.PROTECT_AUDIO_FILES:
return True
if not preferences.get('common__api_authentication_required'):
return True
user = getattr(request, 'user', None)
if user and user.is_authenticated:
return True
......
......@@ -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':
......
from dynamic_preferences import types
from dynamic_preferences.registries import global_preferences_registry
from funkwhale_api.common import preferences
playlists = types.Section('playlists')
@global_preferences_registry.register
class MaxTracks(preferences.DefaultFromSettingMixin, types.IntegerPreference):
show_in_api = True
section = playlists
name = 'max_tracks'
verbose_name = 'Max tracks per playlist'
setting = 'PLAYLISTS_MAX_TRACKS'
......@@ -6,6 +6,7 @@ from django.utils import timezone
from rest_framework import exceptions
from funkwhale_api.common import fields
from funkwhale_api.common import preferences
class Playlist(models.Model):
......@@ -81,10 +82,11 @@ class Playlist(models.Model):
existing = self.playlist_tracks.select_for_update()
now = timezone.now()
total = existing.filter(index__isnull=False).count()
if existing.count() + len(tracks) > settings.PLAYLISTS_MAX_TRACKS:
max_tracks = preferences.get('playlists__max_tracks')
if existing.count() + len(tracks) > max_tracks:
raise exceptions.ValidationError(
'Playlist would reach the maximum of {} tracks'.format(
settings.PLAYLISTS_MAX_TRACKS))
max_tracks))
self.save(update_fields=['modification_date'])
start = total
plts = [
......
......@@ -3,6 +3,7 @@ from django.db import transaction
from rest_framework import serializers
from taggit.models import Tag
from funkwhale_api.common import preferences
from funkwhale_api.music.models import Track
from funkwhale_api.music.serializers import TrackSerializerNested
from funkwhale_api.users.serializers import UserBasicSerializer
......@@ -32,10 +33,11 @@ class PlaylistTrackWriteSerializer(serializers.ModelSerializer):
raise serializers.ValidationError(
'You do not have the permission to edit this playlist')
existing = value.playlist_tracks.count()
if existing >= settings.PLAYLISTS_MAX_TRACKS:
max_tracks = preferences.get('playlists__max_tracks')
if existing >= max_tracks:
raise serializers.ValidationError(
'Playlist has reached the maximum of {} tracks'.format(
settings.PLAYLISTS_MAX_TRACKS))
max_tracks))
return value
@transaction.atomic
......
......@@ -4,8 +4,8 @@ from funkwhale_api.activity import serializers
from funkwhale_api.activity import utils
def test_activity_view(factories, api_client, settings, anonymous_user):
settings.API_AUTHENTICATION_REQUIRED = False
def test_activity_view(factories, api_client, preferences, anonymous_user):
preferences['common__api_authentication_required'] = False
favorite = factories['favorites.TrackFavorite'](
user__privacy_level='everyone')
listening = factories['history.Listening']()
......
......@@ -99,8 +99,8 @@ def test_user_can_remove_favorite_via_api_using_track_id(
@pytest.mark.parametrize('url,method', [
('api:v1:favorites:tracks-list', 'get'),
])
def test_url_require_auth(url, method, db, settings, client):
settings.API_AUTHENTICATION_REQUIRED = True
def test_url_require_auth(url, method, db, preferences, client):
preferences['common__api_authentication_required'] = True
url = reverse(url)
response = getattr(client, method)(url)
assert response.status_code == 401
......
......@@ -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')
......
......@@ -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()
......
......@@ -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',
......
......@@ -14,8 +14,9 @@ def test_can_create_listening(factories):
l = models.Listening.objects.create(user=user, track=track)
def test_anonymous_user_can_create_listening_via_api(client, factories, settings):
settings.API_AUTHENTICATION_REQUIRED = False
def test_anonymous_user_can_create_listening_via_api(
client, factories, preferences):
preferences['common__api_authentication_required'] = False
track = factories['music.Track']()
url = reverse('api:v1:history:listenings-list')
response = client.post(url, {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment