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

This commit is contained in:
Eliot Berriot 2017-06-29 02:27:35 +02:00
parent 795cd7beb9
commit 3ccb70d0a8
9 changed files with 98 additions and 8 deletions

View File

@ -1,3 +1,3 @@
BACKEND_URL=http://localhost:12081 BACKEND_URL=http://localhost:6001
YOUTUBE_API_KEY= YOUTUBE_API_KEY=
API_AUTHENTICATION_REQUIRED=False API_AUTHENTICATION_REQUIRED=True

View File

@ -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',

View File

@ -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)

View File

@ -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)

View File

@ -1,5 +1,7 @@
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):
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)

View File

@ -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 {
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,

View File

@ -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 {
} }
}, },
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 () {

View File

@ -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 {
}, },
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 () {

11
front/src/utils/url.js Normal file
View File

@ -0,0 +1,11 @@
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
}
}
}