diff options
author | mira.bat <97340105+irafire@users.noreply.github.com> | 2023-07-27 17:01:15 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-27 17:01:15 +0200 |
commit | f862be2749b4f2d8dee99128d7e3064a69920e11 (patch) | |
tree | 20643166e07cb31ca3e0d05b4490000150b2f1c4 /server/lib | |
parent | 787d822cd471d1e4bd5a37c687c78cd5f69d8645 (diff) | |
download | PeerTube-f862be2749b4f2d8dee99128d7e3064a69920e11.tar.gz PeerTube-f862be2749b4f2d8dee99128d7e3064a69920e11.tar.zst PeerTube-f862be2749b4f2d8dee99128d7e3064a69920e11.zip |
Add an option to sign federated fetches for mastodon compatibility (#5898)
* Fix player error modal
Not hidden when we change the video
* Correctly dispose player components
* Sign cross-server fetch requests for mastodon AUTHORIZED_FETCH compatibilty
* Add a remote fetch sign configuration knob
* Federated fetches refactoring
---------
Co-authored-by: Chocobozzz <me@florianbigard.com>
Co-authored-by: ira <ira@foxgirl.space>
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/activitypub/activity.ts | 50 | ||||
-rw-r--r-- | server/lib/activitypub/actors/get.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/actors/shared/url-to-object.ts | 6 | ||||
-rw-r--r-- | server/lib/activitypub/crawl.ts | 8 | ||||
-rw-r--r-- | server/lib/activitypub/playlists/shared/url-to-object.ts | 6 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-create.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-undo.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-update.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/send/http.ts | 13 | ||||
-rw-r--r-- | server/lib/activitypub/share.ts | 5 | ||||
-rw-r--r-- | server/lib/activitypub/video-comments.ts | 5 | ||||
-rw-r--r-- | server/lib/activitypub/videos/shared/url-to-object.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/videos/shared/video-sync-attributes.ts | 12 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/activitypub-cleaner.ts | 5 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/activitypub-http-broadcast.ts | 2 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/activitypub-http-unicast.ts | 2 |
16 files changed, 79 insertions, 55 deletions
diff --git a/server/lib/activitypub/activity.ts b/server/lib/activitypub/activity.ts index 0fed3e8fd..391bcd9c6 100644 --- a/server/lib/activitypub/activity.ts +++ b/server/lib/activitypub/activity.ts | |||
@@ -1,22 +1,26 @@ | |||
1 | import { doJSONRequest } from '@server/helpers/requests' | 1 | import { doJSONRequest, PeerTubeRequestOptions } from '@server/helpers/requests' |
2 | import { APObjectId, ActivityObject, ActivityPubActor, ActivityType } from '@shared/models' | 2 | import { CONFIG } from '@server/initializers/config' |
3 | import { ActivityObject, ActivityPubActor, ActivityType, APObjectId } from '@shared/models' | ||
4 | import { buildSignedRequestOptions } from './send' | ||
3 | 5 | ||
4 | function getAPId (object: string | { id: string }) { | 6 | export function getAPId (object: string | { id: string }) { |
5 | if (typeof object === 'string') return object | 7 | if (typeof object === 'string') return object |
6 | 8 | ||
7 | return object.id | 9 | return object.id |
8 | } | 10 | } |
9 | 11 | ||
10 | function getActivityStreamDuration (duration: number) { | 12 | export function getActivityStreamDuration (duration: number) { |
11 | // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration | 13 | // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration |
12 | return 'PT' + duration + 'S' | 14 | return 'PT' + duration + 'S' |
13 | } | 15 | } |
14 | 16 | ||
15 | function getDurationFromActivityStream (duration: string) { | 17 | export function getDurationFromActivityStream (duration: string) { |
16 | return parseInt(duration.replace(/[^\d]+/, '')) | 18 | return parseInt(duration.replace(/[^\d]+/, '')) |
17 | } | 19 | } |
18 | 20 | ||
19 | function buildAvailableActivities (): ActivityType[] { | 21 | // --------------------------------------------------------------------------- |
22 | |||
23 | export function buildAvailableActivities (): ActivityType[] { | ||
20 | return [ | 24 | return [ |
21 | 'Create', | 25 | 'Create', |
22 | 'Update', | 26 | 'Update', |
@@ -33,9 +37,25 @@ function buildAvailableActivities (): ActivityType[] { | |||
33 | ] | 37 | ] |
34 | } | 38 | } |
35 | 39 | ||
36 | async function fetchAPObject <T extends (ActivityObject | ActivityPubActor)> (object: APObjectId) { | 40 | // --------------------------------------------------------------------------- |
41 | |||
42 | export async function fetchAP <T> (url: string, moreOptions: PeerTubeRequestOptions = {}) { | ||
43 | const options = { | ||
44 | activityPub: true, | ||
45 | |||
46 | httpSignature: CONFIG.FEDERATION.SIGN_FEDERATED_FETCHES | ||
47 | ? await buildSignedRequestOptions({ hasPayload: false }) | ||
48 | : undefined, | ||
49 | |||
50 | ...moreOptions | ||
51 | } | ||
52 | |||
53 | return doJSONRequest<T>(url, options) | ||
54 | } | ||
55 | |||
56 | export async function fetchAPObjectIfNeeded <T extends (ActivityObject | ActivityPubActor)> (object: APObjectId) { | ||
37 | if (typeof object === 'string') { | 57 | if (typeof object === 'string') { |
38 | const { body } = await doJSONRequest<Exclude<T, string>>(object, { activityPub: true }) | 58 | const { body } = await fetchAP<Exclude<T, string>>(object) |
39 | 59 | ||
40 | return body | 60 | return body |
41 | } | 61 | } |
@@ -43,10 +63,12 @@ async function fetchAPObject <T extends (ActivityObject | ActivityPubActor)> (ob | |||
43 | return object as Exclude<T, string> | 63 | return object as Exclude<T, string> |
44 | } | 64 | } |
45 | 65 | ||
46 | export { | 66 | export async function findLatestAPRedirection (url: string, iteration = 1) { |
47 | getAPId, | 67 | if (iteration > 10) throw new Error('Too much iterations to find final URL ' + url) |
48 | fetchAPObject, | 68 | |
49 | getActivityStreamDuration, | 69 | const { headers } = await fetchAP(url, { followRedirect: false }) |
50 | buildAvailableActivities, | 70 | |
51 | getDurationFromActivityStream | 71 | if (headers.location) return findLatestAPRedirection(headers.location, iteration + 1) |
72 | |||
73 | return url | ||
52 | } | 74 | } |
diff --git a/server/lib/activitypub/actors/get.ts b/server/lib/activitypub/actors/get.ts index b2be3f5fb..dd2bc9f03 100644 --- a/server/lib/activitypub/actors/get.ts +++ b/server/lib/activitypub/actors/get.ts | |||
@@ -5,7 +5,7 @@ import { ActorLoadByUrlType, loadActorByUrl } from '@server/lib/model-loaders' | |||
5 | import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models' | 5 | import { MActor, MActorAccountChannelId, MActorAccountChannelIdActor, MActorAccountId, MActorFullActor } from '@server/types/models' |
6 | import { arrayify } from '@shared/core-utils' | 6 | import { arrayify } from '@shared/core-utils' |
7 | import { ActivityPubActor, APObjectId } from '@shared/models' | 7 | import { ActivityPubActor, APObjectId } from '@shared/models' |
8 | import { fetchAPObject, getAPId } from '../activity' | 8 | import { fetchAPObjectIfNeeded, getAPId } from '../activity' |
9 | import { checkUrlsSameHost } from '../url' | 9 | import { checkUrlsSameHost } from '../url' |
10 | import { refreshActorIfNeeded } from './refresh' | 10 | import { refreshActorIfNeeded } from './refresh' |
11 | import { APActorCreator, fetchRemoteActor } from './shared' | 11 | import { APActorCreator, fetchRemoteActor } from './shared' |
@@ -87,7 +87,7 @@ async function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: stri | |||
87 | 87 | ||
88 | async function findOwner (rootUrl: string, attributedTo: APObjectId[] | APObjectId, type: 'Person' | 'Group') { | 88 | async function findOwner (rootUrl: string, attributedTo: APObjectId[] | APObjectId, type: 'Person' | 'Group') { |
89 | for (const actorToCheck of arrayify(attributedTo)) { | 89 | for (const actorToCheck of arrayify(attributedTo)) { |
90 | const actorObject = await fetchAPObject<ActivityPubActor>(getAPId(actorToCheck)) | 90 | const actorObject = await fetchAPObjectIfNeeded<ActivityPubActor>(getAPId(actorToCheck)) |
91 | 91 | ||
92 | if (!actorObject) { | 92 | if (!actorObject) { |
93 | logger.warn('Unknown attributed to actor %s for owner %s', actorToCheck, rootUrl) | 93 | logger.warn('Unknown attributed to actor %s for owner %s', actorToCheck, rootUrl) |
diff --git a/server/lib/activitypub/actors/shared/url-to-object.ts b/server/lib/activitypub/actors/shared/url-to-object.ts index 208d108ee..73766bd50 100644 --- a/server/lib/activitypub/actors/shared/url-to-object.ts +++ b/server/lib/activitypub/actors/shared/url-to-object.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor' | 1 | import { sanitizeAndCheckActorObject } from '@server/helpers/custom-validators/activitypub/actor' |
2 | import { logger } from '@server/helpers/logger' | 2 | import { logger } from '@server/helpers/logger' |
3 | import { doJSONRequest } from '@server/helpers/requests' | ||
4 | import { ActivityPubActor, ActivityPubOrderedCollection } from '@shared/models' | 3 | import { ActivityPubActor, ActivityPubOrderedCollection } from '@shared/models' |
4 | import { fetchAP } from '../../activity' | ||
5 | import { checkUrlsSameHost } from '../../url' | 5 | import { checkUrlsSameHost } from '../../url' |
6 | 6 | ||
7 | async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode: number, actorObject: ActivityPubActor }> { | 7 | async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode: number, actorObject: ActivityPubActor }> { |
8 | logger.info('Fetching remote actor %s.', actorUrl) | 8 | logger.info('Fetching remote actor %s.', actorUrl) |
9 | 9 | ||
10 | const { body, statusCode } = await doJSONRequest<ActivityPubActor>(actorUrl, { activityPub: true }) | 10 | const { body, statusCode } = await fetchAP<ActivityPubActor>(actorUrl) |
11 | 11 | ||
12 | if (sanitizeAndCheckActorObject(body) === false) { | 12 | if (sanitizeAndCheckActorObject(body) === false) { |
13 | logger.debug('Remote actor JSON is not valid.', { actorJSON: body }) | 13 | logger.debug('Remote actor JSON is not valid.', { actorJSON: body }) |
@@ -46,7 +46,7 @@ export { | |||
46 | 46 | ||
47 | async function fetchActorTotalItems (url: string) { | 47 | async function fetchActorTotalItems (url: string) { |
48 | try { | 48 | try { |
49 | const { body } = await doJSONRequest<ActivityPubOrderedCollection<unknown>>(url, { activityPub: true }) | 49 | const { body } = await fetchAP<ActivityPubOrderedCollection<unknown>>(url) |
50 | 50 | ||
51 | return body.totalItems || 0 | 51 | return body.totalItems || 0 |
52 | } catch (err) { | 52 | } catch (err) { |
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts index 336129b82..b8348e8cf 100644 --- a/server/lib/activitypub/crawl.ts +++ b/server/lib/activitypub/crawl.ts | |||
@@ -3,8 +3,8 @@ import { URL } from 'url' | |||
3 | import { retryTransactionWrapper } from '@server/helpers/database-utils' | 3 | import { retryTransactionWrapper } from '@server/helpers/database-utils' |
4 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' | 4 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { doJSONRequest } from '../../helpers/requests' | ||
7 | import { ACTIVITY_PUB, WEBSERVER } from '../../initializers/constants' | 6 | import { ACTIVITY_PUB, WEBSERVER } from '../../initializers/constants' |
7 | import { fetchAP } from './activity' | ||
8 | 8 | ||
9 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) | 9 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) |
10 | type CleanerFunction = (startedDate: Date) => Promise<any> | 10 | type CleanerFunction = (startedDate: Date) => Promise<any> |
@@ -14,11 +14,9 @@ async function crawlCollectionPage <T> (argUrl: string, handler: HandlerFunction | |||
14 | 14 | ||
15 | logger.info('Crawling ActivityPub data on %s.', url) | 15 | logger.info('Crawling ActivityPub data on %s.', url) |
16 | 16 | ||
17 | const options = { activityPub: true } | ||
18 | |||
19 | const startDate = new Date() | 17 | const startDate = new Date() |
20 | 18 | ||
21 | const response = await doJSONRequest<ActivityPubOrderedCollection<T>>(url, options) | 19 | const response = await fetchAP<ActivityPubOrderedCollection<T>>(url) |
22 | const firstBody = response.body | 20 | const firstBody = response.body |
23 | 21 | ||
24 | const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT | 22 | const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT |
@@ -34,7 +32,7 @@ async function crawlCollectionPage <T> (argUrl: string, handler: HandlerFunction | |||
34 | 32 | ||
35 | url = nextLink | 33 | url = nextLink |
36 | 34 | ||
37 | const res = await doJSONRequest<ActivityPubOrderedCollection<T>>(url, options) | 35 | const res = await fetchAP<ActivityPubOrderedCollection<T>>(url) |
38 | body = res.body | 36 | body = res.body |
39 | } else { | 37 | } else { |
40 | // nextLink is already the object we want | 38 | // nextLink is already the object we want |
diff --git a/server/lib/activitypub/playlists/shared/url-to-object.ts b/server/lib/activitypub/playlists/shared/url-to-object.ts index 41bee3752..fd9fe5558 100644 --- a/server/lib/activitypub/playlists/shared/url-to-object.ts +++ b/server/lib/activitypub/playlists/shared/url-to-object.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '@server/helpers/custom-validators/activitypub/playlist' | 1 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '@server/helpers/custom-validators/activitypub/playlist' |
2 | import { isArray } from '@server/helpers/custom-validators/misc' | 2 | import { isArray } from '@server/helpers/custom-validators/misc' |
3 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 3 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
4 | import { doJSONRequest } from '@server/helpers/requests' | ||
5 | import { PlaylistElementObject, PlaylistObject } from '@shared/models' | 4 | import { PlaylistElementObject, PlaylistObject } from '@shared/models' |
5 | import { fetchAP } from '../../activity' | ||
6 | import { checkUrlsSameHost } from '../../url' | 6 | import { checkUrlsSameHost } from '../../url' |
7 | 7 | ||
8 | async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> { | 8 | async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> { |
@@ -10,7 +10,7 @@ async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusC | |||
10 | 10 | ||
11 | logger.info('Fetching remote playlist %s.', playlistUrl, lTags()) | 11 | logger.info('Fetching remote playlist %s.', playlistUrl, lTags()) |
12 | 12 | ||
13 | const { body, statusCode } = await doJSONRequest<any>(playlistUrl, { activityPub: true }) | 13 | const { body, statusCode } = await fetchAP<any>(playlistUrl) |
14 | 14 | ||
15 | if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) { | 15 | if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) { |
16 | logger.debug('Remote video playlist JSON is not valid.', { body, ...lTags() }) | 16 | logger.debug('Remote video playlist JSON is not valid.', { body, ...lTags() }) |
@@ -30,7 +30,7 @@ async function fetchRemotePlaylistElement (elementUrl: string): Promise<{ status | |||
30 | 30 | ||
31 | logger.debug('Fetching remote playlist element %s.', elementUrl, lTags()) | 31 | logger.debug('Fetching remote playlist element %s.', elementUrl, lTags()) |
32 | 32 | ||
33 | const { body, statusCode } = await doJSONRequest<PlaylistElementObject>(elementUrl, { activityPub: true }) | 33 | const { body, statusCode } = await fetchAP<PlaylistElementObject>(elementUrl) |
34 | 34 | ||
35 | if (!isPlaylistElementObjectValid(body)) throw new Error(`Invalid body in fetch playlist element ${elementUrl}`) | 35 | if (!isPlaylistElementObjectValid(body)) throw new Error(`Invalid body in fetch playlist element ${elementUrl}`) |
36 | 36 | ||
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 2e64d981e..5f980de65 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -18,7 +18,7 @@ import { sequelizeTypescript } from '../../../initializers/database' | |||
18 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 18 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
19 | import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models' | 19 | import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models' |
20 | import { Notifier } from '../../notifier' | 20 | import { Notifier } from '../../notifier' |
21 | import { fetchAPObject } from '../activity' | 21 | import { fetchAPObjectIfNeeded } from '../activity' |
22 | import { createOrUpdateCacheFile } from '../cache-file' | 22 | import { createOrUpdateCacheFile } from '../cache-file' |
23 | import { createOrUpdateLocalVideoViewer } from '../local-video-viewer' | 23 | import { createOrUpdateLocalVideoViewer } from '../local-video-viewer' |
24 | import { createOrUpdateVideoPlaylist } from '../playlists' | 24 | import { createOrUpdateVideoPlaylist } from '../playlists' |
@@ -31,7 +31,7 @@ async function processCreateActivity (options: APProcessorOptions<ActivityCreate | |||
31 | 31 | ||
32 | // Only notify if it is not from a fetcher job | 32 | // Only notify if it is not from a fetcher job |
33 | const notify = options.fromFetch !== true | 33 | const notify = options.fromFetch !== true |
34 | const activityObject = await fetchAPObject<Exclude<ActivityObject, AbuseObject>>(activity.object) | 34 | const activityObject = await fetchAPObjectIfNeeded<Exclude<ActivityObject, AbuseObject>>(activity.object) |
35 | const activityType = activityObject.type | 35 | const activityType = activityObject.type |
36 | 36 | ||
37 | if (activityType === 'Video') { | 37 | if (activityType === 'Video') { |
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index 25f68724d..a9d8199de 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts | |||
@@ -19,7 +19,7 @@ import { VideoRedundancyModel } from '../../../models/redundancy/video-redundanc | |||
19 | import { VideoShareModel } from '../../../models/video/video-share' | 19 | import { VideoShareModel } from '../../../models/video/video-share' |
20 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 20 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
21 | import { MActorSignature } from '../../../types/models' | 21 | import { MActorSignature } from '../../../types/models' |
22 | import { fetchAPObject } from '../activity' | 22 | import { fetchAPObjectIfNeeded } from '../activity' |
23 | import { forwardVideoRelatedActivity } from '../send/shared/send-utils' | 23 | import { forwardVideoRelatedActivity } from '../send/shared/send-utils' |
24 | import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos' | 24 | import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos' |
25 | 25 | ||
@@ -32,7 +32,7 @@ async function processUndoActivity (options: APProcessorOptions<ActivityUndo<Act | |||
32 | } | 32 | } |
33 | 33 | ||
34 | if (activityToUndo.type === 'Create') { | 34 | if (activityToUndo.type === 'Create') { |
35 | const objectToUndo = await fetchAPObject<CacheFileObject>(activityToUndo.object) | 35 | const objectToUndo = await fetchAPObjectIfNeeded<CacheFileObject>(activityToUndo.object) |
36 | 36 | ||
37 | if (objectToUndo.type === 'CacheFile') { | 37 | if (objectToUndo.type === 'CacheFile') { |
38 | return retryTransactionWrapper(processUndoCacheFile, byActor, activity, objectToUndo) | 38 | return retryTransactionWrapper(processUndoCacheFile, byActor, activity, objectToUndo) |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 9caa74e04..304ed9de6 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -10,7 +10,7 @@ import { sequelizeTypescript } from '../../../initializers/database' | |||
10 | import { ActorModel } from '../../../models/actor/actor' | 10 | import { ActorModel } from '../../../models/actor/actor' |
11 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 11 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
12 | import { MActorFull, MActorSignature } from '../../../types/models' | 12 | import { MActorFull, MActorSignature } from '../../../types/models' |
13 | import { fetchAPObject } from '../activity' | 13 | import { fetchAPObjectIfNeeded } from '../activity' |
14 | import { APActorUpdater } from '../actors/updater' | 14 | import { APActorUpdater } from '../actors/updater' |
15 | import { createOrUpdateCacheFile } from '../cache-file' | 15 | import { createOrUpdateCacheFile } from '../cache-file' |
16 | import { createOrUpdateVideoPlaylist } from '../playlists' | 16 | import { createOrUpdateVideoPlaylist } from '../playlists' |
@@ -20,7 +20,7 @@ import { APVideoUpdater, getOrCreateAPVideo } from '../videos' | |||
20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) { | 20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) { |
21 | const { activity, byActor } = options | 21 | const { activity, byActor } = options |
22 | 22 | ||
23 | const object = await fetchAPObject(activity.object) | 23 | const object = await fetchAPObjectIfNeeded(activity.object) |
24 | const objectType = object.type | 24 | const objectType = object.type |
25 | 25 | ||
26 | if (objectType === 'Video') { | 26 | if (objectType === 'Video') { |
diff --git a/server/lib/activitypub/send/http.ts b/server/lib/activitypub/send/http.ts index ad7869853..b461aa55d 100644 --- a/server/lib/activitypub/send/http.ts +++ b/server/lib/activitypub/send/http.ts | |||
@@ -23,11 +23,14 @@ async function computeBody <T> ( | |||
23 | return body | 23 | return body |
24 | } | 24 | } |
25 | 25 | ||
26 | async function buildSignedRequestOptions (payload: Payload<any>) { | 26 | async function buildSignedRequestOptions (options: { |
27 | signatureActorId?: number | ||
28 | hasPayload: boolean | ||
29 | }) { | ||
27 | let actor: MActor | null | 30 | let actor: MActor | null |
28 | 31 | ||
29 | if (payload.signatureActorId) { | 32 | if (options.signatureActorId) { |
30 | actor = await ActorModel.load(payload.signatureActorId) | 33 | actor = await ActorModel.load(options.signatureActorId) |
31 | if (!actor) throw new Error('Unknown signature actor id.') | 34 | if (!actor) throw new Error('Unknown signature actor id.') |
32 | } else { | 35 | } else { |
33 | // We need to sign the request, so use the server | 36 | // We need to sign the request, so use the server |
@@ -40,7 +43,9 @@ async function buildSignedRequestOptions (payload: Payload<any>) { | |||
40 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, | 43 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, |
41 | keyId, | 44 | keyId, |
42 | key: actor.privateKey, | 45 | key: actor.privateKey, |
43 | headers: HTTP_SIGNATURE.HEADERS_TO_SIGN | 46 | headers: options.hasPayload |
47 | ? HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD | ||
48 | : HTTP_SIGNATURE.HEADERS_TO_SIGN_WITHOUT_PAYLOAD | ||
44 | } | 49 | } |
45 | } | 50 | } |
46 | 51 | ||
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index af0dd510a..792a73f2a 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts | |||
@@ -2,11 +2,10 @@ import { map } from 'bluebird' | |||
2 | import { Transaction } from 'sequelize' | 2 | import { Transaction } from 'sequelize' |
3 | import { getServerActor } from '@server/models/application/application' | 3 | import { getServerActor } from '@server/models/application/application' |
4 | import { logger, loggerTagsFactory } from '../../helpers/logger' | 4 | import { logger, loggerTagsFactory } from '../../helpers/logger' |
5 | import { doJSONRequest } from '../../helpers/requests' | ||
6 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 5 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
7 | import { VideoShareModel } from '../../models/video/video-share' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
8 | import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video' | 7 | import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../types/models/video' |
9 | import { getAPId } from './activity' | 8 | import { fetchAP, getAPId } from './activity' |
10 | import { getOrCreateAPActor } from './actors' | 9 | import { getOrCreateAPActor } from './actors' |
11 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' | 10 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' |
12 | import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url' | 11 | import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url' |
@@ -56,7 +55,7 @@ export { | |||
56 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
57 | 56 | ||
58 | async function addVideoShare (shareUrl: string, video: MVideoId) { | 57 | async function addVideoShare (shareUrl: string, video: MVideoId) { |
59 | const { body } = await doJSONRequest<any>(shareUrl, { activityPub: true }) | 58 | const { body } = await fetchAP<any>(shareUrl) |
60 | if (!body?.actor) throw new Error('Body or body actor is invalid') | 59 | if (!body?.actor) throw new Error('Body or body actor is invalid') |
61 | 60 | ||
62 | const actorUrl = getAPId(body.actor) | 61 | const actorUrl = getAPId(body.actor) |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 4fdb4e0b7..b861be5bd 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import { map } from 'bluebird' | 1 | import { map } from 'bluebird' |
2 | |||
2 | import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' | 3 | import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' |
3 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
4 | import { doJSONRequest } from '../../helpers/requests' | ||
5 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 5 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
6 | import { VideoCommentModel } from '../../models/video/video-comment' | 6 | import { VideoCommentModel } from '../../models/video/video-comment' |
7 | import { MComment, MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video' | 7 | import { MComment, MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video' |
8 | import { isRemoteVideoCommentAccepted } from '../moderation' | 8 | import { isRemoteVideoCommentAccepted } from '../moderation' |
9 | import { Hooks } from '../plugins/hooks' | 9 | import { Hooks } from '../plugins/hooks' |
10 | import { fetchAP } from './activity' | ||
10 | import { getOrCreateAPActor } from './actors' | 11 | import { getOrCreateAPActor } from './actors' |
11 | import { checkUrlsSameHost } from './url' | 12 | import { checkUrlsSameHost } from './url' |
12 | import { getOrCreateAPVideo } from './videos' | 13 | import { getOrCreateAPVideo } from './videos' |
@@ -139,7 +140,7 @@ async function resolveRemoteParentComment (params: ResolveThreadParams) { | |||
139 | throw new Error('Recursion limit reached when resolving a thread') | 140 | throw new Error('Recursion limit reached when resolving a thread') |
140 | } | 141 | } |
141 | 142 | ||
142 | const { body } = await doJSONRequest<any>(url, { activityPub: true }) | 143 | const { body } = await fetchAP<any>(url) |
143 | 144 | ||
144 | if (sanitizeAndCheckVideoCommentObject(body) === false) { | 145 | if (sanitizeAndCheckVideoCommentObject(body) === false) { |
145 | throw new Error(`Remote video comment JSON ${url} is not valid:` + JSON.stringify(body)) | 146 | throw new Error(`Remote video comment JSON ${url} is not valid:` + JSON.stringify(body)) |
diff --git a/server/lib/activitypub/videos/shared/url-to-object.ts b/server/lib/activitypub/videos/shared/url-to-object.ts index 5b7007530..7fe008419 100644 --- a/server/lib/activitypub/videos/shared/url-to-object.ts +++ b/server/lib/activitypub/videos/shared/url-to-object.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos' | 1 | import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos' |
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
3 | import { doJSONRequest } from '@server/helpers/requests' | ||
4 | import { VideoObject } from '@shared/models' | 3 | import { VideoObject } from '@shared/models' |
4 | import { fetchAP } from '../../activity' | ||
5 | import { checkUrlsSameHost } from '../../url' | 5 | import { checkUrlsSameHost } from '../../url' |
6 | 6 | ||
7 | const lTags = loggerTagsFactory('ap', 'video') | 7 | const lTags = loggerTagsFactory('ap', 'video') |
@@ -9,7 +9,7 @@ const lTags = loggerTagsFactory('ap', 'video') | |||
9 | async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> { | 9 | async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> { |
10 | logger.info('Fetching remote video %s.', videoUrl, lTags(videoUrl)) | 10 | logger.info('Fetching remote video %s.', videoUrl, lTags(videoUrl)) |
11 | 11 | ||
12 | const { statusCode, body } = await doJSONRequest<any>(videoUrl, { activityPub: true }) | 12 | const { statusCode, body } = await fetchAP<any>(videoUrl) |
13 | 13 | ||
14 | if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) { | 14 | if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) { |
15 | logger.debug('Remote video JSON is not valid.', { body, ...lTags(videoUrl) }) | 15 | logger.debug('Remote video JSON is not valid.', { body, ...lTags(videoUrl) }) |
diff --git a/server/lib/activitypub/videos/shared/video-sync-attributes.ts b/server/lib/activitypub/videos/shared/video-sync-attributes.ts index aa37f3d34..7fb933559 100644 --- a/server/lib/activitypub/videos/shared/video-sync-attributes.ts +++ b/server/lib/activitypub/videos/shared/video-sync-attributes.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import { runInReadCommittedTransaction } from '@server/helpers/database-utils' | 1 | import { runInReadCommittedTransaction } from '@server/helpers/database-utils' |
2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' | 2 | import { logger, loggerTagsFactory } from '@server/helpers/logger' |
3 | import { doJSONRequest } from '@server/helpers/requests' | ||
4 | import { JobQueue } from '@server/lib/job-queue' | 3 | import { JobQueue } from '@server/lib/job-queue' |
5 | import { VideoModel } from '@server/models/video/video' | 4 | import { VideoModel } from '@server/models/video/video' |
6 | import { VideoCommentModel } from '@server/models/video/video-comment' | 5 | import { VideoCommentModel } from '@server/models/video/video-comment' |
7 | import { VideoShareModel } from '@server/models/video/video-share' | 6 | import { VideoShareModel } from '@server/models/video/video-share' |
8 | import { MVideo } from '@server/types/models' | 7 | import { MVideo } from '@server/types/models' |
9 | import { ActivitypubHttpFetcherPayload, ActivityPubOrderedCollection, VideoObject } from '@shared/models' | 8 | import { ActivitypubHttpFetcherPayload, ActivityPubOrderedCollection, VideoObject } from '@shared/models' |
9 | import { fetchAP } from '../../activity' | ||
10 | import { crawlCollectionPage } from '../../crawl' | 10 | import { crawlCollectionPage } from '../../crawl' |
11 | import { addVideoShares } from '../../share' | 11 | import { addVideoShares } from '../../share' |
12 | import { addVideoComments } from '../../video-comments' | 12 | import { addVideoComments } from '../../video-comments' |
@@ -63,17 +63,15 @@ async function getRatesCount (type: 'like' | 'dislike', video: MVideo, fetchedVi | |||
63 | : fetchedVideo.dislikes | 63 | : fetchedVideo.dislikes |
64 | 64 | ||
65 | logger.info('Sync %s of video %s', type, video.url) | 65 | logger.info('Sync %s of video %s', type, video.url) |
66 | const options = { activityPub: true } | ||
67 | 66 | ||
68 | const response = await doJSONRequest<ActivityPubOrderedCollection<any>>(uri, options) | 67 | const { body } = await fetchAP<ActivityPubOrderedCollection<any>>(uri) |
69 | const totalItems = response.body.totalItems | ||
70 | 68 | ||
71 | if (isNaN(totalItems)) { | 69 | if (isNaN(body.totalItems)) { |
72 | logger.error('Cannot sync %s of video %s, totalItems is not a number', type, video.url, { body: response.body }) | 70 | logger.error('Cannot sync %s of video %s, totalItems is not a number', type, video.url, { body }) |
73 | return | 71 | return |
74 | } | 72 | } |
75 | 73 | ||
76 | return totalItems | 74 | return body.totalItems |
77 | } | 75 | } |
78 | 76 | ||
79 | function syncShares (video: MVideo, fetchedVideo: VideoObject, isSync: boolean) { | 77 | function syncShares (video: MVideo, fetchedVideo: VideoObject, isSync: boolean) { |
diff --git a/server/lib/job-queue/handlers/activitypub-cleaner.ts b/server/lib/job-queue/handlers/activitypub-cleaner.ts index a25f00b0a..6ee9e2429 100644 --- a/server/lib/job-queue/handlers/activitypub-cleaner.ts +++ b/server/lib/job-queue/handlers/activitypub-cleaner.ts | |||
@@ -6,8 +6,9 @@ import { | |||
6 | isLikeActivityValid | 6 | isLikeActivityValid |
7 | } from '@server/helpers/custom-validators/activitypub/activity' | 7 | } from '@server/helpers/custom-validators/activitypub/activity' |
8 | import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments' | 8 | import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments' |
9 | import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests' | 9 | import { PeerTubeRequestError } from '@server/helpers/requests' |
10 | import { AP_CLEANER } from '@server/initializers/constants' | 10 | import { AP_CLEANER } from '@server/initializers/constants' |
11 | import { fetchAP } from '@server/lib/activitypub/activity' | ||
11 | import { checkUrlsSameHost } from '@server/lib/activitypub/url' | 12 | import { checkUrlsSameHost } from '@server/lib/activitypub/url' |
12 | import { Redis } from '@server/lib/redis' | 13 | import { Redis } from '@server/lib/redis' |
13 | import { VideoModel } from '@server/models/video/video' | 14 | import { VideoModel } from '@server/models/video/video' |
@@ -85,7 +86,7 @@ async function updateObjectIfNeeded <T> (options: { | |||
85 | } | 86 | } |
86 | 87 | ||
87 | try { | 88 | try { |
88 | const { body } = await doJSONRequest<any>(url, { activityPub: true }) | 89 | const { body } = await fetchAP<any>(url) |
89 | 90 | ||
90 | // If not same id, check same host and update | 91 | // If not same id, check same host and update |
91 | if (!body?.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`) | 92 | if (!body?.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`) |
diff --git a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts index 57ecf0acc..8904d086f 100644 --- a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts | |||
@@ -38,7 +38,7 @@ export { | |||
38 | 38 | ||
39 | async function buildRequestOptions (payload: ActivitypubHttpBroadcastPayload) { | 39 | async function buildRequestOptions (payload: ActivitypubHttpBroadcastPayload) { |
40 | const body = await computeBody(payload) | 40 | const body = await computeBody(payload) |
41 | const httpSignatureOptions = await buildSignedRequestOptions(payload) | 41 | const httpSignatureOptions = await buildSignedRequestOptions({ signatureActorId: payload.signatureActorId, hasPayload: true }) |
42 | 42 | ||
43 | return { | 43 | return { |
44 | method: 'POST' as 'POST', | 44 | method: 'POST' as 'POST', |
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts index 9e4e84002..50fca3f94 100644 --- a/server/lib/job-queue/handlers/activitypub-http-unicast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts | |||
@@ -12,7 +12,7 @@ async function processActivityPubHttpUnicast (job: Job) { | |||
12 | const uri = payload.uri | 12 | const uri = payload.uri |
13 | 13 | ||
14 | const body = await computeBody(payload) | 14 | const body = await computeBody(payload) |
15 | const httpSignatureOptions = await buildSignedRequestOptions(payload) | 15 | const httpSignatureOptions = await buildSignedRequestOptions({ signatureActorId: payload.signatureActorId, hasPayload: true }) |
16 | 16 | ||
17 | const options = { | 17 | const options = { |
18 | method: 'POST' as 'POST', | 18 | method: 'POST' as 'POST', |