diff --git a/api/funkwhale_api/common/permissions.py b/api/funkwhale_api/common/permissions.py
index ecfea4c9a51582031a69c8592d4c2592cb2988e6..c99c275c1f636b292c71ab1abb427daa658566fb 100644
--- a/api/funkwhale_api/common/permissions.py
+++ b/api/funkwhale_api/common/permissions.py
@@ -1,4 +1,7 @@
+import operator
+
 from django.conf import settings
+from django.http import Http404
 
 from rest_framework.permissions import BasePermission, DjangoModelPermissions
 
@@ -20,3 +23,39 @@ class HasModelPermission(DjangoModelPermissions):
     """
     def get_required_permissions(self, method, model_cls):
         return super().get_required_permissions(method, self.model)
+
+
+class OwnerPermission(BasePermission):
+    """
+    Ensure the request user is the owner of the object.
+
+    Usage:
+
+    class MyView(APIView):
+        model = MyModel
+        permission_classes = [OwnerPermission]
+        owner_field = 'owner'
+        owner_checks = ['read', 'write']
+    """
+    perms_map = {
+        'GET': 'read',
+        'OPTIONS': 'read',
+        'HEAD': 'read',
+        'POST': 'write',
+        'PUT': 'write',
+        'PATCH': 'write',
+        'DELETE': 'write',
+    }
+
+    def has_object_permission(self, request, view, obj):
+        method_check = self.perms_map[request.method]
+        owner_checks = getattr(view, 'owner_checks', ['read', 'write'])
+        if method_check not in owner_checks:
+            # check not enabled
+            return True
+
+        owner_field = getattr(view, 'owner_field', 'user')
+        owner = operator.attrgetter(owner_field)(obj)
+        if owner != request.user:
+            raise Http404
+        return True
diff --git a/api/tests/common/test_permissions.py b/api/tests/common/test_permissions.py
new file mode 100644
index 0000000000000000000000000000000000000000..95ad6c88d1029b77cf051db55860c68dc2ffef61
--- /dev/null
+++ b/api/tests/common/test_permissions.py
@@ -0,0 +1,43 @@
+import pytest
+
+from rest_framework.views import APIView
+
+from django.contrib.auth.models import AnonymousUser
+from django.http import Http404
+
+from funkwhale_api.common import permissions
+
+
+def test_owner_permission_owner_field_ok(nodb_factories, api_request):
+    playlist = nodb_factories['playlists.Playlist']()
+    view = APIView.as_view()
+    permission = permissions.OwnerPermission()
+    request = api_request.get('/')
+    setattr(request, 'user', playlist.user)
+    check = permission.has_object_permission(request, view, playlist)
+
+    assert check is True
+
+
+def test_owner_permission_owner_field_not_ok(nodb_factories, api_request):
+    playlist = nodb_factories['playlists.Playlist']()
+    view = APIView.as_view()
+    permission = permissions.OwnerPermission()
+    request = api_request.get('/')
+    setattr(request, 'user', AnonymousUser())
+
+    with pytest.raises(Http404):
+        permission.has_object_permission(request, view, playlist)
+
+
+
+def test_owner_permission_read_only(nodb_factories, api_request):
+    playlist = nodb_factories['playlists.Playlist']()
+    view = APIView.as_view()
+    setattr(view, 'owner_checks', ['write'])
+    permission = permissions.OwnerPermission()
+    request = api_request.get('/')
+    setattr(request, 'user', AnonymousUser())
+    check = permission.has_object_permission(request, view, playlist)
+
+    assert check is True