Skip to content

Commit 829e1bd

Browse files
author
David Teller
authored
Towards opinions in PolicyLists. (#336)
Towards opinions in PolicyLists. This changeset is part of an ongoing effort to implement "opinions" within policy lists, as per MSC3847. For the time being: - we rename BanList into PolicyList; - we cleanup a little dead code; - we replace a few `string`s with `enum`; - `ListRule` becomes an abstract class with two concrete subclasses `ListRuleBan` and `ListRuleOpinion`.
1 parent 4aad5c4 commit 829e1bd

File tree

15 files changed

+591
-403
lines changed

15 files changed

+591
-403
lines changed

src/Mjolnir.ts

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {
2727
TextualMessageEventContent
2828
} from "matrix-bot-sdk";
2929

30-
import BanList, { ALL_RULE_TYPES as ALL_BAN_LIST_RULE_TYPES, ListRuleChange, RULE_ROOM, RULE_SERVER, RULE_USER } from "./models/BanList";
30+
import { ALL_RULE_TYPES as ALL_BAN_LIST_RULE_TYPES, RULE_ROOM, RULE_SERVER, RULE_USER } from "./models/ListRule";
3131
import { applyServerAcls } from "./actions/ApplyAcl";
3232
import { RoomUpdateError } from "./models/RoomUpdateError";
3333
import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
@@ -50,6 +50,7 @@ import RuleServer from "./models/RuleServer";
5050
import { RoomMemberManager } from "./RoomMembers";
5151
import { ProtectedRoomActivityTracker } from "./queues/ProtectedRoomActivityTracker";
5252
import { ThrottlingQueue } from "./queues/ThrottlingQueue";
53+
import PolicyList, { ListRuleChange } from "./models/PolicyList";
5354

5455
const levelToFn = {
5556
[LogLevel.DEBUG.toString()]: LogService.debug,
@@ -153,7 +154,7 @@ export class Mjolnir {
153154
* @returns A new Mjolnir instance that can be started without further setup.
154155
*/
155156
static async setupMjolnirFromConfig(client: MatrixClient): Promise<Mjolnir> {
156-
const banLists: BanList[] = [];
157+
const policyLists: PolicyList[] = [];
157158
const protectedRooms: { [roomId: string]: string } = {};
158159
const joinedRooms = await client.getJoinedRooms();
159160
// Ensure we're also joined to the rooms we're protecting
@@ -178,7 +179,7 @@ export class Mjolnir {
178179
}
179180

180181
const ruleServer = config.web.ruleServer ? new RuleServer() : null;
181-
const mjolnir = new Mjolnir(client, managementRoomId, protectedRooms, banLists, ruleServer);
182+
const mjolnir = new Mjolnir(client, managementRoomId, protectedRooms, policyLists, ruleServer);
182183
await mjolnir.logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
183184
Mjolnir.addJoinOnInviteListener(mjolnir, client, config);
184185
return mjolnir;
@@ -192,9 +193,9 @@ export class Mjolnir {
192193
* If `config.protectAllJoinedRooms` is specified, then `protectedRooms` will be all joined rooms except watched banlists that we can't protect (because they aren't curated by us).
193194
*/
194195
public readonly protectedRooms: { [roomId: string]: string },
195-
private banLists: BanList[],
196+
private policyLists: PolicyList[],
196197
// Combines the rules from ban lists so they can be served to a homeserver module or another consumer.
197-
public readonly ruleServer: RuleServer|null,
198+
public readonly ruleServer: RuleServer | null,
198199
) {
199200
this.explicitlyProtectedRoomIds = Object.keys(this.protectedRooms);
200201

@@ -275,8 +276,8 @@ export class Mjolnir {
275276
this.taskQueue = new ThrottlingQueue(this, config.backgroundDelayMS);
276277
}
277278

278-
public get lists(): BanList[] {
279-
return this.banLists;
279+
public get lists(): PolicyList[] {
280+
return this.policyLists;
280281
}
281282

282283
public get state(): string {
@@ -343,7 +344,7 @@ export class Mjolnir {
343344
} catch (e) {
344345
LogService.warn("Mjolnir", extractRequestError(e));
345346
}
346-
await this.buildWatchedBanLists();
347+
await this.buildWatchedPolicyLists();
347348
this.applyUnprotectedRooms();
348349

349350
if (config.verifyPermissionsOnStartup) {
@@ -554,12 +555,12 @@ export class Mjolnir {
554555
const validatedSettings: { [setting: string]: any } = {}
555556
for (let [key, value] of Object.entries(savedSettings)) {
556557
if (
557-
// is this a setting name with a known parser?
558-
key in settingDefinitions
559-
// is the datatype of this setting's value what we expect?
560-
&& typeof(settingDefinitions[key].value) === typeof(value)
561-
// is this setting's value valid for the setting?
562-
&& settingDefinitions[key].validate(value)
558+
// is this a setting name with a known parser?
559+
key in settingDefinitions
560+
// is the datatype of this setting's value what we expect?
561+
&& typeof (settingDefinitions[key].value) === typeof (value)
562+
// is this setting's value valid for the setting?
563+
&& settingDefinitions[key].validate(value)
563564
) {
564565
validatedSettings[key] = value;
565566
} else {
@@ -593,8 +594,8 @@ export class Mjolnir {
593594
if (!(key in protection.settings)) {
594595
throw new ProtectionSettingValidationError(`Failed to find protection setting by name: ${key}`);
595596
}
596-
if (typeof(protection.settings[key].value) !== typeof(value)) {
597-
throw new ProtectionSettingValidationError(`Invalid type for protection setting: ${key} (${typeof(value)})`);
597+
if (typeof (protection.settings[key].value) !== typeof (value)) {
598+
throw new ProtectionSettingValidationError(`Invalid type for protection setting: ${key} (${typeof (value)})`);
598599
}
599600
if (!protection.settings[key].validate(value)) {
600601
throw new ProtectionSettingValidationError(`Invalid value for protection setting: ${key} (${value})`);
@@ -644,16 +645,16 @@ export class Mjolnir {
644645
}
645646

646647
/**
647-
* Helper for constructing `BanList`s and making sure they have the right listeners set up.
648-
* @param roomId The room id for the `BanList`.
649-
* @param roomRef A reference (matrix.to URL) for the `BanList`.
648+
* Helper for constructing `PolicyList`s and making sure they have the right listeners set up.
649+
* @param roomId The room id for the `PolicyList`.
650+
* @param roomRef A reference (matrix.to URL) for the `PolicyList`.
650651
*/
651-
private async addBanList(roomId: string, roomRef: string): Promise<BanList> {
652-
const list = new BanList(roomId, roomRef, this.client);
652+
private async addPolicyList(roomId: string, roomRef: string): Promise<PolicyList> {
653+
const list = new PolicyList(roomId, roomRef, this.client);
653654
this.ruleServer?.watch(list);
654-
list.on('BanList.batch', this.syncWithBanList.bind(this));
655+
list.on('PolicyList.batch', this.syncWithPolicyList.bind(this));
655656
await list.updateList();
656-
this.banLists.push(list);
657+
this.policyLists.push(list);
657658
return list;
658659
}
659660

@@ -667,7 +668,7 @@ export class Mjolnir {
667668
return this.protections.get(protectionName) ?? null;
668669
}
669670

670-
public async watchList(roomRef: string): Promise<BanList | null> {
671+
public async watchList(roomRef: string): Promise<PolicyList | null> {
671672
const joinedRooms = await this.client.getJoinedRooms();
672673
const permalink = Permalinks.parseUrl(roomRef);
673674
if (!permalink.roomIdOrAlias) return null;
@@ -677,37 +678,37 @@ export class Mjolnir {
677678
await this.client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers);
678679
}
679680

680-
if (this.banLists.find(b => b.roomId === roomId)) return null;
681+
if (this.policyLists.find(b => b.roomId === roomId)) return null;
681682

682-
const list = await this.addBanList(roomId, roomRef);
683+
const list = await this.addPolicyList(roomId, roomRef);
683684

684685
await this.client.setAccountData(WATCHED_LISTS_EVENT_TYPE, {
685-
references: this.banLists.map(b => b.roomRef),
686+
references: this.policyLists.map(b => b.roomRef),
686687
});
687688

688-
await this.warnAboutUnprotectedBanListRoom(roomId);
689+
await this.warnAboutUnprotectedPolicyListRoom(roomId);
689690

690691
return list;
691692
}
692693

693-
public async unwatchList(roomRef: string): Promise<BanList | null> {
694+
public async unwatchList(roomRef: string): Promise<PolicyList | null> {
694695
const permalink = Permalinks.parseUrl(roomRef);
695696
if (!permalink.roomIdOrAlias) return null;
696697

697698
const roomId = await this.client.resolveRoom(permalink.roomIdOrAlias);
698-
const list = this.banLists.find(b => b.roomId === roomId) || null;
699+
const list = this.policyLists.find(b => b.roomId === roomId) || null;
699700
if (list) {
700-
this.banLists.splice(this.banLists.indexOf(list), 1);
701+
this.policyLists.splice(this.policyLists.indexOf(list), 1);
701702
this.ruleServer?.unwatch(list);
702703
}
703704

704705
await this.client.setAccountData(WATCHED_LISTS_EVENT_TYPE, {
705-
references: this.banLists.map(b => b.roomRef),
706+
references: this.policyLists.map(b => b.roomRef),
706707
});
707708
return list;
708709
}
709710

710-
public async warnAboutUnprotectedBanListRoom(roomId: string) {
711+
public async warnAboutUnprotectedPolicyListRoom(roomId: string) {
711712
if (!config.protectAllJoinedRooms) return; // doesn't matter
712713
if (this.explicitlyProtectedRoomIds.includes(roomId)) return; // explicitly protected
713714

@@ -735,8 +736,8 @@ export class Mjolnir {
735736
}
736737
}
737738

738-
private async buildWatchedBanLists() {
739-
this.banLists = [];
739+
private async buildWatchedPolicyLists() {
740+
this.policyLists = [];
740741
const joinedRooms = await this.client.getJoinedRooms();
741742

742743
let watchedListsEvent: { references?: string[] } | null = null;
@@ -755,8 +756,8 @@ export class Mjolnir {
755756
await this.client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers);
756757
}
757758

758-
await this.warnAboutUnprotectedBanListRoom(roomId);
759-
await this.addBanList(roomId, roomRef);
759+
await this.warnAboutUnprotectedPolicyListRoom(roomId);
760+
await this.addPolicyList(roomId, roomRef);
760761
}
761762
}
762763

@@ -882,15 +883,15 @@ export class Mjolnir {
882883
* @param verbose Whether to report any errors to the management room.
883884
*/
884885
public async syncLists(verbose = true) {
885-
for (const list of this.banLists) {
886+
for (const list of this.policyLists) {
886887
const changes = await list.updateList();
887888
await this.printBanlistChanges(changes, list, true);
888889
}
889890

890891
let hadErrors = false;
891892
const [aclErrors, banErrors] = await Promise.all([
892-
applyServerAcls(this.banLists, this.protectedRoomsByActivity(), this),
893-
applyUserBans(this.banLists, this.protectedRoomsByActivity(), this)
893+
applyServerAcls(this.policyLists, this.protectedRoomsByActivity(), this),
894+
applyUserBans(this.policyLists, this.protectedRoomsByActivity(), this)
894895
]);
895896
const redactionErrors = await this.processRedactionQueue();
896897
hadErrors = hadErrors || await this.printActionResult(aclErrors, "Errors updating server ACLs:");
@@ -912,16 +913,16 @@ export class Mjolnir {
912913
/**
913914
* Pulls any changes to the rules that are in a policy room and updates all protected rooms
914915
* with those changes. Does not fail if there are errors updating the room, these are reported to the management room.
915-
* @param banList The `BanList` which we will check for changes and apply them to all protected rooms.
916+
* @param policyList The `PolicyList` which we will check for changes and apply them to all protected rooms.
916917
* @returns When all of the protected rooms have been updated.
917918
*/
918-
private async syncWithBanList(banList: BanList): Promise<void> {
919-
const changes = await banList.updateList();
919+
private async syncWithPolicyList(policyList: PolicyList): Promise<void> {
920+
const changes = await policyList.updateList();
920921

921922
let hadErrors = false;
922923
const [aclErrors, banErrors] = await Promise.all([
923-
applyServerAcls(this.banLists, this.protectedRoomsByActivity(), this),
924-
applyUserBans(this.banLists, this.protectedRoomsByActivity(), this)
924+
applyServerAcls(this.policyLists, this.protectedRoomsByActivity(), this),
925+
applyUserBans(this.policyLists, this.protectedRoomsByActivity(), this)
925926
]);
926927
const redactionErrors = await this.processRedactionQueue();
927928
hadErrors = hadErrors || await this.printActionResult(aclErrors, "Errors updating server ACLs:");
@@ -939,7 +940,7 @@ export class Mjolnir {
939940
});
940941
}
941942
// This can fail if the change is very large and it is much less important than applying bans, so do it last.
942-
await this.printBanlistChanges(changes, banList, true);
943+
await this.printBanlistChanges(changes, policyList, true);
943944
}
944945

945946
private async handleConsequence(protection: Protection, roomId: string, eventId: string, sender: string, consequence: Consequence) {
@@ -989,10 +990,10 @@ export class Mjolnir {
989990

990991
// Check for updated ban lists before checking protected rooms - the ban lists might be protected
991992
// themselves.
992-
const banList = this.banLists.find(list => list.roomId === roomId);
993-
if (banList !== undefined) {
993+
const policyList = this.policyLists.find(list => list.roomId === roomId);
994+
if (policyList !== undefined) {
994995
if (ALL_BAN_LIST_RULE_TYPES.includes(event['type']) || event['type'] === 'm.room.redaction') {
995-
banList.updateForEvent(event)
996+
policyList.updateForEvent(event)
996997
}
997998
}
998999

@@ -1037,7 +1038,7 @@ export class Mjolnir {
10371038
// we cannot eagerly ban users (that is to ban them when they have never been a member)
10381039
// as they can be force joined to a room they might not have known existed.
10391040
// Only apply bans and then redactions in the room we are currently looking at.
1040-
const banErrors = await applyUserBans(this.banLists, [roomId], this);
1041+
const banErrors = await applyUserBans(this.policyLists, [roomId], this);
10411042
const redactionErrors = await this.processRedactionQueue(roomId);
10421043
await this.printActionResult(banErrors);
10431044
await this.printActionResult(redactionErrors);
@@ -1051,7 +1052,7 @@ export class Mjolnir {
10511052
* @param ignoreSelf Whether to exclude changes that have been made by Mjolnir.
10521053
* @returns true if the message was sent, false if it wasn't (because there there were no changes to report).
10531054
*/
1054-
private async printBanlistChanges(changes: ListRuleChange[], list: BanList, ignoreSelf = false): Promise<boolean> {
1055+
private async printBanlistChanges(changes: ListRuleChange[], list: PolicyList, ignoreSelf = false): Promise<boolean> {
10551056
if (ignoreSelf) {
10561057
const sender = await this.client.getUserId();
10571058
changes = changes.filter(change => change.sender !== sender);

src/actions/ApplyAcl.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import BanList from "../models/BanList";
17+
import PolicyList from "../models/PolicyList";
1818
import { ServerAcl } from "../models/ServerAcl";
1919
import { RoomUpdateError } from "../models/RoomUpdateError";
2020
import { Mjolnir } from "../Mjolnir";
@@ -26,11 +26,11 @@ import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
2626
* Applies the server ACLs represented by the ban lists to the provided rooms, returning the
2727
* room IDs that could not be updated and their error.
2828
* Does not update the banLists before taking their rules to build the server ACL.
29-
* @param {BanList[]} lists The lists to construct ACLs from.
29+
* @param {PolicyList[]} lists The lists to construct ACLs from.
3030
* @param {string[]} roomIds The room IDs to apply the ACLs in.
3131
* @param {Mjolnir} mjolnir The Mjolnir client to apply the ACLs with.
3232
*/
33-
export async function applyServerAcls(lists: BanList[], roomIds: string[], mjolnir: Mjolnir): Promise<RoomUpdateError[]> {
33+
export async function applyServerAcls(lists: PolicyList[], roomIds: string[], mjolnir: Mjolnir): Promise<RoomUpdateError[]> {
3434
const serverName: string = new UserID(await mjolnir.client.getUserId()).domain;
3535

3636
// Construct a server ACL first
@@ -78,7 +78,7 @@ export async function applyServerAcls(lists: BanList[], roomIds: string[], mjoln
7878
} catch (e) {
7979
const message = e.message || (e.body ? e.body.error : '<no message>');
8080
const kind = message && message.includes("You don't have permission to post that to the room") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL;
81-
errors.push({roomId, errorMessage: message, errorKind: kind});
81+
errors.push({ roomId, errorMessage: message, errorKind: kind });
8282
}
8383
}
8484

src/actions/ApplyBan.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import BanList from "../models/BanList";
17+
import PolicyList from "../models/PolicyList";
1818
import { RoomUpdateError } from "../models/RoomUpdateError";
1919
import { Mjolnir } from "../Mjolnir";
2020
import config from "../config";
@@ -24,11 +24,11 @@ import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
2424
/**
2525
* Applies the member bans represented by the ban lists to the provided rooms, returning the
2626
* room IDs that could not be updated and their error.
27-
* @param {BanList[]} lists The lists to determine bans from.
27+
* @param {PolicyList[]} lists The lists to determine bans from.
2828
* @param {string[]} roomIds The room IDs to apply the bans in.
2929
* @param {Mjolnir} mjolnir The Mjolnir client to apply the bans with.
3030
*/
31-
export async function applyUserBans(lists: BanList[], roomIds: string[], mjolnir: Mjolnir): Promise<RoomUpdateError[]> {
31+
export async function applyUserBans(lists: PolicyList[], roomIds: string[], mjolnir: Mjolnir): Promise<RoomUpdateError[]> {
3232
// We can only ban people who are not already banned, and who match the rules.
3333
const errors: RoomUpdateError[] = [];
3434
for (const roomId of roomIds) {
@@ -41,12 +41,12 @@ export async function applyUserBans(lists: BanList[], roomIds: string[], mjolnir
4141
if (config.fasterMembershipChecks) {
4242
const memberIds = await mjolnir.client.getJoinedRoomMembers(roomId);
4343
members = memberIds.map(u => {
44-
return {userId: u, membership: "join"};
44+
return { userId: u, membership: "join" };
4545
});
4646
} else {
4747
const state = await mjolnir.client.getRoomState(roomId);
4848
members = state.filter(s => s['type'] === 'm.room.member' && !!s['state_key']).map(s => {
49-
return {userId: s['state_key'], membership: s['content'] ? s['content']['membership'] : 'leave'};
49+
return { userId: s['state_key'], membership: s['content'] ? s['content']['membership'] : 'leave' };
5050
});
5151
}
5252

src/commands/CommandHandler.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ import { execRedactCommand } from "./RedactCommand";
2828
import { execImportCommand } from "./ImportCommand";
2929
import { execSetDefaultListCommand } from "./SetDefaultBanListCommand";
3030
import { execDeactivateCommand } from "./DeactivateCommand";
31-
import { execDisableProtection, execEnableProtection, execListProtections, execConfigGetProtection,
32-
execConfigSetProtection, execConfigAddProtection, execConfigRemoveProtection } from "./ProtectionsCommands";
31+
import {
32+
execDisableProtection, execEnableProtection, execListProtections, execConfigGetProtection,
33+
execConfigSetProtection, execConfigAddProtection, execConfigRemoveProtection
34+
} from "./ProtectionsCommands";
3335
import { execListProtectedRooms } from "./ListProtectedRoomsCommand";
3436
import { execAddProtectedRoom, execRemoveProtectedRoom } from "./AddRemoveProtectedRoomsCommand";
3537
import { execAddRoomToDirectoryCommand, execRemoveRoomFromDirectoryCommand } from "./AddRemoveRoomFromDirectoryCommand";

0 commit comments

Comments
 (0)