diff --git a/src/protections/RoomTakedown/RoomTakedown.ts b/src/protections/RoomTakedown/RoomTakedown.ts index 206bc27a..817210f2 100644 --- a/src/protections/RoomTakedown/RoomTakedown.ts +++ b/src/protections/RoomTakedown/RoomTakedown.ts @@ -20,7 +20,10 @@ import { RoomTakedownCapability } from "../../capabilities/RoomTakedownCapabilit const log = new Logger("RoomTakedown"); -type RoomTakedown = { +// FIXME: How can we segment this so that rooms are takendown on prompt in +// the abscence of policy approval? + +export type RoomTakedownService = { handleDiscoveredRooms(rooms: StringRoomID[]): Promise>; handlePolicyChange( revision: PolicyListRevision, @@ -35,7 +38,7 @@ type RoomTakedown = { * moving discovered rooms into the hashStore. Although it's not clear * whether we need to do that? */ -export class StandardRoomTakedown implements RoomTakedown { +export class StandardRoomTakedown implements RoomTakedownService { public constructor( private readonly hashStore: SHA256RoomHashStore, private readonly auditLog: RoomAuditLog, @@ -43,6 +46,7 @@ export class StandardRoomTakedown implements RoomTakedown { ) { // nothing to do } + // FIXME: We don't use this public async handleDiscoveredRooms( rooms: StringRoomID[] ): Promise> { @@ -85,6 +89,8 @@ export class StandardRoomTakedown implements RoomTakedown { change.rule.matchType === PolicyRuleMatchType.Literal && change.rule.recommendation === Recommendation.Takedown ) { + // FIXME: We probably do not want check the audit log for new policies + // and instead let the capability decide if ( !this.auditLog.isRoomTakendown(change.rule.entity as StringRoomID) ) { @@ -114,6 +120,7 @@ export class StandardRoomTakedown implements RoomTakedown { const roomPolicies = revision .allRulesOfType(PolicyRuleType.Room, Recommendation.Takedown) .filter((policy) => policy.matchType === PolicyRuleMatchType.Literal); + // FIXME: We don't seem to check the audit log for (const policy of roomPolicies) { const takedownResult = await this.takedownRoom( policy.entity as StringRoomID, diff --git a/test/unit/protections/RoomTakedownServiceTest.ts b/test/unit/protections/RoomTakedownServiceTest.ts new file mode 100644 index 00000000..9b9b820b --- /dev/null +++ b/test/unit/protections/RoomTakedownServiceTest.ts @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2025 Gnuxie +// +// SPDX-License-Identifier: AFL-3.0 + +import { createMock } from "ts-auto-mock"; +import { StandardRoomTakedown } from "../../../src/protections/RoomTakedown/RoomTakedown"; +import { + describePolicyRule, + describeProtectedRoomsSet, + Ok, + parsePolicyRule, + PolicyRuleType, + randomRoomID, + Recommendation, + SHA256RoomHashStore, + SimpleChangeType, + StandardPolicyListRevision, +} from "matrix-protection-suite"; +import { RoomAuditLog } from "../../../src/protections/RoomTakedown/RoomAuditLog"; +import { RoomTakedownCapability } from "../../../src/capabilities/RoomTakedownCapability"; +import { StringRoomID } from "@the-draupnir-project/matrix-basic-types"; +import expect from "expect"; + +function makeServiceMocks(): { + auditLogItems: Parameters[]; + auditLog: RoomAuditLog; + takedownCapabilityItems: StringRoomID[]; + takedownCapability: RoomTakedownCapability; +} { + const auditLogItems: Parameters[] = []; + const takedownCapabilityItems: StringRoomID[] = []; + return { + auditLogItems, + auditLog: createMock({ + async takedownRoom(...args: Parameters) { + auditLogItems.push(args); + return Ok(undefined); + }, + }), + takedownCapabilityItems, + takedownCapability: createMock({ + async takedownRoom(roomID: StringRoomID) { + takedownCapabilityItems.push(roomID); + return Ok({ + room_id: roomID, + }); + }, + async isRoomTakendown(_roomID) { + return Ok(false); + }, + }), + }; +} + +describe("", function () { + const storeFake = createMock({}); + it("Test rooms are takendown when policies are added", async function () { + const policyRoom = randomRoomID([]); + const bannedRoom = randomRoomID([]); + const policy = parsePolicyRule( + describePolicyRule({ + room_id: policyRoom.toRoomIDOrAlias(), + entity: bannedRoom.toRoomIDOrAlias(), + reason: "spam", + recommendation: Recommendation.Takedown, + type: PolicyRuleType.Room, + }) as never + ).expect("Should be able to parse the policy rule."); + const { + auditLog, + takedownCapability, + auditLogItems, + takedownCapabilityItems, + } = makeServiceMocks(); + const takedownService = new StandardRoomTakedown( + storeFake, + auditLog, + takedownCapability + ); + // we give a blank revision because i haven't updated the describeProtectedRoomsSet code + // to use hashed policies... although thinking about it we don't use them here either lol + ( + await takedownService.handlePolicyChange( + StandardPolicyListRevision.blankRevision(), + [ + { + changeType: SimpleChangeType.Added, + rule: policy, + event: policy.sourceEvent, + sender: policy.sourceEvent.sender, + }, + ] + ) + ).expect("Should have run just fine"); + expect(auditLogItems.length).toBe(1); + expect(takedownCapabilityItems.length).toBe(1); + }); + it("Test rooms are takendown at startup", async function () { + const policyRoom = randomRoomID([]); + const { protectedRoomsSet } = await describeProtectedRoomsSet({ + lists: [ + { + room: policyRoom, + policyDescriptions: [ + { + entity: randomRoomID([]).toRoomIDOrAlias(), + recommendation: Recommendation.Takedown, + type: PolicyRuleType.Room, + }, + { + entity: randomRoomID([]).toRoomIDOrAlias(), + recommendation: Recommendation.Takedown, + type: PolicyRuleType.Room, + }, + ], + }, + ], + }); + const { + auditLog, + takedownCapability, + auditLogItems, + takedownCapabilityItems, + } = makeServiceMocks(); + const takedownService = new StandardRoomTakedown( + storeFake, + auditLog, + takedownCapability + ); + ( + await takedownService.checkAllRooms( + protectedRoomsSet.watchedPolicyRooms.currentRevision + ) + ).expect("Should be able to check all rooms"); + expect(auditLogItems.length).toBe(2); + expect(takedownCapabilityItems.length).toBe(2); + }); +});