mirror of
https://activitypub.software/TransFem-org/Sharkey
synced 2024-11-24 06:55:12 +00:00
fix: primitive 13: check attribution against actor in notes
This commit is contained in:
parent
b9080da75d
commit
c04f344049
3 changed files with 48 additions and 29 deletions
|
@ -436,7 +436,7 @@ export class ApInboxService {
|
|||
const exist = await this.apNoteService.fetchNote(note);
|
||||
if (exist) return 'skip: note exists';
|
||||
|
||||
await this.apNoteService.createNote(note, resolver, silent);
|
||||
await this.apNoteService.createNote(note, actor, resolver, silent);
|
||||
return 'ok';
|
||||
} catch (err) {
|
||||
if (err instanceof StatusError && !err.isRetryable) {
|
||||
|
@ -791,7 +791,7 @@ export class ApInboxService {
|
|||
await this.apQuestionService.updateQuestion(object, actor, resolver).catch(err => console.error(err));
|
||||
return 'ok: Question updated';
|
||||
} else if (getApType(object) === 'Note') {
|
||||
await this.apNoteService.updateNote(object, resolver).catch(err => console.error(err));
|
||||
await this.apNoteService.updateNote(object, actor, resolver).catch(err => console.error(err));
|
||||
return 'ok: Note updated';
|
||||
} else {
|
||||
return `skip: Unknown type: ${getApType(object)}`;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { PollsRepository, EmojisRepository, NotesRepository, MiMeta } from '@/models/_.js';
|
||||
import type { UsersRepository, PollsRepository, EmojisRepository, NotesRepository, MiMeta } from '@/models/_.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { MiRemoteUser } from '@/models/User.js';
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
|
@ -49,6 +49,9 @@ export class ApNoteService {
|
|||
@Inject(DI.meta)
|
||||
private meta: MiMeta,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.pollsRepository)
|
||||
private pollsRepository: PollsRepository,
|
||||
|
||||
|
@ -82,7 +85,13 @@ export class ApNoteService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public validateNote(object: IObject, uri: string): Error | null {
|
||||
public validateNote(
|
||||
object: IObject,
|
||||
uri: string,
|
||||
actor?: MiRemoteUser,
|
||||
user?: MiRemoteUser,
|
||||
note?: MiNote,
|
||||
): Error | null {
|
||||
const expectHost = this.utilityService.extractDbHost(uri);
|
||||
const apType = getApType(object);
|
||||
|
||||
|
@ -99,10 +108,27 @@ export class ApNoteService {
|
|||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
|
||||
}
|
||||
|
||||
if (actor) {
|
||||
const attribution = (object.attributedTo) ? getOneApId(object.attributedTo) : actor.uri;
|
||||
if (attribution !== actor.uri) {
|
||||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attribution does not match the actor that send it. attribution: ${attribution}, actor: ${actor.uri}`);
|
||||
}
|
||||
if (user && attribution !== user.uri) {
|
||||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: updated attribution does not match original attribution. updated attribution: ${user.uri}, original attribution: ${attribution}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
|
||||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed');
|
||||
}
|
||||
|
||||
if (note) {
|
||||
const url = (object.url) ? getOneApId(object.url) : note.url;
|
||||
if (url && url !== note.url) {
|
||||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: updated url does not match original url. updated url: ${url}, original url: ${note.url}`);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -120,14 +146,14 @@ export class ApNoteService {
|
|||
* Noteを作成します。
|
||||
*/
|
||||
@bindThis
|
||||
public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<MiNote | null> {
|
||||
public async createNote(value: string | IObject, actor?: MiRemoteUser, resolver?: Resolver, silent = false): Promise<MiNote | null> {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
|
||||
const object = await resolver.resolve(value);
|
||||
|
||||
const entryUri = getApId(value);
|
||||
const err = this.validateNote(object, entryUri);
|
||||
const err = this.validateNote(object, entryUri, actor);
|
||||
if (err) {
|
||||
this.logger.error(err.message, {
|
||||
resolver: { history: resolver.getHistory() },
|
||||
|
@ -171,8 +197,9 @@ export class ApNoteService {
|
|||
const uri = getOneApId(note.attributedTo);
|
||||
|
||||
// ローカルで投稿者を検索し、もし凍結されていたらスキップ
|
||||
const cachedActor = await this.apPersonService.fetchPerson(uri) as MiRemoteUser;
|
||||
if (cachedActor && cachedActor.isSuspended) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
actor ??= await this.apPersonService.fetchPerson(uri) as MiRemoteUser | undefined;
|
||||
if (actor && actor.isSuspended) {
|
||||
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
|
||||
}
|
||||
|
||||
|
@ -204,7 +231,8 @@ export class ApNoteService {
|
|||
}
|
||||
//#endregion
|
||||
|
||||
const actor = cachedActor ?? await this.apPersonService.resolvePerson(uri, resolver) as MiRemoteUser;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
actor ??= await this.apPersonService.resolvePerson(uri, resolver) as MiRemoteUser;
|
||||
|
||||
// 解決した投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
|
@ -345,7 +373,7 @@ export class ApNoteService {
|
|||
* Noteを作成します。
|
||||
*/
|
||||
@bindThis
|
||||
public async updateNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<MiNote | null> {
|
||||
public async updateNote(value: string | IObject, actor?: MiRemoteUser, resolver?: Resolver, silent = false): Promise<MiNote | null> {
|
||||
const noteUri = typeof value === 'string' ? value : value.id;
|
||||
if (noteUri == null) throw new Error('uri is null');
|
||||
|
||||
|
@ -356,6 +384,9 @@ export class ApNoteService {
|
|||
const UpdatedNote = await this.notesRepository.findOneBy({ uri: noteUri });
|
||||
if (UpdatedNote == null) throw new Error('Note is not registered');
|
||||
|
||||
const user = await this.usersRepository.findOneBy({ id: UpdatedNote.userId }) as MiRemoteUser | null;
|
||||
if (user == null) throw new Error('Note is not registered');
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
if (resolver == null) resolver = this.apResolverService.createResolver();
|
||||
|
||||
|
@ -372,6 +403,10 @@ export class ApNoteService {
|
|||
throw err;
|
||||
}
|
||||
|
||||
// `validateNote` checks that the actor and user are one and the same
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
actor ??= user;
|
||||
|
||||
const note = object as IPost;
|
||||
|
||||
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
|
||||
|
@ -402,16 +437,7 @@ export class ApNoteService {
|
|||
|
||||
this.logger.info(`Creating the Note: ${note.id}`);
|
||||
|
||||
// 投稿者をフェッチ
|
||||
if (note.attributedTo == null) {
|
||||
throw new Error('invalid note.attributedTo: ' + note.attributedTo);
|
||||
}
|
||||
|
||||
const uri = getOneApId(note.attributedTo);
|
||||
|
||||
// ローカルで投稿者を検索し、もし凍結されていたらスキップ
|
||||
const cachedActor = await this.apPersonService.fetchPerson(uri) as MiRemoteUser;
|
||||
if (cachedActor && cachedActor.isSuspended) {
|
||||
if (actor.isSuspended) {
|
||||
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
|
||||
}
|
||||
|
||||
|
@ -443,13 +469,6 @@ export class ApNoteService {
|
|||
}
|
||||
//#endregion
|
||||
|
||||
const actor = cachedActor ?? await this.apPersonService.resolvePerson(uri, resolver) as MiRemoteUser;
|
||||
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
|
||||
}
|
||||
|
||||
const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
|
||||
let visibility = noteAudience.visibility;
|
||||
const visibleUsers = noteAudience.visibleUsers;
|
||||
|
@ -610,7 +629,7 @@ export class ApNoteService {
|
|||
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
|
||||
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
|
||||
const createFrom = options.sentFrom?.origin === new URL(uri).origin ? value : uri;
|
||||
return await this.createNote(createFrom, options.resolver, true);
|
||||
return await this.createNote(createFrom, undefined, options.resolver, true);
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
return await this.mergePack(
|
||||
me,
|
||||
isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null,
|
||||
isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, true) : null,
|
||||
isPost(object) ? await this.apNoteService.createNote(getApId(object), undefined, undefined, true) : null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue