From 553e0837da7d466b1307d859d7a9907dbba94e88 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@hat.eliotberriot.com>
Date: Wed, 15 Jun 2016 21:06:18 +0200
Subject: [PATCH] Fixed #3: can now require authentication via environment
 variable

---
 config/settings/common.py                     |  3 ++-
 funkwhale_api/common/__init__.py              |  0
 funkwhale_api/common/permissions.py           | 11 +++++++++
 .../favorites/tests/test_favorites.py         | 21 +++++++++++++++++
 funkwhale_api/favorites/views.py              |  3 +++
 funkwhale_api/history/views.py                |  5 ++--
 funkwhale_api/music/tests/test_api.py         | 23 +++++++++++++++++++
 funkwhale_api/music/views.py                  | 10 ++++----
 funkwhale_api/musicbrainz/views.py            |  9 ++++----
 funkwhale_api/providers/youtube/views.py      |  4 ++--
 funkwhale_api/radios/views.py                 |  5 ++--
 test.yml                                      |  1 +
 12 files changed, 79 insertions(+), 16 deletions(-)
 create mode 100644 funkwhale_api/common/__init__.py
 create mode 100644 funkwhale_api/common/permissions.py

diff --git a/config/settings/common.py b/config/settings/common.py
index 1af80e9..a176a4d 100644
--- a/config/settings/common.py
+++ b/config/settings/common.py
@@ -271,11 +271,12 @@ CORS_ORIGIN_ALLOW_ALL = True
 #     'funkwhale.localhost',
 # )
 CORS_ALLOW_CREDENTIALS = True
