diff --git a/api/funkwhale_api/users/migrations/0006_auto_20180517_2324.py b/api/funkwhale_api/users/migrations/0006_auto_20180517_2324.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c9ab0fadc99016e8a62f609ace117ea0b941965
--- /dev/null
+++ b/api/funkwhale_api/users/migrations/0006_auto_20180517_2324.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.0.4 on 2018-05-17 23:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0005_user_subsonic_api_token'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='user',
+            name='permission_federation',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='permission_library',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AddField(
+            model_name='user',
+            name='permission_settings',
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py
index 8273507c49bb23f1986b6b691fe90cc1fc8fea45..1de23092aedcb1063da2f970a0b6820610732c94 100644
--- a/api/funkwhale_api/users/models.py
+++ b/api/funkwhale_api/users/models.py
@@ -19,6 +19,13 @@ def get_token():
     return binascii.b2a_hex(os.urandom(15)).decode('utf-8')
 
 
+PERMISSIONS = [
+    'federation',
+    'library',
+    'settings',
+]
+
+
 @python_2_unicode_compatible
 class User(AbstractUser):
 
@@ -28,20 +35,6 @@ class User(AbstractUser):
 
     # updated on logout or password change, to invalidate JWT
     secret_key = models.UUIDField(default=uuid.uuid4, null=True)
-    # permissions that are used for API access and that worth serializing
-    relevant_permissions = {
-        # internal_codename : {external_codename}
-        'music.add_importbatch': {
-            'external_codename': 'import.launch',
-        },
-        'dynamic_preferences.change_globalpreferencemodel': {
-            'external_codename': 'settings.change',
-        },
-        'federation.change_library': {
-            'external_codename': 'federation.manage',
-        },
-    }
-
     privacy_level = fields.get_privacy_field()
 
     # Unfortunately, Subsonic API assumes a MD5/password authentication
@@ -52,12 +45,24 @@ class User(AbstractUser):
     subsonic_api_token = models.CharField(
         blank=True, null=True, max_length=255)
 
+    # permissions
+    permission_federation = models.BooleanField(default=False)
+    permission_library = models.BooleanField(default=False)
+    permission_settings = models.BooleanField(default=False)
+
     def __str__(self):
         return self.username
 
-    def add_permission(self, codename):
-        p = Permission.objects.get(codename=codename)
-        self.user_permissions.add(p)
+    def get_permissions(self):
+        perms = {}
+        for p in PERMISSIONS:
+            v = self.is_superuser or getattr(self, 'permission_{}'.format(p))
+            perms[p] = v
+        return perms
+
+    def has_permissions(self, *perms):
+        permissions = self.get_permissions()
+        return all([permissions[p] for p in perms])
 
     def get_absolute_url(self):
         return reverse('users:detail', kwargs={'username': self.username})
diff --git a/api/funkwhale_api/users/permissions.py b/api/funkwhale_api/users/permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ff49ff3fa6661aecd0616fdb87b5b43f89a95c2
--- /dev/null
+++ b/api/funkwhale_api/users/permissions.py
@@ -0,0 +1,19 @@
+from rest_framework.permissions import BasePermission
+
+
+class HasUserPermission(BasePermission):
+    """
+    Ensure the request user has the proper permissions.
+
+    Usage:
+
+    class MyView(APIView):
+        permission_classes = [HasUserPermission]
+        required_permissions = ['federation']
+    """
+    def has_permission(self, request, view):
+        if not hasattr(request, 'user') or not request.user:
+            return False
+        if request.user.is_anonymous:
+            return False
+        return request.user.has_permissions(*view.required_permissions)
diff --git a/api/tests/users/test_models.py b/api/tests/users/test_models.py
index c7cd12e9e3ba19a457fa0d66d68e6b3679925c84..49199e0a781b990268431f109ac0c8804f8391ea 100644
--- a/api/tests/users/test_models.py
+++ b/api/tests/users/test_models.py
@@ -1,3 +1,7 @@
+import pytest
+
+from funkwhale_api.users import models
+
 
 def test__str__(factories):
     user = factories['users.User'](username='hello')
@@ -16,3 +20,33 @@ def test_changing_password_updates_subsonic_api_token(factories):
 
     assert user.subsonic_api_token is not None
     assert user.subsonic_api_token != 'test'
+
+
+def test_get_permissions_superuser(factories):
+    user = factories['users.User'](is_superuser=True)
+
+    perms = user.get_permissions()
+    for p in models.PERMISSIONS:
+        assert perms[p] is True
+
+
+def test_get_permissions_regular(factories):
+    user = factories['users.User'](permission_library=True)
+
+    perms = user.get_permissions()
+    for p in models.PERMISSIONS:
+        if p == 'library':
+            assert perms[p] is True
+        else:
+            assert perms[p] is False
+
+
+@pytest.mark.parametrize('args,perms,expected', [
+    ({'is_superuser': True}, ['federation', 'library'], True),
+    ({'is_superuser': False}, ['federation'], False),
+    ({'permission_library': True}, ['library'], True),
+    ({'permission_library': True}, ['library', 'federation'], False),
+])
+def test_has_permissions(args, perms, expected, factories):
+    user = factories['users.User'](**args)
+    assert user.has_permissions(*perms) is expected
diff --git a/api/tests/users/test_permissions.py b/api/tests/users/test_permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..1564c761db59239eee5169bffd6ee92c2c148301
--- /dev/null
+++ b/api/tests/users/test_permissions.py
@@ -0,0 +1,56 @@
+import pytest
+from rest_framework.views import APIView
+
+from funkwhale_api.users import permissions
+
+
+def test_has_user_permission_no_user(api_request):
+    view = APIView.as_view()
+    permission = permissions.HasUserPermission()
+    request = api_request.get('/')
+    assert permission.has_permission(request, view) is False
+
+
+def test_has_user_permission_anonymous(anonymous_user, api_request):
+    view = APIView.as_view()
+    permission = permissions.HasUserPermission()
+    request = api_request.get('/')
+    setattr(request, 'user', anonymous_user)
+    assert permission.has_permission(request, view) is False
+
+
+@pytest.mark.parametrize('value', [True, False])
+def test_has_user_permission_logged_in_single(value, factories, api_request):
+    user = factories['users.User'](permission_federation=value)
+
+    class View(APIView):
+        required_permissions = ['federation']
+    view = View()
+    permission = permissions.HasUserPermission()
+    request = api_request.get('/')
+    setattr(request, 'user', user)
+    result = permission.has_permission(request, view)
+    assert result == user.has_permissions('federation') == value
+
+
+@pytest.mark.parametrize('federation,library,expected', [
+    (True, False, False),
+    (False, True, False),
+    (False, False, False),
+    (True, True, True),
+])
+def test_has_user_permission_logged_in_single(
+        federation, library, expected, factories, api_request):
+    user = factories['users.User'](
+        permission_federation=federation,
+        permission_library=library,
+    )
+
+    class View(APIView):
+        required_permissions = ['federation', 'library']
+    view = View()
+    permission = permissions.HasUserPermission()
+    request = api_request.get('/')
+    setattr(request, 'user', user)
+    result = permission.has_permission(request, view)
+    assert result == user.has_permissions('federation', 'library') == expected