From 4ba3b8ea1be85d95a508ac479f26b96ceea15971 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 26 Jan 2018 15:49:57 +0100 Subject: [PATCH] Don't rehost announced video activities --- server/controllers/activitypub/client.ts | 2 +- server/controllers/activitypub/outbox.ts | 5 +- server/initializers/constants.ts | 5 +- .../migrations/0185-video-share-url.ts | 38 +++++++++++++++ .../activitypub/process/process-announce.ts | 7 ++- server/lib/activitypub/send/send-announce.ts | 35 +++----------- server/lib/activitypub/share.ts | 24 ++++++---- server/models/video/video-share.ts | 15 +++++- server/models/video/video.ts | 46 +++++++++++++++---- 9 files changed, 124 insertions(+), 53 deletions(-) create mode 100644 server/initializers/migrations/0185-video-share-url.ts diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 2587ee212..7b60cc311 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts @@ -100,7 +100,7 @@ async function videoController (req: express.Request, res: express.Response, nex async function videoAnnounceController (req: express.Request, res: express.Response, next: express.NextFunction) { const share = res.locals.videoShare as VideoShareModel - const object = await buildVideoAnnounceToFollowers(share.Actor, res.locals.video, undefined) + const object = await buildVideoAnnounceToFollowers(share.Actor, share, res.locals.video, undefined) return res.json(activityPubContextify(object)) } diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index 41c6ffaeb..86cdcf4cd 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts @@ -5,7 +5,6 @@ import { pageToStartAndCount } from '../../helpers/core-utils' import { ACTIVITY_PUB } from '../../initializers/constants' import { announceActivityData, createActivityData } from '../../lib/activitypub/send' import { buildAudience } from '../../lib/activitypub/send/misc' -import { getAnnounceActivityPubUrl } from '../../lib/activitypub/url' import { asyncMiddleware, localAccountValidator } from '../../middlewares' import { AccountModel } from '../../models/account/account' import { ActorModel } from '../../models/activitypub/actor' @@ -48,9 +47,9 @@ async function outboxController (req: express.Request, res: express.Response, ne // This is a shared video if (video.VideoShares !== undefined && video.VideoShares.length !== 0) { + const videoShare = video.VideoShares[0] const announceAudience = buildAudience(followersMatrix[actor.id]) - const url = getAnnounceActivityPubUrl(video.url, actor) - const announceActivity = await announceActivityData(url, actor, video.url, undefined, announceAudience) + const announceActivity = await announceActivityData(videoShare.url, actor, video.url, undefined, announceAudience) activities.push(announceActivity) } else { diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 329d0ffe8..a88f9642c 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -12,7 +12,7 @@ let config: IConfig = require('config') // --------------------------------------------------------------------------- -const LAST_MIGRATION_VERSION = 180 +const LAST_MIGRATION_VERSION = 185 // --------------------------------------------------------------------------- @@ -196,6 +196,9 @@ const CONSTRAINTS_FIELDS = { VIDEO_COMMENTS: { TEXT: { min: 2, max: 3000 }, // Length URL: { min: 3, max: 2000 } // Length + }, + VIDEO_SHARE: { + URL: { min: 3, max: 2000 } // Length } } diff --git a/server/initializers/migrations/0185-video-share-url.ts b/server/initializers/migrations/0185-video-share-url.ts new file mode 100644 index 000000000..f7eeb0878 --- /dev/null +++ b/server/initializers/migrations/0185-video-share-url.ts @@ -0,0 +1,38 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize +}): Promise { + { + const query = 'DELETE FROM "videoShare" s1 ' + + 'USING (SELECT MIN(id) as id, "actorId", "videoId" FROM "videoShare" GROUP BY "actorId", "videoId" HAVING COUNT(*) > 1) s2 ' + + 'WHERE s1."actorId" = s2."actorId" AND s1."videoId" = s2."videoId" AND s1.id <> s2.id' + await utils.sequelize.query(query) + } + + { + const data = { + type: Sequelize.STRING, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.addColumn('videoShare', 'url', data) + + const query = `UPDATE "videoShare" SET "url" = (SELECT "url" FROM "video" WHERE "id" = "videoId") || '/announces/' || "actorId"` + await utils.sequelize.query(query) + + data.allowNull = false + await utils.queryInterface.changeColumn('videoShare', 'url', data) + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index 7dafc0593..09f2e80f3 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts @@ -43,11 +43,14 @@ async function shareVideo (actorAnnouncer: ActorModel, activity: ActivityAnnounc const share = { actorId: actorAnnouncer.id, - videoId: video.id + videoId: video.id, + url: activity.id } const [ , created ] = await VideoShareModel.findOrCreate({ - where: share, + where: { + url: activity.id + }, defaults: share, transaction: t }) diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts index 76cb3f80c..ed551a2b2 100644 --- a/server/lib/activitypub/send/send-announce.ts +++ b/server/lib/activitypub/send/send-announce.ts @@ -2,45 +2,23 @@ import { Transaction } from 'sequelize' import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub' import { ActorModel } from '../../../models/activitypub/actor' import { VideoModel } from '../../../models/video/video' -import { getAnnounceActivityPubUrl } from '../url' -import { - broadcastToFollowers, - getActorsInvolvedInVideo, - getAudience, - getObjectFollowersAudience, - getOriginVideoAudience, - unicastTo -} from './misc' -import { createActivityData } from './send-create' +import { VideoShareModel } from '../../../models/video/video-share' +import { broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from './misc' -async function buildVideoAnnounceToFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { - const url = getAnnounceActivityPubUrl(video.url, byActor) +async function buildVideoAnnounceToFollowers (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { const announcedObject = video.url const accountsToForwardView = await getActorsInvolvedInVideo(video, t) const audience = getObjectFollowersAudience(accountsToForwardView) - return announceActivityData(url, byActor, announcedObject, t, audience) + return announceActivityData(videoShare.url, byActor, announcedObject, t, audience) } -async function sendVideoAnnounceToFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { - const data = await buildVideoAnnounceToFollowers(byActor, video, t) +async function sendVideoAnnounceToFollowers (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { + const data = await buildVideoAnnounceToFollowers(byActor, videoShare, video, t) return broadcastToFollowers(data, byActor, [ byActor ], t) } -async function sendVideoAnnounceToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { - const url = getAnnounceActivityPubUrl(video.url, byActor) - - const videoObject = video.toActivityPubObject() - const announcedActivity = await createActivityData(url, video.VideoChannel.Account.Actor, videoObject, t) - - const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) - const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) - const data = await createActivityData(url, byActor, announcedActivity, t, audience) - - return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) -} - async function announceActivityData ( url: string, byActor: ActorModel, @@ -66,7 +44,6 @@ async function announceActivityData ( export { sendVideoAnnounceToFollowers, - sendVideoAnnounceToOrigin, announceActivityData, buildVideoAnnounceToFollowers } diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index fd374d03d..53ecd3dab 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts @@ -4,28 +4,36 @@ import { getServerActor } from '../../helpers/utils' import { VideoModel } from '../../models/video/video' import { VideoShareModel } from '../../models/video/video-share' import { sendVideoAnnounceToFollowers } from './send' +import { getAnnounceActivityPubUrl } from './url' async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { if (video.privacy === VideoPrivacy.PRIVATE) return undefined const serverActor = await getServerActor() - const serverShare = VideoShareModel.create({ + const serverShareUrl = getAnnounceActivityPubUrl(video.url, serverActor) + const serverSharePromise = VideoShareModel.create({ actorId: serverActor.id, - videoId: video.id + videoId: video.id, + url: serverShareUrl }, { transaction: t }) - const videoChannelShare = VideoShareModel.create({ + const videoChannelShareUrl = getAnnounceActivityPubUrl(video.url, video.VideoChannel.Actor) + const videoChannelSharePromise = VideoShareModel.create({ actorId: video.VideoChannel.actorId, - videoId: video.id + videoId: video.id, + url: videoChannelShareUrl }, { transaction: t }) - await Promise.all([ - serverShare, - videoChannelShare + const [ serverShare, videoChannelShare ] = await Promise.all([ + serverSharePromise, + videoChannelSharePromise ]) - return sendVideoAnnounceToFollowers(serverActor, video, t) + return Promise.all([ + sendVideoAnnounceToFollowers(serverActor, videoChannelShare, video, t), + sendVideoAnnounceToFollowers(serverActor, serverShare, video, t) + ]) } export { diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index 56576f98c..48ba68a4a 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts @@ -1,7 +1,10 @@ import * as Sequelize from 'sequelize' -import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' +import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' +import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' +import { CONSTRAINTS_FIELDS } from '../../initializers' import { AccountModel } from '../account/account' import { ActorModel } from '../activitypub/actor' +import { throwIfNotValid } from '../utils' import { VideoModel } from './video' import { VideoChannelModel } from './video-channel' @@ -40,10 +43,20 @@ enum ScopeNames { }, { fields: [ 'videoId' ] + }, + { + fields: [ 'url' ], + unique: true } ] }) export class VideoShareModel extends Model { + + @AllowNull(false) + @Is('VideoShareUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_SHARE.URL.max)) + url: string + @CreatedAt createdAt: Date diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 81d8a64ff..bd834b088 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts @@ -5,8 +5,26 @@ import * as parseTorrent from 'parse-torrent' import { join } from 'path' import * as Sequelize from 'sequelize' import { - AfterDestroy, AllowNull, BeforeDestroy, BelongsTo, BelongsToMany, Column, CreatedAt, DataType, Default, ForeignKey, HasMany, - IFindOptions, Is, IsInt, IsUUID, Min, Model, Scopes, Table, UpdatedAt + AfterDestroy, + AllowNull, + BeforeDestroy, + BelongsTo, + BelongsToMany, + Column, + CreatedAt, + DataType, + Default, + ForeignKey, + HasMany, + IFindOptions, + Is, + IsInt, + IsUUID, + Min, + Model, + Scopes, + Table, + UpdatedAt } from 'sequelize-typescript' import { VideoPrivacy, VideoResolution } from '../../../shared' import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' @@ -16,17 +34,30 @@ import { createTorrentPromise, renamePromise, statPromise, unlinkPromise, writeF import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' import { isBooleanValid } from '../../helpers/custom-validators/misc' import { - isVideoCategoryValid, isVideoDescriptionValid, isVideoDurationValid, isVideoLanguageValid, isVideoLicenceValid, isVideoNameValid, + isVideoCategoryValid, + isVideoDescriptionValid, + isVideoDurationValid, + isVideoLanguageValid, + isVideoLicenceValid, + isVideoNameValid, isVideoPrivacyValid } from '../../helpers/custom-validators/videos' import { generateImageFromVideoFile, getVideoFileHeight, transcode } from '../../helpers/ffmpeg-utils' import { logger } from '../../helpers/logger' import { getServerActor } from '../../helpers/utils' import { - API_VERSION, CONFIG, CONSTRAINTS_FIELDS, PREVIEWS_SIZE, REMOTE_SCHEME, STATIC_PATHS, THUMBNAILS_SIZE, VIDEO_CATEGORIES, - VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES + API_VERSION, + CONFIG, + CONSTRAINTS_FIELDS, + PREVIEWS_SIZE, + REMOTE_SCHEME, + STATIC_PATHS, + THUMBNAILS_SIZE, + VIDEO_CATEGORIES, + VIDEO_LANGUAGES, + VIDEO_LICENCES, + VIDEO_PRIVACIES } from '../../initializers' -import { getAnnounceActivityPubUrl } from '../../lib/activitypub' import { sendDeleteVideo } from '../../lib/activitypub/send' import { AccountModel } from '../account/account' import { AccountVideoRateModel } from '../account/account-video-rate' @@ -936,8 +967,7 @@ export class VideoModel extends Model { const shares: string[] = [] for (const videoShare of this.VideoShares) { - const shareUrl = getAnnounceActivityPubUrl(this.url, videoShare.Actor) - shares.push(shareUrl) + shares.push(videoShare.url) } sharesObject = activityPubCollection(shares)