+API_AUTHENTICATION_REQUIRED = env.bool("API_AUTHENTICATION_REQUIRED", True)
 REST_FRAMEWORK = {
     'DEFAULT_PERMISSION_CLASSES': (
         'rest_framework.permissions.IsAuthenticated',
     ),
-    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
+    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
     'PAGE_SIZE': 25,
 
     'DEFAULT_AUTHENTICATION_CLASSES': (
diff --git a/funkwhale_api/common/__init__.py b/funkwhale_api/common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/funkwhale_api/common/permissions.py b/funkwhale_api/common/permissions.py
new file mode 100644
index 0000000..3f13b20
--- /dev/null
+++ b/funkwhale_api/common/permissions.py
@@ -0,0 +1,11 @@
+from django.conf import settings
+
+from rest_framework.permissions import BasePermission
+
+
+class ConditionalAuthentication(BasePermission):
+
+    def has_permission(self, request, view):
+        if settings.API_AUTHENTICATION_REQUIRED:
+            return request.user and request.user.is_authenticated()
+        return True
diff --git a/funkwhale_api/favorites/tests/test_favorites.py b/funkwhale_api/favorites/tests/test_favorites.py
index 9918c67..1182222 100644
--- a/funkwhale_api/favorites/tests/test_favorites.py
+++ b/funkwhale_api/favorites/tests/test_favorites.py
@@ -66,3 +66,24 @@ class TestFavorites(TestCase):
 
         self.assertEqual(response.status_code, 204)
         self.assertEqual(TrackFavorite.objects.count(), 0)
+
+    from funkwhale_api.users.models import User
+
+    def test_can_restrict_api_views_to_authenticated_users(self):
+        urls = [
+            ('api:favorites:tracks-list', 'get'),
+        ]
+
+        for route_name, method in urls:
+            url = self.reverse(route_name)
+            with self.settings(API_AUTHENTICATION_REQUIRED=True):
+                response = getattr(self.client, method)(url)
+            self.assertEqual(response.status_code, 401)
+
+        self.client.login(username=self.user.username, password='test')
+
+        for route_name, method in urls:
+            url = self.reverse(route_name)
+            with self.settings(API_AUTHENTICATION_REQUIRED=False):
+                response = getattr(self.client, method)(url)
+            self.assertEqual(response.status_code, 200)
diff --git a/funkwhale_api/favorites/views.py b/funkwhale_api/favorites/views.py
index edcb00f..dc8098b 100644
--- a/funkwhale_api/favorites/views.py
+++ b/funkwhale_api/favorites/views.py
@@ -3,6 +3,7 @@ from rest_framework import status
 from rest_framework.response import Response
 
 from funkwhale_api.music.models import Track
+from funkwhale_api.common.permissions import ConditionalAuthentication
 
 from . import models
 from . import serializers
@@ -14,7 +15,9 @@ class TrackFavoriteViewSet(mixins.CreateModelMixin,
 
     serializer_class = serializers.UserTrackFavoriteSerializer
     queryset = models.TrackFavorite.objects.all()
+    permission_classes = [ConditionalAuthentication]
 
+    
     def create(self, request, *args, **kwargs):
         serializer = self.get_serializer(data=request.data)
         serializer.is_valid(raise_exception=True)
diff --git a/funkwhale_api/history/views.py b/funkwhale_api/history/views.py
index 317ca78..d65a70f 100644
--- a/funkwhale_api/history/views.py
+++ b/funkwhale_api/history/views.py
@@ -4,6 +4,7 @@ from rest_framework.response import Response
 from rest_framework.decorators import detail_route
 
 from funkwhale_api.music.serializers import TrackSerializerNested
+from funkwhale_api.common.permissions import ConditionalAuthentication
 
 from . import models
 from . import serializers
@@ -14,11 +15,11 @@ class ListeningViewSet(mixins.CreateModelMixin,
 
     serializer_class = serializers.ListeningSerializer
     queryset = models.Listening.objects.all()
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
 
     def create(self, request, *args, **kwargs):
         return super().create(request, *args, **kwargs)
-        
+
     def get_queryset(self):
         queryset = super().get_queryset()
         if self.request.user.is_authenticated():
diff --git a/funkwhale_api/music/tests/test_api.py b/funkwhale_api/music/tests/test_api.py
index d4a1c14..36c1fa6 100644
--- a/funkwhale_api/music/tests/test_api.py
+++ b/funkwhale_api/music/tests/test_api.py
@@ -139,3 +139,26 @@ class TestAPI(TMPDirTestCaseMixin, TestCase):
         response = self.client.get(url + '?query={0}'.format(query))
 
         self.assertJSONEqual(expected, json.loads(response.content.decode('utf-8')))
+
+    def test_can_restrict_api_views_to_authenticated_users(self):
+        urls = [
+            ('api:tags-list', 'get'),
+            ('api:tracks-list', 'get'),
+            ('api:artists-list', 'get'),
+            ('api:albums-list', 'get'),
+        ]
+
+        for route_name, method in urls:
+            url = self.reverse(route_name)
+            with self.settings(API_AUTHENTICATION_REQUIRED=True):
+                response = getattr(self.client, method)(url)
+            self.assertEqual(response.status_code, 401)
+
+        user = User.objects.create_user(username='test', email='test@test.com', password='test')
+        self.client.login(username=user.username, password='test')
+
+        for route_name, method in urls:
+            url = self.reverse(route_name)
+            with self.settings(API_AUTHENTICATION_REQUIRED=False):
+                response = getattr(self.client, method)(url)
+            self.assertEqual(response.status_code, 200)
diff --git a/funkwhale_api/music/views.py b/funkwhale_api/music/views.py
index 971fbd7..6622b1e 100644
--- a/funkwhale_api/music/views.py
+++ b/funkwhale_api/music/views.py
@@ -11,6 +11,7 @@ from django.contrib.auth.decorators import login_required
 from django.utils.decorators import method_decorator
 
 from funkwhale_api.musicbrainz import api
+from funkwhale_api.common.permissions import ConditionalAuthentication
 from taggit.models import Tag
 
 from . import models
@@ -40,14 +41,14 @@ class TagViewSetMixin(object):
 class ArtistViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet):
     queryset = models.Artist.objects.all().order_by('-creation_date').prefetch_related('albums__tracks__files', 'albums__tracks__tags')
     serializer_class = serializers.ArtistSerializerNested
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
     search_fields = ['name']
     ordering_fields = ('creation_date',)
 
 class AlbumViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet):
     queryset = models.Album.objects.all().order_by('-creation_date').prefetch_related('tracks__tags')
     serializer_class = serializers.AlbumSerializerNested
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
     search_fields = ['title']
     ordering_fields = ('creation_date',)
 
@@ -64,15 +65,14 @@ class TrackViewSet(TagViewSetMixin, SearchMixin, viewsets.ReadOnlyModelViewSet):
     """
     queryset = models.Track.objects.all().select_related('album__artist', 'artist').prefetch_related('files')
     serializer_class = serializers.TrackSerializerNested
-    permission_classes = []
-    authentication_classes = []
+    permission_classes = [ConditionalAuthentication]
     search_fields = ['title', 'artist__name']
     ordering_fields = ('creation_date',)
 
 class TagViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = Tag.objects.all()
     serializer_class = serializers.TagSerializer
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
 
 
 class Search(views.APIView):
diff --git a/funkwhale_api/musicbrainz/views.py b/funkwhale_api/musicbrainz/views.py
index d6d8d38..21b2d88 100644
--- a/funkwhale_api/musicbrainz/views.py
+++ b/funkwhale_api/musicbrainz/views.py
@@ -3,19 +3,20 @@ from rest_framework.views import APIView
 from rest_framework.response import Response
 from rest_framework.decorators import list_route
 
+from funkwhale_api.common.permissions import ConditionalAuthentication
+
+
 from .client import api
 
 
 class ReleaseDetail(APIView):
-    authentication_classes = []
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
     def get(self, request, *args, **kwargs):
         result = api.releases.get(id=kwargs['uuid'], includes=['artists', 'recordings'])
         return Response(result)
 
 class SearchViewSet(viewsets.ViewSet):
-    authentication_classes = []
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
 
     @list_route(methods=['get'])
     def recordings(self, request, *args, **kwargs):
diff --git a/funkwhale_api/providers/youtube/views.py b/funkwhale_api/providers/youtube/views.py
index 8a41551..9e909d7 100644
--- a/funkwhale_api/providers/youtube/views.py
+++ b/funkwhale_api/providers/youtube/views.py
@@ -1,11 +1,11 @@
 from rest_framework.views import APIView
 from rest_framework.response import Response
+from funkwhale_api.common.permissions import ConditionalAuthentication
 
 from .client import client
 
 class APISearch(APIView):
-    authentication_classes = []
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
 
     def get(self, request, *args, **kwargs):
         results = client.search(request.GET['query'])
diff --git a/funkwhale_api/radios/views.py b/funkwhale_api/radios/views.py
index 37874d0..1ae788f 100644
--- a/funkwhale_api/radios/views.py
+++ b/funkwhale_api/radios/views.py
@@ -4,6 +4,7 @@ from rest_framework.response import Response
 from rest_framework.decorators import detail_route
 
 from funkwhale_api.music.serializers import TrackSerializerNested
+from funkwhale_api.common.permissions import ConditionalAuthentication
 
 from . import models
 from . import serializers
@@ -14,7 +15,7 @@ class RadioSessionViewSet(mixins.CreateModelMixin,
 
     serializer_class = serializers.RadioSessionSerializer
     queryset = models.RadioSession.objects.all()
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
 
     def get_queryset(self):
         queryset = super().get_queryset()
@@ -36,7 +37,7 @@ class RadioSessionTrackViewSet(mixins.CreateModelMixin,
                                viewsets.GenericViewSet):
     serializer_class = serializers.RadioSessionTrackSerializer
     queryset = models.RadioSessionTrack.objects.all()
-    permission_classes = []
+    permission_classes = [ConditionalAuthentication]
 
     def create(self, request, *args, **kwargs):
         serializer = self.get_serializer(data=request.data)
diff --git a/test.yml b/test.yml
index dc50a9b..c28a813 100644
--- a/test.yml
+++ b/test.yml
@@ -6,3 +6,4 @@ test:
     - .:/app
   environment:
     - DJANGO_SETTINGS_MODULE=config.settings.test
+    - API_AUTHENTICATION_REQUIRED=False
-- 
GitLab