mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 05:36:06 +00:00
Factor out protected rooms config management from Mjolnir.
The combination of `resyncJoinedRooms`, `unprotectedWatchedListRooms`, `explicitlyProtectedRoomIds`, `protectedJoinedRoomIds` was incomprehensible. https://github.com/matrix-org/mjolnir/issues/370 Separating out the management of `explicitlyProtectedRoomIds`, then making sure all policy lists have to be explicitly protected (in either setting of `config.protectAllJoinedRooms`) will make this code much much simpler. We will later change the `status` command to explicitly show which lists are watched and which are watched and protected.
This commit is contained in:
parent
da084328a9
commit
58e36d4e23
@ -36,6 +36,7 @@
|
|||||||
"typescript-formatter": "^7.2"
|
"typescript-formatter": "^7.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"await-lock": "^2.2.2",
|
||||||
"express": "^4.17",
|
"express": "^4.17",
|
||||||
"html-to-text": "^8.0.0",
|
"html-to-text": "^8.0.0",
|
||||||
"humanize-duration": "^3.27.1",
|
"humanize-duration": "^3.27.1",
|
||||||
|
@ -35,7 +35,7 @@ import RuleServer from "./models/RuleServer";
|
|||||||
import { ThrottlingQueue } from "./queues/ThrottlingQueue";
|
import { ThrottlingQueue } from "./queues/ThrottlingQueue";
|
||||||
import { IConfig } from "./config";
|
import { IConfig } from "./config";
|
||||||
import PolicyList from "./models/PolicyList";
|
import PolicyList from "./models/PolicyList";
|
||||||
import { ProtectedRooms } from "./ProtectedRooms";
|
import { ProtectedRoomsSet } from "./ProtectedRoomsSet";
|
||||||
import ManagementRoomOutput from "./ManagementRoomOutput";
|
import ManagementRoomOutput from "./ManagementRoomOutput";
|
||||||
import { ProtectionManager } from "./protections/ProtectionManager";
|
import { ProtectionManager } from "./protections/ProtectionManager";
|
||||||
import { RoomMemberManager } from "./RoomMembers";
|
import { RoomMemberManager } from "./RoomMembers";
|
||||||
@ -45,7 +45,6 @@ export const STATE_CHECKING_PERMISSIONS = "checking_permissions";
|
|||||||
export const STATE_SYNCING = "syncing";
|
export const STATE_SYNCING = "syncing";
|
||||||
export const STATE_RUNNING = "running";
|
export const STATE_RUNNING = "running";
|
||||||
|
|
||||||
const PROTECTED_ROOMS_EVENT_TYPE = "org.matrix.mjolnir.protected_rooms";
|
|
||||||
const WATCHED_LISTS_EVENT_TYPE = "org.matrix.mjolnir.watched_lists";
|
const WATCHED_LISTS_EVENT_TYPE = "org.matrix.mjolnir.watched_lists";
|
||||||
const WARN_UNPROTECTED_ROOM_EVENT_PREFIX = "org.matrix.mjolnir.unprotected_room_warning.for.";
|
const WARN_UNPROTECTED_ROOM_EVENT_PREFIX = "org.matrix.mjolnir.unprotected_room_warning.for.";
|
||||||
|
|
||||||
@ -78,7 +77,7 @@ export class Mjolnir {
|
|||||||
* These are eventually are exluded from `protectedRooms` in `applyUnprotectedRooms` via `resyncJoinedRooms`.
|
* These are eventually are exluded from `protectedRooms` in `applyUnprotectedRooms` via `resyncJoinedRooms`.
|
||||||
*/
|
*/
|
||||||
private unprotectedWatchedListRooms: string[] = [];
|
private unprotectedWatchedListRooms: string[] = [];
|
||||||
public readonly protectedRoomsTracker: ProtectedRooms;
|
public readonly protectedRoomsTracker: ProtectedRoomsSet;
|
||||||
private webapis: WebAPIs;
|
private webapis: WebAPIs;
|
||||||
public taskQueue: ThrottlingQueue;
|
public taskQueue: ThrottlingQueue;
|
||||||
/**
|
/**
|
||||||
@ -272,7 +271,7 @@ export class Mjolnir {
|
|||||||
|
|
||||||
this.managementRoomOutput = new ManagementRoomOutput(managementRoomId, client, config);
|
this.managementRoomOutput = new ManagementRoomOutput(managementRoomId, client, config);
|
||||||
const protections = new ProtectionManager(this);
|
const protections = new ProtectionManager(this);
|
||||||
this.protectedRoomsTracker = new ProtectedRooms(client, clientUserId, managementRoomId, this.managementRoomOutput, protections, config);
|
this.protectedRoomsTracker = new ProtectedRoomsSet(client, clientUserId, managementRoomId, this.managementRoomOutput, protections, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get lists(): PolicyList[] {
|
public get lists(): PolicyList[] {
|
||||||
|
129
src/ProtectedRoomsConfig.ts
Normal file
129
src/ProtectedRoomsConfig.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019, 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import AwaitLock from 'await-lock';
|
||||||
|
import { extractRequestError, LogService, MatrixClient, Permalinks } from "matrix-bot-sdk";
|
||||||
|
import { IConfig } from "./config";
|
||||||
|
const PROTECTED_ROOMS_EVENT_TYPE = "org.matrix.mjolnir.protected_rooms";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the set of rooms that the user has EXPLICITLY asked to be protected.
|
||||||
|
*/
|
||||||
|
export default class ProtectedRoomsConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are rooms that we EXPLICITLY asked Mjolnir to protect, usually via the `rooms add` command.
|
||||||
|
* These are NOT all of the rooms that mjolnir is protecting as with `config.protectAllJoinedRooms`.
|
||||||
|
*/
|
||||||
|
private explicitlyProtectedRooms = new Set</*room id*/string>();
|
||||||
|
/** This is to prevent clobbering the account data for the protected rooms if several rooms are explicitly protected concurrently. */
|
||||||
|
private accountDataLock = new AwaitLock();
|
||||||
|
|
||||||
|
constructor(private readonly client: MatrixClient) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load any rooms that have been explicitly protected from a Mjolnir config.
|
||||||
|
* Will also ensure we are able to join all of the rooms.
|
||||||
|
* @param config The config to load the rooms from under `config.protectedRooms`.
|
||||||
|
*/
|
||||||
|
public async loadProtectedRoomsFromConfig(config: IConfig): Promise<void> {
|
||||||
|
// Ensure we're also joined to the rooms we're protecting
|
||||||
|
LogService.info("ProtectedRoomsConfig", "Resolving protected rooms...");
|
||||||
|
const joinedRooms = await this.client.getJoinedRooms();
|
||||||
|
for (const roomRef of config.protectedRooms) {
|
||||||
|
const permalink = Permalinks.parseUrl(roomRef);
|
||||||
|
if (!permalink.roomIdOrAlias) continue;
|
||||||
|
|
||||||
|
let roomId = await this.client.resolveRoom(permalink.roomIdOrAlias);
|
||||||
|
if (!joinedRooms.includes(roomId)) {
|
||||||
|
roomId = await this.client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers);
|
||||||
|
}
|
||||||
|
this.explicitlyProtectedRooms.add(roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load any rooms that have been explicitly protected from the account data of the mjolnir user.
|
||||||
|
* Will not ensure we can join all the rooms. This so mjolnir can continue to operate if bogus rooms have been persisted to the account data.
|
||||||
|
*/
|
||||||
|
public async loadProtectedRoomsFromAccountData(): Promise<void> {
|
||||||
|
LogService.debug("ProtectedRoomsConfig", "Loading protected rooms...");
|
||||||
|
try {
|
||||||
|
const data: { rooms?: string[] } | null = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE);
|
||||||
|
if (data && data['rooms']) {
|
||||||
|
for (const roomId of data['rooms']) {
|
||||||
|
this.explicitlyProtectedRooms.add(roomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e.statusCode === 404) {
|
||||||
|
LogService.warn("ProtectedRoomsConfig", "Couldn't find any explicitly protected rooms from Mjolnir's account data, assuming first start.", extractRequestError(e));
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the room as explicitly protected.
|
||||||
|
* @param roomId The room to persist as explicitly protected.
|
||||||
|
*/
|
||||||
|
public async addProtectedRoom(roomId: string): Promise<void> {
|
||||||
|
this.explicitlyProtectedRooms.add(roomId);
|
||||||
|
await this.saveProtectedRoomsToAccountData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the room from the explicitly protected set of rooms.
|
||||||
|
* @param roomId The room that should no longer be persisted as protected.
|
||||||
|
*/
|
||||||
|
public async removeProtectedRoom(roomId: string): Promise<void> {
|
||||||
|
this.explicitlyProtectedRooms.delete(roomId);
|
||||||
|
await this.saveProtectedRoomsToAccountData([roomId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of explicitly protected rooms.
|
||||||
|
* This will NOT be the complete set of protected rooms, if `config.protectAllJoinedRooms` is true and should never be treated as the complete set.
|
||||||
|
* @returns The rooms that are marked as explicitly protected in both the config and Mjolnir's account data.
|
||||||
|
*/
|
||||||
|
public getExplicitlyProtectedRooms(): string[] {
|
||||||
|
return [...this.explicitlyProtectedRooms.keys()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist the set of explicitly protected rooms to the client's account data.
|
||||||
|
* @param excludeRooms Rooms that should not be persisted to the account data, and removed if already present.
|
||||||
|
*/
|
||||||
|
private async saveProtectedRoomsToAccountData(excludeRooms: string[] = []): Promise<void> {
|
||||||
|
// NOTE: this stops Mjolnir from racing with itself when saving the config
|
||||||
|
// but it doesn't stop a third party client on the same account racing with us instead.
|
||||||
|
await this.accountDataLock.acquireAsync();
|
||||||
|
try {
|
||||||
|
const additionalProtectedRooms: string[] = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE)
|
||||||
|
.then((rooms: {rooms?: string[]}) => Array.isArray(rooms?.rooms) ? rooms.rooms : [])
|
||||||
|
.catch(e => (LogService.warn("ProtectedRoomsConfig", "Could not load protected rooms from account data", extractRequestError(e)), []));
|
||||||
|
|
||||||
|
const roomsToSave = new Set([...this.explicitlyProtectedRooms.keys(), ...additionalProtectedRooms]);
|
||||||
|
excludeRooms.forEach(roomsToSave.delete, roomsToSave);
|
||||||
|
await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, { rooms: Array.from(roomsToSave.keys()) });
|
||||||
|
} finally {
|
||||||
|
this.accountDataLock.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -42,7 +42,7 @@ import { htmlEscape } from "./utils";
|
|||||||
* It is also important not to tie this to the one group of rooms that a mjolnir may watch
|
* It is also important not to tie this to the one group of rooms that a mjolnir may watch
|
||||||
* as in future we might want to borrow this class to represent a space https://github.com/matrix-org/mjolnir/issues/283.
|
* as in future we might want to borrow this class to represent a space https://github.com/matrix-org/mjolnir/issues/283.
|
||||||
*/
|
*/
|
||||||
export class ProtectedRooms {
|
export class ProtectedRoomsSet {
|
||||||
|
|
||||||
private protectedRooms = new Set</* room id */string>();
|
private protectedRooms = new Set</* room id */string>();
|
||||||
|
|
||||||
@ -228,7 +228,7 @@ export class ProtectedRooms {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addProtectedRoom(roomId: string): Promise<void> {
|
public addProtectedRoom(roomId: string): void {
|
||||||
if (this.protectedRooms.has(roomId)) {
|
if (this.protectedRooms.has(roomId)) {
|
||||||
// we need to protect ourselves form syncing all the lists unnecessarily
|
// we need to protect ourselves form syncing all the lists unnecessarily
|
||||||
// as Mjolnir does call this method repeatedly.
|
// as Mjolnir does call this method repeatedly.
|
||||||
@ -236,7 +236,6 @@ export class ProtectedRooms {
|
|||||||
}
|
}
|
||||||
this.protectedRooms.add(roomId);
|
this.protectedRooms.add(roomId);
|
||||||
this.protectedRoomActivityTracker.addProtectedRoom(roomId);
|
this.protectedRoomActivityTracker.addProtectedRoom(roomId);
|
||||||
await this.syncLists(this.config.verboseLogging);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeProtectedRoom(roomId: string): void {
|
public removeProtectedRoom(roomId: string): void {
|
@ -389,6 +389,11 @@ asynckit@^0.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
|
||||||
|
|
||||||
|
await-lock@^2.2.2:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/await-lock/-/await-lock-2.2.2.tgz#a95a9b269bfd2f69d22b17a321686f551152bcef"
|
||||||
|
integrity sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==
|
||||||
|
|
||||||
aws-sign2@~0.7.0:
|
aws-sign2@~0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||||
|
Loading…
Reference in New Issue
Block a user