diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts index 279ad83dc..7df47cf15 100644 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ b/server/helpers/custom-validators/activitypub/misc.ts @@ -51,7 +51,8 @@ function setValidAttributedTo (obj: any) { } obj.attributedTo = obj.attributedTo.filter(a => { - return (a.type === 'Group' || a.type === 'Person') && isActivityPubUrlValid(a.id) + return isActivityPubUrlValid(a) || + ((a.type === 'Group' || a.type === 'Person') && isActivityPubUrlValid(a.id)) }) return true diff --git a/server/lib/activitypub/actors/get.ts b/server/lib/activitypub/actors/get.ts index e73b7d707..b2be3f5fb 100644 --- a/server/lib/activitypub/actors/get.ts +++ b/server/lib/activitypub/actors/get.ts @@ -3,8 +3,9 @@ import { logger } from '@server/helpers/logger' import { JobQueue } from '@server/lib/job-queue' import { ActorLoadByUrlType, loadActorByUrl } from '@server/lib/model-loaders' import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models' -import { ActivityPubActor } from '@shared/models' -import { getAPId } from '../activity' +import { arrayify } from '@shared/core-utils' +import { ActivityPubActor, APObjectId } from '@shared/models' +import { fetchAPObject, getAPId } from '../activity' import { checkUrlsSameHost } from '../url' import { refreshActorIfNeeded } from './refresh' import { APActorCreator, fetchRemoteActor } from './shared' @@ -40,7 +41,7 @@ async function getOrCreateAPActor ( const { actorObject } = await fetchRemoteActor(actorUrl) if (actorObject === undefined) throw new Error('Cannot fetch remote actor ' + actorUrl) - // actorUrl is just an alias/rediraction, so process object id instead + // actorUrl is just an alias/redirection, so process object id instead if (actorObject.id !== actorUrl) return getOrCreateAPActor(actorObject, 'all', recurseIfNeeded, updateCollections) // Create the attributed to actor @@ -68,29 +69,48 @@ async function getOrCreateAPActor ( return actorRefreshed } -function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: string) { - const accountAttributedTo = actorObject.attributedTo.find(a => a.type === 'Person') - if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actorUrl) - - if (checkUrlsSameHost(accountAttributedTo.id, actorUrl) !== true) { - throw new Error(`Account attributed to ${accountAttributedTo.id} does not have the same host than actor url ${actorUrl}`) +async function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: string) { + const accountAttributedTo = await findOwner(actorUrl, actorObject.attributedTo, 'Person') + if (!accountAttributedTo) { + throw new Error(`Cannot find account attributed to video channel ${actorUrl}`) } try { // Don't recurse another time const recurseIfNeeded = false - return getOrCreateAPActor(accountAttributedTo.id, 'all', recurseIfNeeded) + return getOrCreateAPActor(accountAttributedTo, 'all', recurseIfNeeded) } catch (err) { logger.error('Cannot get or create account attributed to video channel ' + actorUrl) throw new Error(err) } } +async function findOwner (rootUrl: string, attributedTo: APObjectId[] | APObjectId, type: 'Person' | 'Group') { + for (const actorToCheck of arrayify(attributedTo)) { + const actorObject = await fetchAPObject(getAPId(actorToCheck)) + + if (!actorObject) { + logger.warn('Unknown attributed to actor %s for owner %s', actorToCheck, rootUrl) + continue + } + + if (checkUrlsSameHost(actorObject.id, rootUrl) !== true) { + logger.warn(`Account attributed to ${actorObject.id} does not have the same host than owner actor url ${rootUrl}`) + continue + } + + if (actorObject.type === type) return actorObject + } + + return undefined +} + // --------------------------------------------------------------------------- export { getOrCreateAPOwner, - getOrCreateAPActor + getOrCreateAPActor, + findOwner } // --------------------------------------------------------------------------- diff --git a/server/lib/activitypub/playlists/create-update.ts b/server/lib/activitypub/playlists/create-update.ts index 9339e8ea4..920d3943a 100644 --- a/server/lib/activitypub/playlists/create-update.ts +++ b/server/lib/activitypub/playlists/create-update.ts @@ -77,7 +77,7 @@ async function setVideoChannel (playlistObject: PlaylistObject, playlistAttribut throw new Error('Not attributed to for playlist object ' + getAPId(playlistObject)) } - const actor = await getOrCreateAPActor(playlistObject.attributedTo[0], 'all') + const actor = await getOrCreateAPActor(getAPId(playlistObject.attributedTo[0]), 'all') if (!actor.VideoChannel) { logger.warn('Playlist "attributedTo" %s is not a video channel.', playlistObject.id, { playlistObject, ...lTags(playlistObject.id) }) diff --git a/server/lib/activitypub/videos/shared/abstract-builder.ts b/server/lib/activitypub/videos/shared/abstract-builder.ts index 7c5c73139..8af67ecac 100644 --- a/server/lib/activitypub/videos/shared/abstract-builder.ts +++ b/server/lib/activitypub/videos/shared/abstract-builder.ts @@ -18,8 +18,7 @@ import { MVideoThumbnail } from '@server/types/models' import { ActivityTagObject, ThumbnailType, VideoObject, VideoStreamingPlaylistType } from '@shared/models' -import { getOrCreateAPActor } from '../../actors' -import { checkUrlsSameHost } from '../../url' +import { findOwner, getOrCreateAPActor } from '../../actors' import { getCaptionAttributesFromObject, getFileAttributesFromUrl, @@ -37,13 +36,9 @@ export abstract class APVideoAbstractBuilder { protected abstract lTags: LoggerTagsFn protected async getOrCreateVideoChannelFromVideoObject () { - const channel = this.videoObject.attributedTo.find(a => a.type === 'Group') + const channel = await findOwner(this.videoObject.id, this.videoObject.attributedTo, 'Group') if (!channel) throw new Error('Cannot find associated video channel to video ' + this.videoObject.url) - if (checkUrlsSameHost(channel.id, this.videoObject.id) !== true) { - throw new Error(`Video channel url ${channel.id} does not have the same host than video object id ${this.videoObject.id}`) - } - return getOrCreateAPActor(channel.id, 'all') } diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts index 9bf994379..db9c73658 100644 --- a/shared/models/activitypub/objects/common-objects.ts +++ b/shared/models/activitypub/objects/common-objects.ts @@ -114,10 +114,7 @@ export type ActivityUrlObject = | ActivityVideoFileMetadataUrlObject | ActivityTrackerUrlObject -export interface ActivityPubAttributedTo { - type: 'Group' | 'Person' - id: string -} +export type ActivityPubAttributedTo = { type: 'Group' | 'Person', id: string } | string export interface ActivityTombstoneObject { '@context'?: any diff --git a/shared/models/activitypub/objects/playlist-object.ts b/shared/models/activitypub/objects/playlist-object.ts index 842c03790..0ccb71828 100644 --- a/shared/models/activitypub/objects/playlist-object.ts +++ b/shared/models/activitypub/objects/playlist-object.ts @@ -1,4 +1,4 @@ -import { ActivityIconObject } from './common-objects' +import { ActivityIconObject, ActivityPubAttributedTo } from './common-objects' export interface PlaylistObject { id: string @@ -12,7 +12,7 @@ export interface PlaylistObject { uuid: string totalItems: number - attributedTo: string[] + attributedTo: ActivityPubAttributedTo[] icon?: ActivityIconObject diff --git a/shared/models/activitypub/objects/video-comment-object.ts b/shared/models/activitypub/objects/video-comment-object.ts index ba9001730..fb1e6f8db 100644 --- a/shared/models/activitypub/objects/video-comment-object.ts +++ b/shared/models/activitypub/objects/video-comment-object.ts @@ -1,4 +1,4 @@ -import { ActivityTagObject } from './common-objects' +import { ActivityPubAttributedTo, ActivityTagObject } from './common-objects' export interface VideoCommentObject { type: 'Note' @@ -11,6 +11,6 @@ export interface VideoCommentObject { published: string updated: string url: string - attributedTo: string + attributedTo: ActivityPubAttributedTo tag: ActivityTagObject[] }