diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts
index ebfb42711..1834f7be5 100644
--- a/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts
+++ b/client/src/app/+videos/+video-watch/shared/metadata/video-attributes.component.ts
@@ -22,11 +22,11 @@ export class VideoAttributesComponent implements OnInit {
constructor (private hooks: HooksService) { }
async ngOnInit () {
- this.pluginMetadata = await this.hooks.wrapFunResult(
- this.buildPluginMetadata.bind(this),
- { video: this.video },
+ this.pluginMetadata = await this.hooks.wrapObject(
+ this.pluginMetadata,
'video-watch',
- 'filter:video-watch.video-plugin-metadata.result'
+ 'filter:video-watch.video-plugin-metadata.result',
+ { video: this.video }
)
}
@@ -39,11 +39,4 @@ export class VideoAttributesComponent implements OnInit {
return this.video.tags
}
-
- // Used for plugin hooks
- private buildPluginMetadata (_options: {
- video: VideoDetails
- }): PluginMetadata[] {
- return []
- }
}
diff --git a/client/src/app/core/plugins/hooks.service.ts b/client/src/app/core/plugins/hooks.service.ts
index f325605e9..d9fef8389 100644
--- a/client/src/app/core/plugins/hooks.service.ts
+++ b/client/src/app/core/plugins/hooks.service.ts
@@ -48,15 +48,6 @@ export class HooksService {
return this.pluginService.runHook(hookResultName, result, params)
}
- async wrapFunResult
- (fun: RawFunction
, params: P, scope: PluginClientScope, hookResultName: H) {
- await this.pluginService.ensurePluginsAreLoaded(scope)
-
- const result = fun(params)
-
- return this.pluginService.runHook(hookResultName, result, params)
- }
-
runAction (hookName: U, scope: PluginClientScope, params?: T) {
// Use setTimeout to give priority to Angular change detector
setTimeout(() => {
@@ -66,13 +57,13 @@ export class HooksService {
})
}
- async wrapObject (result: T, scope: PluginClientScope, hookName: U) {
+ async wrapObject (result: T, scope: PluginClientScope, hookName: U, context?: any) {
await this.pluginService.ensurePluginsAreLoaded(scope)
- return this.wrapObjectWithoutScopeLoad(result, hookName)
+ return this.wrapObjectWithoutScopeLoad(result, hookName, context)
}
- private wrapObjectWithoutScopeLoad (result: T, hookName: U) {
- return this.pluginService.runHook(hookName, result)
+ private wrapObjectWithoutScopeLoad (result: T, hookName: U, context?: any) {
+ return this.pluginService.runHook(hookName, result, context)
}
}
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index def320730..750e3091c 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -48,7 +48,7 @@ activityPubClientRouter.get(
[ '/accounts?/:name', '/accounts?/:name/video-channels', '/a/:name', '/a/:name/video-channels' ],
executeIfActivityPub,
asyncMiddleware(localAccountValidator),
- accountController
+ asyncMiddleware(accountController)
)
activityPubClientRouter.get('/accounts?/:name/followers',
executeIfActivityPub,
@@ -69,13 +69,13 @@ activityPubClientRouter.get('/accounts?/:name/likes/:videoId',
executeIfActivityPub,
cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
asyncMiddleware(getAccountVideoRateValidatorFactory('like')),
- getAccountVideoRateFactory('like')
+ asyncMiddleware(getAccountVideoRateFactory('like'))
)
activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
executeIfActivityPub,
cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')),
- getAccountVideoRateFactory('dislike')
+ asyncMiddleware(getAccountVideoRateFactory('dislike'))
)
activityPubClientRouter.get(
@@ -131,7 +131,7 @@ activityPubClientRouter.get(
executeIfActivityPub,
asyncMiddleware(videoChannelsNameWithHostValidator),
ensureIsLocalChannel,
- videoChannelController
+ asyncMiddleware(videoChannelController)
)
activityPubClientRouter.get('/video-channels/:nameWithHost/followers',
executeIfActivityPub,
@@ -172,13 +172,13 @@ activityPubClientRouter.get(
activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId',
executeIfActivityPub,
asyncMiddleware(videoPlaylistElementAPGetValidator),
- videoPlaylistElementController
+ asyncMiddleware(videoPlaylistElementController)
)
activityPubClientRouter.get('/videos/local-viewer/:localViewerId',
executeIfActivityPub,
asyncMiddleware(getVideoLocalViewerValidator),
- getVideoLocalViewerController
+ asyncMiddleware(getVideoLocalViewerController)
)
// ---------------------------------------------------------------------------
@@ -189,10 +189,10 @@ export {
// ---------------------------------------------------------------------------
-function accountController (req: express.Request, res: express.Response) {
+async function accountController (req: express.Request, res: express.Response) {
const account = res.locals.account
- return activityPubResponse(activityPubContextify(account.toActivityPubObject(), 'Actor'), res)
+ return activityPubResponse(activityPubContextify(await account.toActivityPubObject(), 'Actor'), res)
}
async function accountFollowersController (req: express.Request, res: express.Response) {
@@ -246,7 +246,7 @@ async function videoController (req: express.Request, res: express.Response) {
const videoWithCaptions = Object.assign(video, { VideoCaptions: captions })
const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC)
- const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience)
+ const videoObject = audiencify(await videoWithCaptions.toActivityPubObject(), audience)
if (req.path.endsWith('/activity')) {
const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience)
@@ -321,10 +321,10 @@ async function videoCommentsController (req: express.Request, res: express.Respo
return activityPubResponse(activityPubContextify(json, 'Collection'), res)
}
-function videoChannelController (req: express.Request, res: express.Response) {
+async function videoChannelController (req: express.Request, res: express.Response) {
const videoChannel = res.locals.videoChannel
- return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject(), 'Actor'), res)
+ return activityPubResponse(activityPubContextify(await videoChannel.toActivityPubObject(), 'Actor'), res)
}
async function videoChannelFollowersController (req: express.Request, res: express.Response) {
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index f385c9927..681a5660c 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -63,7 +63,7 @@ async function buildActivities (actor: MActorLight, start: number, count: number
activities.push(announceActivity)
} else {
- const videoObject = video.toActivityPubObject()
+ const videoObject = await video.toActivityPubObject()
const createActivity = buildCreateActivity(video.url, byActor, videoObject, createActivityAudience)
activities.push(createActivity)
diff --git a/server/controllers/activitypub/utils.ts b/server/controllers/activitypub/utils.ts
index f851ef652..5de38eb43 100644
--- a/server/controllers/activitypub/utils.ts
+++ b/server/controllers/activitypub/utils.ts
@@ -1,6 +1,8 @@
import express from 'express'
-function activityPubResponse (data: any, res: express.Response) {
+async function activityPubResponse (promise: Promise, res: express.Response) {
+ const data = await promise
+
return res.type('application/activity+json; charset=utf-8')
.json(data)
}
diff --git a/server/lib/activitypub/context.ts b/server/lib/activitypub/context.ts
index 349c4d227..a3ca52a31 100644
--- a/server/lib/activitypub/context.ts
+++ b/server/lib/activitypub/context.ts
@@ -1,7 +1,8 @@
import { ContextType } from '@shared/models'
+import { Hooks } from '../plugins/hooks'
-function activityPubContextify (data: T, type: ContextType) {
- return { ...getContextData(type), ...data }
+async function activityPubContextify (data: T, type: ContextType) {
+ return { ...await getContextData(type), ...data }
}
// ---------------------------------------------------------------------------
@@ -165,10 +166,13 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
Rate: buildContext()
}
-function getContextData (type: ContextType) {
- return {
- '@context': contextStore[type]
- }
+async function getContextData (type: ContextType) {
+ const contextData = await Hooks.wrapObject(
+ contextStore[type],
+ 'filter:activity-pub.activity.context.build.result'
+ )
+
+ return { '@context': contextData }
}
function buildContext (contextValue?: ContextValue) {
diff --git a/server/lib/activitypub/send/http.ts b/server/lib/activitypub/send/http.ts
index d8d0b8542..ad7869853 100644
--- a/server/lib/activitypub/send/http.ts
+++ b/server/lib/activitypub/send/http.ts
@@ -52,9 +52,9 @@ function buildGlobalHeaders (body: any) {
}
}
-function signAndContextify (byActor: MActor, data: T, contextType: ContextType | null) {
+async function signAndContextify (byActor: MActor, data: T, contextType: ContextType | null) {
const activity = contextType
- ? activityPubContextify(data, contextType)
+ ? await activityPubContextify(data, contextType)
: data
return signJsonLDObject(byActor, activity)
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 7c3a6bdd0..0e996ab80 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -33,7 +33,7 @@ async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
const byActor = video.VideoChannel.Account.Actor
- const videoObject = video.toActivityPubObject()
+ const videoObject = await video.toActivityPubObject()
const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience)
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 24983dd19..5a66294e6 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -36,7 +36,7 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, transaction: T
video.VideoCaptions = await video.$get('VideoCaptions', { transaction })
}
- const videoObject = video.toActivityPubObject()
+ const videoObject = await video.toActivityPubObject()
const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience)
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index dc989417b..5bf29f45a 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -447,8 +447,8 @@ export class AccountModel extends Model>> {
}
}
- toActivityPubObject (this: MAccountAP) {
- const obj = this.Actor.toActivityPubObject(this.name)
+ async toActivityPubObject (this: MAccountAP) {
+ const obj = await this.Actor.toActivityPubObject(this.name)
return Object.assign(obj, {
summary: this.description
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index b71f5a197..67fccab68 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -809,8 +809,8 @@ export class VideoChannelModel extends Model {
+ const obj = await this.Actor.toActivityPubObject(this.name)
return Object.assign(obj, {
summary: this.description,
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 1a10d2da2..7fe2ec293 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -137,6 +137,7 @@ import { VideoShareModel } from './video-share'
import { VideoSourceModel } from './video-source'
import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
import { VideoTagModel } from './video-tag'
+import { Hooks } from '@server/lib/plugins/hooks'
export enum ScopeNames {
FOR_API = 'FOR_API',
@@ -1713,8 +1714,12 @@ export class VideoModel extends Model>> {
return files
}
- toActivityPubObject (this: MVideoAP): VideoObject {
- return videoModelToActivityPubObject(this)
+ toActivityPubObject (this: MVideoAP): Promise {
+ return Hooks.wrapObject(
+ videoModelToActivityPubObject(this),
+ 'filter:activity-pub.video.jsonld.build.result',
+ { video: this }
+ )
}
getTruncatedDescription () {
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts
index 22fae8331..c6f171633 100644
--- a/server/tests/api/activitypub/security.ts
+++ b/server/tests/api/activitypub/security.ts
@@ -2,10 +2,10 @@
import { expect } from 'chai'
import { buildDigest } from '@server/helpers/peertube-crypto'
-import { HTTP_SIGNATURE } from '@server/initializers/constants'
+import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants'
import { activityPubContextify } from '@server/lib/activitypub/context'
import { buildGlobalHeaders, signAndContextify } from '@server/lib/activitypub/send'
-import { makeFollowRequest, makePOSTAPRequest } from '@server/tests/shared'
+import { makePOSTAPRequest } from '@server/tests/shared'
import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
import { HttpStatusCode } from '@shared/models'
import { cleanupTests, createMultipleServers, killallServers, PeerTubeServer } from '@shared/server-commands'
@@ -43,6 +43,32 @@ function getAnnounceWithoutContext (server: PeerTubeServer) {
return result
}
+async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) {
+ const follow = {
+ type: 'Follow',
+ id: by.url + '/' + new Date().getTime(),
+ actor: by.url,
+ object: to.url
+ }
+
+ const body = await activityPubContextify(follow, 'Follow')
+
+ const httpSignature = {
+ algorithm: HTTP_SIGNATURE.ALGORITHM,
+ authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
+ keyId: by.url,
+ key: by.privateKey,
+ headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
+ }
+ const headers = {
+ 'digest': buildDigest(body),
+ 'content-type': 'application/activity+json',
+ 'accept': ACTIVITY_PUB.ACCEPT_HEADER
+ }
+
+ return makePOSTAPRequest(to.url + '/inbox', body, httpSignature, headers)
+}
+
describe('Test ActivityPub security', function () {
let servers: PeerTubeServer[]
let url: string
@@ -77,7 +103,7 @@ describe('Test ActivityPub security', function () {
describe('When checking HTTP signature', function () {
it('Should fail with an invalid digest', async function () {
- const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
+ const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = {
Digest: buildDigest({ hello: 'coucou' })
}
@@ -91,7 +117,7 @@ describe('Test ActivityPub security', function () {
})
it('Should fail with an invalid date', async function () {
- const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
+ const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
@@ -107,7 +133,7 @@ describe('Test ActivityPub security', function () {
await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
- const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
+ const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
try {
@@ -122,7 +148,7 @@ describe('Test ActivityPub security', function () {
await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
- const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
+ const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
const signatureOptions = baseHttpSignature()
@@ -145,7 +171,7 @@ describe('Test ActivityPub security', function () {
})
it('Should succeed with a valid HTTP signature draft 11 (without date but with (created))', async function () {
- const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
+ const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
const signatureOptions = baseHttpSignature()
@@ -156,7 +182,7 @@ describe('Test ActivityPub security', function () {
})
it('Should succeed with a valid HTTP signature', async function () {
- const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
+ const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
@@ -175,7 +201,7 @@ describe('Test ActivityPub security', function () {
await killallServers([ servers[1] ])
await servers[1].run()
- const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
+ const body = await activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
try {
diff --git a/server/tests/shared/requests.ts b/server/tests/shared/requests.ts
index 57120caca..0cfeab7b2 100644
--- a/server/tests/shared/requests.ts
+++ b/server/tests/shared/requests.ts
@@ -1,7 +1,4 @@
-import { buildDigest } from '@server/helpers/peertube-crypto'
import { doRequest } from '@server/helpers/requests'
-import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants'
-import { activityPubContextify } from '@server/lib/activitypub/context'
export function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) {
const options = {
@@ -13,29 +10,3 @@ export function makePOSTAPRequest (url: string, body: any, httpSignature: any, h
return doRequest(url, options)
}
-
-export async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) {
- const follow = {
- type: 'Follow',
- id: by.url + '/' + new Date().getTime(),
- actor: by.url,
- object: to.url
- }
-
- const body = activityPubContextify(follow, 'Follow')
-
- const httpSignature = {
- algorithm: HTTP_SIGNATURE.ALGORITHM,
- authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
- keyId: by.url,
- key: by.privateKey,
- headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
- }
- const headers = {
- 'digest': buildDigest(body),
- 'content-type': 'application/activity+json',
- 'accept': ACTIVITY_PUB.ACCEPT_HEADER
- }
-
- return makePOSTAPRequest(to.url + '/inbox', body, httpSignature, headers)
-}
diff --git a/shared/core-utils/i18n/i18n.ts b/shared/core-utils/i18n/i18n.ts
index c43e249a6..38c1b0cc9 100644
--- a/shared/core-utils/i18n/i18n.ts
+++ b/shared/core-utils/i18n/i18n.ts
@@ -75,8 +75,7 @@ const I18N_LOCALE_ALIAS = {
'zh': 'zh-Hans-CN'
}
-export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES)
- .concat(Object.keys(I18N_LOCALE_ALIAS))
+export const POSSIBLE_LOCALES = (Object.keys(I18N_LOCALES) as string[]).concat(Object.keys(I18N_LOCALE_ALIAS))
export function getDefaultLocale () {
return 'en-US'
diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts
index bbd08365c..7881beaa2 100644
--- a/shared/models/plugins/server/server-hook.model.ts
+++ b/shared/models/plugins/server/server-hook.model.ts
@@ -113,7 +113,13 @@ export const serverFilterHookObject = {
'filter:transcoding.manual.resolutions-to-transcode.result': true,
'filter:transcoding.auto.resolutions-to-transcode.result': true,
- 'filter:activity-pub.remote-video-comment.create.accept.result': true
+ 'filter:activity-pub.remote-video-comment.create.accept.result': true,
+
+ 'filter:activity-pub.activity.context.build.result': true,
+
+ // Filter the result of video JSON LD builder
+ // You may also need to use filter:activity-pub.activity.context.build.result to also update JSON LD context
+ 'filter:activity-pub.video.jsonld.build.result': true
}
export type ServerFilterHookName = keyof typeof serverFilterHookObject