Skip to content

Commit

Permalink
Avoid spamming the management room with errors
Browse files Browse the repository at this point in the history
The intervals are arbitrarily defined.

Fixes #10
  • Loading branch information
turt2live committed Nov 7, 2019
1 parent 30e186c commit 82214c6
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 11 deletions.
59 changes: 59 additions & 0 deletions src/ErrorCache.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
46 changes: 38 additions & 8 deletions src/Mjolnir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 = `<font color="#00cc00">All permissions look OK.</font>`;
const text = "All permissions look OK.";
Expand Down Expand Up @@ -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 : '<no message>')});
errors.push({
roomId,
errorMessage: e.message || (e.body ? e.body.error : '<no message>'),
errorKind: ERROR_KIND_FATAL,
});
}

return errors;
Expand Down Expand Up @@ -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 <a href="${url}">${roomId}</a> - checking permissions...`;
let text = `Power levels changed in ${url} - checking permissions...`;
Expand Down Expand Up @@ -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 = "";

Expand Down
5 changes: 4 additions & 1 deletion src/actions/ApplyAcl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 : '<no message>')});
const message = e.message || (e.body ? e.body.error : '<no message>');
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});
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/actions/ApplyBan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 : '<no message>')});
const message = e.message || (e.body ? e.body.error : '<no message>');
errors.push({
roomId,
errorMessage: message,
errorKind: message.includes("You don't have permission to ban") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL,
});
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/commands/PermissionCheckCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions src/models/RoomUpdateError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ limitations under the License.
export interface RoomUpdateError {
roomId: string;
errorMessage: string;
errorKind: string;
}

0 comments on commit 82214c6

Please sign in to comment.