From 868314e8bf6bcc325b0fea35887071ef0614a46d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 20 Dec 2022 09:15:49 +0100 Subject: [PATCH] Add ability to get user from file token --- server/controllers/api/videos/token.ts | 2 +- server/lib/plugins/plugin-helpers-builder.ts | 2 +- server/lib/video-tokens-manager.ts | 22 +++++++-- .../middlewares/validators/shared/videos.ts | 12 ++--- .../fixtures/peertube-plugin-test/main.js | 5 +- server/tests/plugins/filter-hooks.ts | 47 ++++++++++++------- server/types/express.d.ts | 5 ++ 7 files changed, 64 insertions(+), 31 deletions(-) diff --git a/server/controllers/api/videos/token.ts b/server/controllers/api/videos/token.ts index 009b6dfb6..22387c3e8 100644 --- a/server/controllers/api/videos/token.ts +++ b/server/controllers/api/videos/token.ts @@ -22,7 +22,7 @@ export { function generateToken (req: express.Request, res: express.Response) { const video = res.locals.onlyVideo - const { token, expires } = VideoTokensManager.Instance.create(video.uuid) + const { token, expires } = VideoTokensManager.Instance.create({ videoUUID: video.uuid, user: res.locals.oauth.token.User }) return res.json({ files: { diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts index 7b1def6e3..e75c0b994 100644 --- a/server/lib/plugins/plugin-helpers-builder.ts +++ b/server/lib/plugins/plugin-helpers-builder.ts @@ -245,7 +245,7 @@ function buildUserHelpers () { }, getAuthUser: (res: express.Response) => { - const user = res.locals.oauth?.token?.User + const user = res.locals.oauth?.token?.User || res.locals.videoFileToken?.user if (!user) return undefined return UserModel.loadByIdFull(user.id) diff --git a/server/lib/video-tokens-manager.ts b/server/lib/video-tokens-manager.ts index c43085d16..17aa29cdd 100644 --- a/server/lib/video-tokens-manager.ts +++ b/server/lib/video-tokens-manager.ts @@ -1,5 +1,7 @@ import LRUCache from 'lru-cache' import { LRU_CACHE } from '@server/initializers/constants' +import { MUserAccountUrl } from '@server/types/models' +import { pick } from '@shared/core-utils' import { buildUUID } from '@shared/extra-utils' // --------------------------------------------------------------------------- @@ -10,19 +12,22 @@ class VideoTokensManager { private static instance: VideoTokensManager - private readonly lruCache = new LRUCache({ + private readonly lruCache = new LRUCache({ max: LRU_CACHE.VIDEO_TOKENS.MAX_SIZE, ttl: LRU_CACHE.VIDEO_TOKENS.TTL }) private constructor () {} - create (videoUUID: string) { + create (options: { + user: MUserAccountUrl + videoUUID: string + }) { const token = buildUUID() const expires = new Date(new Date().getTime() + LRU_CACHE.VIDEO_TOKENS.TTL) - this.lruCache.set(token, videoUUID) + this.lruCache.set(token, pick(options, [ 'user', 'videoUUID' ])) return { token, expires } } @@ -34,7 +39,16 @@ class VideoTokensManager { const value = this.lruCache.get(options.token) if (!value) return false - return value === options.videoUUID + return value.videoUUID === options.videoUUID + } + + getUserFromToken (options: { + token: string + }) { + const value = this.lruCache.get(options.token) + if (!value) return undefined + + return value.user } static get Instance () { diff --git a/server/middlewares/validators/shared/videos.ts b/server/middlewares/validators/shared/videos.ts index ebbfc0a0a..0033a32ff 100644 --- a/server/middlewares/validators/shared/videos.ts +++ b/server/middlewares/validators/shared/videos.ts @@ -180,18 +180,16 @@ async function checkCanAccessVideoStaticFiles (options: { return checkCanSeeVideo(options) } - if (!video.hasPrivateStaticPath()) return true - const videoFileToken = req.query.videoFileToken - if (!videoFileToken) { - res.sendStatus(HttpStatusCode.FORBIDDEN_403) - return false - } + if (videoFileToken && VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) { + const user = VideoTokensManager.Instance.getUserFromToken({ token: videoFileToken }) - if (VideoTokensManager.Instance.hasToken({ token: videoFileToken, videoUUID: video.uuid })) { + res.locals.videoFileToken = { user } return true } + if (!video.hasPrivateStaticPath()) return true + res.sendStatus(HttpStatusCode.FORBIDDEN_403) return false } diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index 19dccf26e..19ba9f278 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js @@ -250,7 +250,10 @@ async function register ({ registerHook, registerSetting, settingsManager, stora registerHook({ target: 'filter:api.download.video.allowed.result', - handler: (result, params) => { + handler: async (result, params) => { + const loggedInUser = await peertubeHelpers.user.getAuthUser(params.res) + if (loggedInUser) return { allowed: true } + if (params && !params.streamingPlaylist && params.video.name.includes('bad file')) { return { allowed: false, errorMessage: 'Cao Cao' } } diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 083fd43ca..6724b3bf8 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts @@ -430,6 +430,7 @@ describe('Test plugin filter hooks', function () { describe('Download hooks', function () { const downloadVideos: VideoDetails[] = [] + let downloadVideo2Token: string before(async function () { this.timeout(120000) @@ -459,6 +460,8 @@ describe('Test plugin filter hooks', function () { for (const uuid of uuids) { downloadVideos.push(await servers[0].videos.get({ id: uuid })) } + + downloadVideo2Token = await servers[0].videoToken.getVideoFileToken({ videoId: downloadVideos[2].uuid }) }) it('Should run filter:api.download.torrent.allowed.result', async function () { @@ -471,32 +474,42 @@ describe('Test plugin filter hooks', function () { it('Should run filter:api.download.video.allowed.result', async function () { { - const res = await makeRawRequest({ url: downloadVideos[1].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) + const refused = downloadVideos[1].files[0].fileDownloadUrl + const allowed = [ + downloadVideos[0].files[0].fileDownloadUrl, + downloadVideos[2].files[0].fileDownloadUrl + ] + + const res = await makeRawRequest({ url: refused, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) expect(res.body.error).to.equal('Cao Cao') - await makeRawRequest({ url: downloadVideos[0].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }) - await makeRawRequest({ url: downloadVideos[2].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }) + for (const url of allowed) { + await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) + await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) + } } { - const res = await makeRawRequest({ - url: downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl, - expectedStatus: HttpStatusCode.FORBIDDEN_403 - }) + const refused = downloadVideos[2].streamingPlaylists[0].files[0].fileDownloadUrl + const allowed = [ + downloadVideos[2].files[0].fileDownloadUrl, + downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl, + downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl + ] + + // Only streaming playlist is refuse + const res = await makeRawRequest({ url: refused, expectedStatus: HttpStatusCode.FORBIDDEN_403 }) expect(res.body.error).to.equal('Sun Jian') - await makeRawRequest({ url: downloadVideos[2].files[0].fileDownloadUrl, expectedStatus: HttpStatusCode.OK_200 }) + // But not we there is a user in res + await makeRawRequest({ url: refused, token: servers[0].accessToken, expectedStatus: HttpStatusCode.OK_200 }) + await makeRawRequest({ url: refused, query: { videoFileToken: downloadVideo2Token }, expectedStatus: HttpStatusCode.OK_200 }) - await makeRawRequest({ - url: downloadVideos[0].streamingPlaylists[0].files[0].fileDownloadUrl, - expectedStatus: HttpStatusCode.OK_200 - }) - - await makeRawRequest({ - url: downloadVideos[1].streamingPlaylists[0].files[0].fileDownloadUrl, - expectedStatus: HttpStatusCode.OK_200 - }) + // Other files work + for (const url of allowed) { + await makeRawRequest({ url, expectedStatus: HttpStatusCode.OK_200 }) + } } }) }) diff --git a/server/types/express.d.ts b/server/types/express.d.ts index 3738ffc47..99244d2a0 100644 --- a/server/types/express.d.ts +++ b/server/types/express.d.ts @@ -10,6 +10,7 @@ import { MChannelBannerAccountDefault, MChannelSyncChannel, MStreamingPlaylist, + MUserAccountUrl, MVideoChangeOwnershipFull, MVideoFile, MVideoFormattableDetails, @@ -187,6 +188,10 @@ declare module 'express' { actor: MActorAccountChannelId } + videoFileToken?: { + user: MUserAccountUrl + } + authenticated?: boolean registeredPlugin?: RegisteredPlugin