Skip to content

Commit 924bed5

Browse files
authored
PolicyListManager watch/unwatch should use MatrixRoomReferences. (#42)
1 parent 0f8826f commit 924bed5

File tree

10 files changed

+89
-73
lines changed

10 files changed

+89
-73
lines changed

src/appservice/MjolnirManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { randomUUID } from "crypto";
1010
import EventEmitter from "events";
1111
import { MatrixEmitter } from "../MatrixEmitter";
1212
import { Permalinks } from "../commands/interface-manager/Permalinks";
13+
import { MatrixRoomReference } from "../commands/interface-manager/MatrixRoomReference";
1314

1415
const log = new Logger('MjolnirManager');
1516

@@ -238,7 +239,7 @@ export class ManagedMjolnir {
238239
[mjolnirOwnerId],
239240
{ name: `${mjolnirOwnerId}'s policy room` }
240241
);
241-
const roomRef = Permalinks.forRoom(listRoomId);
242+
const roomRef = MatrixRoomReference.fromPermalink(Permalinks.forRoom(listRoomId));
242243
await this.mjolnir.addProtectedRoom(listRoomId);
243244
return await this.mjolnir.policyListManager.watchList(roomRef);
244245
}

src/commands/CommandHandler.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import { LogService, RichReply } from "matrix-bot-sdk";
3131
import { execSyncCommand } from "./SyncCommand";
3232
import { execPermissionCheckCommand } from "./PermissionCheckCommand";
3333
import { execCreateListCommand } from "./CreateBanListCommand";
34-
import { execUnwatchCommand, execWatchCommand } from "./WatchUnwatchCommand";
3534
import { execRedactCommand } from "./RedactCommand";
3635
import { execImportCommand } from "./ImportCommand";
3736
import { execSetDefaultListCommand } from "./SetDefaultBanListCommand";
@@ -73,6 +72,7 @@ import "./Ban";
7372
import "./Unban";
7473
import "./StatusCommand";
7574
import "./Rules";
75+
import "./WatchUnwatchCommand";
7676
import "./Help";
7777

