diff --git a/src/ErrorCache.ts b/src/ErrorCache.ts
new file mode 100644
index 0000000..bc66479
--- /dev/null
+++ b/src/ErrorCache.ts
@@ -0,0 +1,59 @@
+/*
+Copyright 2019 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.
+*/
+
+export const ERROR_KIND_PERMISSION = "permission";
+export const ERROR_KIND_FATAL = "fatal";
+
+const TRIGGER_INTERVALS = {
+ [ERROR_KIND_PERMISSION]: 3 * 60 * 60 * 1000, // 3 hours
+ [ERROR_KIND_FATAL]: 15 * 60 * 1000, // 15 minutes
+};
+
+export default class ErrorCache {
+ private static roomsToErrors: { [roomId: string]: { [kind: string]: number } } = {};
+
+ private constructor() {
+ }
+
+ public static resetError(roomId: string, kind: string) {
+ if (!ErrorCache.roomsToErrors[roomId]) {
+ ErrorCache.roomsToErrors[roomId] = {};
+ }
+ ErrorCache.roomsToErrors[roomId][kind] = 0;
+ }
+
+ public static triggerError(roomId: string, kind: string): boolean {
+ if (!ErrorCache.roomsToErrors[roomId]) {
+ ErrorCache.roomsToErrors[roomId] = {};
+ }
+
+ const triggers = ErrorCache.roomsToErrors[roomId];
+ if (!triggers[kind]) {
+ triggers[kind] = 0;
+ }
+
+ const lastTriggerTime = triggers[kind];
+ const now = new Date().getTime();
+ const interval = TRIGGER_INTERVALS[kind];
+
+ if ((now - lastTriggerTime) >= interval) {
+ triggers[kind] = now;
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/Mjolnir.ts b/src/Mjolnir.ts
index 564b4e3..505f6be 100644
--- a/src/Mjolnir.ts
+++ b/src/Mjolnir.ts
@@ -22,6 +22,7 @@ import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
import { applyUserBans } from "./actions/ApplyBan";
import config from "./config";
import { logMessage } from "./LogProxy";
+import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
export const STATE_NOT_STARTED = "not_started";
export const STATE_CHECKING_PERMISSIONS = "checking_permissions";
@@ -166,13 +167,13 @@ export class Mjolnir {
this.banLists = banLists;
}
- public async verifyPermissions(verbose = true) {
+ public async verifyPermissions(verbose = true, printRegardless = false) {
const errors: RoomUpdateError[] = [];
for (const roomId of Object.keys(this.protectedRooms)) {
errors.push(...(await this.verifyPermissionsIn(roomId)));
}
- const hadErrors = await this.printActionResult(errors, "Permission errors in protected rooms:");
+ const hadErrors = await this.printActionResult(errors, "Permission errors in protected rooms:", printRegardless);
if (!hadErrors && verbose) {
const html = `All permissions look OK.`;
const text = "All permissions look OK.";
@@ -216,22 +217,42 @@ export class Mjolnir {
// Wants: ban, kick, redact, m.room.server_acl
if (userLevel < ban) {
- errors.push({roomId, errorMessage: `Missing power level for bans: ${userLevel} < ${ban}`});
+ errors.push({
+ roomId,
+ errorMessage: `Missing power level for bans: ${userLevel} < ${ban}`,
+ errorKind: ERROR_KIND_PERMISSION,
+ });
}
if (userLevel < kick) {
- errors.push({roomId, errorMessage: `Missing power level for kicks: ${userLevel} < ${kick}`});
+ errors.push({
+ roomId,
+ errorMessage: `Missing power level for kicks: ${userLevel} < ${kick}`,
+ errorKind: ERROR_KIND_PERMISSION,
+ });
}
if (userLevel < redact) {
- errors.push({roomId, errorMessage: `Missing power level for redactions: ${userLevel} < ${redact}`});
+ errors.push({
+ roomId,
+ errorMessage: `Missing power level for redactions: ${userLevel} < ${redact}`,
+ errorKind: ERROR_KIND_PERMISSION,
+ });
}
if (userLevel < aclLevel) {
- errors.push({roomId, errorMessage: `Missing power level for server ACLs: ${userLevel} < ${aclLevel}`});
+ errors.push({
+ roomId,
+ errorMessage: `Missing power level for server ACLs: ${userLevel} < ${aclLevel}`,
+ errorKind: ERROR_KIND_PERMISSION,
+ });
}
// Otherwise OK
} catch (e) {
LogService.error("Mjolnir", e);
- errors.push({roomId, errorMessage: e.message || (e.body ? e.body.error : '')});
+ errors.push({
+ roomId,
+ errorMessage: e.message || (e.body ? e.body.error : ''),
+ errorKind: ERROR_KIND_FATAL,
+ });
}
return errors;
@@ -294,6 +315,7 @@ export class Mjolnir {
if (event['sender'] === await this.client.getUserId()) return; // Ignore ourselves
if (event['type'] === 'm.room.power_levels' && event['state_key'] === '') {
// power levels were updated - recheck permissions
+ ErrorCache.resetError(roomId, ERROR_KIND_PERMISSION);
const url = this.protectedRooms[roomId];
let html = `Power levels changed in ${roomId} - checking permissions...`;
let text = `Power levels changed in ${url} - checking permissions...`;
@@ -329,9 +351,17 @@ export class Mjolnir {
}
}
- private async printActionResult(errors: RoomUpdateError[], title: string = null) {
+ private async printActionResult(errors: RoomUpdateError[], title: string = null, logAnyways = false) {
if (errors.length <= 0) return false;
+ if (!logAnyways) {
+ errors = errors.filter(e => ErrorCache.triggerError(e.roomId, e.errorKind));
+ if (errors.length <= 0) {
+ LogService.warn("Mjolnir", "Multiple errors are happening, however they are muted. Please check the management room.");
+ return true;
+ }
+ }
+
let html = "";
let text = "";
diff --git a/src/actions/ApplyAcl.ts b/src/actions/ApplyAcl.ts
index 232daf8..1abceef 100644
--- a/src/actions/ApplyAcl.ts
+++ b/src/actions/ApplyAcl.ts
@@ -21,6 +21,7 @@ import { Mjolnir } from "../Mjolnir";
import config from "../config";
import { LogLevel } from "matrix-bot-sdk";
import { logMessage } from "../LogProxy";
+import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
/**
* Applies the server ACLs represented by the ban lists to the provided rooms, returning the
@@ -69,7 +70,9 @@ export async function applyServerAcls(lists: BanList[], roomIds: string[], mjoln
await logMessage(LogLevel.WARN, "ApplyAcl", `Tried to apply ACL in ${roomId} but Mjolnir is running in no-op mode`);
}
} catch (e) {
- errors.push({roomId, errorMessage: e.message || (e.body ? e.body.error : '')});
+ const message = e.message || (e.body ? e.body.error : '');
+ const kind = message.includes("You don't have permission to post that to the room") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL;
+ errors.push({roomId, errorMessage: message, errorKind: kind});
}
}
diff --git a/src/actions/ApplyBan.ts b/src/actions/ApplyBan.ts
index 3e17722..36fbce4 100644
--- a/src/actions/ApplyBan.ts
+++ b/src/actions/ApplyBan.ts
@@ -20,6 +20,7 @@ import { Mjolnir } from "../Mjolnir";
import config from "../config";
import { logMessage } from "../LogProxy";
import { LogLevel } from "matrix-bot-sdk";
+import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";
/**
* Applies the member bans represented by the ban lists to the provided rooms, returning the
@@ -80,7 +81,12 @@ export async function applyUserBans(lists: BanList[], roomIds: string[], mjolnir
}
}
} catch (e) {
- errors.push({roomId, errorMessage: e.message || (e.body ? e.body.error : '')});
+ const message = e.message || (e.body ? e.body.error : '');
+ errors.push({
+ roomId,
+ errorMessage: message,
+ errorKind: message.includes("You don't have permission to ban") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL,
+ });
}
}
diff --git a/src/commands/PermissionCheckCommand.ts b/src/commands/PermissionCheckCommand.ts
index bc9e032..d3459f1 100644
--- a/src/commands/PermissionCheckCommand.ts
+++ b/src/commands/PermissionCheckCommand.ts
@@ -18,5 +18,5 @@ import { Mjolnir } from "../Mjolnir";
// !mjolnir verify
export async function execPermissionCheckCommand(roomId: string, event: any, mjolnir: Mjolnir) {
- return mjolnir.verifyPermissions();
+ return mjolnir.verifyPermissions(true, true);
}
diff --git a/src/models/RoomUpdateError.ts b/src/models/RoomUpdateError.ts
index bf40ccf..c5f1048 100644
--- a/src/models/RoomUpdateError.ts
+++ b/src/models/RoomUpdateError.ts
@@ -17,4 +17,5 @@ limitations under the License.
export interface RoomUpdateError {
roomId: string;
errorMessage: string;
+ errorKind: string;
}