Don't cause panic if the bot can't protect a ban list it didn't create

This only affects users with `protectAllJoinedRooms` enabled, as the bot can't possibly protect ban lists it did not create itself.
This commit is contained in:
Travis Ralston 2020-02-18 17:06:27 -07:00
parent 697ada6592
commit ba7ea90fcb
2 changed files with 66 additions and 14 deletions

View File

@ -81,6 +81,9 @@ protectedRooms:
# #
# Note: the management room is *excluded* from this condition. Add it to the # Note: the management room is *excluded* from this condition. Add it to the
# protected rooms to protect it. # protected rooms to protect it.
#
# Note: ban list rooms the bot is watching but didn't create will not be protected.
# Manually add these rooms to the protected rooms list if you want them protected.
protectAllJoinedRooms: false protectAllJoinedRooms: false
# An optional server for the Synapse Mjolnir plugin to connect to. This is # An optional server for the Synapse Mjolnir plugin to connect to. This is

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { LogLevel, LogService, MatrixClient, MatrixGlob, Permalinks } from "matrix-bot-sdk"; import { CreateEvent, LogLevel, LogService, MatrixClient, MatrixGlob, Permalinks } from "matrix-bot-sdk";
import BanList, { ALL_RULE_TYPES } from "./models/BanList"; import BanList, { ALL_RULE_TYPES } from "./models/BanList";
import { applyServerAcls } from "./actions/ApplyAcl"; import { applyServerAcls } from "./actions/ApplyAcl";
import { RoomUpdateError } from "./models/RoomUpdateError"; import { RoomUpdateError } from "./models/RoomUpdateError";
@ -35,6 +35,7 @@ export const STATE_RUNNING = "running";
const WATCHED_LISTS_EVENT_TYPE = "org.matrix.mjolnir.watched_lists"; const WATCHED_LISTS_EVENT_TYPE = "org.matrix.mjolnir.watched_lists";
const ENABLED_PROTECTIONS_EVENT_TYPE = "org.matrix.mjolnir.enabled_protections"; const ENABLED_PROTECTIONS_EVENT_TYPE = "org.matrix.mjolnir.enabled_protections";
const PROTECTED_ROOMS_EVENT_TYPE = "org.matrix.mjolnir.protected_rooms"; const PROTECTED_ROOMS_EVENT_TYPE = "org.matrix.mjolnir.protected_rooms";
const WARN_UNPROTECTED_ROOM_EVENT_PREFIX = "org.matrix.mjolnir.unprotected_room_warning.for.";
export class Mjolnir { export class Mjolnir {
@ -45,12 +46,16 @@ export class Mjolnir {
private redactionQueue = new AutomaticRedactionQueue(); private redactionQueue = new AutomaticRedactionQueue();
private automaticRedactionReasons: MatrixGlob[] = []; private automaticRedactionReasons: MatrixGlob[] = [];
private protectedJoinedRoomIds: string[] = []; private protectedJoinedRoomIds: string[] = [];
private explicitlyProtectedRoomIds: string[] = [];
private knownUnprotectedRooms: string[] = [];
constructor( constructor(
public readonly client: MatrixClient, public readonly client: MatrixClient,
public readonly protectedRooms: { [roomId: string]: string }, public readonly protectedRooms: { [roomId: string]: string },
private banLists: BanList[], private banLists: BanList[],
) { ) {
this.explicitlyProtectedRoomIds = Object.keys(this.protectedRooms);
for (const reason of config.automaticallyRedactForReasons) { for (const reason of config.automaticallyRedactForReasons) {
this.automaticRedactionReasons.push(new MatrixGlob(reason.toLowerCase())); this.automaticRedactionReasons.push(new MatrixGlob(reason.toLowerCase()));
} }
@ -134,12 +139,7 @@ export class Mjolnir {
public start() { public start() {
return this.client.start().then(async () => { return this.client.start().then(async () => {
this.currentState = STATE_CHECKING_PERMISSIONS; this.currentState = STATE_CHECKING_PERMISSIONS;
if (config.verifyPermissionsOnStartup) {
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Checking permissions...");
await this.verifyPermissions(config.verboseLogging);
}
}).then(async () => {
this.currentState = STATE_SYNCING;
await logMessage(LogLevel.DEBUG, "Mjolnir@startup", "Loading protected rooms..."); await logMessage(LogLevel.DEBUG, "Mjolnir@startup", "Loading protected rooms...");
await this.resyncJoinedRooms(false); await this.resyncJoinedRooms(false);
try { try {
@ -147,14 +147,23 @@ export class Mjolnir {
if (data && data['rooms']) { if (data && data['rooms']) {
for (const roomId of data['rooms']) { for (const roomId of data['rooms']) {
this.protectedRooms[roomId] = Permalinks.forRoom(roomId); this.protectedRooms[roomId] = Permalinks.forRoom(roomId);
this.explicitlyProtectedRoomIds.push(roomId);
} }
} }
} catch (e) { } catch (e) {
LogService.warn("Mjolnir", e); LogService.warn("Mjolnir", e);
} }
await this.buildWatchedBanLists();
this.applyUnprotectedRooms();
if (config.verifyPermissionsOnStartup) {
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Checking permissions...");
await this.verifyPermissions(config.verboseLogging);
}
}).then(async () => {
this.currentState = STATE_SYNCING;
if (config.syncOnStartup) { if (config.syncOnStartup) {
await logMessage(LogLevel.INFO, "Mjolnir@startup", "Syncing lists..."); await logMessage(LogLevel.INFO, "Mjolnir@startup", "Syncing lists...");
await this.buildWatchedBanLists();
await this.syncLists(config.verboseLogging); await this.syncLists(config.verboseLogging);
await this.enableProtections(); await this.enableProtections();
} }
@ -167,6 +176,10 @@ export class Mjolnir {
public async addProtectedRoom(roomId: string) { public async addProtectedRoom(roomId: string) {
this.protectedRooms[roomId] = Permalinks.forRoom(roomId); this.protectedRooms[roomId] = Permalinks.forRoom(roomId);
const unprotectedIdx = this.knownUnprotectedRooms.indexOf(roomId);
if (unprotectedIdx >= 0) this.knownUnprotectedRooms.splice(unprotectedIdx, 1);
this.explicitlyProtectedRoomIds.push(roomId);
let additionalProtectedRooms; let additionalProtectedRooms;
try { try {
additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE); additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE);
@ -182,6 +195,9 @@ export class Mjolnir {
public async removeProtectedRoom(roomId: string) { public async removeProtectedRoom(roomId: string) {
delete this.protectedRooms[roomId]; delete this.protectedRooms[roomId];
const idx = this.explicitlyProtectedRoomIds.indexOf(roomId);
if (idx >= 0) this.explicitlyProtectedRoomIds.splice(idx, 1);
let additionalProtectedRooms; let additionalProtectedRooms;
try { try {
additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE); additionalProtectedRooms = await this.client.getAccountData(PROTECTED_ROOMS_EVENT_TYPE);
@ -205,6 +221,8 @@ export class Mjolnir {
this.protectedRooms[roomId] = Permalinks.forRoom(roomId); this.protectedRooms[roomId] = Permalinks.forRoom(roomId);
} }
this.applyUnprotectedRooms();
if (withSync) { if (withSync) {
await this.syncLists(config.verboseLogging); await this.syncLists(config.verboseLogging);
} }
@ -277,6 +295,8 @@ export class Mjolnir {
references: this.banLists.map(b => b.roomRef), references: this.banLists.map(b => b.roomRef),
}); });
await this.warnAboutUnprotectedBanListRoom(roomId);
return list; return list;
} }
@ -295,6 +315,33 @@ export class Mjolnir {
return list; return list;
} }
public async warnAboutUnprotectedBanListRoom(roomId: string) {
if (!config.protectAllJoinedRooms) return; // doesn't matter
if (this.explicitlyProtectedRoomIds.includes(roomId)) return; // explicitly protected
const createEvent = new CreateEvent(await this.client.getRoomStateEvent(roomId, "m.room.create", ""));
if (createEvent.creator === await this.client.getUserId()) return; // we created it
if (!this.knownUnprotectedRooms.includes(roomId)) this.knownUnprotectedRooms.push(roomId);
this.applyUnprotectedRooms();
try {
const accountData = await this.client.getAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId);
if (accountData && accountData['warned']) return; // already warned
} catch (e) {
// Ignore - probably haven't warned about it yet
}
await logMessage(LogLevel.WARN, "Mjolnir", `Not protecting ${roomId} - it is a ban list that this bot did not create. Add the room as protected if it is supposed to be protected. This warning will not appear again.`);
await this.client.setAccountData(WARN_UNPROTECTED_ROOM_EVENT_PREFIX + roomId, {warned: true});
}
private applyUnprotectedRooms() {
for (const roomId of this.knownUnprotectedRooms) {
delete this.protectedRooms[roomId];
}
}
public async buildWatchedBanLists() { public async buildWatchedBanLists() {
const banLists: BanList[] = []; const banLists: BanList[] = [];
const joinedRooms = await this.client.getJoinedRooms(); const joinedRooms = await this.client.getJoinedRooms();
@ -315,6 +362,8 @@ export class Mjolnir {
await this.client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers); await this.client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers);
} }
await this.warnAboutUnprotectedBanListRoom(roomId);
const list = new BanList(roomId, roomRef, this.client); const list = new BanList(roomId, roomRef, this.client);
await list.updateList(); await list.updateList();
banLists.push(list); banLists.push(list);