diff --git a/config/default.yaml b/config/default.yaml index a0f2eb3a177..53a72861d0d 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -178,6 +178,10 @@ plugins: check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions url: 'https://packages.joinpeertube.org' +federation: + videos: + federate_unlisted: true + cache: previews: size: 500 # Max number of previews you want to cache diff --git a/config/production.yaml.example b/config/production.yaml.example index 8b8c98f8c1e..64e541b6de1 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -179,6 +179,10 @@ plugins: check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions url: 'https://packages.joinpeertube.org' +federation: + videos: + federate_unlisted: true + ############################################################################### # diff --git a/server/helpers/video.ts b/server/helpers/video.ts index 6f76cbdfc75..4bcc6d0b949 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts @@ -15,7 +15,8 @@ import { import { Response } from 'express' import { DEFAULT_AUDIO_RESOLUTION } from '@server/initializers/constants' import { JobQueue } from '@server/lib/job-queue' -import { VideoTranscodingPayload } from '@shared/models' +import { VideoPrivacy, VideoTranscodingPayload } from '@shared/models' +import { CONFIG } from "@server/initializers/config" type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 'only-immutable-attributes' @@ -96,6 +97,19 @@ function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) { : videoOrPlaylist } +function isPrivacyForFederation (privacy: VideoPrivacy) { + const castedPrivacy = parseInt(privacy + '', 10) + + return castedPrivacy === VideoPrivacy.PUBLIC || + (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED) +} + +function getPrivaciesForFederation () { + return (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true) + ? [ { privacy: VideoPrivacy.PUBLIC }, { privacy: VideoPrivacy.UNLISTED } ] + : [ { privacy: VideoPrivacy.PUBLIC } ] +} + export { VideoFetchType, VideoFetchByUrlType, @@ -103,5 +117,7 @@ export { getVideoWithAttributes, fetchVideoByUrl, addOptimizeOrMergeAudioJob, - extractVideo + extractVideo, + isPrivacyForFederation, + getPrivaciesForFederation } diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 56f8156c67a..bd8f02bc02e 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts @@ -34,7 +34,8 @@ function checkMissedConfig () { 'history.videos.max_age', 'views.videos.remote.max_age', 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', 'theme.default', - 'remote_redundancy.videos.accept_from' + 'remote_redundancy.videos.accept_from', + 'federation.videos.federate_unlisted' ] const requiredAlternatives = [ [ // set diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 6932b41e145..b64405ebad2 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts @@ -154,6 +154,11 @@ const CONFIG = { URL: config.get('plugins.index.url') } }, + FEDERATION: { + VIDEOS: { + FEDERATE_UNLISTED: config.get('federation.videos.federate_unlisted') + } + }, ADMIN: { get EMAIL () { return config.get('admin.email') } }, diff --git a/server/models/video/video.ts b/server/models/video/video.ts index f5194e25937..02a4c851c2f 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -126,6 +126,7 @@ import { ModelCache } from '@server/models/model-cache' import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder' import { buildNSFWFilter } from '@server/helpers/express-utils' import { getServerActor } from '@server/models/application/application' +import { getPrivaciesForFederation, isPrivacyForFederation } from "@server/helpers/video" export enum ScopeNames { AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', @@ -864,10 +865,7 @@ export class VideoModel extends Model { id: { [Op.in]: Sequelize.literal('(' + rawQuery + ')') }, - [Op.or]: [ - { privacy: VideoPrivacy.PUBLIC }, - { privacy: VideoPrivacy.UNLISTED } - ] + [Op.or]: getPrivaciesForFederation() }, include: [ { @@ -1582,12 +1580,6 @@ export class VideoModel extends Model { return videos } - private static isPrivacyForFederation (privacy: VideoPrivacy) { - const castedPrivacy = parseInt(privacy + '', 10) - - return castedPrivacy === VideoPrivacy.PUBLIC || castedPrivacy === VideoPrivacy.UNLISTED - } - static getCategoryLabel (id: number) { return VIDEO_CATEGORIES[id] || 'Misc' } @@ -1813,11 +1805,11 @@ export class VideoModel extends Model { } hasPrivacyForFederation () { - return VideoModel.isPrivacyForFederation(this.privacy) + return isPrivacyForFederation(this.privacy) } isNewVideo (newPrivacy: VideoPrivacy) { - return this.hasPrivacyForFederation() === false && VideoModel.isPrivacyForFederation(newPrivacy) === true + return this.hasPrivacyForFederation() === false && isPrivacyForFederation(newPrivacy) === true } setAsRefreshed () { diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts index 4bbbb90f3df..38e93bbe631 100644 --- a/server/tests/api/videos/video-privacy.ts +++ b/server/tests/api/videos/video-privacy.ts @@ -5,7 +5,7 @@ import 'mocha' import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' import { cleanupTests, - flushAndRunMultipleServers, + flushAndRunServer, getVideosList, getVideosListWithToken, ServerInfo, @@ -22,7 +22,7 @@ import { Video } from '@shared/models' const expect = chai.expect describe('Test video privacy', function () { - let servers: ServerInfo[] = [] + const servers: ServerInfo[] = [] let anotherUserToken: string let privateVideoId: number @@ -32,14 +32,24 @@ describe('Test video privacy', function () { let internalVideoUUID: string let unlistedVideoUUID: string + let nonFederatedUnlistedVideoUUID: string let now: number + const dontFederateUnlistedConfig = { + federation: { + videos: { + federate_unlisted: false + } + } + } + before(async function () { this.timeout(50000) // Run servers - servers = await flushAndRunMultipleServers(2) + servers.push(await flushAndRunServer(1, dontFederateUnlistedConfig)) + servers.push(await flushAndRunServer(2)) // Get the access tokens await setAccessTokensToServers(servers) @@ -164,6 +174,37 @@ describe('Test video privacy', function () { } }) + it('Should upload a non-federating unlisted video to server 1', async function () { + this.timeout(30000) + + const attributes = { + name: 'unlisted video', + privacy: VideoPrivacy.UNLISTED + } + await uploadVideo(servers[0].url, servers[0].accessToken, attributes) + + await waitJobs(servers) + }) + + it('Should list my new unlisted video', async function () { + const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 3) + + expect(res.body.total).to.equal(3) + expect(res.body.data).to.have.lengthOf(3) + + nonFederatedUnlistedVideoUUID = res.body.data[0].uuid + }) + + it('Should be able to get non-federated unlisted video from origin', async function () { + const res = await getVideo(servers[0].url, nonFederatedUnlistedVideoUUID) + + expect(res.body.name).to.equal('unlisted video') + }) + + it('Should not be able to get non-federated unlisted video from federated server', async function () { + await getVideo(servers[1].url, nonFederatedUnlistedVideoUUID, 404) + }) + it('Should update the private and internal videos to public on server 1', async function () { this.timeout(10000) @@ -230,8 +271,8 @@ describe('Test video privacy', function () { const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5) const videos = res.body.data - expect(res.body.total).to.equal(2) - expect(videos).to.have.lengthOf(2) + expect(res.body.total).to.equal(3) + expect(videos).to.have.lengthOf(3) const privateVideo = videos.find(v => v.name === 'private video becomes public') const internalVideo = videos.find(v => v.name === 'internal video becomes public')