diff --git a/api/funkwhale_api/playlists/views.py b/api/funkwhale_api/playlists/views.py index f78b5b8016258f5ed018ac00aace509c4501f9e9..5ef00ebeaa7f8abdba0bf8cf693b688dcb16eade 100644 --- a/api/funkwhale_api/playlists/views.py +++ b/api/funkwhale_api/playlists/views.py @@ -1,9 +1,11 @@ from rest_framework import generics, mixins, viewsets from rest_framework import status from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticatedOrReadOnly from funkwhale_api.music.models import Track -from funkwhale_api.common.permissions import ConditionalAuthentication +from funkwhale_api.common import permissions +from funkwhale_api.common import fields from . import models from . import serializers @@ -12,24 +14,22 @@ from . import serializers class PlaylistViewSet( mixins.RetrieveModelMixin, mixins.CreateModelMixin, + mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet): serializer_class = serializers.PlaylistSerializer queryset = (models.Playlist.objects.all()) - permission_classes = [ConditionalAuthentication] - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - instance = self.perform_create(serializer) - serializer = self.get_serializer(instance=instance) - headers = self.get_success_headers(serializer.data) - return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + permission_classes = [ + permissions.ConditionalAuthentication, + permissions.OwnerPermission, + IsAuthenticatedOrReadOnly, + ] def get_queryset(self): - return self.queryset.filter(user=self.request.user) + return self.queryset.filter( + fields.privacy_level_query(self.request.user)) def perform_create(self, serializer): return serializer.save( @@ -41,23 +41,39 @@ class PlaylistViewSet( class PlaylistTrackViewSet( + mixins.RetrieveModelMixin, mixins.CreateModelMixin, + mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet): serializer_class = serializers.PlaylistTrackSerializer queryset = (models.PlaylistTrack.objects.all()) - permission_classes = [ConditionalAuthentication] + permission_classes = [ + permissions.ConditionalAuthentication, + permissions.OwnerPermission, + IsAuthenticatedOrReadOnly, + ] def create(self, request, *args, **kwargs): serializer = serializers.PlaylistTrackCreateSerializer( data=request.data) serializer.is_valid(raise_exception=True) + if serializer.validated_data['playlist'].user != request.user: + return Response( + {'playlist': [ + 'This playlist does not exists or you do not have the' + 'permission to edit it'] + }, + status=400) instance = self.perform_create(serializer) serializer = self.get_serializer(instance=instance) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def get_queryset(self): - return self.queryset.filter(playlist__user=self.request.user) + return self.queryset.filter( + fields.privacy_level_query( + self.request.user, + lookup_field='playlist__privacy_level')) diff --git a/api/tests/playlists/test_views.py b/api/tests/playlists/test_views.py index f3084e6a06b93121fd7f9c696d446543b6d2b0e9..0a3549b84c3df3c8cff0651a36d7ce79d685e46c 100644 --- a/api/tests/playlists/test_views.py +++ b/api/tests/playlists/test_views.py @@ -1,4 +1,6 @@ import json +import pytest + from django.urls import reverse from django.core.exceptions import ValidationError from django.utils import timezone @@ -48,3 +50,67 @@ def test_can_add_playlist_track_via_api(factories, logged_in_api_client): response = logged_in_api_client.post(url, data) plts = logged_in_api_client.user.playlists.latest('id').playlist_tracks.all() assert plts.first().track == tracks[0] + + +@pytest.mark.parametrize('name,method', [ + ('api:v1:playlist-tracks-list', 'post'), + ('api:v1:playlists-list', 'post'), +]) +def test_url_requires_login(name, method, factories, api_client): + url = reverse(name) + + response = getattr(api_client, method)(url, {}) + + assert response.status_code == 401 + + +def test_only_can_add_track_on_own_playlist_via_api( + factories, logged_in_api_client): + track = factories['music.Track']() + playlist = factories['playlists.Playlist']() + url = reverse('api:v1:playlist-tracks-list') + data = { + 'playlist': playlist.pk, + 'track': track.pk + } + + response = logged_in_api_client.post(url, data) + assert response.status_code == 400 + assert playlist.playlist_tracks.count() == 0 + + +@pytest.mark.parametrize('level', ['instance', 'me', 'followers']) +def test_playlist_privacy_respected_in_list_anon(level, factories, api_client): + factories['playlists.Playlist'](privacy_level=level) + url = reverse('api:v1:playlists-list') + response = api_client.get(url) + + assert response.data['count'] == 0 + + +@pytest.mark.parametrize('method', ['PUT', 'PATCH', 'DELETE']) +def test_only_owner_can_edit_playlist(method, factories, api_client): + playlist = factories['playlists.Playlist']() + url = reverse('api:v1:playlists-detail', kwargs={'pk': playlist.pk}) + response = api_client.get(url) + + assert response.status_code == 404 + + +@pytest.mark.parametrize('method', ['PUT', 'PATCH', 'DELETE']) +def test_only_owner_can_edit_playlist_track(method, factories, api_client): + plt = factories['playlists.PlaylistTrack']() + url = reverse('api:v1:playlist-tracks-detail', kwargs={'pk': plt.pk}) + response = api_client.get(url) + + assert response.status_code == 404 + + +@pytest.mark.parametrize('level', ['instance', 'me', 'followers']) +def test_playlist_track_privacy_respected_in_list_anon( + level, factories, api_client): + factories['playlists.PlaylistTrack'](playlist__privacy_level=level) + url = reverse('api:v1:playlist-tracks-list') + response = api_client.get(url) + + assert response.data['count'] == 0