From 2ef36928c6e6abea869ec374f0f0ea7991d1b70b Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Sat, 26 May 2018 11:47:12 +0200 Subject: [PATCH 1/5] See #236: added a basic string list preference --- api/funkwhale_api/common/preferences.py | 39 ++++++++++++++++++++++ api/tests/common/test_preferences.py | 44 +++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 api/tests/common/test_preferences.py diff --git a/api/funkwhale_api/common/preferences.py b/api/funkwhale_api/common/preferences.py index e6eb8beda..a2d3f04b7 100644 --- a/api/funkwhale_api/common/preferences.py +++ b/api/funkwhale_api/common/preferences.py @@ -1,4 +1,8 @@ from django.conf import settings +from django import forms + +from dynamic_preferences import serializers +from dynamic_preferences import types from dynamic_preferences.registries import global_preferences_registry @@ -10,3 +14,38 @@ class DefaultFromSettingMixin(object): def get(pref): manager = global_preferences_registry.manager() return manager[pref] + + +class StringListSerializer(serializers.BaseSerializer): + separator = ',' + sort = True + + @classmethod + def to_db(cls, value, **kwargs): + if not value: + return + + if type(value) not in [list, tuple]: + raise cls.exception( + "Cannot serialize, value {} is not a list or a tuple".format( + value)) + + if cls.sort: + value = sorted(value) + return cls.separator.join(value) + + @classmethod + def to_python(cls, value, **kwargs): + if not value: + return [] + return value.split(',') + + +class StringListPreference(types.BasePreferenceType): + serializer = StringListSerializer + field_class = forms.MultipleChoiceField + + def get_api_additional_data(self): + d = super(StringListPreference, self).get_api_additional_data() + d['choices'] = self.get('choices') + return d diff --git a/api/tests/common/test_preferences.py b/api/tests/common/test_preferences.py new file mode 100644 index 000000000..475610a93 --- /dev/null +++ b/api/tests/common/test_preferences.py @@ -0,0 +1,44 @@ +import pytest + +from dynamic_preferences.registries import global_preferences_registry +from funkwhale_api.common import preferences as common_preferences + + +@pytest.fixture +def string_list_pref(preferences): + + @global_preferences_registry.register + class P(common_preferences.StringListPreference): + default = ['hello'] + section = 'test' + name = 'string_list' + yield + del global_preferences_registry['test']['string_list'] + + +@pytest.mark.parametrize('input,output', [ + (['a', 'b', 'c'], 'a,b,c'), + (['a', 'c', 'b'], 'a,b,c'), + (('a', 'c', 'b'), 'a,b,c'), + ([], None), +]) +def test_string_list_serializer_to_db(input, output): + s = common_preferences.StringListSerializer.to_db(input) == output + + +@pytest.mark.parametrize('input,output', [ + ('a,b,c', ['a', 'b', 'c'], ), + (None, []), + ('', []), +]) +def test_string_list_serializer_to_python(input, output): + s = common_preferences.StringListSerializer.to_python(input) == output + + +def test_string_list_pref_default(string_list_pref, preferences): + assert preferences['test__string_list'] == ['hello'] + + +def test_string_list_pref_set(string_list_pref, preferences): + preferences['test__string_list'] = ['world', 'hello'] + assert preferences['test__string_list'] == ['hello', 'world'] From a9799e66d72b9f344d3aa1da78c51f2ccf90a377 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Sat, 26 May 2018 12:45:55 +0200 Subject: [PATCH 2/5] See #236: backend for users default permissions --- .../users/dynamic_preferences_registry.py | 24 ++++++++++ api/funkwhale_api/users/models.py | 48 ++++++++++++++----- api/tests/users/test_models.py | 11 +++++ 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/api/funkwhale_api/users/dynamic_preferences_registry.py b/api/funkwhale_api/users/dynamic_preferences_registry.py index 4f7360530..7108360b9 100644 --- a/api/funkwhale_api/users/dynamic_preferences_registry.py +++ b/api/funkwhale_api/users/dynamic_preferences_registry.py @@ -1,6 +1,10 @@ from dynamic_preferences import types from dynamic_preferences.registries import global_preferences_registry +from funkwhale_api.common import preferences as common_preferences + +from . import models + users = types.Section('users') @@ -14,3 +18,23 @@ class RegistrationEnabled(types.BooleanPreference): help_text = ( 'When enabled, new users will be able to register on this instance.' ) + + +@global_preferences_registry.register +class DefaultPermissions(common_preferences.StringListPreference): + show_in_api = True + section = users + name = 'default_permissions' + default = [] + verbose_name = 'Default permissions' + help_text = ( + 'A list of default preferences to give to all registered users.' + ) + choices = [ + (k, c['label']) + for k, c in models.PERMISSIONS_CONFIGURATION.items() + ] + field_kwargs = { + 'choices': choices, + 'required': False, + } diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py index a739c1e38..a3c5bd0bf 100644 --- a/api/funkwhale_api/users/models.py +++ b/api/funkwhale_api/users/models.py @@ -13,18 +13,33 @@ from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from funkwhale_api.common import fields +from funkwhale_api.common import preferences def get_token(): return binascii.b2a_hex(os.urandom(15)).decode('utf-8') -PERMISSIONS = [ - 'federation', - 'library', - 'settings', - 'upload', -] +PERMISSIONS_CONFIGURATION = { + 'federation': { + 'label': 'Manage library federation', + 'help_text': 'Follow other instances, accept/deny library follow requests...', + }, + 'library': { + 'label': 'Manage library', + 'help_text': 'Manage library', + }, + 'settings': { + 'label': 'Manage instance-level settings', + 'help_text': '', + }, + 'upload': { + 'label': 'Upload new content to the library', + 'help_text': '', + }, +} + +PERMISSIONS = sorted(PERMISSIONS_CONFIGURATION.keys()) @python_2_unicode_compatible @@ -48,27 +63,34 @@ class User(AbstractUser): # permissions permission_federation = models.BooleanField( - 'Manage library federation', - help_text='Follow other instances, accept/deny library follow requests...', + PERMISSIONS_CONFIGURATION['federation']['label'], + help_text=PERMISSIONS_CONFIGURATION['federation']['help_text'], default=False) permission_library = models.BooleanField( - 'Manage library', - help_text='Manage library', + PERMISSIONS_CONFIGURATION['library']['label'], + help_text=PERMISSIONS_CONFIGURATION['library']['help_text'], default=False) permission_settings = models.BooleanField( - 'Manage instance-level settings', + PERMISSIONS_CONFIGURATION['settings']['label'], + help_text=PERMISSIONS_CONFIGURATION['settings']['help_text'], default=False) permission_upload = models.BooleanField( - 'Upload new content to the library', + PERMISSIONS_CONFIGURATION['upload']['label'], + help_text=PERMISSIONS_CONFIGURATION['upload']['help_text'], default=False) def __str__(self): return self.username def get_permissions(self): + defaults = preferences.get('users__default_permissions') perms = {} for p in PERMISSIONS: - v = self.is_superuser or getattr(self, 'permission_{}'.format(p)) + v = ( + self.is_superuser or + getattr(self, 'permission_{}'.format(p)) or + p in defaults + ) perms[p] = v return perms diff --git a/api/tests/users/test_models.py b/api/tests/users/test_models.py index 445744802..42123b5e8 100644 --- a/api/tests/users/test_models.py +++ b/api/tests/users/test_models.py @@ -41,6 +41,17 @@ def test_get_permissions_regular(factories): assert perms[p] is False +def test_get_permissions_default(factories, preferences): + preferences['users__default_permissions'] = ['upload', 'federation'] + user = factories['users.User']() + + perms = user.get_permissions() + assert perms['upload'] is True + assert perms['federation'] is True + assert perms['library'] is False + assert perms['settings'] is False + + @pytest.mark.parametrize('args,perms,expected', [ ({'is_superuser': True}, ['federation', 'library'], True), ({'is_superuser': False}, ['federation'], False), From 7ad19a3fbb58cb3aadefff86b400c3408796b16e Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Sat, 26 May 2018 12:46:13 +0200 Subject: [PATCH 3/5] Ensure 403 errors are probably handled on the front-end --- front/src/main.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/front/src/main.js b/front/src/main.js index 2e92fbbd2..eb2e3a23d 100644 --- a/front/src/main.js +++ b/front/src/main.js @@ -81,6 +81,8 @@ axios.interceptors.response.use(function (response) { } if (error.response.status === 404) { error.backendErrors.push('Resource not found') + } else if (error.response.status === 403) { + error.backendErrors.push('Permission denied') } else if (error.response.status === 500) { error.backendErrors.push('A server error occured') } else if (error.response.data) { From 67aef15aca5b1e6efa2494b75e878ef0da7b129d Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Sat, 26 May 2018 12:46:28 +0200 Subject: [PATCH 4/5] See #236: front-end to manage users default permissions --- front/src/components/admin/SettingsGroup.vue | 7 +++++++ front/src/views/admin/Settings.vue | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/front/src/components/admin/SettingsGroup.vue b/front/src/components/admin/SettingsGroup.vue index 255f04488..f6d57c239 100644 --- a/front/src/components/admin/SettingsGroup.vue +++ b/front/src/components/admin/SettingsGroup.vue @@ -50,6 +50,13 @@

{{ setting.help_text }}

+