Commit ea6bfbc7 authored by Georg Abenthung's avatar Georg Abenthung
Browse files

Merge remote-tracking branch 'upstream/develop' into develop

parents 04dd1067 1a362a24
Pipeline #15450 passed with stages
in 15 minutes and 20 seconds
......@@ -8,7 +8,9 @@ application = ProtocolTypeRouter(
{
# Empty for now (http->django views is added by default)
"websocket": AuthMiddlewareStack(
URLRouter([url("^api/v1/activity$", consumers.InstanceActivityConsumer)])
URLRouter(
[url("^api/v1/activity$", consumers.InstanceActivityConsumer.as_asgi())]
)
)
}
)
......@@ -36,7 +36,7 @@ pymemoize~=1.0.0
django-dynamic-preferences~=1.10
python-magic~=0.4.0
channels~=2.4.0
channels~=3.0.3
channels_redis~=3.3.0
uvicorn[standard]~=0.14.0
gunicorn~=20.1.0
......
......@@ -8,5 +8,6 @@ pytest-env~=0.6.0
pytest-mock~=3.6.0
pytest-randomly~=3.8.0
pytest-sugar~=0.9.0
pytest-asyncio~=0.15.1
requests-mock~=1.9.0
faker~=8.9.1
from funkwhale_api.common import consumers
import pytest
from channels.testing import WebsocketCommunicator
from funkwhale_api.common.consumers import JsonAuthConsumer
def test_auth_consumer_requires_valid_user(mocker):
m = mocker.patch("funkwhale_api.common.consumers.JsonAuthConsumer.close")
scope = {"user": None}
consumer = consumers.JsonAuthConsumer(scope=scope)
consumer.connect()
m.assert_called_once_with()
@pytest.mark.asyncio
async def test_auth_consumer_requires_valid_user():
communicator = WebsocketCommunicator(JsonAuthConsumer.as_asgi(), "api/v1/activity")
communicator.scope["user"] = None
connected, subprotocol = await communicator.connect()
assert not connected
def test_auth_consumer_requires_user_in_scope(mocker):
m = mocker.patch("funkwhale_api.common.consumers.JsonAuthConsumer.close")
scope = {}
consumer = consumers.JsonAuthConsumer(scope=scope)
consumer.connect()
m.assert_called_once_with()
def test_auth_consumer_accepts_connection(mocker, factories):
user = factories["users.User"]()
m = mocker.patch("funkwhale_api.common.consumers.JsonAuthConsumer.accept")
scope = {"user": user}
consumer = consumers.JsonAuthConsumer(scope=scope)
consumer.connect()
m.assert_called_once_with()
@pytest.mark.asyncio
async def test_auth_consumer_requires_user_in_scope():
communicator = WebsocketCommunicator(JsonAuthConsumer.as_asgi(), "api/v1/activity")
connected, subprotocol = await communicator.connect()
assert not connected
......@@ -13,7 +13,8 @@ module.exports = {
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module'
sourceType: 'module',
parser: 'babel-eslint'
},
plugins: [
'vue'
......
import Vue from "vue"
import Router from "vue-router"
import Vue from 'vue'
import Router from 'vue-router'
import store from '@/store'
Vue.use(Router)
function adminPermissions (to, from, next) {
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.settings === true) {
next()
} else {
console.log('Not authenticated. Redirecting to library.')
next({ name: 'library.index' })
}
}
function moderatorPermissions (to, from, next) {
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.moderation === true) {
next()
} else {
console.log('Not authenticated. Redirecting to library.')
next({ name: 'library.index' })
}
}
function libraryPermissions (to, from, next) {
if (store.state.auth.authenticated === true && store.state.auth.availablePermissions.library === true) {
next()
} else {
console.log('Not authenticated. Redirecting to library.')
next({ name: 'library.index' })
}
}
console.log('PROCESS', process.env)
export default new Router({
mode: "history",
linkActiveClass: "active",
base: process.env.VUE_APP_ROUTER_BASE_URL || "/",
scrollBehavior(to, from, savedPosition) {
mode: 'history',
linkActiveClass: 'active',
base: process.env.VUE_APP_ROUTER_BASE_URL || '/',
scrollBehavior (to, from, savedPosition) {
if (to.meta.preserveScrollPosition) {
return savedPosition
}
return new Promise(resolve => {
setTimeout(() => {
if (to.hash) {
resolve({ selector: to.hash });
resolve({ selector: to.hash })
}
let pos = savedPosition || { x: 0, y: 0 };
resolve(pos);
}, 100);
});
const pos = savedPosition || { x: 0, y: 0 }
resolve(pos)
}, 100)
})
},
routes: [
{
path: "/",
name: "index",
path: '/',
name: 'index',
component: () =>
import(/* webpackChunkName: "core" */ "@/components/Home")
import(/* webpackChunkName: "core" */ '@/components/Home')
},
{
path: "/front",
name: "front",
path: '/front',
name: 'front',
redirect: to => {
const { hash, query } = to
return { name: 'index', hash, query }
}
},
{
path: "/about",
name: "about",
path: '/about',
name: 'about',
component: () =>
import(/* webpackChunkName: "about" */ "@/components/About")
import(/* webpackChunkName: "about" */ '@/components/About')
},
{
path: "/login",
name: "login",
path: '/login',
name: 'login',
component: () =>
import(/* webpackChunkName: "login" */ "@/views/auth/Login"),
props: route => ({ next: route.query.next || "/library" })
import(/* webpackChunkName: "login" */ '@/views/auth/Login'),
props: route => ({ next: route.query.next || '/library' })
},
{
path: "/notifications",
name: "notifications",
path: '/notifications',
name: 'notifications',
component: () =>
import(/* webpackChunkName: "notifications" */ "@/views/Notifications")
import(/* webpackChunkName: "notifications" */ '@/views/Notifications')
},
{
path: "/auth/password/reset",
name: "auth.password-reset",
path: '/auth/password/reset',
name: 'auth.password-reset',
component: () =>
import(/* webpackChunkName: "password-reset" */ "@/views/auth/PasswordReset"),
import(/* webpackChunkName: "password-reset" */ '@/views/auth/PasswordReset'),
props: route => ({
defaultEmail: route.query.email
})
},
{
path: "/auth/callback",
name: "auth.callback",
path: '/auth/callback',
name: 'auth.callback',
component: () =>
import(/* webpackChunkName: "auth-callback" */ "@/views/auth/Callback"),
import(/* webpackChunkName: "auth-callback" */ '@/views/auth/Callback'),
props: route => ({
code: route.query.code,
state: route.query.state,
state: route.query.state
})
},
{
path: "/auth/email/confirm",
name: "auth.email-confirm",
path: '/auth/email/confirm',
name: 'auth.email-confirm',
component: () =>
import(/* webpackChunkName: "signup" */ "@/views/auth/EmailConfirm"),
import(/* webpackChunkName: "signup" */ '@/views/auth/EmailConfirm'),
props: route => ({
defaultKey: route.query.key
})
},
{
path: "/search",
name: "search",
path: '/search',
name: 'search',
component: () =>
import(/* webpackChunkName: "core" */ "@/views/Search"),
import(/* webpackChunkName: "core" */ '@/views/Search'),
props: route => ({
initialId: route.query.id,
initialType: route.query.type || 'artists',
initialQuery: route.query.q,
initialPage: parseInt(route.query.page) || 1,
initialPage: parseInt(route.query.page) || 1
})
},
{
path: "/auth/password/reset/confirm",
name: "auth.password-reset-confirm",
path: '/auth/password/reset/confirm',
name: 'auth.password-reset-confirm',
component: () =>
import(
/* webpackChunkName: "password-reset" */ "@/views/auth/PasswordResetConfirm"
/* webpackChunkName: "password-reset" */ '@/views/auth/PasswordResetConfirm'
),
props: route => ({
defaultUid: route.query.uid,
......@@ -109,10 +137,10 @@ export default new Router({
})
},
{
path: "/authorize",
name: "authorize",
path: '/authorize',
name: 'authorize',
component: () =>
import(/* webpackChunkName: "settings" */ "@/components/auth/Authorize"),
import(/* webpackChunkName: "settings" */ '@/components/auth/Authorize'),
props: route => ({
clientId: route.query.client_id,
redirectUri: route.query.redirect_uri,
......@@ -123,29 +151,29 @@ export default new Router({
})
},
{
path: "/signup",
name: "signup",
path: '/signup',
name: 'signup',
component: () =>
import(/* webpackChunkName: "signup" */ "@/views/auth/Signup"),
import(/* webpackChunkName: "signup" */ '@/views/auth/Signup'),
props: route => ({
defaultInvitation: route.query.invitation
})
},
{
path: "/logout",
name: "logout",
path: '/logout',
name: 'logout',
component: () =>
import(/* webpackChunkName: "login" */ "@/components/auth/Logout")
import(/* webpackChunkName: "login" */ '@/components/auth/Logout')
},
{
path: "/settings",
name: "settings",
path: '/settings',
name: 'settings',
component: () =>
import(/* webpackChunkName: "settings" */ "@/components/auth/Settings")
import(/* webpackChunkName: "settings" */ '@/components/auth/Settings')
},
{
path: "/settings/applications/new",
name: "settings.applications.new",
path: '/settings/applications/new',
name: 'settings.applications.new',
props: route => ({
scopes: route.query.scopes,
name: route.query.name,
......@@ -153,58 +181,58 @@ export default new Router({
}),
component: () =>
import(
/* webpackChunkName: "settings" */ "@/components/auth/ApplicationNew"
/* webpackChunkName: "settings" */ '@/components/auth/ApplicationNew'
)
},
{
path: "/settings/plugins",
name: "settings.plugins",
path: '/settings/plugins',
name: 'settings.plugins',
component: () =>
import(
/* webpackChunkName: "settings" */ "@/views/auth/Plugins"
/* webpackChunkName: "settings" */ '@/views/auth/Plugins'
)
},
{
path: "/settings/applications/:id/edit",
name: "settings.applications.edit",
path: '/settings/applications/:id/edit',
name: 'settings.applications.edit',
component: () =>
import(
/* webpackChunkName: "settings" */ "@/components/auth/ApplicationEdit"
/* webpackChunkName: "settings" */ '@/components/auth/ApplicationEdit'
),
props: true
},
...[{suffix: '.full', path: '/@:username@:domain'}, {suffix: '', path: '/@:username'}].map((route) => {
...[{ suffix: '.full', path: '/@:username@:domain' }, { suffix: '', path: '/@:username' }].map((route) => {
return {
path: route.path,
name: `profile${route.suffix}`,
component: () =>
import(/* webpackChunkName: "core" */ "@/views/auth/ProfileBase"),
import(/* webpackChunkName: "core" */ '@/views/auth/ProfileBase'),
props: true,
children: [
{
path: "",
path: '',
name: `profile${route.suffix}.overview`,
component: () =>
import(
/* webpackChunkName: "core" */ "@/views/auth/ProfileOverview"
/* webpackChunkName: "core" */ '@/views/auth/ProfileOverview'
)
},
{
path: "activity",
path: 'activity',
name: `profile${route.suffix}.activity`,
component: () =>
import(
/* webpackChunkName: "core" */ "@/views/auth/ProfileActivity"
/* webpackChunkName: "core" */ '@/views/auth/ProfileActivity'
)
},
}
]
}
}),
{
path: "/favorites",
name: "favorites",
path: '/favorites',
name: 'favorites',
component: () =>
import(/* webpackChunkName: "favorites" */ "@/components/favorites/List"),
import(/* webpackChunkName: "favorites" */ '@/components/favorites/List'),
props: route => ({
defaultOrdering: route.query.ordering,
defaultPage: route.query.page,
......@@ -212,29 +240,29 @@ export default new Router({
})
},
{
path: "/content",
path: '/content',
component: () =>
import(/* webpackChunkName: "core" */ "@/views/content/Base"),
import(/* webpackChunkName: "core" */ '@/views/content/Base'),
children: [
{
path: "",
name: "content.index",
path: '',
name: 'content.index',
component: () =>
import(/* webpackChunkName: "core" */ "@/views/content/Home")
import(/* webpackChunkName: "core" */ '@/views/content/Home')
}
]
},
{
path: "/content/libraries/tracks",
path: '/content/libraries/tracks',
component: () =>
import(/* webpackChunkName: "auth-libraries" */ "@/views/content/Base"),
import(/* webpackChunkName: "auth-libraries" */ '@/views/content/Base'),
children: [
{
path: "",
name: "content.libraries.files",
path: '',
name: 'content.libraries.files',
component: () =>
import(
/* webpackChunkName: "auth-libraries" */ "@/views/content/libraries/Files"
/* webpackChunkName: "auth-libraries" */ '@/views/content/libraries/Files'
),
props: route => ({
query: route.query.q
......@@ -243,50 +271,52 @@ export default new Router({
]
},
{
path: "/content/libraries",
path: '/content/libraries',
component: () =>
import(/* webpackChunkName: "auth-libraries" */ "@/views/content/Base"),
import(/* webpackChunkName: "auth-libraries" */ '@/views/content/Base'),
children: [
{
path: "",
name: "content.libraries.index",
path: '',
name: 'content.libraries.index',
component: () =>
import(
/* webpackChunkName: "auth-libraries" */ "@/views/content/libraries/Home"
/* webpackChunkName: "auth-libraries" */ '@/views/content/libraries/Home'
)
}
]
},
{
path: "/content/remote",
path: '/content/remote',
component: () =>
import(/* webpackChunkName: "auth-libraries" */ "@/views/content/Base"),
import(/* webpackChunkName: "auth-libraries" */ '@/views/content/Base'),
children: [
{
path: "",
name: "content.remote.index",
path: '',
name: 'content.remote.index',
component: () =>
import(/* webpackChunkName: "auth-libraries" */ "@/views/content/remote/Home")
import(/* webpackChunkName: "auth-libraries" */ '@/views/content/remote/Home')
}
]
},
{
path: "/manage/settings",
name: "manage.settings",
path: '/manage/settings',
name: 'manage.settings',
beforeEnter: adminPermissions,
component: () =>
import(/* webpackChunkName: "admin" */ "@/views/admin/Settings")
import(/* webpackChunkName: "admin" */ '@/views/admin/Settings')
},
{
path: "/manage/library",
path: '/manage/library',
beforeEnter: libraryPermissions,
component: () =>
import(/* webpackChunkName: "admin" */ "@/views/admin/library/Base"),
import(/* webpackChunkName: "admin" */ '@/views/admin/library/Base'),
children: [
{
path: "edits",
name: "manage.library.edits",
path: 'edits',
name: 'manage.library.edits',
component: () =>
import(
/* webpackChunkName: "admin" */ "@/views/admin/library/EditsList"
/* webpackChunkName: "admin" */ '@/views/admin/library/EditsList'
),
props: route => {
return {
......@@ -295,11 +325,11 @@ export default new Router({
}
},
{
path: "artists",
name: "manage.library.artists",
path: 'artists',
name: 'manage.library.artists',
component: () =>
import(
/* webpackChunkName: "admin" */ "@/views/admin/library/ArtistsList"
/* webpackChunkName: "admin" */ '@/views/admin/library/ArtistsList'
),
props: route => {
return {
......@@ -308,20 +338,20 @@ export default new Router({
}
},
{
path: "artists/:id",
name: "manage.library.artists.detail",
path: 'artists/:id',
name: 'manage.library.artists.detail',
component: () =>
import(
/* webpackChunkName: "admin" */ "@/views/admin/library/ArtistDetail"
/* webpackChunkName: "admin" */ '@/views/admin/library/ArtistDetail'
),
props: true
},
{
path: "channels",
name: "manage.channels",
path: 'channels',
name: 'manage.channels',
component: () =>
import(
/* webpackChunkName: "admin" */ "@/views/admin/ChannelsList"
/* webpackChunkName: "admin" */ '@/views/admin/ChannelsList'
),
props: route => {
return {
......@@ -330,20 +360,20 @@ export default new Router({
}
},
{
path: "channels/:id",
name: "manage.channels.detail",
path: 'channels/:id',
name: 'manage.channels.detail',
component: () =>
import(
/* webpackChunkName: "admin" */ "@/views/admin/ChannelDetail"
/* webpackChunkName: "admin" */ '@/views/admin/ChannelDetail'
),
props: true
},
{
path: "albums",
name: "manage.library.albums",
path: 'albums',
name: 'manage.library.albums',
component: () =>
import(
/* webpackChunkName: "admin" */ "@/views/admin/library/AlbumsList"
/* webpackChunkName: "admin" */ '@/views/admin/library/AlbumsList'
),
props: route => {
return {
......@@ -352,20 +382,20 @@ export default new Router({
}
},
{
path: "albums/:id",
name: "manage.library.albums.detail",
path: 'albums/:id',
name: 'manage.library.albums.detail',
component: () =>
import(
/* webpackChunkName: "admin" */ "@/views/admin/library/AlbumDetail"
/* webpackChunkName: "admin" */ '@/views/admin/library/AlbumDetail'
),
props: true
},
{
path: "tracks",
name: "manage.library.tracks",
path: 'tracks',
name: 'manage.library.tracks',
component: () =>
import(
/* webpackChunkName: "admin" */ "@/views/admin/library/TracksList"
/* webpackChunkName: "admin" */ '@/views/admin/library/TracksList'
),
props: route => {
return {
......@@ -374,20 +404,20 @@ export default new Router({
}
},
{
path: "tracks/:id",