From 22a73cb879a5cc775d4bec3d72fa9c9cf52e5175 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 12 Dec 2019 15:47:47 +0100 Subject: [PATCH] Add internal privacy mode --- .../video/modals/video-download.component.ts | 2 +- client/src/app/shared/video/video.service.ts | 30 ++-- server/controllers/api/videos/index.ts | 24 ++-- server/controllers/api/videos/ownership.ts | 4 +- server/helpers/custom-validators/videos.ts | 2 +- server/initializers/constants.ts | 3 +- server/lib/activitypub/send/send-create.ts | 2 +- server/lib/activitypub/send/send-update.ts | 2 +- server/lib/activitypub/share.ts | 5 +- server/lib/activitypub/videos.ts | 2 +- server/lib/client-html.ts | 2 +- .../lib/schedulers/update-videos-scheduler.ts | 10 +- .../middlewares/validators/videos/videos.ts | 9 +- server/models/account/user.ts | 18 ++- server/models/video/schedule-video-update.ts | 2 +- server/models/video/video.ts | 55 +++++++- server/tests/api/videos/video-privacy.ts | 128 ++++++++++++++---- shared/models/videos/video-privacy.enum.ts | 3 +- .../videos/video-schedule-update.model.ts | 2 +- 19 files changed, 217 insertions(+), 88 deletions(-) diff --git a/client/src/app/shared/video/modals/video-download.component.ts b/client/src/app/shared/video/modals/video-download.component.ts index 5849ee458..712740086 100644 --- a/client/src/app/shared/video/modals/video-download.component.ts +++ b/client/src/app/shared/video/modals/video-download.component.ts @@ -59,7 +59,7 @@ export class VideoDownloadComponent { return } - const suffix = this.video.privacy.id === VideoPrivacy.PRIVATE + const suffix = this.video.privacy.id === VideoPrivacy.PRIVATE || this.video.privacy.id === VideoPrivacy.INTERNAL ? '?access_token=' + this.auth.getAccessToken() : '' diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts index 45366e3e3..b0fa55966 100644 --- a/client/src/app/shared/video/video.service.ts +++ b/client/src/app/shared/video/video.service.ts @@ -332,18 +332,26 @@ export class VideoService implements VideosProvider { } explainedPrivacyLabels (privacies: VideoConstant[]) { - const newPrivacies = privacies.slice() - - const privatePrivacy = newPrivacies.find(p => p.id === VideoPrivacy.PRIVATE) - if (privatePrivacy) privatePrivacy.label = this.i18n('Only I can see this video') - - const unlistedPrivacy = newPrivacies.find(p => p.id === VideoPrivacy.UNLISTED) - if (unlistedPrivacy) unlistedPrivacy.label = this.i18n('Only people with the private link can see this video') - - const publicPrivacy = newPrivacies.find(p => p.id === VideoPrivacy.PUBLIC) - if (publicPrivacy) publicPrivacy.label = this.i18n('Anyone can see this video') + const base = [ + { + id: VideoPrivacy.PRIVATE, + label: this.i18n('Only I can see this video') + }, + { + id: VideoPrivacy.UNLISTED, + label: this.i18n('Only people with the private link can see this video') + }, + { + id: VideoPrivacy.PUBLIC, + label: this.i18n('Anyone can see this video') + }, + { + id: VideoPrivacy.INTERNAL, + label: this.i18n('Only users of this instance can see this video') + } + ] - return privacies + return base.filter(o => !!privacies.find(p => p.id === o.id)) } private setVideoRate (id: number, rateType: UserVideoRateType) { diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 337795541..35f0b3152 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -326,9 +326,8 @@ async function updateVideo (req: express.Request, res: express.Response) { const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) const videoInfoToUpdate: VideoUpdate = req.body - const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE - const wasNotPrivateVideo = videoInstance.privacy !== VideoPrivacy.PRIVATE - const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED + const wasConfidentialVideo = videoInstance.isConfidential() + const hadPrivacyForFederation = videoInstance.hasPrivacyForFederation() // Process thumbnail or create it from the video const thumbnailModel = req.files && req.files['thumbnailfile'] @@ -359,17 +358,15 @@ async function updateVideo (req: express.Request, res: express.Response) { videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt) } + let isNewVideo = false if (videoInfoToUpdate.privacy !== undefined) { - const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) - videoInstance.privacy = newPrivacy + isNewVideo = videoInstance.isNewVideo(videoInfoToUpdate.privacy) - // The video was private, and is not anymore -> publish it - if (wasPrivateVideo === true && newPrivacy !== VideoPrivacy.PRIVATE) { - videoInstance.publishedAt = new Date() - } + const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) + videoInstance.setPrivacy(newPrivacy) - // The video was not private, but now it is -> we need to unfederate it - if (wasNotPrivateVideo === true && newPrivacy === VideoPrivacy.PRIVATE) { + // Unfederate the video if the new privacy is not compatible with federation + if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) { await VideoModel.sendDelete(videoInstance, { transaction: t }) } } @@ -392,7 +389,7 @@ async function updateVideo (req: express.Request, res: express.Response) { await videoInstanceUpdated.$set('VideoChannel', res.locals.videoChannel, { transaction: t }) videoInstanceUpdated.VideoChannel = res.locals.videoChannel - if (wasPrivateVideo === false) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t) + if (hadPrivacyForFederation === true) await changeVideoChannelShare(videoInstanceUpdated, oldVideoChannel, t) } // Schedule an update in the future? @@ -414,7 +411,6 @@ async function updateVideo (req: express.Request, res: express.Response) { transaction: t }) - const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t) auditLogger.update( @@ -427,7 +423,7 @@ async function updateVideo (req: express.Request, res: express.Response) { return videoInstanceUpdated }) - if (wasUnlistedVideo || wasPrivateVideo) { + if (wasConfidentialVideo) { Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated) } diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts index abb34082e..41d7cdc43 100644 --- a/server/controllers/api/videos/ownership.ts +++ b/server/controllers/api/videos/ownership.ts @@ -12,7 +12,7 @@ import { videosTerminateChangeOwnershipValidator } from '../../../middlewares' import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' -import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos' +import { VideoChangeOwnershipStatus, VideoState } from '../../../../shared/models/videos' import { VideoChannelModel } from '../../../models/video/video-channel' import { getFormattedObjects } from '../../../helpers/utils' import { changeVideoChannelShare } from '../../../lib/activitypub' @@ -111,7 +111,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) { const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight targetVideoUpdated.VideoChannel = channel - if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) { + if (targetVideoUpdated.hasPrivacyForFederation() && targetVideoUpdated.state === VideoState.PUBLISHED) { await changeVideoChannelShare(targetVideoUpdated, oldVideoChannel, t) await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor) } diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index e92ef9b92..ff93f2e99 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts @@ -102,7 +102,7 @@ function isVideoPrivacyValid (value: number) { } function isScheduleVideoUpdatePrivacyValid (value: number) { - return value === VideoPrivacy.UNLISTED || value === VideoPrivacy.PUBLIC + return value === VideoPrivacy.UNLISTED || value === VideoPrivacy.PUBLIC || value === VideoPrivacy.INTERNAL } function isVideoOriginallyPublishedAtValid (value: string | null) { diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index bdabe7f66..d0cf4d5de 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -353,7 +353,8 @@ let VIDEO_LANGUAGES: { [id: string]: string } = {} const VIDEO_PRIVACIES = { [ VideoPrivacy.PUBLIC ]: 'Public', [ VideoPrivacy.UNLISTED ]: 'Unlisted', - [ VideoPrivacy.PRIVATE ]: 'Private' + [ VideoPrivacy.PRIVATE ]: 'Private', + [ VideoPrivacy.INTERNAL ]: 'Internal' } const VIDEO_STATES = { diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index edbc14a73..1709d8348 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts @@ -18,7 +18,7 @@ import { } from '../../../typings/models' async function sendCreateVideo (video: MVideoAP, t: Transaction) { - if (video.privacy === VideoPrivacy.PRIVATE) return undefined + if (!video.hasPrivacyForFederation()) return undefined logger.info('Creating job to send video creation of %s.', video.url) diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index 44e0e1161..cb14b8dbf 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts @@ -25,7 +25,7 @@ import { async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) { const video = videoArg as MVideoAP - if (video.privacy === VideoPrivacy.PRIVATE) return undefined + if (!video.hasPrivacyForFederation()) return undefined logger.info('Creating job to update video %s.', video.url) diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index fdca9bed7..e847c4b7d 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts @@ -1,5 +1,4 @@ import { Transaction } from 'sequelize' -import { VideoPrivacy } from '../../../shared/models/videos' import { getServerActor } from '../../helpers/utils' import { VideoShareModel } from '../../models/video/video-share' import { sendUndoAnnounce, sendVideoAnnounce } from './send' @@ -10,10 +9,10 @@ import { getOrCreateActorAndServerAndModel } from './actor' import { logger } from '../../helpers/logger' import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' -import { MChannelActor, MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video' +import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video' async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) { - if (video.privacy === VideoPrivacy.PRIVATE) return undefined + if (!video.hasPrivacyForFederation()) return undefined return Promise.all([ shareByServer(video, t), diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index d80173e03..2fb1f8d49 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts @@ -79,7 +79,7 @@ async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVid // Check this is not a blacklisted video, or unfederated blacklisted video (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) && // Check the video is public/unlisted and published - video.privacy !== VideoPrivacy.PRIVATE && video.state === VideoState.PUBLISHED + video.hasPrivacyForFederation() && video.state === VideoState.PUBLISHED ) { // Fetch more attributes that we will need to serialize in AP object if (isArray(video.VideoCaptions) === false) { diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index a1f4ae858..42a30f84f 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts @@ -46,7 +46,7 @@ export class ClientHtml { ]) // Let Angular application handle errors - if (!video || video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { + if (!video || video.privacy === VideoPrivacy.PRIVATE || video.privacy === VideoPrivacy.INTERNAL || video.VideoBlacklist) { return ClientHtml.getIndexHTML(req, res) } diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts index 293bba91f..350a335d3 100644 --- a/server/lib/schedulers/update-videos-scheduler.ts +++ b/server/lib/schedulers/update-videos-scheduler.ts @@ -35,16 +35,14 @@ export class UpdateVideosScheduler extends AbstractScheduler { logger.info('Executing scheduled video update on %s.', video.uuid) if (schedule.privacy) { - const oldPrivacy = video.privacy - const isNewVideo = oldPrivacy === VideoPrivacy.PRIVATE - - video.privacy = schedule.privacy - if (isNewVideo === true) video.publishedAt = new Date() + const wasConfidentialVideo = video.isConfidential() + const isNewVideo = video.isNewVideo(schedule.privacy) + video.setPrivacy(schedule.privacy) await video.save({ transaction: t }) await federateVideoIfNeeded(video, isNewVideo, t) - if (oldPrivacy === VideoPrivacy.UNLISTED || oldPrivacy === VideoPrivacy.PRIVATE) { + if (wasConfidentialVideo) { const videoToPublish: MVideoFullLight = Object.assign(video, { ScheduleVideoUpdate: schedule, UserVideoHistories: [] }) publishedVideos.push(videoToPublish) } diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index ab984d84a..5e0182cc3 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts @@ -161,18 +161,15 @@ const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video- const videoAll = video as MVideoFullLight // Video private or blacklisted - if (video.privacy === VideoPrivacy.PRIVATE || videoAll.VideoBlacklist) { + if (videoAll.requiresAuth()) { await authenticatePromiseIfNeeded(req, res, authenticateInQuery) const user = res.locals.oauth ? res.locals.oauth.token.User : null // Only the owner or a user that have blacklist rights can see the video - if ( - !user || - (videoAll.VideoChannel && videoAll.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) - ) { + if (!user || !user.canGetVideo(videoAll)) { return res.status(403) - .json({ error: 'Cannot get this private or blacklisted video.' }) + .json({ error: 'Cannot get this private/internal or blacklisted video.' }) } return next() diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 38c6d474a..522ea3310 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -19,7 +19,7 @@ import { Table, UpdatedAt } from 'sequelize-typescript' -import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' +import { hasUserRight, USER_ROLE_LABELS, UserRight, VideoPrivacy } from '../../../shared' import { User, UserRole } from '../../../shared/models/users' import { isNoInstanceConfigWarningModal, @@ -63,7 +63,7 @@ import { MUserFormattable, MUserId, MUserNotifSettingChannelDefault, - MUserWithNotificationSetting + MUserWithNotificationSetting, MVideoFullLight } from '@server/typings/models' enum ScopeNames { @@ -575,6 +575,20 @@ export class UserModel extends Model { .then(u => u.map(u => u.username)) } + canGetVideo (video: MVideoFullLight) { + if (video.privacy === VideoPrivacy.INTERNAL) return true + + if (video.privacy === VideoPrivacy.PRIVATE) { + return video.VideoChannel && video.VideoChannel.Account.userId === this.id + } + + if (video.isBlacklisted()) { + return this.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) + } + + return false + } + hasRight (right: UserRight) { return hasUserRight(this.role, right) } diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts index eefc10f14..00b7f5524 100644 --- a/server/models/video/schedule-video-update.ts +++ b/server/models/video/schedule-video-update.ts @@ -26,7 +26,7 @@ export class ScheduleVideoUpdateModel extends Model { @AllowNull(true) @Default(null) @Column - privacy: VideoPrivacy.PUBLIC | VideoPrivacy.UNLISTED + privacy: VideoPrivacy.PUBLIC | VideoPrivacy.UNLISTED | VideoPrivacy.INTERNAL @CreatedAt createdAt: Date diff --git a/server/models/video/video.ts b/server/models/video/video.ts index af6fae0b6..7e18af497 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -348,9 +348,8 @@ export type AvailableForListIDsOptions = { // Only list public/published videos if (!options.filter || options.filter !== 'all-local') { - const privacyWhere = { - // Always list public videos - privacy: VideoPrivacy.PUBLIC, + + const publishWhere = { // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding [ Op.or ]: [ { @@ -364,8 +363,26 @@ export type AvailableForListIDsOptions = { } ] } + whereAnd.push(publishWhere) - whereAnd.push(privacyWhere) + // List internal videos if the user is logged in + if (options.user) { + const privacyWhere = { + [Op.or]: [ + { + privacy: VideoPrivacy.INTERNAL + }, + { + privacy: VideoPrivacy.PUBLIC + } + ] + } + + whereAnd.push(privacyWhere) + } else { // Or only public videos + const privacyWhere = { privacy: VideoPrivacy.PUBLIC } + whereAnd.push(privacyWhere) + } } if (options.videoPlaylistId) { @@ -1773,6 +1790,10 @@ export class VideoModel extends Model { } } + private static isPrivacyForFederation (privacy: VideoPrivacy) { + return privacy === VideoPrivacy.PUBLIC || privacy === VideoPrivacy.UNLISTED + } + static getCategoryLabel (id: number) { return VIDEO_CATEGORIES[ id ] || 'Misc' } @@ -1980,12 +2001,38 @@ export class VideoModel extends Model { return isOutdated(this, ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL) } + hasPrivacyForFederation () { + return VideoModel.isPrivacyForFederation(this.privacy) + } + + isNewVideo (newPrivacy: VideoPrivacy) { + return this.hasPrivacyForFederation() === false && VideoModel.isPrivacyForFederation(newPrivacy) === true + } + setAsRefreshed () { this.changed('updatedAt', true) return this.save() } + requiresAuth () { + return this.privacy === VideoPrivacy.PRIVATE || this.privacy === VideoPrivacy.INTERNAL || !!this.VideoBlacklist + } + + setPrivacy (newPrivacy: VideoPrivacy) { + if (this.privacy === VideoPrivacy.PRIVATE && newPrivacy !== VideoPrivacy.PRIVATE) { + this.publishedAt = new Date() + } + + this.privacy = newPrivacy + } + + isConfidential () { + return this.privacy === VideoPrivacy.PRIVATE || + this.privacy === VideoPrivacy.UNLISTED || + this.privacy === VideoPrivacy.INTERNAL + } + async publishIfNeededAndSave (t: Transaction) { if (this.state !== VideoState.PUBLISHED) { this.state = VideoState.PUBLISHED diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts index 40b539106..e630ca84a 100644 --- a/server/tests/api/videos/video-privacy.ts +++ b/server/tests/api/videos/video-privacy.ts @@ -16,14 +16,22 @@ import { userLogin } from '../../../../shared/extra-utils/users/login' import { createUser } from '../../../../shared/extra-utils/users/users' import { getMyVideos, getVideo, getVideoWithToken, updateVideo } from '../../../../shared/extra-utils/videos/videos' import { waitJobs } from '../../../../shared/extra-utils/server/jobs' +import { Video } from '@shared/models' const expect = chai.expect describe('Test video privacy', function () { let servers: ServerInfo[] = [] + let anotherUserToken: string + let privateVideoId: number let privateVideoUUID: string + + let internalVideoId: number + let internalVideoUUID: string + let unlistedVideoUUID: string + let now: number before(async function () { @@ -39,39 +47,63 @@ describe('Test video privacy', function () { await doubleFollow(servers[0], servers[1]) }) - it('Should upload a private video on server 1', async function () { + it('Should upload a private and internal videos on server 1', async function () { this.timeout(10000) - const attributes = { - privacy: VideoPrivacy.PRIVATE + for (const privacy of [ VideoPrivacy.PRIVATE, VideoPrivacy.INTERNAL ]) { + const attributes = { privacy } + await uploadVideo(servers[0].url, servers[0].accessToken, attributes) } - await uploadVideo(servers[0].url, servers[0].accessToken, attributes) await waitJobs(servers) }) - it('Should not have this private video on server 2', async function () { + it('Should not have these private and internal videos on server 2', async function () { const res = await getVideosList(servers[1].url) expect(res.body.total).to.equal(0) expect(res.body.data).to.have.lengthOf(0) }) - it('Should list my (private) videos', async function () { - const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 1) + it('Should not list the private and internal videos for an unauthenticated user on server 1', async function () { + const res = await getVideosList(servers[0].url) + + expect(res.body.total).to.equal(0) + expect(res.body.data).to.have.lengthOf(0) + }) + + it('Should not list the private video and list the internal video for an authenticated user on server 1', async function () { + const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) expect(res.body.total).to.equal(1) expect(res.body.data).to.have.lengthOf(1) - privateVideoId = res.body.data[0].id - privateVideoUUID = res.body.data[0].uuid + expect(res.body.data[0].privacy.id).to.equal(VideoPrivacy.INTERNAL) + }) + + it('Should list my (private and internal) videos', async function () { + const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 10) + + expect(res.body.total).to.equal(2) + expect(res.body.data).to.have.lengthOf(2) + + const videos: Video[] = res.body.data + + const privateVideo = videos.find(v => v.privacy.id === VideoPrivacy.PRIVATE) + privateVideoId = privateVideo.id + privateVideoUUID = privateVideo.uuid + + const internalVideo = videos.find(v => v.privacy.id === VideoPrivacy.INTERNAL) + internalVideoId = internalVideo.id + internalVideoUUID = internalVideo.uuid }) - it('Should not be able to watch this video with non authenticated user', async function () { + it('Should not be able to watch the private/internal video with non authenticated user', async function () { await getVideo(servers[0].url, privateVideoUUID, 401) + await getVideo(servers[0].url, internalVideoUUID, 401) }) - it('Should not be able to watch this private video with another user', async function () { + it('Should not be able to watch the private video with another user', async function () { this.timeout(10000) const user = { @@ -80,12 +112,16 @@ describe('Test video privacy', function () { } await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) - const token = await userLogin(servers[0], user) - await getVideoWithToken(servers[0].url, token, privateVideoUUID, 403) + anotherUserToken = await userLogin(servers[0], user) + await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, 403) }) - it('Should be able to watch this video with the correct user', async function () { - await getVideoWithToken(servers[0].url, servers[0].accessToken, privateVideoUUID) + it('Should be able to watch the internal video with another user', async function () { + await getVideoWithToken(servers[0].url, anotherUserToken, internalVideoUUID, 200) + }) + + it('Should be able to watch the private video with the correct user', async function () { + await getVideoWithToken(servers[0].url, servers[0].accessToken, privateVideoUUID, 200) }) it('Should upload an unlisted video on server 2', async function () { @@ -127,16 +163,27 @@ describe('Test video privacy', function () { } }) - it('Should update the private video to public on server 1', async function () { + it('Should update the private and internal videos to public on server 1', async function () { this.timeout(10000) - const attribute = { - name: 'super video public', - privacy: VideoPrivacy.PUBLIC + now = Date.now() + + { + const attribute = { + name: 'private video becomes public', + privacy: VideoPrivacy.PUBLIC + } + + await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, privateVideoId, attribute) } - now = Date.now() - await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, attribute) + { + const attribute = { + name: 'internal video becomes public', + privacy: VideoPrivacy.PUBLIC + } + await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, internalVideoId, attribute) + } await waitJobs(servers) }) @@ -144,18 +191,30 @@ describe('Test video privacy', function () { it('Should have this new public video listed on server 1 and 2', async function () { for (const server of servers) { const res = await getVideosList(server.url) + expect(res.body.total).to.equal(2) + expect(res.body.data).to.have.lengthOf(2) + + const videos: Video[] = res.body.data + const privateVideo = videos.find(v => v.name === 'private video becomes public') + const internalVideo = videos.find(v => v.name === 'internal video becomes public') + + expect(privateVideo).to.not.be.undefined + expect(internalVideo).to.not.be.undefined + + expect(new Date(privateVideo.publishedAt).getTime()).to.be.at.least(now) + // We don't change the publish date of internal videos + expect(new Date(internalVideo.publishedAt).getTime()).to.be.below(now) - expect(res.body.total).to.equal(1) - expect(res.body.data).to.have.lengthOf(1) - expect(res.body.data[0].name).to.equal('super video public') - expect(new Date(res.body.data[0].publishedAt).getTime()).to.be.at.least(now) + expect(privateVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC) + expect(internalVideo.privacy.id).to.equal(VideoPrivacy.PUBLIC) } }) - it('Should set this new video as private', async function () { + it('Should set these videos as private and internal', async function () { this.timeout(10000) - await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, { privacy: VideoPrivacy.PRIVATE }) + await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, { privacy: VideoPrivacy.PRIVATE }) + await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, { privacy: VideoPrivacy.INTERNAL }) await waitJobs(servers) @@ -168,10 +227,19 @@ 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) + + const privateVideo = videos.find(v => v.name === 'private video becomes public') + const internalVideo = videos.find(v => v.name === 'internal video becomes public') + + expect(privateVideo).to.not.be.undefined + expect(internalVideo).to.not.be.undefined - expect(res.body.total).to.equal(1) - expect(res.body.data).to.have.lengthOf(1) - expect(res.body.data[0].name).to.equal('super video public') + expect(privateVideo.privacy.id).to.equal(VideoPrivacy.INTERNAL) + expect(internalVideo.privacy.id).to.equal(VideoPrivacy.PRIVATE) } }) diff --git a/shared/models/videos/video-privacy.enum.ts b/shared/models/videos/video-privacy.enum.ts index 29888c7b8..17ed0c9bb 100644 --- a/shared/models/videos/video-privacy.enum.ts +++ b/shared/models/videos/video-privacy.enum.ts @@ -1,5 +1,6 @@ export enum VideoPrivacy { PUBLIC = 1, UNLISTED = 2, - PRIVATE = 3 + PRIVATE = 3, + INTERNAL = 4 } diff --git a/shared/models/videos/video-schedule-update.model.ts b/shared/models/videos/video-schedule-update.model.ts index b865c1614..87d74f654 100644 --- a/shared/models/videos/video-schedule-update.model.ts +++ b/shared/models/videos/video-schedule-update.model.ts @@ -2,5 +2,5 @@ import { VideoPrivacy } from './video-privacy.enum' export interface VideoScheduleUpdate { updateAt: Date | string - privacy?: VideoPrivacy.PUBLIC | VideoPrivacy.UNLISTED // Cannot schedule an update to PRIVATE + privacy?: VideoPrivacy.PUBLIC | VideoPrivacy.UNLISTED | VideoPrivacy.INTERNAL // Cannot schedule an update to PRIVATE } -- 2.41.0