

























@@ -59,6 +59,10 @@ const MATRIX_QA_ROOM_KEY_BACKUP_VERSION_ENDPOINT = "/_matrix/client/v3/room_keys
5959const MATRIX_QA_ROOM_KEY_BACKUP_FAULT_RULE_ID = "room-key-backup-version-unavailable";
6060const MATRIX_QA_OWNER_SIGNATURE_UPLOAD_BLOCKED_RULE_ID = "owner-signature-upload-blocked";
6161const MATRIX_QA_KEYS_SIGNATURES_UPLOAD_ENDPOINT = "/_matrix/client/v3/keys/signatures/upload";
62+const MATRIX_QA_SYNC_ENDPOINT = "/_matrix/client/v3/sync";
63+const MATRIX_QA_SYNC_STATE_AFTER_FAULT_RULE_ID = "sync-state-after-missing-encryption";
64+const MATRIX_QA_SYNC_STATE_AFTER_KEY = "org.matrix.msc4222.state_after";
65+const MATRIX_QA_SYNC_STATE_AFTER_PARAM = "org.matrix.msc4222.use_state_after";
62666367type MatrixQaE2eeBootstrapResult = Awaited<ReturnType<typeof runMatrixQaE2eeBootstrap>>;
6468type MatrixQaCliVerificationStatus = {
@@ -951,6 +955,58 @@ function buildOwnerSignatureUploadBlockedFaultRule(accessToken: string): MatrixQ
951955};
952956}
953957958+function removeMatrixQaSyncStateAfterEncryptionEvents(payload: unknown) {
959+if (!isMatrixQaPlainRecord(payload)) {
960+return 0;
961+}
962+const rooms = isMatrixQaPlainRecord(payload.rooms) ? payload.rooms : {};
963+const join = isMatrixQaPlainRecord(rooms.join) ? rooms.join : {};
964+let removed = 0;
965+for (const room of Object.values(join)) {
966+if (!isMatrixQaPlainRecord(room)) {
967+continue;
968+}
969+const stateAfter = room[MATRIX_QA_SYNC_STATE_AFTER_KEY];
970+if (!isMatrixQaPlainRecord(stateAfter) || !Array.isArray(stateAfter.events)) {
971+continue;
972+}
973+const filtered = stateAfter.events.filter((event) => {
974+if (isMatrixQaPlainRecord(event) && event.type === "m.room.encryption") {
975+removed += 1;
976+return false;
977+}
978+return true;
979+});
980+stateAfter.events = filtered;
981+}
982+return removed;
983+}
984+985+function buildSyncStateAfterMissingEncryptionFaultRule(
986+accessToken: string,
987+): MatrixQaFaultProxyRule {
988+return {
989+id: MATRIX_QA_SYNC_STATE_AFTER_FAULT_RULE_ID,
990+match: (request) =>
991+request.method === "GET" &&
992+request.path === MATRIX_QA_SYNC_ENDPOINT &&
993+request.bearerToken === accessToken &&
994+new URLSearchParams(request.search).get(MATRIX_QA_SYNC_STATE_AFTER_PARAM) === "true",
995+mutateResponse: ({ response }) => {
996+const contentType = response.headers.get("content-type") ?? "";
997+if (!contentType.includes("json")) {
998+return response;
999+}
1000+const payload = JSON.parse(response.body.toString("utf8")) as unknown;
1001+removeMatrixQaSyncStateAfterEncryptionEvents(payload);
1002+return {
1003+ ...response,
1004+body: Buffer.from(JSON.stringify(payload)),
1005+};
1006+},
1007+};
1008+}
1009+9541010async function runMatrixQaFaultedE2eeBootstrap(context: MatrixQaScenarioContext): Promise<{
9551011faultHits: MatrixQaFaultProxyHit[];
9561012result: MatrixQaE2eeBootstrapResult;
@@ -1312,6 +1368,97 @@ export async function runMatrixQaE2eeBasicReplyScenario(
13121368};
13131369}
131413701371+export async function runMatrixQaE2eeStateAfterMissingEncryptionScenario(
1372+context: MatrixQaScenarioContext,
1373+): Promise<MatrixQaScenarioExecution> {
1374+if (!context.restartGatewayAfterStateMutation) {
1375+throw new Error("Matrix E2EE state_after QA scenario requires hard gateway restart support");
1376+}
1377+const accountId = context.sutAccountId ?? "sut";
1378+const configPath = requireMatrixQaGatewayConfigPath(context);
1379+const originalAccountConfig = await readMatrixQaGatewayMatrixAccount({
1380+ accountId,
1381+ configPath,
1382+});
1383+const proxy = await startMatrixQaFaultProxy({
1384+targetBaseUrl: context.baseUrl,
1385+rules: [buildSyncStateAfterMissingEncryptionFaultRule(context.sutAccessToken)],
1386+});
1387+let gatewayPatched = false;
1388+try {
1389+await context.restartGatewayAfterStateMutation(
1390+async () => {
1391+await patchMatrixQaGatewayMatrixAccount({
1392+ accountId,
1393+accountPatch: {
1394+homeserver: proxy.baseUrl,
1395+network: {
1396+dangerouslyAllowPrivateNetwork: true,
1397+},
1398+},
1399+ configPath,
1400+});
1401+gatewayPatched = true;
1402+},
1403+{
1404+timeoutMs: context.timeoutMs,
1405+waitAccountId: accountId,
1406+},
1407+);
1408+const result = await runMatrixQaE2eeTopLevelScenario(context, {
1409+scenarioId: "matrix-e2ee-state-after-missing-encryption",
1410+tokenPrefix: "MATRIX_QA_E2EE_STATE_AFTER",
1411+});
1412+const stateAfterHits = proxy
1413+.hits()
1414+.filter((hit) => hit.ruleId === MATRIX_QA_SYNC_STATE_AFTER_FAULT_RULE_ID);
1415+if (stateAfterHits.length > 0) {
1416+throw new Error(
1417+`Matrix E2EE gateway still sent ${MATRIX_QA_SYNC_STATE_AFTER_PARAM}=true on /sync`,
1418+);
1419+}
1420+return {
1421+artifacts: {
1422+driverEventId: result.driverEventId,
1423+faultProxyBaseUrl: proxy.baseUrl,
1424+reply: result.reply,
1425+roomKey: result.roomKey,
1426+roomId: result.roomId,
1427+stateAfterFaultHitCount: stateAfterHits.length,
1428+stateAfterFaultRuleId: MATRIX_QA_SYNC_STATE_AFTER_FAULT_RULE_ID,
1429+strippedSyncStateAfterParam: true,
1430+},
1431+details: [
1432+`encrypted room key: ${result.roomKey}`,
1433+`encrypted room id: ${result.roomId}`,
1434+`driver event: ${result.driverEventId}`,
1435+`fault proxy: ${proxy.baseUrl}`,
1436+`state_after sync opt-in hits: ${stateAfterHits.length}`,
1437+ ...buildMatrixReplyDetails("E2EE state_after reply", result.reply),
1438+].join("\n"),
1439+};
1440+} finally {
1441+if (gatewayPatched) {
1442+await context
1443+.restartGatewayAfterStateMutation(
1444+async () => {
1445+await replaceMatrixQaGatewayMatrixAccount({
1446+accountConfig: originalAccountConfig,
1447+ accountId,
1448+ configPath,
1449+});
1450+},
1451+{
1452+timeoutMs: context.timeoutMs,
1453+waitAccountId: accountId,
1454+},
1455+)
1456+.catch(() => undefined);
1457+}
1458+await proxy.stop().catch(() => undefined);
1459+}
1460+}
1461+13151462export async function runMatrixQaE2eeThreadFollowUpScenario(
13161463context: MatrixQaScenarioContext,
13171464): Promise<MatrixQaScenarioExecution> {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。