mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 07:24:13 +00:00 
			
		
		
		
	fix(federation): InboxにきたCreate, AnnounceのobjectがBearcaps urlだった際はスキップするように (#13610)
* fix(federation): AnnounceのobjectがLike出なかったらキューにためない Fix https://github.com/misskey-dev/misskey/issues/13552 * revert * better reason handlings * result * improve announce handling * skip bearcaps * also announce
This commit is contained in:
		
							parent
							
								
									1bb1a32986
								
							
						
					
					
						commit
						89b27d8587
					
				
					 4 changed files with 85 additions and 56 deletions
				
			
		| 
						 | 
				
			
			@ -28,6 +28,7 @@ import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserR
 | 
			
		|||
import { bindThis } from '@/decorators.js';
 | 
			
		||||
import type { MiRemoteUser } from '@/models/User.js';
 | 
			
		||||
import { isNotNull } from '@/misc/is-not-null.js';
 | 
			
		||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
 | 
			
		||||
import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
 | 
			
		||||
import { ApNoteService } from './models/ApNoteService.js';
 | 
			
		||||
import { ApLoggerService } from './ApLoggerService.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -36,9 +37,8 @@ import { ApResolverService } from './ApResolverService.js';
 | 
			
		|||
import { ApAudienceService } from './ApAudienceService.js';
 | 
			
		||||
import { ApPersonService } from './models/ApPersonService.js';
 | 
			
		||||
import { ApQuestionService } from './models/ApQuestionService.js';
 | 
			
		||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
 | 
			
		||||
import type { Resolver } from './ApResolverService.js';
 | 
			
		||||
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js';
 | 
			
		||||
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class ApInboxService {
 | 
			
		||||
| 
						 | 
				
			
			@ -90,13 +90,15 @@ export class ApInboxService {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
 | 
			
		||||
	public async performActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
 | 
			
		||||
		let result = undefined as string | void;
 | 
			
		||||
		if (isCollectionOrOrderedCollection(activity)) {
 | 
			
		||||
			const results = [] as [string, string | void][];
 | 
			
		||||
			const resolver = this.apResolverService.createResolver();
 | 
			
		||||
			for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
 | 
			
		||||
				const act = await resolver.resolve(item);
 | 
			
		||||
				try {
 | 
			
		||||
					await this.performOneActivity(actor, act);
 | 
			
		||||
					results.push([getApId(item), await this.performOneActivity(actor, act)]);
 | 
			
		||||
				} catch (err) {
 | 
			
		||||
					if (err instanceof Error || typeof err === 'string') {
 | 
			
		||||
						this.logger.error(err);
 | 
			
		||||
| 
						 | 
				
			
			@ -105,8 +107,13 @@ export class ApInboxService {
 | 
			
		|||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			const hasReason = results.some(([, reason]) => (reason != null && !reason.startsWith('ok')));
 | 
			
		||||
			if (hasReason) {
 | 
			
		||||
				result = results.map(([id, reason]) => `${id}: ${reason}`).join('\n');
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			await this.performOneActivity(actor, activity);
 | 
			
		||||
			result = await this.performOneActivity(actor, activity);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// ついでにリモートユーザーの情報が古かったら更新しておく
 | 
			
		||||
| 
						 | 
				
			
			@ -117,42 +124,43 @@ export class ApInboxService {
 | 
			
		|||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<void> {
 | 
			
		||||
	public async performOneActivity(actor: MiRemoteUser, activity: IObject): Promise<string | void> {
 | 
			
		||||
		if (actor.isSuspended) return;
 | 
			
		||||
 | 
			
		||||
		if (isCreate(activity)) {
 | 
			
		||||
			await this.create(actor, activity);
 | 
			
		||||
			return await this.create(actor, activity);
 | 
			
		||||
		} else if (isDelete(activity)) {
 | 
			
		||||
			await this.delete(actor, activity);
 | 
			
		||||
			return await this.delete(actor, activity);
 | 
			
		||||
		} else if (isUpdate(activity)) {
 | 
			
		||||
			await this.update(actor, activity);
 | 
			
		||||
			return await this.update(actor, activity);
 | 
			
		||||
		} else if (isFollow(activity)) {
 | 
			
		||||
			await this.follow(actor, activity);
 | 
			
		||||
			return await this.follow(actor, activity);
 | 
			
		||||
		} else if (isAccept(activity)) {
 | 
			
		||||
			await this.accept(actor, activity);
 | 
			
		||||
			return await this.accept(actor, activity);
 | 
			
		||||
		} else if (isReject(activity)) {
 | 
			
		||||
			await this.reject(actor, activity);
 | 
			
		||||
			return await this.reject(actor, activity);
 | 
			
		||||
		} else if (isAdd(activity)) {
 | 
			
		||||
			await this.add(actor, activity).catch(err => this.logger.error(err));
 | 
			
		||||
			return await this.add(actor, activity);
 | 
			
		||||
		} else if (isRemove(activity)) {
 | 
			
		||||
			await this.remove(actor, activity).catch(err => this.logger.error(err));
 | 
			
		||||
			return await this.remove(actor, activity);
 | 
			
		||||
		} else if (isAnnounce(activity)) {
 | 
			
		||||
			await this.announce(actor, activity);
 | 
			
		||||
			return await this.announce(actor, activity);
 | 
			
		||||
		} else if (isLike(activity)) {
 | 
			
		||||
			await this.like(actor, activity);
 | 
			
		||||
			return await this.like(actor, activity);
 | 
			
		||||
		} else if (isUndo(activity)) {
 | 
			
		||||
			await this.undo(actor, activity);
 | 
			
		||||
			return await this.undo(actor, activity);
 | 
			
		||||
		} else if (isBlock(activity)) {
 | 
			
		||||
			await this.block(actor, activity);
 | 
			
		||||
			return await this.block(actor, activity);
 | 
			
		||||
		} else if (isFlag(activity)) {
 | 
			
		||||
			await this.flag(actor, activity);
 | 
			
		||||
			return await this.flag(actor, activity);
 | 
			
		||||
		} else if (isMove(activity)) {
 | 
			
		||||
			await this.move(actor, activity);
 | 
			
		||||
			return await this.move(actor, activity);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.logger.warn(`unrecognized activity type: ${activity.type}`);
 | 
			
		||||
			return `unrecognized activity type: ${activity.type}`;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -234,38 +242,49 @@ export class ApInboxService {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	private async add(actor: MiRemoteUser, activity: IAdd): Promise<void> {
 | 
			
		||||
	private async add(actor: MiRemoteUser, activity: IAdd): Promise<string | void> {
 | 
			
		||||
		if (actor.uri !== activity.actor) {
 | 
			
		||||
			throw new Error('invalid actor');
 | 
			
		||||
			return 'invalid actor';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (activity.target == null) {
 | 
			
		||||
			throw new Error('target is null');
 | 
			
		||||
			return 'target is null';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (activity.target === actor.featured) {
 | 
			
		||||
			const note = await this.apNoteService.resolveNote(activity.object);
 | 
			
		||||
			if (note == null) throw new Error('note not found');
 | 
			
		||||
			if (note == null) return 'note not found';
 | 
			
		||||
			await this.notePiningService.addPinned(actor, note.id);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		throw new Error(`unknown target: ${activity.target}`);
 | 
			
		||||
		return `unknown target: ${activity.target}`;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise<void> {
 | 
			
		||||
	private async announce(actor: MiRemoteUser, activity: IAnnounce): Promise<string | void> {
 | 
			
		||||
		const uri = getApId(activity);
 | 
			
		||||
 | 
			
		||||
		this.logger.info(`Announce: ${uri}`);
 | 
			
		||||
 | 
			
		||||
		const targetUri = getApId(activity.object);
 | 
			
		||||
		const resolver = this.apResolverService.createResolver();
 | 
			
		||||
 | 
			
		||||
		await this.announceNote(actor, activity, targetUri);
 | 
			
		||||
		if (!activity.object) return 'skip: activity has no object property';
 | 
			
		||||
		const targetUri = getApId(activity.object);
 | 
			
		||||
		if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
 | 
			
		||||
 | 
			
		||||
		const target = await resolver.resolve(activity.object).catch(e => {
 | 
			
		||||
			this.logger.error(`Resolution failed: ${e}`);
 | 
			
		||||
			return e;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		if (isPost(target)) return await this.announceNote(actor, activity, target);
 | 
			
		||||
 | 
			
		||||
		return `skip: unknown object type ${getApType(target)}`;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	private async announceNote(actor: MiRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
 | 
			
		||||
	private async announceNote(actor: MiRemoteUser, activity: IAnnounce, target: IPost): Promise<string | void> {
 | 
			
		||||
		const uri = getApId(activity);
 | 
			
		||||
 | 
			
		||||
		if (actor.isSuspended) {
 | 
			
		||||
| 
						 | 
				
			
			@ -288,24 +307,21 @@ export class ApInboxService {
 | 
			
		|||
			// Announce対象をresolve
 | 
			
		||||
			let renote;
 | 
			
		||||
			try {
 | 
			
		||||
				renote = await this.apNoteService.resolveNote(targetUri);
 | 
			
		||||
				if (renote == null) throw new Error('announce target is null');
 | 
			
		||||
				renote = await this.apNoteService.resolveNote(target);
 | 
			
		||||
				if (renote == null) return 'announce target is null';
 | 
			
		||||
			} catch (err) {
 | 
			
		||||
				// 対象が4xxならスキップ
 | 
			
		||||
				if (err instanceof StatusError) {
 | 
			
		||||
					if (!err.isRetryable) {
 | 
			
		||||
						this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
 | 
			
		||||
						return;
 | 
			
		||||
						return `Ignored announce target ${target.id} - ${err.statusCode}`;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode}`);
 | 
			
		||||
					return `Error in announce target ${target.id} - ${err.statusCode}`;
 | 
			
		||||
				}
 | 
			
		||||
				throw err;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) {
 | 
			
		||||
				this.logger.warn('skip: invalid actor for this activity');
 | 
			
		||||
				return;
 | 
			
		||||
				return 'skip: invalid actor for this activity';
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			this.logger.info(`Creating the (Re)Note: ${uri}`);
 | 
			
		||||
| 
						 | 
				
			
			@ -314,8 +330,7 @@ export class ApInboxService {
 | 
			
		|||
			const createdAt = activity.published ? new Date(activity.published) : null;
 | 
			
		||||
 | 
			
		||||
			if (createdAt && createdAt < this.idService.parse(renote.id).date) {
 | 
			
		||||
				this.logger.warn('skip: malformed createdAt');
 | 
			
		||||
				return;
 | 
			
		||||
				return 'skip: malformed createdAt';
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			await this.noteCreateService.create(actor, {
 | 
			
		||||
| 
						 | 
				
			
			@ -349,11 +364,15 @@ export class ApInboxService {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	private async create(actor: MiRemoteUser, activity: ICreate): Promise<void> {
 | 
			
		||||
	private async create(actor: MiRemoteUser, activity: ICreate): Promise<string | void> {
 | 
			
		||||
		const uri = getApId(activity);
 | 
			
		||||
 | 
			
		||||
		this.logger.info(`Create: ${uri}`);
 | 
			
		||||
 | 
			
		||||
		if (!activity.object) return 'skip: activity has no object property';
 | 
			
		||||
		const targetUri = getApId(activity.object);
 | 
			
		||||
		if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
 | 
			
		||||
 | 
			
		||||
		// copy audiences between activity <=> object.
 | 
			
		||||
		if (typeof activity.object === 'object') {
 | 
			
		||||
			const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
 | 
			
		||||
| 
						 | 
				
			
			@ -380,7 +399,7 @@ export class ApInboxService {
 | 
			
		|||
		if (isPost(object)) {
 | 
			
		||||
			await this.createNote(resolver, actor, object, false, activity);
 | 
			
		||||
		} else {
 | 
			
		||||
			this.logger.warn(`Unknown type: ${getApType(object)}`);
 | 
			
		||||
			return `Unknown type: ${getApType(object)}`;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -422,7 +441,7 @@ export class ApInboxService {
 | 
			
		|||
	@bindThis
 | 
			
		||||
	private async delete(actor: MiRemoteUser, activity: IDelete): Promise<string> {
 | 
			
		||||
		if (actor.uri !== activity.actor) {
 | 
			
		||||
			throw new Error('invalid actor');
 | 
			
		||||
			return 'invalid actor';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 削除対象objectのtype
 | 
			
		||||
| 
						 | 
				
			
			@ -581,29 +600,29 @@ export class ApInboxService {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	private async remove(actor: MiRemoteUser, activity: IRemove): Promise<void> {
 | 
			
		||||
	private async remove(actor: MiRemoteUser, activity: IRemove): Promise<string | void> {
 | 
			
		||||
		if (actor.uri !== activity.actor) {
 | 
			
		||||
			throw new Error('invalid actor');
 | 
			
		||||
			return 'invalid actor';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (activity.target == null) {
 | 
			
		||||
			throw new Error('target is null');
 | 
			
		||||
			return 'target is null';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (activity.target === actor.featured) {
 | 
			
		||||
			const note = await this.apNoteService.resolveNote(activity.object);
 | 
			
		||||
			if (note == null) throw new Error('note not found');
 | 
			
		||||
			if (note == null) return 'note not found';
 | 
			
		||||
			await this.notePiningService.removePinned(actor, note.id);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		throw new Error(`unknown target: ${activity.target}`);
 | 
			
		||||
		return `unknown target: ${activity.target}`;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@bindThis
 | 
			
		||||
	private async undo(actor: MiRemoteUser, activity: IUndo): Promise<string> {
 | 
			
		||||
		if (actor.uri !== activity.actor) {
 | 
			
		||||
			throw new Error('invalid actor');
 | 
			
		||||
			return 'invalid actor';
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const uri = activity.id ?? activity;
 | 
			
		||||
| 
						 | 
				
			
			@ -614,7 +633,7 @@ export class ApInboxService {
 | 
			
		|||
 | 
			
		||||
		const object = await resolver.resolve(activity.object).catch(e => {
 | 
			
		||||
			this.logger.error(`Resolution failed: ${e}`);
 | 
			
		||||
			throw e;
 | 
			
		||||
			return e;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// don't queue because the sender may attempt again when timeout
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,20 +81,20 @@ export class ApNoteService {
 | 
			
		|||
		const expectHost = this.utilityService.extractDbHost(uri);
 | 
			
		||||
 | 
			
		||||
		if (!validPost.includes(getApType(object))) {
 | 
			
		||||
			return new Error(`invalid Note: invalid object type ${getApType(object)}`);
 | 
			
		||||
			return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: invalid object type ${getApType(object)}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) {
 | 
			
		||||
			return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
 | 
			
		||||
			return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
 | 
			
		||||
		if (object.attributedTo && actualHost !== expectHost) {
 | 
			
		||||
			return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
 | 
			
		||||
			return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
 | 
			
		||||
			return new Error('invalid Note: published timestamp is malformed');
 | 
			
		||||
			return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -328,3 +328,4 @@ export const isAnnounce = (object: IObject): object is IAnnounce => getApType(ob
 | 
			
		|||
export const isBlock = (object: IObject): object is IBlock => getApType(object) === 'Block';
 | 
			
		||||
export const isFlag = (object: IObject): object is IFlag => getApType(object) === 'Flag';
 | 
			
		||||
export const isMove = (object: IObject): object is IMove => getApType(object) === 'Move';
 | 
			
		||||
export const isNote = (object: IObject): object is IPost => getApType(object) === 'Note';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -204,13 +204,22 @@ export class InboxProcessorService {
 | 
			
		|||
 | 
			
		||||
		// アクティビティを処理
 | 
			
		||||
		try {
 | 
			
		||||
			await this.apInboxService.performActivity(authUser.user, activity);
 | 
			
		||||
			const result = await this.apInboxService.performActivity(authUser.user, activity);
 | 
			
		||||
			if (result && !result.startsWith('ok')) {
 | 
			
		||||
				this.logger.warn(`inbox activity ignored (maybe): id=${activity.id} reason=${result}`);
 | 
			
		||||
				return result;
 | 
			
		||||
			}
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			if (e instanceof IdentifiableError) {
 | 
			
		||||
				if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
 | 
			
		||||
					return 'blocked notes with prohibited words';
 | 
			
		||||
				}
 | 
			
		||||
				if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') return 'actor has been suspended';
 | 
			
		||||
				if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') {
 | 
			
		||||
					return 'actor has been suspended';
 | 
			
		||||
				}
 | 
			
		||||
				if (e.id === 'd450b8a9-48e4-4dab-ae36-f4db763fda7c') { // invalid Note
 | 
			
		||||
					return e.message;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			throw e;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue