Merge branch '236-default-permission' into 'develop'
Resolve "Implement default permissions for logged in users" Closes #236 See merge request funkwhale/funkwhale!213
This commit is contained in:
commit
9745b57f13
|
@ -1,4 +1,8 @@
|
||||||
from django.conf import settings
|
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
|
from dynamic_preferences.registries import global_preferences_registry
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,3 +14,38 @@ class DefaultFromSettingMixin(object):
|
||||||
def get(pref):
|
def get(pref):
|
||||||
manager = global_preferences_registry.manager()
|
manager = global_preferences_registry.manager()
|
||||||
return manager[pref]
|
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
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
from dynamic_preferences import types
|
from dynamic_preferences import types
|
||||||
from dynamic_preferences.registries import global_preferences_registry
|
from dynamic_preferences.registries import global_preferences_registry
|
||||||
|
|
||||||
|
from funkwhale_api.common import preferences as common_preferences
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
users = types.Section('users')
|
users = types.Section('users')
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,3 +18,23 @@ class RegistrationEnabled(types.BooleanPreference):
|
||||||
help_text = (
|
help_text = (
|
||||||
'When enabled, new users will be able to register on this instance.'
|
'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,
|
||||||
|
}
|
||||||
|
|
|
@ -13,18 +13,33 @@ from django.utils.encoding import python_2_unicode_compatible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from funkwhale_api.common import fields
|
from funkwhale_api.common import fields
|
||||||
|
from funkwhale_api.common import preferences
|
||||||
|
|
||||||
|
|
||||||
def get_token():
|
def get_token():
|
||||||
return binascii.b2a_hex(os.urandom(15)).decode('utf-8')
|
return binascii.b2a_hex(os.urandom(15)).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
PERMISSIONS = [
|
PERMISSIONS_CONFIGURATION = {
|
||||||
'federation',
|
'federation': {
|
||||||
'library',
|
'label': 'Manage library federation',
|
||||||
'settings',
|
'help_text': 'Follow other instances, accept/deny library follow requests...',
|
||||||
'upload',
|
},
|
||||||
]
|
'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
|
@python_2_unicode_compatible
|
||||||
|
@ -48,27 +63,34 @@ class User(AbstractUser):
|
||||||
|
|
||||||
# permissions
|
# permissions
|
||||||
permission_federation = models.BooleanField(
|
permission_federation = models.BooleanField(
|
||||||
'Manage library federation',
|
PERMISSIONS_CONFIGURATION['federation']['label'],
|
||||||
help_text='Follow other instances, accept/deny library follow requests...',
|
help_text=PERMISSIONS_CONFIGURATION['federation']['help_text'],
|
||||||
default=False)
|
default=False)
|
||||||
permission_library = models.BooleanField(
|
permission_library = models.BooleanField(
|
||||||
'Manage library',
|
PERMISSIONS_CONFIGURATION['library']['label'],
|
||||||
help_text='Manage library',
|
help_text=PERMISSIONS_CONFIGURATION['library']['help_text'],
|
||||||
default=False)
|
default=False)
|
||||||
permission_settings = models.BooleanField(
|
permission_settings = models.BooleanField(
|
||||||
'Manage instance-level settings',
|
PERMISSIONS_CONFIGURATION['settings']['label'],
|
||||||
|
help_text=PERMISSIONS_CONFIGURATION['settings']['help_text'],
|
||||||
default=False)
|
default=False)
|
||||||
permission_upload = models.BooleanField(
|
permission_upload = models.BooleanField(
|
||||||
'Upload new content to the library',
|
PERMISSIONS_CONFIGURATION['upload']['label'],
|
||||||
|
help_text=PERMISSIONS_CONFIGURATION['upload']['help_text'],
|
||||||
default=False)
|
default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
|
defaults = preferences.get('users__default_permissions')
|
||||||
perms = {}
|
perms = {}
|
||||||
for p in PERMISSIONS:
|
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
|
perms[p] = v
|
||||||
return perms
|
return perms
|
||||||
|
|
||||||
|
|
|
@ -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']
|
|
@ -41,6 +41,17 @@ def test_get_permissions_regular(factories):
|
||||||
assert perms[p] is False
|
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', [
|
@pytest.mark.parametrize('args,perms,expected', [
|
||||||
({'is_superuser': True}, ['federation', 'library'], True),
|
({'is_superuser': True}, ['federation', 'library'], True),
|
||||||
({'is_superuser': False}, ['federation'], False),
|
({'is_superuser': False}, ['federation'], False),
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Admins can now configure default permissions that will be granted to all
|
||||||
|
registered users (#236)
|
|
@ -50,6 +50,13 @@
|
||||||
<label :for="setting.identifier">{{ setting.verbose_name }}</label>
|
<label :for="setting.identifier">{{ setting.verbose_name }}</label>
|
||||||
<p v-if="setting.help_text">{{ setting.help_text }}</p>
|
<p v-if="setting.help_text">{{ setting.help_text }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<select
|
||||||
|
v-else-if="setting.field.class === 'MultipleChoiceField'"
|
||||||
|
v-model="values[setting.identifier]"
|
||||||
|
multiple
|
||||||
|
class="ui search selection dropdown">
|
||||||
|
<option v-for="v in setting.additional_data.choices" :value="v[0]">{{ v[1] }}</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|
|
@ -81,6 +81,8 @@ axios.interceptors.response.use(function (response) {
|
||||||
}
|
}
|
||||||
if (error.response.status === 404) {
|
if (error.response.status === 404) {
|
||||||
error.backendErrors.push('Resource not found')
|
error.backendErrors.push('Resource not found')
|
||||||
|
} else if (error.response.status === 403) {
|
||||||
|
error.backendErrors.push('Permission denied')
|
||||||
} else if (error.response.status === 500) {
|
} else if (error.response.status === 500) {
|
||||||
error.backendErrors.push('A server error occured')
|
error.backendErrors.push('A server error occured')
|
||||||
} else if (error.response.data) {
|
} else if (error.response.data) {
|
||||||
|
|
|
@ -51,12 +51,12 @@ export default {
|
||||||
if (self.$store.state.route.hash) {
|
if (self.$store.state.route.hash) {
|
||||||
self.scrollTo(self.$store.state.route.hash.substr(1))
|
self.scrollTo(self.$store.state.route.hash.substr(1))
|
||||||
}
|
}
|
||||||
|
$('select.dropdown').dropdown()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
scrollTo (id) {
|
scrollTo (id) {
|
||||||
console.log(id, 'hello')
|
|
||||||
this.current = id
|
this.current = id
|
||||||
document.getElementById(id).scrollIntoView()
|
document.getElementById(id).scrollIntoView()
|
||||||
},
|
},
|
||||||
|
@ -86,7 +86,8 @@ export default {
|
||||||
id: 'users',
|
id: 'users',
|
||||||
settings: [
|
settings: [
|
||||||
'users__registration_enabled',
|
'users__registration_enabled',
|
||||||
'common__api_authentication_required'
|
'common__api_authentication_required',
|
||||||
|
'users__default_permissions'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue