Refactor AP context builder

pull/4897/head
Chocobozzz 2022-03-23 16:14:33 +01:00
parent 7e98a7df7d
commit a219c9100b
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
28 changed files with 515 additions and 287 deletions

View File

@ -186,35 +186,35 @@ export {
function accountController (req: express.Request, res: express.Response) {
const account = res.locals.account
return activityPubResponse(activityPubContextify(account.toActivityPubObject()), res)
return activityPubResponse(activityPubContextify(account.toActivityPubObject(), 'Actor'), res)
}
async function accountFollowersController (req: express.Request, res: express.Response) {
const account = res.locals.account
const activityPubResult = await actorFollowers(req, account.Actor)
return activityPubResponse(activityPubContextify(activityPubResult), res)
return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
}
async function accountFollowingController (req: express.Request, res: express.Response) {
const account = res.locals.account
const activityPubResult = await actorFollowing(req, account.Actor)
return activityPubResponse(activityPubContextify(activityPubResult), res)
return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
}
async function accountPlaylistsController (req: express.Request, res: express.Response) {
const account = res.locals.account
const activityPubResult = await actorPlaylists(req, { account })
return activityPubResponse(activityPubContextify(activityPubResult), res)
return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
}
async function videoChannelPlaylistsController (req: express.Request, res: express.Response) {
const channel = res.locals.videoChannel
const activityPubResult = await actorPlaylists(req, { channel })
return activityPubResponse(activityPubContextify(activityPubResult), res)
return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
}
function getAccountVideoRateFactory (rateType: VideoRateType) {
@ -226,7 +226,7 @@ function getAccountVideoRateFactory (rateType: VideoRateType) {
? buildLikeActivity(accountVideoRate.url, byActor, accountVideoRate.Video)
: buildDislikeActivity(accountVideoRate.url, byActor, accountVideoRate.Video)
return activityPubResponse(activityPubContextify(APObject), res)
return activityPubResponse(activityPubContextify(APObject, 'Rate'), res)
}
}
@ -244,10 +244,10 @@ async function videoController (req: express.Request, res: express.Response) {
if (req.path.endsWith('/activity')) {
const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience)
return activityPubResponse(activityPubContextify(data), res)
return activityPubResponse(activityPubContextify(data, 'Video'), res)
}
return activityPubResponse(activityPubContextify(videoObject), res)
return activityPubResponse(activityPubContextify(videoObject, 'Video'), res)
}
async function videoAnnounceController (req: express.Request, res: express.Response) {
@ -274,7 +274,7 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
}
const json = await activityPubCollectionPagination(getLocalVideoSharesActivityPubUrl(video), handler, req.query.page)
return activityPubResponse(activityPubContextify(json), res)
return activityPubResponse(activityPubContextify(json, 'Collection'), res)
}
async function videoLikesController (req: express.Request, res: express.Response) {
@ -284,7 +284,7 @@ async function videoLikesController (req: express.Request, res: express.Response
const json = await videoRates(req, 'like', video, getLocalVideoLikesActivityPubUrl(video))
return activityPubResponse(activityPubContextify(json), res)
return activityPubResponse(activityPubContextify(json, 'Collection'), res)
}
async function videoDislikesController (req: express.Request, res: express.Response) {
@ -294,7 +294,7 @@ async function videoDislikesController (req: express.Request, res: express.Respo
const json = await videoRates(req, 'dislike', video, getLocalVideoDislikesActivityPubUrl(video))
return activityPubResponse(activityPubContextify(json), res)
return activityPubResponse(activityPubContextify(json, 'Collection'), res)
}
async function videoCommentsController (req: express.Request, res: express.Response) {
@ -312,27 +312,27 @@ async function videoCommentsController (req: express.Request, res: express.Respo
}
const json = await activityPubCollectionPagination(getLocalVideoCommentsActivityPubUrl(video), handler, req.query.page)
return activityPubResponse(activityPubContextify(json), res)
return activityPubResponse(activityPubContextify(json, 'Collection'), res)
}
function videoChannelController (req: express.Request, res: express.Response) {
const videoChannel = res.locals.videoChannel
return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res)
return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject(), 'Actor'), res)
}
async function videoChannelFollowersController (req: express.Request, res: express.Response) {
const videoChannel = res.locals.videoChannel
const activityPubResult = await actorFollowers(req, videoChannel.Actor)
return activityPubResponse(activityPubContextify(activityPubResult), res)
return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
}
async function videoChannelFollowingController (req: express.Request, res: express.Response) {
const videoChannel = res.locals.videoChannel
const activityPubResult = await actorFollowing(req, videoChannel.Actor)
return activityPubResponse(activityPubContextify(activityPubResult), res)
return activityPubResponse(activityPubContextify(activityPubResult, 'Collection'), res)
}
async function videoCommentController (req: express.Request, res: express.Response) {
@ -350,11 +350,11 @@ async function videoCommentController (req: express.Request, res: express.Respon
if (req.path.endsWith('/activity')) {
const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject, audience)
return activityPubResponse(activityPubContextify(data), res)
return activityPubResponse(activityPubContextify(data, 'Comment'), res)
}
}
return activityPubResponse(activityPubContextify(videoCommentObject), res)
return activityPubResponse(activityPubContextify(videoCommentObject, 'Comment'), res)
}
async function videoRedundancyController (req: express.Request, res: express.Response) {
@ -387,7 +387,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo
const audience = getAudience(playlist.OwnerAccount.Actor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
const object = audiencify(json, audience)
return activityPubResponse(activityPubContextify(object), res)
return activityPubResponse(activityPubContextify(object, 'Playlist'), res)
}
function videoPlaylistElementController (req: express.Request, res: express.Response) {
@ -396,7 +396,7 @@ function videoPlaylistElementController (req: express.Request, res: express.Resp
if (redirectIfNotOwned(videoPlaylistElement.url, res)) return
const json = videoPlaylistElement.toActivityPubObject()
return activityPubResponse(activityPubContextify(json), res)
return activityPubResponse(activityPubContextify(json, 'Playlist'), res)
}
// ---------------------------------------------------------------------------

View File

@ -45,7 +45,7 @@ async function outboxController (req: express.Request, res: express.Response) {
const handler = (start: number, count: number) => buildActivities(actor, start, count)
const json = await activityPubCollectionPagination(actorOutboxUrl, handler, req.query.page, req.query.size)
return activityPubResponse(activityPubContextify(json), res)
return activityPubResponse(activityPubContextify(json, 'Collection'), res)
}
async function buildActivities (actor: MActorLight, start: number, count: number) {

View File

@ -1,14 +1,3 @@
import { signJsonLDObject } from '@server/helpers/peertube-crypto'
import { MActor } from '@server/types/models'
import { ContextType } from '@shared/models'
import { activityPubContextify } from './context'
function buildSignedActivity <T> (byActor: MActor, data: T, contextType?: ContextType) {
const activity = activityPubContextify(data, contextType)
return signJsonLDObject(byActor, activity)
}
function getAPId (object: string | { id: string }) {
if (typeof object === 'string') return object
@ -16,6 +5,5 @@ function getAPId (object: string | { id: string }) {
}
export {
buildSignedActivity,
getAPId
}

View File

@ -22,7 +22,7 @@ function buildAudience (followerUrls: string[], isPublic = true) {
}
function audiencify<T> (object: T, audience: ActivityAudience) {
return Object.assign(object, audience)
return { ...audience, ...object }
}
// ---------------------------------------------------------------------------

View File

@ -1,7 +1,151 @@
import { ContextType } from '@shared/models'
function activityPubContextify <T> (data: T, type: ContextType) {
return { ...getContextData(type), ...data }
}
// ---------------------------------------------------------------------------
export {
getContextData,
activityPubContextify
}
// ---------------------------------------------------------------------------
type ContextValue = { [ id: string ]: (string | { '@type': string, '@id': string }) }
const contextStore = {
Video: buildContext({
Hashtag: 'as:Hashtag',
uuid: 'sc:identifier',
category: 'sc:category',
licence: 'sc:license',
subtitleLanguage: 'sc:subtitleLanguage',
sensitive: 'as:sensitive',
language: 'sc:inLanguage',
// TODO: remove in a few versions, introduced in 4.2
icons: 'as:icon',
isLiveBroadcast: 'sc:isLiveBroadcast',
liveSaveReplay: {
'@type': 'sc:Boolean',
'@id': 'pt:liveSaveReplay'
},
permanentLive: {
'@type': 'sc:Boolean',
'@id': 'pt:permanentLive'
},
latencyMode: {
'@type': 'sc:Number',
'@id': 'pt:latencyMode'
},
Infohash: 'pt:Infohash',
originallyPublishedAt: 'sc:datePublished',
views: {
'@type': 'sc:Number',
'@id': 'pt:views'
},
state: {
'@type': 'sc:Number',
'@id': 'pt:state'
},
size: {
'@type': 'sc:Number',
'@id': 'pt:size'
},
fps: {
'@type': 'sc:Number',
'@id': 'pt:fps'
},
commentsEnabled: {
'@type': 'sc:Boolean',
'@id': 'pt:commentsEnabled'
},
downloadEnabled: {
'@type': 'sc:Boolean',
'@id': 'pt:downloadEnabled'
},
waitTranscoding: {
'@type': 'sc:Boolean',
'@id': 'pt:waitTranscoding'
},
support: {
'@type': 'sc:Text',
'@id': 'pt:support'
},
likes: {
'@id': 'as:likes',
'@type': '@id'
},
dislikes: {
'@id': 'as:dislikes',
'@type': '@id'
},
shares: {
'@id': 'as:shares',
'@type': '@id'
},
comments: {
'@id': 'as:comments',
'@type': '@id'
}
}),
Playlist: buildContext({
Playlist: 'pt:Playlist',
PlaylistElement: 'pt:PlaylistElement',
position: {
'@type': 'sc:Number',
'@id': 'pt:position'
},
startTimestamp: {
'@type': 'sc:Number',
'@id': 'pt:startTimestamp'
},
stopTimestamp: {
'@type': 'sc:Number',
'@id': 'pt:stopTimestamp'
}
}),
CacheFile: buildContext({
expires: 'sc:expires',
CacheFile: 'pt:CacheFile'
}),
Flag: buildContext({
Hashtag: 'as:Hashtag'
}),
Actor: buildContext({
playlists: {
'@id': 'pt:playlists',
'@type': '@id'
}
}),
Follow: buildContext(),
Reject: buildContext(),
Accept: buildContext(),
View: buildContext(),
Announce: buildContext(),
Comment: buildContext(),
Delete: buildContext(),
Rate: buildContext()
}
function getContextData (type: ContextType) {
const context: any[] = [
return {
'@context': contextStore[type]
}
}
function buildContext (contextValue?: ContextValue) {
const baseContext = [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1',
{
@ -9,129 +153,16 @@ function getContextData (type: ContextType) {
}
]
if (type !== 'View' && type !== 'Announce') {
const additional = {
if (!contextValue) return baseContext
return [
...baseContext,
{
pt: 'https://joinpeertube.org/ns#',
sc: 'http://schema.org#'
sc: 'http://schema.org#',
...contextValue
}
if (type === 'CacheFile') {
Object.assign(additional, {
expires: 'sc:expires',
CacheFile: 'pt:CacheFile'
})
} else {
Object.assign(additional, {
Hashtag: 'as:Hashtag',
uuid: 'sc:identifier',
category: 'sc:category',
licence: 'sc:license',
subtitleLanguage: 'sc:subtitleLanguage',
sensitive: 'as:sensitive',
language: 'sc:inLanguage',
// TODO: remove in a few versions, introduced in 4.2
icons: 'as:icon',
isLiveBroadcast: 'sc:isLiveBroadcast',
liveSaveReplay: {
'@type': 'sc:Boolean',
'@id': 'pt:liveSaveReplay'
},
permanentLive: {
'@type': 'sc:Boolean',
'@id': 'pt:permanentLive'
},
latencyMode: {
'@type': 'sc:Number',
'@id': 'pt:latencyMode'
},
Infohash: 'pt:Infohash',
Playlist: 'pt:Playlist',
PlaylistElement: 'pt:PlaylistElement',
originallyPublishedAt: 'sc:datePublished',
views: {
'@type': 'sc:Number',
'@id': 'pt:views'
},
state: {
'@type': 'sc:Number',
'@id': 'pt:state'
},
size: {
'@type': 'sc:Number',
'@id': 'pt:size'
},
fps: {
'@type': 'sc:Number',
'@id': 'pt:fps'
},
startTimestamp: {
'@type': 'sc:Number',
'@id': 'pt:startTimestamp'
},
stopTimestamp: {
'@type': 'sc:Number',
'@id': 'pt:stopTimestamp'
},
position: {
'@type': 'sc:Number',
'@id': 'pt:position'
},
commentsEnabled: {
'@type': 'sc:Boolean',
'@id': 'pt:commentsEnabled'
},
downloadEnabled: {
'@type': 'sc:Boolean',
'@id': 'pt:downloadEnabled'
},
waitTranscoding: {
'@type': 'sc:Boolean',
'@id': 'pt:waitTranscoding'
},
support: {
'@type': 'sc:Text',
'@id': 'pt:support'
},
likes: {
'@id': 'as:likes',
'@type': '@id'
},
dislikes: {
'@id': 'as:dislikes',
'@type': '@id'
},
playlists: {
'@id': 'pt:playlists',
'@type': '@id'
},
shares: {
'@id': 'as:shares',
'@type': '@id'
},
comments: {
'@id': 'as:comments',
'@type': '@id'
}
})
}
context.push(additional)
}
return {
'@context': context
}
}
function activityPubContextify <T> (data: T, type: ContextType = 'All') {
return Object.assign({}, data, getContextData(type))
}
export {
getContextData,
activityPubContextify
]
}

View File

@ -1,12 +1,12 @@
import { buildDigest } from '@server/helpers/peertube-crypto'
import { buildSignedActivity } from '@server/lib/activitypub/activity'
import { buildDigest, signJsonLDObject } from '@server/helpers/peertube-crypto'
import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants'
import { ActorModel } from '@server/models/actor/actor'
import { getServerActor } from '@server/models/application/application'
import { MActor } from '@server/types/models'
import { ContextType } from '@shared/models/activitypub/context'
import { ACTIVITY_PUB, HTTP_SIGNATURE } from '../../../../initializers/constants'
import { ActorModel } from '../../../../models/actor/actor'
import { MActor } from '../../../../types/models'
import { activityPubContextify } from '../context'
type Payload <T> = { body: T, contextType?: ContextType, signatureActorId?: number }
type Payload <T> = { body: T, contextType: ContextType, signatureActorId?: number }
async function computeBody <T> (
payload: Payload<T>
@ -17,7 +17,7 @@ async function computeBody <T> (
const actorSignature = await ActorModel.load(payload.signatureActorId)
if (!actorSignature) throw new Error('Unknown signature actor id.')
body = await buildSignedActivity(actorSignature, payload.body, payload.contextType)
body = await signAndContextify(actorSignature, payload.body, payload.contextType)
}
return body
@ -52,8 +52,17 @@ function buildGlobalHeaders (body: any) {
}
}
function signAndContextify <T> (byActor: MActor, data: T, contextType: ContextType | null) {
const activity = contextType
? activityPubContextify(data, contextType)
: data
return signJsonLDObject(byActor, activity)
}
export {
buildGlobalHeaders,
computeBody,
buildSignedRequestOptions
buildSignedRequestOptions,
signAndContextify
}

View File

@ -1,4 +1,4 @@
export * from './send-accept'
export * from './http'
export * from './send-accept'
export * from './send-announce'
export * from './send-create'

View File

@ -21,7 +21,12 @@ function sendAccept (actorFollow: MActorFollowActors) {
const url = getLocalActorFollowAcceptActivityPubUrl(actorFollow)
const data = buildAcceptActivity(url, me, followData)
return unicastTo(data, me, follower.inboxUrl)
return unicastTo({
data,
byActor: me,
toActorUrl: follower.inboxUrl,
contextType: 'Accept'
})
}
// ---------------------------------------------------------------------------

View File

@ -23,13 +23,19 @@ async function buildAnnounceWithVideoAudience (
return { activity, actorsInvolvedInVideo }
}
async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) {
const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction)
logger.info('Creating job to send announce %s.', videoShare.url)
const followersException = [ byActor ]
return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException, 'Announce')
return broadcastToFollowers({
data: activity,
byActor,
toFollowersOf: actorsInvolvedInVideo,
transaction,
actorsException: [ byActor ],
contextType: 'Announce'
})
}
function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {

View File

@ -25,7 +25,7 @@ import {
const lTags = loggerTagsFactory('ap', 'create')
async function sendCreateVideo (video: MVideoAP, t: Transaction) {
async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
if (!video.hasPrivacyForFederation()) return undefined
logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
@ -36,7 +36,13 @@ async function sendCreateVideo (video: MVideoAP, t: Transaction) {
const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience)
return broadcastToFollowers(createActivity, byActor, [ byActor ], t)
return broadcastToFollowers({
data: createActivity,
byActor,
toFollowersOf: [ byActor ],
transaction,
contextType: 'Video'
})
}
async function sendCreateCacheFile (
@ -55,7 +61,7 @@ async function sendCreateCacheFile (
})
}
async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) {
async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
@ -63,7 +69,7 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transac
const byActor = playlist.OwnerAccount.Actor
const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
const object = await playlist.toActivityPubObject(null, t)
const object = await playlist.toActivityPubObject(null, transaction)
const createActivity = buildCreateActivity(playlist.url, byActor, object, audience)
const serverActor = await getServerActor()
@ -71,19 +77,25 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transac
if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor)
return broadcastToFollowers(createActivity, byActor, toFollowersOf, t)
return broadcastToFollowers({
data: createActivity,
byActor,
toFollowersOf,
transaction,
contextType: 'Playlist'
})
}
async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) {
async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
logger.info('Creating job to send comment %s.', comment.url)
const isOrigin = comment.Video.isOwned()
const byActor = comment.Account.Actor
const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t)
const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, transaction)
const commentObject = comment.toActivityPubObject(threadParentComments)
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, transaction)
// Add the actor that commented too
actorsInvolvedInComment.push(byActor)
@ -101,16 +113,45 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transacti
// This was a reply, send it to the parent actors
const actorsException = [ byActor ]
await broadcastToActors(createActivity, byActor, parentsCommentActors, t, actorsException)
await broadcastToActors({
data: createActivity,
byActor,
toActors: parentsCommentActors,
transaction,
actorsException,
contextType: 'Comment'
})
// Broadcast to our followers
await broadcastToFollowers(createActivity, byActor, [ byActor ], t)
await broadcastToFollowers({
data: createActivity,
byActor,
toFollowersOf: [ byActor ],
transaction,
contextType: 'Comment'
})
// Send to actors involved in the comment
if (isOrigin) return broadcastToFollowers(createActivity, byActor, actorsInvolvedInComment, t, actorsException)
if (isOrigin) {
return broadcastToFollowers({
data: createActivity,
byActor,
toFollowersOf: actorsInvolvedInComment,
transaction,
actorsException,
contextType: 'Comment'
})
}
// Send to origin
t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.getSharedInbox()))
return transaction.afterCommit(() => {
return unicastTo({
data: createActivity,
byActor,
toActorUrl: comment.Video.VideoChannel.Account.Actor.getSharedInbox(),
contextType: 'Comment'
})
})
}
function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
@ -144,8 +185,8 @@ async function sendVideoRelatedCreateActivity (options: {
video: MVideoAccountLight
url: string
object: any
contextType: ContextType
transaction?: Transaction
contextType?: ContextType
}) {
const activityBuilder = (audience: ActivityAudience) => {
return buildCreateActivity(options.url, options.byActor, options.object, audience)

View File

@ -23,16 +23,16 @@ async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transact
return buildDeleteActivity(url, video.url, byActor, audience)
}
return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction })
return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'Delete', transaction })
}
async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
async function sendDeleteActor (byActor: ActorModel, transaction: Transaction) {
logger.info('Creating job to broadcast delete of actor %s.', byActor.url)
const url = getDeleteActivityPubUrl(byActor.url)
const activity = buildDeleteActivity(url, byActor.url, byActor)
const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t)
const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction)
// In case the actor did not have any videos
const serverActor = await getServerActor()
@ -40,10 +40,16 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
actorsInvolved.push(byActor)
return broadcastToFollowers(activity, byActor, actorsInvolved, t)
return broadcastToFollowers({
data: activity,
byActor,
toFollowersOf: actorsInvolved,
contextType: 'Delete',
transaction
})
}
async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Transaction) {
async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, transaction: Transaction) {
logger.info('Creating job to send delete of comment %s.', videoComment.url)
const isVideoOrigin = videoComment.Video.isOwned()
@ -53,10 +59,10 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Tran
? videoComment.Account.Actor
: videoComment.Video.VideoChannel.Account.Actor
const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t)
const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, transaction)
const threadParentCommentsFiltered = threadParentComments.filter(c => !c.isDeleted())
const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t)
const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, transaction)
actorsInvolvedInComment.push(byActor) // Add the actor that commented the video
const audience = getVideoCommentAudience(videoComment, threadParentCommentsFiltered, actorsInvolvedInComment, isVideoOrigin)
@ -64,19 +70,48 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Tran
// This was a reply, send it to the parent actors
const actorsException = [ byActor ]
await broadcastToActors(activity, byActor, threadParentCommentsFiltered.map(c => c.Account.Actor), t, actorsException)
await broadcastToActors({
data: activity,
byActor,
toActors: threadParentCommentsFiltered.map(c => c.Account.Actor),
transaction,
contextType: 'Delete',
actorsException
})
// Broadcast to our followers
await broadcastToFollowers(activity, byActor, [ byActor ], t)
await broadcastToFollowers({
data: activity,
byActor,
toFollowersOf: [ byActor ],
contextType: 'Delete',
transaction
})
// Send to actors involved in the comment
if (isVideoOrigin) return broadcastToFollowers(activity, byActor, actorsInvolvedInComment, t, actorsException)
if (isVideoOrigin) {
return broadcastToFollowers({
data: activity,
byActor,
toFollowersOf: actorsInvolvedInComment,
transaction,
contextType: 'Delete',
actorsException
})
}
// Send to origin
t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.getSharedInbox()))
return transaction.afterCommit(() => {
return unicastTo({
data: activity,
byActor,
toActorUrl: videoComment.Video.VideoChannel.Account.Actor.getSharedInbox(),
contextType: 'Delete'
})
})
}
async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) {
async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, transaction: Transaction) {
logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url)
const byActor = videoPlaylist.OwnerAccount.Actor
@ -89,7 +124,13 @@ async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary
if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor)
return broadcastToFollowers(activity, byActor, toFollowersOf, t)
return broadcastToFollowers({
data: activity,
byActor,
toFollowersOf,
contextType: 'Delete',
transaction
})
}
// ---------------------------------------------------------------------------

View File

@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience'
import { getVideoDislikeActivityPubUrlByLocalActor } from '../url'
import { sendVideoActivityToOrigin } from './shared/send-utils'
function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
function sendDislike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) {
logger.info('Creating job to dislike %s.', video.url)
const activityBuilder = (audience: ActivityAudience) => {
@ -15,7 +15,7 @@ function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction
return buildDislikeActivity(url, byActor, video, audience)
}
return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction: t })
return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' })
}
function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike {

View File

@ -17,16 +17,20 @@ function sendAbuse (byActor: MActor, abuse: MAbuseAP, flaggedAccount: MAccountLi
const audience = { to: [ flaggedAccount.Actor.url ], cc: [] }
const flagActivity = buildFlagActivity(url, byActor, abuse, audience)
t.afterCommit(() => unicastTo(flagActivity, byActor, flaggedAccount.Actor.getSharedInbox()))
return t.afterCommit(() => {
return unicastTo({
data: flagActivity,
byActor,
toActorUrl: flaggedAccount.Actor.getSharedInbox(),
contextType: 'Flag'
})
})
}
function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag {
if (!audience) audience = getAudience(byActor)
const activity = Object.assign(
{ id: url, actor: byActor.url },
abuse.toActivityPubObject()
)
const activity = { id: url, actor: byActor.url, ...abuse.toActivityPubObject() }
return audiencify(activity, audience)
}

View File

@ -15,7 +15,9 @@ function sendFollow (actorFollow: MActorFollowActors, t: Transaction) {
const data = buildFollowActivity(actorFollow.url, me, following)
t.afterCommit(() => unicastTo(data, me, following.inboxUrl))
return t.afterCommit(() => {
return unicastTo({ data, byActor: me, toActorUrl: following.inboxUrl, contextType: 'Follow' })
})
}
function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow {

View File

@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience'
import { getVideoLikeActivityPubUrlByLocalActor } from '../url'
import { sendVideoActivityToOrigin } from './shared/send-utils'
function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
function sendLike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) {
logger.info('Creating job to like %s.', video.url)
const activityBuilder = (audience: ActivityAudience) => {
@ -15,7 +15,7 @@ function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
return buildLikeActivity(url, byActor, video, audience)
}
return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction: t })
return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' })
}
function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike {

View File

@ -18,7 +18,7 @@ function sendReject (followUrl: string, follower: MActor, following: MActor) {
const url = getLocalActorFollowRejectActivityPubUrl(follower, following)
const data = buildRejectActivity(url, following, followData)
return unicastTo(data, following, follower.inboxUrl)
return unicastTo({ data, byActor: following, toActorUrl: follower.inboxUrl, contextType: 'Reject' })
}
// ---------------------------------------------------------------------------

View File

@ -6,7 +6,8 @@ import {
ActivityDislike,
ActivityFollow,
ActivityLike,
ActivityUndo
ActivityUndo,
ContextType
} from '@shared/models'
import { logger } from '../../../helpers/logger'
import { VideoModel } from '../../../models/video/video'
@ -43,24 +44,37 @@ function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
const followActivity = buildFollowActivity(actorFollow.url, me, following)
const undoActivity = undoActivityData(undoUrl, me, followActivity)
t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl))
return t.afterCommit(() => {
return unicastTo({
data: undoActivity,
byActor: me,
toActorUrl: following.inboxUrl,
contextType: 'Follow'
})
})
}
// ---------------------------------------------------------------------------
async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) {
async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) {
logger.info('Creating job to undo announce %s.', videoShare.url)
const undoUrl = getUndoActivityPubUrl(videoShare.url)
const { activity: announceActivity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
const undoActivity = undoActivityData(undoUrl, byActor, announceActivity)
const { activity: announce, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction)
const undoActivity = undoActivityData(undoUrl, byActor, announce)
const followersException = [ byActor ]
return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
return broadcastToFollowers({
data: undoActivity,
byActor,
toFollowersOf: actorsInvolvedInVideo,
transaction,
actorsException: [ byActor ],
contextType: 'Announce'
})
}
async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) {
async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, transaction: Transaction) {
logger.info('Creating job to undo cache file %s.', redundancyModel.url)
const associatedVideo = redundancyModel.getVideo()
@ -72,7 +86,14 @@ async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedund
const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(associatedVideo.id)
const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject())
return sendUndoVideoRelatedActivity({ byActor, video, url: redundancyModel.url, activity: createActivity, transaction: t })
return sendUndoVideoRelatedActivity({
byActor,
video,
url: redundancyModel.url,
activity: createActivity,
contextType: 'CacheFile',
transaction
})
}
// ---------------------------------------------------------------------------
@ -83,7 +104,7 @@ async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Tran
const likeUrl = getVideoLikeActivityPubUrlByLocalActor(byActor, video)
const likeActivity = buildLikeActivity(likeUrl, byActor, video)
return sendUndoVideoToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
return sendUndoVideoRateToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
}
async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
@ -92,7 +113,7 @@ async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: T
const dislikeUrl = getVideoDislikeActivityPubUrlByLocalActor(byActor, video)
const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video)
return sendUndoVideoToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t })
return sendUndoVideoRateToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t })
}
// ---------------------------------------------------------------------------
@ -131,6 +152,7 @@ async function sendUndoVideoRelatedActivity (options: {
video: MVideoAccountLight
url: string
activity: ActivityFollow | ActivityCreate | ActivityAnnounce
contextType: ContextType
transaction: Transaction
}) {
const activityBuilder = (audience: ActivityAudience) => {
@ -142,7 +164,7 @@ async function sendUndoVideoRelatedActivity (options: {
return sendVideoRelatedActivity(activityBuilder, options)
}
async function sendUndoVideoToOriginActivity (options: {
async function sendUndoVideoRateToOriginActivity (options: {
byActor: MActor
video: MVideoAccountLight
url: string
@ -155,5 +177,5 @@ async function sendUndoVideoToOriginActivity (options: {
return undoActivityData(undoUrl, options.byActor, options.activity, audience)
}
return sendVideoActivityToOrigin(activityBuilder, options)
return sendVideoActivityToOrigin(activityBuilder, { ...options, contextType: 'Rate' })
}

View File

@ -20,20 +20,20 @@ import { getUpdateActivityPubUrl } from '../url'
import { getActorsInvolvedInVideo } from './shared'
import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils'
async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) {
async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, transaction: Transaction, overriddenByActor?: MActor) {
const video = videoArg as MVideoAP
if (!video.hasPrivacyForFederation()) return undefined
logger.info('Creating job to update video %s.', video.url)
const byActor = overrodeByActor || video.VideoChannel.Account.Actor
const byActor = overriddenByActor || video.VideoChannel.Account.Actor
const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
// Needed to build the AP object
if (!video.VideoCaptions) {
video.VideoCaptions = await video.$get('VideoCaptions', { transaction: t })
video.VideoCaptions = await video.$get('VideoCaptions', { transaction })
}
const videoObject = video.toActivityPubObject()
@ -41,13 +41,19 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction
const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience)
const actorsInvolved = await getActorsInvolvedInVideo(video, t)
if (overrodeByActor) actorsInvolved.push(overrodeByActor)
const actorsInvolved = await getActorsInvolvedInVideo(video, transaction)
if (overriddenByActor) actorsInvolved.push(overriddenByActor)
return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
return broadcastToFollowers({
data: updateActivity,
byActor,
toFollowersOf: actorsInvolved,
contextType: 'Video',
transaction
})
}
async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) {
async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
const byActor = accountOrChannel.Actor
logger.info('Creating job to update actor %s.', byActor.url)
@ -60,15 +66,21 @@ async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefa
let actorsInvolved: MActor[]
if (accountOrChannel instanceof AccountModel) {
// Actors that shared my videos are involved too
actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t)
actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction)
} else {
// Actors that shared videos of my channel are involved too
actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, t)
actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, transaction)
}
actorsInvolved.push(byActor)
return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t)
return broadcastToFollowers({
data: updateActivity,
byActor,
toFollowersOf: actorsInvolved,
transaction,
contextType: 'Actor'
})
}
async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
@ -92,7 +104,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide
return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
}
async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) {
async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
const byActor = videoPlaylist.OwnerAccount.Actor
@ -101,7 +113,7 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Tr
const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString())
const object = await videoPlaylist.toActivityPubObject(null, t)
const object = await videoPlaylist.toActivityPubObject(null, transaction)
const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC)
const updateActivity = buildUpdateActivity(url, byActor, object, audience)
@ -111,7 +123,13 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Tr
if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor)
return broadcastToFollowers(updateActivity, byActor, toFollowersOf, t)
return broadcastToFollowers({
data: updateActivity,
byActor,
toFollowersOf,
transaction,
contextType: 'Playlist'
})
}
// ---------------------------------------------------------------------------

View File

@ -1,7 +1,7 @@
import { Transaction } from 'sequelize'
import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache'
import { getServerActor } from '@server/models/application/application'
import { Activity, ActivityAudience } from '@shared/models'
import { Activity, ActivityAudience, ActivitypubHttpBroadcastPayload } from '@shared/models'
import { ContextType } from '@shared/models/activitypub/context'
import { afterCommitIfTransaction } from '../../../../helpers/database-utils'
import { logger } from '../../../../helpers/logger'
@ -14,8 +14,8 @@ import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAud
async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
byActor: MActorLight
video: MVideoImmutable | MVideoAccountLight
contextType: ContextType
transaction?: Transaction
contextType?: ContextType
}) {
const { byActor, video, transaction, contextType } = options
@ -32,15 +32,23 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
const actorsException = [ byActor ]
return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType)
return broadcastToFollowers({
data: activity,
byActor,
toFollowersOf: actorsInvolvedInVideo,
transaction,
actorsException,
contextType
})
}
async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: {
byActor: MActorLight
video: MVideoImmutable | MVideoAccountLight
contextType: ContextType
actorsInvolvedInVideo?: MActorLight[]
transaction?: Transaction
contextType?: ContextType
}) {
const { byActor, video, actorsInvolvedInVideo, transaction, contextType } = options
@ -53,7 +61,12 @@ async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAu
const activity = activityBuilder(audience)
return afterCommitIfTransaction(transaction, () => {
return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType)
return unicastTo({
data: activity,
byActor,
toActorUrl: accountActor.getSharedInbox(),
contextType
})
})
}
@ -100,41 +113,69 @@ async function forwardActivity (
logger.debug('Creating forwarding job.', { uris })
const payload = {
const payload: ActivitypubHttpBroadcastPayload = {
uris,
body: activity
body: activity,
contextType: null
}
return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }))
}
// ---------------------------------------------------------------------------
async function broadcastToFollowers (
data: any,
byActor: MActorId,
toFollowersOf: MActorId[],
t: Transaction,
actorsException: MActorWithInboxes[] = [],
contextType?: ContextType
) {
const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
async function broadcastToFollowers (options: {
data: any
byActor: MActorId
toFollowersOf: MActorId[]
transaction: Transaction
contextType: ContextType
return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
actorsException?: MActorWithInboxes[]
}) {
const { data, byActor, toFollowersOf, transaction, contextType, actorsException = [] } = options
const uris = await computeFollowerUris(toFollowersOf, actorsException, transaction)
return afterCommitIfTransaction(transaction, () => {
return broadcastTo({
uris,
data,
byActor,
contextType
})
})
}
async function broadcastToActors (
data: any,
byActor: MActorId,
toActors: MActor[],
t?: Transaction,
actorsException: MActorWithInboxes[] = [],
contextType?: ContextType
) {
async function broadcastToActors (options: {
data: any
byActor: MActorId
toActors: MActor[]
transaction: Transaction
contextType: ContextType
actorsException?: MActorWithInboxes[]
}) {
const { data, byActor, toActors, transaction, contextType, actorsException = [] } = options
const uris = await computeUris(toActors, actorsException)
return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
return afterCommitIfTransaction(transaction, () => {
return broadcastTo({
uris,
data,
byActor,
contextType
})
})
}
function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) {
function broadcastTo (options: {
uris: string[]
data: any
byActor: MActorId
contextType: ContextType
}) {
const { uris, data, byActor, contextType } = options
if (uris.length === 0) return undefined
const broadcastUris: string[] = []
@ -174,7 +215,14 @@ function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?
}
}
function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) {
function unicastTo (options: {
data: any
byActor: MActorId
toActorUrl: string
contextType: ContextType
}) {
const { data, byActor, toActorUrl, contextType } = options
logger.debug('Creating unicast job.', { uri: toActorUrl })
const payload = {

View File

@ -1,11 +1,11 @@
import { map } from 'bluebird'
import { Job } from 'bull'
import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from '@server/lib/activitypub/send'
import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache'
import { ActivitypubHttpBroadcastPayload } from '@shared/models'
import { logger } from '../../../helpers/logger'
import { doRequest } from '../../../helpers/requests'
import { BROADCAST_CONCURRENCY } from '../../../initializers/constants'
import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
async function processActivityPubHttpBroadcast (job: Job) {
logger.info('Processing ActivityPub broadcast in job %d.', job.id)

View File

@ -1,9 +1,9 @@
import { Job } from 'bull'
import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from '@server/lib/activitypub/send'
import { ActivitypubHttpUnicastPayload } from '@shared/models'
import { logger } from '../../../helpers/logger'
import { doRequest } from '../../../helpers/requests'
import { ActorFollowHealthCache } from '../../actor-follow-health-cache'
import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
async function processActivityPubHttpUnicast (job: Job) {
logger.info('Processing ActivityPub unicast in job %d.', job.id)

View File

@ -605,7 +605,7 @@ export class ActorModel extends Model<Partial<AttributesOnly<ActorModel>>> {
image
}
return activityPubContextify(json)
return activityPubContextify(json, 'Actor')
}
getFollowerSharedInboxUrls (t: Transaction) {

View File

@ -3,10 +3,10 @@
import 'mocha'
import { expect } from 'chai'
import { cloneDeep } from 'lodash'
import { signAndContextify } from '@server/lib/activitypub/send'
import { buildRequestStub } from '@server/tests/shared'
import { buildAbsoluteFixturePath } from '@shared/core-utils'
import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto'
import { buildSignedActivity } from '../../../lib/activitypub/activity'
describe('Test activity pub helpers', function () {
describe('When checking the Linked Signature', function () {
@ -46,7 +46,7 @@ describe('Test activity pub helpers', function () {
const body = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
const signedBody = await buildSignedActivity(actorSignature as any, body)
const signedBody = await signAndContextify(actorSignature as any, body, 'Announce')
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
@ -59,7 +59,7 @@ describe('Test activity pub helpers', function () {
const body = require(buildAbsoluteFixturePath('./ap-json/peertube/announce-without-context.json'))
const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey }
const signedBody = await buildSignedActivity(actorSignature as any, body)
const signedBody = await signAndContextify(actorSignature as any, body, 'Announce')
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)

View File

@ -4,9 +4,8 @@ import 'mocha'
import * as chai from 'chai'
import { buildDigest } from '@server/helpers/peertube-crypto'
import { HTTP_SIGNATURE } from '@server/initializers/constants'
import { buildSignedActivity } from '@server/lib/activitypub/activity'
import { activityPubContextify } from '@server/lib/activitypub/context'
import { buildGlobalHeaders } from '@server/lib/job-queue/handlers/utils/activitypub-http-utils'
import { buildGlobalHeaders, signAndContextify } from '@server/lib/activitypub/send'
import { makeFollowRequest, makePOSTAPRequest } from '@server/tests/shared'
import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
import { HttpStatusCode } from '@shared/models'
@ -81,7 +80,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]))
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = {
Digest: buildDigest({ hello: 'coucou' })
}
@ -95,7 +94,7 @@ describe('Test ActivityPub security', function () {
})
it('Should fail with an invalid date', async function () {
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
@ -111,7 +110,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]))
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
try {
@ -126,7 +125,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]))
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
const signatureOptions = baseHttpSignature()
@ -149,7 +148,7 @@ describe('Test ActivityPub security', function () {
})
it('Should succeed with a valid HTTP signature', async function () {
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
const { statusCode } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
@ -168,7 +167,7 @@ describe('Test ActivityPub security', function () {
await killallServers([ servers[1] ])
await servers[1].run()
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
const body = activityPubContextify(getAnnounceWithoutContext(servers[1]), 'Announce')
const headers = buildGlobalHeaders(body)
try {
@ -204,7 +203,7 @@ describe('Test ActivityPub security', function () {
body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
const signedBody = await buildSignedActivity(signer, body)
const signedBody = await signAndContextify(signer, body, 'Announce')
const headers = buildGlobalHeaders(signedBody)
@ -226,7 +225,7 @@ describe('Test ActivityPub security', function () {
body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
const signedBody = await buildSignedActivity(signer, body)
const signedBody = await signAndContextify(signer, body, 'Announce')
signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
@ -247,7 +246,7 @@ describe('Test ActivityPub security', function () {
body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
const signedBody = await buildSignedActivity(signer, body)
const signedBody = await signAndContextify(signer, body, 'Announce')
const headers = buildGlobalHeaders(signedBody)
@ -269,7 +268,7 @@ describe('Test ActivityPub security', function () {
body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
const signedBody = await buildSignedActivity(signer, body)
const signedBody = await signAndContextify(signer, body, 'Announce')
const headers = buildGlobalHeaders(signedBody)

View File

@ -22,7 +22,7 @@ export async function makeFollowRequest (to: { url: string }, by: { url: string,
object: to.url
}
const body = activityPubContextify(follow)
const body = activityPubContextify(follow, 'Follow')
const httpSignature = {
algorithm: HTTP_SIGNATURE.ALGORITHM,

View File

@ -1 +1,15 @@
export type ContextType = 'All' | 'View' | 'Announce' | 'CacheFile'
export type ContextType =
'Video' |
'Comment' |
'Playlist' |
'Follow' |
'Reject' |
'Accept' |
'View' |
'Announce' |
'CacheFile' |
'Delete' |
'Rate' |
'Flag' |
'Actor' |
'Collection'

View File

@ -46,7 +46,7 @@ export type ActivityTrackerUrlObject = {
href: string
}
export type ActivityPlaylistInfohashesObject = {
export type ActivityStreamingPlaylistInfohashesObject = {
type: 'Infohash'
name: string
}
@ -97,7 +97,7 @@ export interface ActivityFlagReasonObject {
export type ActivityTagObject =
ActivityPlaylistSegmentHashesObject
| ActivityPlaylistInfohashesObject
| ActivityStreamingPlaylistInfohashesObject
| ActivityVideoUrlObject
| ActivityHashTagObject
| ActivityMentionObject

View File

@ -40,9 +40,9 @@ export interface Job {
export type ActivitypubHttpBroadcastPayload = {
uris: string[]
signatureActorId?: number
contextType: ContextType
body: any
contextType?: ContextType
signatureActorId?: number
}
export type ActivitypubFollowPayload = {
@ -62,9 +62,9 @@ export type ActivitypubHttpFetcherPayload = {
export type ActivitypubHttpUnicastPayload = {
uri: string
contextType: ContextType
signatureActorId?: number
body: object
contextType?: ContextType
}
export type RefreshPayload = {