Commit 3ccb70d0 authored by Agate's avatar Agate 💬

Fixed #15 again, now check authorization also using query param

parent 795cd7be
BACKEND_URL=http://localhost:12081 BACKEND_URL=http://localhost:6001
YOUTUBE_API_KEY= YOUTUBE_API_KEY=
API_AUTHENTICATION_REQUIRED=False API_AUTHENTICATION_REQUIRED=True
...@@ -288,6 +288,7 @@ REST_FRAMEWORK = { ...@@ -288,6 +288,7 @@ REST_FRAMEWORK = {
'PAGE_SIZE': 25, 'PAGE_SIZE': 25,
'DEFAULT_AUTHENTICATION_CLASSES': ( 'DEFAULT_AUTHENTICATION_CLASSES': (
'funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.BasicAuthentication',
......
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)
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)
import os import os
import json import json
import unicodedata
import urllib
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import models, transaction from django.db import models, transaction
from django.db.models.functions import Length from django.db.models.functions import Length
...@@ -137,8 +139,10 @@ class TrackFileViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -137,8 +139,10 @@ class TrackFileViewSet(viewsets.ReadOnlyModelViewSet):
return Response(status=404) return Response(status=404)
response = Response() response = Response()
response["Content-Disposition"] = "attachment; filename={0}".format( filename = "filename*=UTF-8''{}{}".format(
f.audio_file.name) 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( response['X-Accel-Redirect'] = "{}{}".format(
settings.PROTECT_FILES_PATH, settings.PROTECT_FILES_PATH,
f.audio_file.url) f.audio_file.url)
......
...@@ -5,6 +5,8 @@ import Audio from '@/audio' ...@@ -5,6 +5,8 @@ import Audio from '@/audio'
import backend from '@/audio/backend' import backend from '@/audio/backend'
import radios from '@/radios' import radios from '@/radios'
import Vue from 'vue' import Vue from 'vue'
import url from '@/utils/url'
import auth from '@/auth'
class Queue { class Queue {
constructor (options = {}) { constructor (options = {}) {
...@@ -181,7 +183,17 @@ class Queue { ...@@ -181,7 +183,17 @@ class Queue {
if (!file) { if (!file) {
return this.next() 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, preload: true,
autoplay: true, autoplay: true,
rate: 1, rate: 1,
......
...@@ -50,7 +50,7 @@ export default { ...@@ -50,7 +50,7 @@ export default {
checkAuth () { checkAuth () {
logger.default.info('Checking authentication...') logger.default.info('Checking authentication...')
var jwt = cache.get('token') var jwt = this.getAuthToken()
var username = cache.get('username') var username = cache.get('username')
if (jwt) { if (jwt) {
this.user.authenticated = true this.user.authenticated = true
...@@ -63,9 +63,13 @@ export default { ...@@ -63,9 +63,13 @@ export default {
} }
}, },
getAuthToken () {
return cache.get('token')
},
// The object to be passed as a header for authenticated requests // The object to be passed as a header for authenticated requests
getAuthHeader () { getAuthHeader () {
return 'JWT ' + cache.get('token') return 'JWT ' + this.getAuthToken()
}, },
fetchProfile () { fetchProfile () {
......
...@@ -61,6 +61,8 @@ ...@@ -61,6 +61,8 @@
<script> <script>
import auth from '@/auth'
import url from '@/utils/url'
import logger from '@/logging' import logger from '@/logging'
import backend from '@/audio/backend' import backend from '@/audio/backend'
import PlayButton from '@/components/audio/PlayButton' import PlayButton from '@/components/audio/PlayButton'
...@@ -121,7 +123,11 @@ export default { ...@@ -121,7 +123,11 @@ export default {
}, },
downloadUrl () { downloadUrl () {
if (this.track.files.length > 0) { if (this.track.files.length > 0) {
return backend.absoluteUrl(this.track.files[0].path) let u = backend.absoluteUrl(this.track.files[0].path)
if (auth.user.authenticated) {
u = url.updateQueryString(u, 'jwt', auth.getAuthToken())
}
return u
} }
}, },
lyricsSearchUrl () { lyricsSearchUrl () {
......
export default {
updateQueryString (uri, key, value) {
var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i')
var separator = uri.indexOf('?') !== -1 ? '&' : '?'
if (uri.match(re)) {
return uri.replace(re, '$1' + key + '=' + value + '$2')
} else {
return uri + separator + key + '=' + value
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment