From a421dd401ceb03e4937ba2b0fab925449b0790bf Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 4 Dec 2022 00:29:45 +0100 Subject: [PATCH] activitypub: refactor to always apply recursion limit Refactor to remove as many "new Resolver" as possible. --- .../backend/src/queue/processors/inbox.ts | 2 +- .../remote/activitypub/kernel/accept/index.ts | 4 +--- .../activitypub/kernel/announce/index.ts | 4 +--- .../remote/activitypub/kernel/create/index.ts | 4 +--- .../src/remote/activitypub/kernel/index.ts | 21 +++++++++---------- .../remote/activitypub/kernel/reject/index.ts | 4 +--- .../remote/activitypub/kernel/undo/index.ts | 3 +-- .../remote/activitypub/kernel/update/index.ts | 4 +--- .../src/remote/activitypub/misc/auth-user.ts | 2 +- .../src/remote/activitypub/models/image.ts | 10 ++++----- .../src/remote/activitypub/models/mention.ts | 4 +--- .../src/remote/activitypub/models/note.ts | 6 +++--- .../src/remote/activitypub/models/person.ts | 16 +++++++------- .../src/remote/activitypub/models/question.ts | 4 ++-- .../backend/src/remote/activitypub/perform.ts | 7 ++++--- packages/backend/src/remote/resolve-user.ts | 7 ++++--- .../src/server/api/endpoints/ap/show.ts | 4 ++-- .../federation/update-remote-user.ts | 3 ++- 18 files changed, 48 insertions(+), 61 deletions(-) diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 06dcf16c0..bd208546e 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -137,6 +137,6 @@ export default async (job: Bull.Job): Promise => { }); // アクティビティを処理 - await perform(authUser.user, activity); + await perform(authUser.user, activity, resolver); return 'ok'; }; diff --git a/packages/backend/src/remote/activitypub/kernel/accept/index.ts b/packages/backend/src/remote/activitypub/kernel/accept/index.ts index ee0b6ed88..e2f5be16b 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/index.ts @@ -4,13 +4,11 @@ import Resolver from '@/remote/activitypub/resolver.js'; import { IAccept, isFollow, getApType } from '@/remote/activitypub/type.js'; import acceptFollow from './follow.js'; -export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAccept, resolver: Resolver): Promise => { const uri = activity.id || activity; apLogger.info(`Accept: ${uri}`); - const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts index e32e8fef4..4c13d763d 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts @@ -4,13 +4,11 @@ import Resolver from '@/remote/activitypub/resolver.js'; import { IAnnounce, getApId } from '@/remote/activitypub/type.js'; import announceNote from './note.js'; -export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAnnounce, resolver: Resolver): Promise => { const uri = getApId(activity); apLogger.info(`Announce: ${uri}`); - const resolver = new Resolver(); - const targetUri = getApId(activity.object); announceNote(resolver, actor, activity, targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/create/index.ts b/packages/backend/src/remote/activitypub/kernel/create/index.ts index e7531d3d3..a3abd4815 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/index.ts @@ -5,7 +5,7 @@ import { ICreate, getApId, isPost, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; import createNote from './note.js'; -export default async (actor: CacheableRemoteUser, activity: ICreate): Promise => { +export default async (actor: CacheableRemoteUser, activity: ICreate, resolver: Resolver): Promise => { const uri = getApId(activity); apLogger.info(`Create: ${uri}`); @@ -26,8 +26,6 @@ export default async (actor: CacheableRemoteUser, activity: ICreate): Promise { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index e2da77ef0..d1c1199b7 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -18,13 +18,12 @@ import remove from './remove/index.js'; import block from './block/index.js'; import flag from './flag/index.js'; -export async function performActivity(actor: CacheableRemoteUser, activity: IObject) { +export async function performActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver) { if (isCollectionOrOrderedCollection(activity)) { - const resolver = new Resolver(); for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { const act = await resolver.resolve(item); try { - await performOneActivity(actor, act); + await performOneActivity(actor, act, resolver); } catch (err) { if (err instanceof Error || typeof err === 'string') { apLogger.error(err); @@ -32,37 +31,37 @@ export async function performActivity(actor: CacheableRemoteUser, activity: IObj } } } else { - await performOneActivity(actor, activity); + await performOneActivity(actor, activity, resolver); } } -async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { +async function performOneActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise { if (actor.isSuspended) return; if (isCreate(activity)) { - await create(actor, activity); + await create(actor, activity, resolver); } else if (isDelete(activity)) { await performDeleteActivity(actor, activity); } else if (isUpdate(activity)) { - await performUpdateActivity(actor, activity); + await performUpdateActivity(actor, activity, resolver); } else if (isRead(activity)) { await performReadActivity(actor, activity); } else if (isFollow(activity)) { await follow(actor, activity); } else if (isAccept(activity)) { - await accept(actor, activity); + await accept(actor, activity, resolver); } else if (isReject(activity)) { - await reject(actor, activity); + await reject(actor, activity, resolver); } else if (isAdd(activity)) { await add(actor, activity).catch(err => apLogger.error(err)); } else if (isRemove(activity)) { await remove(actor, activity).catch(err => apLogger.error(err)); } else if (isAnnounce(activity)) { - await announce(actor, activity); + await announce(actor, activity, resolver); } else if (isLike(activity)) { await like(actor, activity); } else if (isUndo(activity)) { - await undo(actor, activity); + await undo(actor, activity, resolver); } else if (isBlock(activity)) { await block(actor, activity); } else if (isFlag(activity)) { diff --git a/packages/backend/src/remote/activitypub/kernel/reject/index.ts b/packages/backend/src/remote/activitypub/kernel/reject/index.ts index f75b8b44f..bb6138a6d 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/index.ts @@ -4,13 +4,11 @@ import { IReject, isFollow, getApType } from '../../type.js'; import Resolver from '../../resolver.js'; import rejectFollow from './follow.js'; -export default async (actor: CacheableRemoteUser, activity: IReject): Promise => { +export default async (actor: CacheableRemoteUser, activity: IReject, resolver: Resolver): Promise => { const uri = activity.id || activity; apLogger.info(`Reject: ${uri}`); - const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/kernel/undo/index.ts b/packages/backend/src/remote/activitypub/kernel/undo/index.ts index d85a68fdc..5233a8a5b 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/index.ts @@ -8,7 +8,7 @@ import undoLike from './like.js'; import undoAccept from './accept.js'; import { undoAnnounce } from './announce.js'; -export default async (actor: CacheableRemoteUser, activity: IUndo): Promise => { +export default async (actor: CacheableRemoteUser, activity: IUndo, resolver: Resolver): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -17,7 +17,6 @@ export default async (actor: CacheableRemoteUser, activity: IUndo): Promise { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 29a352f2f..d9ca414c8 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -8,15 +8,13 @@ import { updatePerson } from '@/remote/activitypub/models/person.js'; /** * Updateアクティビティを捌きます */ -export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise => { +export default async (actor: CacheableRemoteUser, activity: IUpdate, resolver: Resolver): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { return 'skip: invalid actor'; } apLogger.debug('Update'); - const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/misc/auth-user.ts b/packages/backend/src/remote/activitypub/misc/auth-user.ts index 172fa8809..d8a7202f2 100644 --- a/packages/backend/src/remote/activitypub/misc/auth-user.ts +++ b/packages/backend/src/remote/activitypub/misc/auth-user.ts @@ -31,7 +31,7 @@ function authUserFromApId(uri: string): Promise { export async function getAuthUser(keyId: string, actorUri: string, resolver: Resolver): Promise { let authUser = await publicKeyCache.fetch(keyId) - .then(key => { + .then(async key => { if (!key) return null; else return { user: await userByIdCache.fetch(key.userId), diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index d9d533f4d..d13412579 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -11,13 +11,13 @@ import { apLogger } from '../logger.js'; /** * Imageを作成します。 */ -export async function createImage(actor: CacheableRemoteUser, value: any): Promise { +export async function createImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { throw new Error('actor has been suspended'); } - const image = await new Resolver().resolve(value) as any; + const image = await resolver.resolve(value) as any; if (image.url == null) { throw new Error('invalid image: url not privided'); @@ -58,9 +58,9 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi * If the target Image is registered in FoundKey, return it; otherwise, fetch it from the remote server and return it. * Fetch the image from the remote server, register it in FoundKey and return it. */ -export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise { +export async function resolveImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise { // TODO - // リモートサーバーからフェッチしてきて登録 - return await createImage(actor, value); + // Fetch from remote server and register it. + return await createImage(actor, value, resolver); } diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index 9baf82f59..38ac7dd2e 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -5,11 +5,9 @@ import { IObject, isMention, IApMention } from '../type.js'; import Resolver from '../resolver.js'; import { resolvePerson } from './person.js'; -export async function extractApMentions(tags: IObject | IObject[] | null | undefined) { +export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); - const resolver = new Resolver(); - const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))), diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index a53783785..c1aa07f05 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -63,7 +63,7 @@ export async function fetchNote(object: string | IObject): Promise /** * Noteを作成します。 */ -export async function createNote(value: string | IObject, resolver?: Resolver = new Resolver(), silent = false): Promise { +export async function createNote(value: string | IObject, resolver: Resolver, silent = false): Promise { const object: any = await resolver.resolve(value); const entryUri = getApId(value); @@ -107,7 +107,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver = let isTalk = note._misskey_talk && visibility === 'specified'; - const apMentions = await extractApMentions(note.tag); + const apMentions = await extractApMentions(note.tag, resolver); const apHashtags = await extractApHashtags(note.tag); // 添付ファイル @@ -119,7 +119,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver = note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; const files = note.attachment .map(attach => attach.sensitive = note.sensitive) - ? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x)) as Promise))) + ? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x, resolver)) as Promise))) .filter(image => image != null) : []; diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index a34612a74..13107f24f 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -131,7 +131,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { +export async function createPerson(uri: string, resolver: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); if (uri.startsWith(config.url)) { @@ -238,7 +238,7 @@ export async function createPerson(uri: string, resolver?: Resolver = new Resolv ].map(img => img == null ? Promise.resolve(null) - : resolveImage(user!, img).catch(() => null), + : resolveImage(user!, img, resolver).catch(() => null), )); const avatarId = avatar ? avatar.id : null; @@ -278,7 +278,7 @@ export async function createPerson(uri: string, resolver?: Resolver = new Resolv * @param resolver Resolver * @param hint Hint of Person object (If this value is a valid Person, it is used for updating without Remote resolve.) */ -export async function updatePerson(uri: string, resolver?: Resolver = new Resolver(), hint?: IObject): Promise { +export async function updatePerson(uri: string, resolver: Resolver, hint?: IObject): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); // URIがこのサーバーを指しているならスキップ @@ -307,7 +307,7 @@ export async function updatePerson(uri: string, resolver?: Resolver = new Resolv ].map(img => img == null ? Promise.resolve(null) - : resolveImage(exist, img).catch(() => null), + : resolveImage(exist, img, resolver).catch(() => null), )); // カスタム絵文字取得 @@ -386,7 +386,7 @@ export async function updatePerson(uri: string, resolver?: Resolver = new Resolv * If the target Person is registered in FoundKey, return it; otherwise, fetch it from a remote server and return it. * Fetch the person from the remote server, register it in FoundKey, and return it. */ -export async function resolvePerson(uri: string, resolver?: Resolver): Promise { +export async function resolvePerson(uri: string, resolver: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); //#region このサーバーに既に登録されていたらそれを返す @@ -398,7 +398,7 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise { +export async function extractPollFromQuestion(source: string | IObject, resolver: Resolver): Promise { const question = await resolver.resolve(source); if (!isQuestion(question)) { @@ -39,7 +39,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver * @param resolver Resolver to use * @returns true if updated */ -export async function updateQuestion(value: string | IObject, resolver?: Resolver = new Resolver()) { +export async function updateQuestion(value: string | IObject, resolver: Resolver) { const uri = typeof value === 'string' ? value : value.id; // URIがこのサーバーを指しているならスキップ diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts index c8ecff3e7..0d90ba2cf 100644 --- a/packages/backend/src/remote/activitypub/perform.ts +++ b/packages/backend/src/remote/activitypub/perform.ts @@ -3,15 +3,16 @@ import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IObject } from './type.js'; import { performActivity } from './kernel/index.js'; import { updatePerson } from './models/person.js'; +import Resolver from './resolver.js'; -export default async (actor: CacheableRemoteUser, activity: IObject): Promise => { - await performActivity(actor, activity); +export default async (actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise => { + await performActivity(actor, activity, resolver); // And while I'm at it, I'll update the remote user information if it's out of date. if (actor.uri) { if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > DAY) { setImmediate(() => { - updatePerson(actor.uri!); + updatePerson(actor.uri!, resolver); }); } } diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index 77dfaad7c..309304d5e 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -9,10 +9,11 @@ import { Users } from '@/models/index.js'; import webFinger from './webfinger.js'; import { createPerson, updatePerson } from './activitypub/models/person.js'; import { remoteLogger } from './logger.js'; +import Resolver from './activitypub/resolver.js'; const logger = remoteLogger.createSubLogger('resolve-user'); -export async function resolveUser(username: string, idnHost: string | null): Promise { +export async function resolveUser(username: string, idnHost: string | null, resolver: Resolver = new Resolver()): Promise { const usernameLower = username.toLowerCase(); if (idnHost == null) { @@ -47,7 +48,7 @@ export async function resolveUser(username: string, idnHost: string | null): Pro const self = await resolveSelf(acctLower); logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); - return await createPerson(self); + return await createPerson(self, resolver); } // If user information is out of date, start over with webfinger @@ -81,7 +82,7 @@ export async function resolveUser(username: string, idnHost: string | null): Pro logger.info(`uri is fine: ${acctLower}`); } - await updatePerson(self); + await updatePerson(self, resolver); logger.info(`return resynced remote user: ${acctLower}`); return await Users.findOneBy({ uri: self }).then(u => { diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index ef66bac3f..0fb006b70 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -114,8 +114,8 @@ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): return await mergePack( me, - isActor(object) ? await createPerson(getApId(object)) : null, - isPost(object) ? await createNote(getApId(object), undefined, true) : null, + isActor(object) ? await createPerson(getApId(object), resolver) : null, + isPost(object) ? await createNote(getApId(object), resolver, true) : null, ); } diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 8b7be8e36..88134684e 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -1,3 +1,4 @@ +import Resolver from '@/remote/activitypub/resolver.js'; import { updatePerson } from '@/remote/activitypub/models/person.js'; import define from '../../define.js'; import { getRemoteUser } from '../../common/getters.js'; @@ -19,5 +20,5 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { const user = await getRemoteUser(ps.userId); - await updatePerson(user.uri!); + await updatePerson(user.uri!, new Resolver()); });