Fixed #15 again, now check authorization also using query param
This commit is contained in:
parent
795cd7beb9
commit
3ccb70d0a8
4
.env.dev
4
.env.dev
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue