From 3ccb70d0a827060e79ef3a84a5d9cbe884b1b412 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Thu, 29 Jun 2017 02:27:35 +0200 Subject: [PATCH] Fixed #15 again, now check authorization also using query param --- .env.dev | 4 +-- api/config/settings/common.py | 1 + api/funkwhale_api/common/authentication.py | 20 ++++++++++++ .../common/tests/test_jwt_querystring.py | 32 +++++++++++++++++++ api/funkwhale_api/music/views.py | 8 +++-- front/src/audio/queue.js | 14 +++++++- front/src/auth/index.js | 8 +++-- front/src/components/browse/Track.vue | 8 ++++- front/src/utils/url.js | 11 +++++++ 9 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 api/funkwhale_api/common/authentication.py create mode 100644 api/funkwhale_api/common/tests/test_jwt_querystring.py create mode 100644 front/src/utils/url.js diff --git a/.env.dev b/.env.dev index de58e2758..a7413b0ff 100644 --- a/.env.dev +++ b/.env.dev @@ -1,3 +1,3 @@ -BACKEND_URL=http://localhost:12081 +BACKEND_URL=http://localhost:6001 YOUTUBE_API_KEY= -API_AUTHENTICATION_REQUIRED=False +API_AUTHENTICATION_REQUIRED=True diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 5ba197145..b6e195ca2 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -288,6 +288,7 @@ REST_FRAMEWORK = { 'PAGE_SIZE': 25, 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS', 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication', diff --git a/api/funkwhale_api/common/authentication.py b/api/funkwhale_api/common/authentication.py new file mode 100644 index 000000000..b75f3b516 --- /dev/null +++ b/api/funkwhale_api/common/authentication.py @@ -0,0 +1,20 @@ +from rest_framework import exceptions +from rest_framework_jwt import authentication +from rest_framework_jwt.settings import api_settings + + +class JSONWebTokenAuthenticationQS( + authentication.BaseJSONWebTokenAuthentication): + + www_authenticate_realm = 'api' + + def get_jwt_value(self, request): + token = request.query_params.get('jwt') + if 'jwt' in request.query_params and not token: + msg = _('Invalid Authorization header. No credentials provided.') + raise exceptions.AuthenticationFailed(msg) + return token + + def authenticate_header(self, request): + return '{0} realm="{1}"'.format( + api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm) diff --git a/api/funkwhale_api/common/tests/test_jwt_querystring.py b/api/funkwhale_api/common/tests/test_jwt_querystring.py new file mode 100644 index 000000000..90e63775d --- /dev/null +++ b/api/funkwhale_api/common/tests/test_jwt_querystring.py @@ -0,0 +1,32 @@ +from test_plus.test import TestCase +from rest_framework_jwt.settings import api_settings + +from funkwhale_api.users.models import User + + +jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER +jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER + + +class TestJWTQueryString(TestCase): + www_authenticate_realm = 'api' + + def test_can_authenticate_using_token_param_in_url(self): + user = User.objects.create_superuser( + username='test', email='test@test.com', password='test') + + url = self.reverse('api:v1:tracks-list') + with self.settings(API_AUTHENTICATION_REQUIRED=True): + response = self.client.get(url) + + self.assertEqual(response.status_code, 401) + + payload = jwt_payload_handler(user) + token = jwt_encode_handler(payload) + print(payload, token) + with self.settings(API_AUTHENTICATION_REQUIRED=True): + response = self.client.get(url, data={ + 'jwt': token + }) + + self.assertEqual(response.status_code, 200) diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 506db1239..4a4032c57 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -1,5 +1,7 @@ import os import json +import unicodedata +import urllib from django.core.urlresolvers import reverse from django.db import models, transaction from django.db.models.functions import Length @@ -137,8 +139,10 @@ class TrackFileViewSet(viewsets.ReadOnlyModelViewSet): return Response(status=404) response = Response() - response["Content-Disposition"] = "attachment; filename={0}".format( - f.audio_file.name) + filename = "filename*=UTF-8''{}{}".format( + urllib.parse.quote(f.track.full_name), + os.path.splitext(f.audio_file.name)[-1]) + response["Content-Disposition"] = "attachment; {}".format(filename) response['X-Accel-Redirect'] = "{}{}".format( settings.PROTECT_FILES_PATH, f.audio_file.url) diff --git a/front/src/audio/queue.js b/front/src/audio/queue.js index c91c1d2ac..efa3dcdf7 100644 --- a/front/src/audio/queue.js +++ b/front/src/audio/queue.js @@ -5,6 +5,8 @@ import Audio from '@/audio' import backend from '@/audio/backend' import radios from '@/radios' import Vue from 'vue' +import url from '@/utils/url' +import auth from '@/auth' class Queue { constructor (options = {}) { @@ -181,7 +183,17 @@ class Queue { if (!file) { return this.next() } - this.audio = new Audio(backend.absoluteUrl(file.path), { + let path = backend.absoluteUrl(file.path) + + if (auth.user.authenticated) { + // we need to send the token directly in url + // so authentication can be checked by the backend + // because for audio files we cannot use the regular Authentication + // header + path = url.updateQueryString(path, 'jwt', auth.getAuthToken()) + } + + this.audio = new Audio(path, { preload: true, autoplay: true, rate: 1, diff --git a/front/src/auth/index.js b/front/src/auth/index.js index b5a3fb5ad..219a1531f 100644 --- a/front/src/auth/index.js +++ b/front/src/auth/index.js @@ -50,7 +50,7 @@ export default { checkAuth () { logger.default.info('Checking authentication...') - var jwt = cache.get('token') + var jwt = this.getAuthToken() var username = cache.get('username') if (jwt) { this.user.authenticated = true @@ -63,9 +63,13 @@ export default { } }, + getAuthToken () { + return cache.get('token') + }, + // The object to be passed as a header for authenticated requests getAuthHeader () { - return 'JWT ' + cache.get('token') + return 'JWT ' + this.getAuthToken() }, fetchProfile () { diff --git a/front/src/components/browse/Track.vue b/front/src/components/browse/Track.vue index 336af285b..1e1568793 100644 --- a/front/src/components/browse/Track.vue +++ b/front/src/components/browse/Track.vue @@ -61,6 +61,8 @@