7878
export const COMMAND_PREFIX = "!mjolnir";
@@ -94,10 +94,6 @@ export async function handleCommand(roomId: string, event: { content: { body: st
9494
return await execPermissionCheckCommand(roomId, event, mjolnir);
9595
} else if (parts.length >= 5 && parts[1] === 'list' && parts[2] === 'create') {
9696
return await execCreateListCommand(roomId, event, mjolnir, parts);
97-
} else if (parts[1] === 'watch' && parts.length > 1) {
98-
return await execWatchCommand(roomId, event, mjolnir, parts);
99-
} else if (parts[1] === 'unwatch' && parts.length > 1) {
100-
return await execUnwatchCommand(roomId, event, mjolnir, parts);
10197
} else if (parts[1] === 'redact' && parts.length > 1) {
10298
return await execRedactCommand(roomId, event, mjolnir, parts);
10399
} else if (parts[1] === 'import' && parts.length > 2) {

src/commands/CreateBanListCommand.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Mjolnir } from "../Mjolnir";
2929
import PolicyList from "../models/PolicyList";
3030
import { RichReply } from "matrix-bot-sdk";
3131
import { Permalinks } from "./interface-manager/Permalinks";
32+
import { MatrixRoomReference } from "./interface-manager/MatrixRoomReference";
3233

3334
// !mjolnir list create <shortcode> <alias localpart>
3435
export async function execCreateListCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
@@ -42,7 +43,7 @@ export async function execCreateListCommand(roomId: string, event: any, mjolnir:
4243
{ room_alias_name: aliasLocalpart }
4344
);
4445

45-
const roomRef = Permalinks.forRoom(listRoomId);
46+
const roomRef = MatrixRoomReference.fromPermalink(Permalinks.forRoom(listRoomId));
4647
await mjolnir.policyListManager.watchList(roomRef);
4748
await mjolnir.addProtectedRoom(listRoomId);
4849

src/commands/Help.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ const oldHelpMenu = "" +
4444
"!mjolnir sync - Force updates of all lists and re-apply rules\n" +
4545
"!mjolnir verify - Ensures Mjolnir can moderate all your rooms\n" +
4646
"!mjolnir list create <shortcode> <alias localpart> - Creates a new ban list with the given shortcode and alias\n" +
47-
"!mjolnir watch <room alias/ID> - Watches a ban list\n" +
48-
"!mjolnir unwatch <room alias/ID> - Unwatches a ban list\n" +
4947
"!mjolnir import <room alias/ID> <list shortcode> - Imports bans and ACLs into the given list\n" +
5048
"!mjolnir default <shortcode> - Sets the default list for commands\n" +
5149
"!mjolnir protections - List all available protections\n" +

src/commands/WatchUnwatchCommand.ts

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,52 @@ limitations under the License.
2525
* are NOT distributed, contributed, committed, or licensed under the Apache License.
2626
*/
2727

28-
import { Mjolnir } from "../Mjolnir";
29-
import { RichReply } from "matrix-bot-sdk";
30-
import { Permalinks } from "./interface-manager/Permalinks";
31-
32-
// !mjolnir watch <room alias or ID>
33-
export async function execWatchCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
34-
const list = await mjolnir.policyListManager.watchList(Permalinks.forRoom(parts[2]));
35-
if (!list) {
36-
const replyText = "Cannot watch list due to error - is that a valid room alias?";
37-
const reply = RichReply.createFor(roomId, event, replyText, replyText);
38-
reply["msgtype"] = "m.notice";
39-
mjolnir.client.sendMessage(roomId, reply);
40-
return;
41-
}
42-
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
43-
}
44-
45-
// !mjolnir unwatch <room alias or ID>
46-
export async function execUnwatchCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
47-
const list = await mjolnir.policyListManager.unwatchList(Permalinks.forRoom(parts[2]));
48-
if (!list) {
49-
const replyText = "Cannot unwatch list due to error - is that a valid room alias?";
50-
const reply = RichReply.createFor(roomId, event, replyText, replyText);
51-
reply["msgtype"] = "m.notice";
52-
mjolnir.client.sendMessage(roomId, reply);
53-
return;
54-
}
55-
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
56-
}
28+
import { defineInterfaceCommand, findTableCommand } from "./interface-manager/InterfaceCommand";
29+
import { findPresentationType, parameters, ParsedKeywords } from "./interface-manager/ParameterParsing";
30+
import { MjolnirContext } from "./CommandHandler";
31+
import { MatrixRoomReference } from "./interface-manager/MatrixRoomReference";
32+
import { CommandError, CommandResult } from "./interface-manager/Validation";
33+
import { tickCrossRenderer } from "./interface-manager/MatrixHelpRenderer";
34+
import { defineMatrixInterfaceAdaptor } from "./interface-manager/MatrixInterfaceAdaptor";
35+
36+
defineInterfaceCommand({
37+
table: "mjolnir",
38+
designator: ["watch"],
39+
summary: "Watches a list and applies the list's assocated policies to draupnir's protected rooms.",
40+
parameters: parameters([
41+
{
42+
name: 'list',
43+
acceptor: findPresentationType("MatrixRoomReference"),
44+
}
45+
]),
46+
command: async function (this: MjolnirContext, _keywords: ParsedKeywords, list: MatrixRoomReference): Promise<CommandResult<void, CommandError>> {
47+
await this.mjolnir.policyListManager.watchList(list);
48+
return CommandResult.Ok(undefined);
49+
},
50+
})
51+
52+
defineMatrixInterfaceAdaptor({
53+
interfaceCommand: findTableCommand("mjolnir", "watch"),
54+
renderer: tickCrossRenderer,
55+
})
56+
57+
defineInterfaceCommand({
58+
table: "mjolnir",
59+
designator: ["unwatch"],
60+
summary: "Unwatches a list and stops applying the list's assocated policies to draupnir's protected rooms.",
61+
parameters: parameters([
62+
{
63+
name: 'list',
64+
acceptor: findPresentationType("MatrixRoomReference"),
65+
}
66+
]),
67+
command: async function (this: MjolnirContext, _keywords: ParsedKeywords, list: MatrixRoomReference): Promise<CommandResult<void, CommandError>> {
68+
await this.mjolnir.policyListManager.unwatchList(list);
69+
return CommandResult.Ok(undefined);
70+
},
71+
})
72+
73+
defineMatrixInterfaceAdaptor({
74+
interfaceCommand: findTableCommand("mjolnir", "unwatch"),
75+
renderer: tickCrossRenderer,
76+
})

src/models/PolicyListManager.ts

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import { Mjolnir } from "../Mjolnir";
3030
import { MatrixDataManager, RawSchemedData, SCHEMA_VERSION_KEY } from "./MatrixDataManager";
3131
import { MatrixRoomReference } from "../commands/interface-manager/MatrixRoomReference";
3232
import { PolicyList, WATCHED_LISTS_EVENT_TYPE, WARN_UNPROTECTED_ROOM_EVENT_PREFIX } from "./PolicyList";
33-
import { Permalinks } from "../commands/interface-manager/Permalinks";
3433

3534
type WatchedListsEvent = RawSchemedData & { references?: string[]; };
3635
/**
@@ -70,18 +69,16 @@ export class PolicyListManager extends MatrixDataManager<WatchedListsEvent> {
7069
return list;
7170
}
7271

73-
public async watchList(roomRef: string): Promise<PolicyList | null> {
74-
const joinedRooms = await this.mjolnir.client.getJoinedRooms();
75-
const permalink = Permalinks.parseUrl(roomRef);
76-
if (!permalink.roomIdOrAlias)
77-
return null;
78-
79-
const roomId = await this.mjolnir.client.resolveRoom(permalink.roomIdOrAlias);
80-
if (!joinedRooms.includes(roomId)) {
81-
await this.mjolnir.client.joinRoom(roomId, permalink.viaServers);
82-
}
83-
84-
if (this.policyLists.find(b => b.roomId === roomId)) {
72+
/**
73+
* Watching a list applies all of its policies to a protected room.
74+
* @param roomRef A matrix room reference to a policy list that should be watched.
75+
*
76+
* @returns The list that has been watched or null if the manager was already
77+
* watching the list.
78+
*/
79+
public async watchList(roomRef: MatrixRoomReference): Promise<PolicyList | null> {
80+
const roomId = await roomRef.joinClient(this.mjolnir.client);
81+
if (this.policyLists.find(b => b.roomId === roomId.toRoomIdOrAlias())) {
8582
// This room was already in our list of policy rooms, nothing else to do.
8683
// Note that we bailout *after* the call to `joinRoom`, in case a user
8784
// calls `watchList` in an attempt to repair something that was broken,
@@ -90,21 +87,21 @@ export class PolicyListManager extends MatrixDataManager<WatchedListsEvent> {
9087
return null;
9188
}
9289

93-
const list = await this.addPolicyList(roomId, roomRef);
90+
const list = await this.addPolicyList(roomId.toRoomIdOrAlias(), roomRef.toPermalink());
9491

9592
await this.storeMatixData();
96-
await this.warnAboutUnprotectedPolicyListRoom(roomId);
97-
93+
await this.warnAboutUnprotectedPolicyListRoom(roomId.toRoomIdOrAlias());
9894
return list;
9995
}
10096

101-
public async unwatchList(roomRef: string): Promise<PolicyList | null> {
102-
const permalink = Permalinks.parseUrl(roomRef);
103-
if (!permalink.roomIdOrAlias)
104-
return null;
105-
106-
const roomId = await this.mjolnir.client.resolveRoom(permalink.roomIdOrAlias);
107-
const list = this.policyLists.find(b => b.roomId === roomId) || null;
97+
/**
98+
* Stop watching the list and applying its associated policies.
99+
* @param roomRef A matrix room reference to a list that should be unwatched.
100+
* @returns The list being unwatched or null if we were not watching the list.
101+
*/
102+
public async unwatchList(roomRef: MatrixRoomReference): Promise<PolicyList | null> {
103+
const roomId = await roomRef.resolve(this.mjolnir.client);
104+
const list = this.policyLists.find(b => b.roomId === roomId.toRoomIdOrAlias()) || null;
108105
if (list) {
109106
this.policyLists.splice(this.policyLists.indexOf(list), 1);
110107
this.mjolnir.ruleServer?.unwatch(list);

test/integration/banListTest.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ALL_RULE_TYPES, Recommendation, RULE_SERVER, RULE_USER, SERVER_RULE_TYP
1010
import AccessControlUnit, { Access, EntityAccess } from "../../src/models/AccessControlUnit";
1111
import { randomUUID } from "crypto";
1212
import { MatrixSendClient } from "../../src/MatrixEmitter";
13+
import { MatrixRoomReference } from "../../src/commands/interface-manager/MatrixRoomReference";
1314

1415
/**
1516
* Create a policy rule in a policy room.
@@ -262,7 +263,7 @@ describe('Test: ACL updates will batch when rules are added in succession.', fun
262263
// Flood the watched list with banned servers, which should prompt Mjolnir to update server ACL in protected rooms.
263264
const banListId = await moderator.createRoom({ invite: [mjolnirId] });
264265
await mjolnir.client.joinRoom(banListId);
265-
await mjolnir.policyListManager.watchList(Permalinks.forRoom(banListId));
266+
await mjolnir.policyListManager.watchList(MatrixRoomReference.fromRoomId(banListId));
266267
const acl = new ServerAcl(serverName).denyIpAddresses().allowServer("*");
267268
const evilServerCount = 200;
268269
for (let i = 0; i < evilServerCount; i++) {
@@ -327,7 +328,7 @@ describe('Test: unbaning entities via the PolicyList.', function() {
327328
await moderator.setUserPowerLevel(await mjolnir.client.getUserId(), banListId, 100);
328329
await moderator.sendStateEvent(banListId, 'org.matrix.mjolnir.shortcode', '', { shortcode: "unban-test" });
329330
await mjolnir.client.joinRoom(banListId);
330-
await mjolnir.policyListManager.watchList(Permalinks.forRoom(banListId));
331+
await mjolnir.policyListManager.watchList(MatrixRoomReference.fromRoomId(banListId));
331332
// we use this to compare changes.
332333
const banList = new PolicyList(banListId, banListId, moderator);
333334
// we need two because we need to test the case where an entity has all rule types in the list
@@ -402,7 +403,7 @@ describe('Test: should apply bans to the most recently active rooms first', func
402403
// Flood the watched list with banned servers, which should prompt Mjolnir to update server ACL in protected rooms.
403404
const banListId = await moderator.createRoom({ invite: [mjolnirId] });
404405
await mjolnir.client.joinRoom(banListId);
405-
await mjolnir.policyListManager.watchList(Permalinks.forRoom(banListId));
406+
await mjolnir.policyListManager.watchList(MatrixRoomReference.fromRoomId(banListId));
406407

407408
await mjolnir.protectedRoomsTracker.syncLists();
408409

@@ -506,7 +507,7 @@ describe('Test: AccessControlUnit interaction with policy lists.', function() {
506507
assertAccess(Access.Allowed, aclUnit.getAccessForUser('@someone:matrix.org', "CHECK_SERVER"));
507508

508509
// protect a room and check that only bad.example.com, *.ddns.example.com are in the deny ACL and not matrix.org
509-
await mjolnir.policyListManager.watchList(policyList.roomRef);
510+
await mjolnir.policyListManager.watchList(MatrixRoomReference.fromPermalink(policyList.roomRef));
510511
const protectedRoom = await mjolnir.client.createRoom();
511512
await mjolnir.protectedRoomsTracker.addProtectedRoom(protectedRoom);
512513
await mjolnir.protectedRoomsTracker.syncLists();

test/integration/banPropagationTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import expect from "expect";
22
import { Mjolnir } from "../../src/Mjolnir";
33
import { newTestUser } from "./clientHelper";
44
import { getFirstEventMatching } from './commands/commandUtils';
5-
import { Permalinks } from "matrix-bot-sdk";
65
import { RULE_USER } from "../../src/models/ListRule";
6+
import { MatrixRoomReference } from "../../src/commands/interface-manager/MatrixRoomReference";
77

88
// We will need to disable this in tests that are banning people otherwise it will cause
99
// mocha to hang for awhile until it times out waiting for a response to a prompt.
@@ -28,7 +28,7 @@ describe("Ban propagation test", function() {
2828
const policyListId = await moderator.createRoom({ invite: [mjolnirId] });
2929
await moderator.setUserPowerLevel(mjolnirId, policyListId, 100);
3030
await mjolnir.client.joinRoom(policyListId);
31-
await mjolnir.policyListManager.watchList(Permalinks.forRoom(policyListId));
31+
await mjolnir.policyListManager.watchList(MatrixRoomReference.fromRoomId(policyListId));
3232

3333
// check for the prompt
3434
const promptEvent = await getFirstEventMatching({

test/integration/protectedRoomsConfigTest.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11

22
import { strict as assert } from "assert";
3-
import { MatrixClient, Permalinks, UserID } from "matrix-bot-sdk";
3+
import { MatrixClient, Permalinks } from "matrix-bot-sdk";
4+
import { MatrixRoomReference } from "../../src/commands/interface-manager/MatrixRoomReference";
45
import { MatrixSendClient } from "../../src/MatrixEmitter";
56
import { Mjolnir } from "../../src/Mjolnir";
67
import PolicyList from "../../src/models/PolicyList";
78
import { newTestUser } from "./clientHelper";
8-
import { createBanList, getFirstReaction } from "./commands/commandUtils";
9+
import { createBanList } from "./commands/commandUtils";
910

1011
async function createPolicyList(client: MatrixClient): Promise<PolicyList> {
11-
const serverName = new UserID(await client.getUserId()).domain;
1212
const policyListId = await client.createRoom({ preset: "public_chat" });
1313
return new PolicyList(policyListId, Permalinks.forRoom(policyListId), client);
1414
}
@@ -37,15 +37,15 @@ describe('Test: config.protectAllJoinedRooms behaves correctly.', function() {
3737
await mjolnir.protectedRoomsTracker.syncLists();
3838
(await getProtectedRoomsFromAccountData(mjolnir.client))
3939
.forEach(roomId => assert.equal(implicitlyProtectedRooms.includes(roomId), false));
40-
40+
4141
// ... but they are protected
4242
mjolnir.protectedRoomsTracker.getProtectedRooms()
4343
.forEach(roomId => assert.equal(implicitlyProtectedRooms.includes(roomId), true));
4444

4545
// We create one policy list with Mjolnir, and we watch another that is maintained by someone else.
4646
const policyListShortcode = await createBanList(mjolnir.managementRoomId, mjolnir.matrixEmitter, moderator);
4747
const unprotectedWatchedList = await createPolicyList(moderator);
48-
await mjolnir.policyListManager.watchList(unprotectedWatchedList.roomRef);
48+
await mjolnir.policyListManager.watchList(MatrixRoomReference.fromPermalink(unprotectedWatchedList.roomRef));
4949
await mjolnir.protectedRoomsTracker.syncLists();
5050

5151
// We expect that the watched list will not be protected, despite config.protectAllJoinedRooms being true

tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
"./test/integration/banListTest.ts",
3131
"./test/integration/reportPollingTest",
3232
"./test/integration/policyConsumptionTest.ts",
33-
"./test/integration/protectionSettingsTest.ts"
33+
"./test/integration/protectionSettingsTest.ts",
34+
"./test/integration/banPropagationTest.ts",
35+
"./test/integration/protectedRoomsConfigTest.ts",
3436
]
3537
}

0 commit comments

Comments
 (0)