Merge pull request from GHSA-qqrm-9grj-6v32

This commit is contained in:
tamaina 2024-02-17 12:41:19 +09:00 committed by dakkar
parent 848e1f9a56
commit 1948ca9aa8
8 changed files with 195 additions and 22 deletions

View file

@ -0,0 +1,40 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
process.env.NODE_ENV = 'test';
import { validateContentTypeSetAsActivityPub, validateContentTypeSetAsJsonLD } from '@/core/activitypub/misc/validator.js';
import { signup, uploadFile, relativeFetch } from '../utils.js';
import type * as misskey from 'misskey-js';
describe('validateContentTypeSetAsActivityPub/JsonLD (deny case)', () => {
let alice: misskey.entities.SignupResponse;
let aliceUploadedFile: any;
beforeAll(async () => {
alice = await signup({ username: 'alice' });
aliceUploadedFile = await uploadFile(alice);
}, 1000 * 60 * 2);
test('ActivityStreams: ファイルはエラーになる', async () => {
const res = await relativeFetch(aliceUploadedFile.webpublicUrl);
function doValidate() {
validateContentTypeSetAsActivityPub(res);
}
expect(doValidate).toThrow('Content type is not');
});
test('JSON-LD: ファイルはエラーになる', async () => {
const res = await relativeFetch(aliceUploadedFile.webpublicUrl);
function doValidate() {
validateContentTypeSetAsJsonLD(res);
}
expect(doValidate).toThrow('Content type is not');
});
});

View file

@ -202,7 +202,7 @@ describe('ActivityPub', () => {
describe('Renderer', () => {
test('Render an announce with visibility: followers', () => {
rendererService.renderAnnounce(null, {
rendererService.renderAnnounce('https://example.com/notes/00example', {
id: genAidx(Date.now()),
visibility: 'followers',
} as MiNote);

View file

@ -13,6 +13,8 @@ import fetch, { File, RequestInit } from 'node-fetch';
import { DataSource } from 'typeorm';
import { JSDOM } from 'jsdom';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import { Packed } from '@/misc/json-schema.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import { entities } from '../src/postgres.js';
import { loadConfig } from '../src/config.js';
import type * as misskey from 'misskey-js';
@ -110,6 +112,20 @@ export function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', len
return randomString;
}
/**
* @brief
* @param p
* @param timeout
*/
function timeoutPromise<T>(p: Promise<T>, timeout: number): Promise<T> {
return Promise.race([
p,
new Promise((reject) => {
setTimeout(() => { reject(new Error('timed out')); }, timeout);
}) as never,
]);
}
export const signup = async (params?: Partial<misskey.Endpoints['signup']['req']>): Promise<NonNullable<misskey.Endpoints['signup']['res']>> => {
const q = Object.assign({
username: randomString(),
@ -304,7 +320,6 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO
});
const body = res.status !== 204 ? await res.json() as misskey.Endpoints['drive/files/create']['res'] : null;
return {
status: res.status,
headers: res.headers,
@ -317,12 +332,13 @@ export const uploadUrl = async (user: UserToken, url: string) => {
const file = new Promise(ok => resolve = ok);
const marker = Math.random().toString();
const ws = await connectStream(user, 'main', (msg) => {
if (msg.type === 'urlUploadFinished' && msg.body.marker === marker) {
ws.close();
resolve(msg.body.file);
}
});
const catcher = makeStreamCatcher(
user,
'main',
(msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker,
(msg) => msg.body.file as Packed<'DriveFile'>,
60 * 1000,
);
await api('drive/files/upload-from-url', {
url,
@ -402,6 +418,35 @@ export const waitFire = async (user: UserToken, channel: string, trgr: () => any
});
};
/**
* @brief WebSocketストリームから特定条件の通知を拾うプロミスを生成
* @param user
* @param channel
* @param cond
* @param extractor
* @param timeout
* @returns extractorを通した値を得る
*/
export function makeStreamCatcher<T>(
user: UserToken,
channel: string,
cond: (message: Record<string, any>) => boolean,
extractor: (message: Record<string, any>) => T,
timeout = 60 * 1000): Promise<T> {
let ws: WebSocket;
const p = new Promise<T>(async (resolve) => {
ws = await connectStream(user, channel, (msg) => {
if (cond(msg)) {
resolve(extractor(msg));
}
});
}).finally(() => {
ws.close();
});
return timeoutPromise(p, timeout);
}
export type SimpleGetResponse = {
status: number,
body: any | JSDOM | null,
@ -425,6 +470,14 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde
'text/html; charset=utf-8',
];
if (res.ok && (
accept.startsWith('application/activity+json') ||
(accept.startsWith('application/ld+json') && accept.includes('https://www.w3.org/ns/activitystreams'))
)) {
// validateContentTypeSetAsActivityPubのテストを兼ねる
validateContentTypeSetAsActivityPub(res);
}
const body =
jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :