Optionally perform permission checks on startup

This commit is contained in:
Travis Ralston 2019-10-04 21:22:34 -06:00
parent 383985c732
commit 7bd23a17d9
5 changed files with 89 additions and 7 deletions

View File

@ -17,7 +17,7 @@ Phase 2:
* [ ] Redact messages on ban (optionally) * [ ] Redact messages on ban (optionally)
* [x] More useful spam in management room * [x] More useful spam in management room
* [ ] Command to import ACLs, etc from rooms * [ ] Command to import ACLs, etc from rooms
* [ ] Vet rooms on startup option * [x] Vet rooms on startup option
* [ ] Command to actually unban users (instead of leaving them stuck) * [ ] Command to actually unban users (instead of leaving them stuck)
Phase 3: Phase 3:

View File

@ -37,6 +37,11 @@ verboseLogging: true
# is the same as running !mjolnir sync immediately after startup. # is the same as running !mjolnir sync immediately after startup.
syncOnStartup: true syncOnStartup: true
# Set to false to prevent Mjolnir from checking its permissions on startup. This
# is recommended to be left as "true" to catch room permission problems (state
# resets, etc) before Mjolnir is needed.
verifyPermissionsOnStartup: true
# The room ID or alias where the bot's own personal ban list is kept. This is # The room ID or alias where the bot's own personal ban list is kept. This is
# where the commands to manage a ban list end up being routed to. Note that # where the commands to manage a ban list end up being routed to. Note that
# this room is NOT automatically added to the banLists list below - you will # this room is NOT automatically added to the banLists list below - you will

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 { MatrixClient } from "matrix-bot-sdk"; import { LogService, MatrixClient } 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";
@ -75,14 +75,91 @@ export class Mjolnir {
} }
public start() { public start() {
return this.client.start().then(() => { return this.client.start().then(async () => {
if (config.syncOnStartup) { this.currentState = STATE_CHECKING_PERMISSIONS;
this.client.sendNotice(this.managementRoomId, "Syncing lists..."); if (config.verifyPermissionsOnStartup) {
return this.syncLists(); await this.client.sendNotice(this.managementRoomId, "Checking permissions...");
await this.verifyPermissions();
} }
}).then(async () => {
this.currentState = STATE_SYNCING;
if (config.syncOnStartup) {
await this.client.sendNotice(this.managementRoomId, "Syncing lists...");
await this.syncLists();
}
}).then(async () => {
this.currentState = STATE_RUNNING;
await this.client.sendNotice(this.managementRoomId, "Startup complete.");
}); });
} }
public async verifyPermissions() {
const ownUserId = await this.client.getUserId();
const errors: RoomUpdateError[] = [];
for (const roomId of Object.keys(this.protectedRooms)) {
try {
const powerLevels = await this.client.getRoomStateEvent(roomId, "m.room.power_levels", "");
if (!powerLevels) {
// noinspection ExceptionCaughtLocallyJS
throw new Error("Missing power levels state event");
}
function plDefault(val: number|undefined|null, def: number): number {
if (!val && val !== 0) return def;
return val;
}
const users = powerLevels['users'] || {};
const events = powerLevels['events'] || {};
const usersDefault = plDefault(powerLevels['users_default'], 0);
const stateDefault = plDefault(powerLevels['state_default'], 50);
const ban = plDefault(powerLevels['ban'], 50);
const kick = plDefault(powerLevels['kick'], 50);
const redact = plDefault(powerLevels['redact'], 50);
const userLevel = plDefault(users[ownUserId], usersDefault);
const aclLevel = plDefault(events["m.room.server_acl"], stateDefault);
// Wants: ban, kick, redact, m.room.server_acl
if (userLevel < ban) {
// noinspection ExceptionCaughtLocallyJS
throw new Error(`Missing power level for bans: ${userLevel} < ${ban}`);
}
if (userLevel < kick) {
// noinspection ExceptionCaughtLocallyJS
throw new Error(`Missing power level for kicks: ${userLevel} < ${kick}`);
}
if (userLevel < redact) {
// noinspection ExceptionCaughtLocallyJS
throw new Error(`Missing power level for redactions: ${userLevel} < ${redact}`);
}
if (userLevel < aclLevel) {
// noinspection ExceptionCaughtLocallyJS
throw new Error(`Missing power level for server ACLs: ${userLevel} < ${aclLevel}`);
}
// Otherwise OK
} catch (e) {
LogService.error("Mjolnir", e);
errors.push({roomId, errorMessage: e.message || (e.body ? e.body.error : '<no message>')});
}
}
const hadErrors = await this.printActionResult(errors, "Permission errors in protected rooms:");
if (!hadErrors) {
const html = `<font color="#00cc00">All permissions look OK.</font>`;
const text = "All permissions look OK.";
await this.client.sendMessage(this.managementRoomId, {
msgtype: "m.notice",
body: text,
format: "org.matrix.custom.html",
formatted_body: html,
});
}
}
public async syncLists() { public async syncLists() {
for (const list of this.banLists) { for (const list of this.banLists) {
await list.updateList(); await list.updateList();

View File

@ -29,6 +29,7 @@ interface IConfig {
managementRoom: string; managementRoom: string;
verboseLogging: boolean; verboseLogging: boolean;
syncOnStartup: boolean; syncOnStartup: boolean;
verifyPermissionsOnStartup: boolean;
publishedBanListRoom: string; publishedBanListRoom: string;
protectedRooms: string[]; // matrix.to urls protectedRooms: string[]; // matrix.to urls
banLists: string[]; // matrix.to urls banLists: string[]; // matrix.to urls

View File

@ -90,7 +90,6 @@ LogService.setLogger(new RichConsoleLogger());
const bot = new Mjolnir(client, managementRoomId, banListRoomId, protectedRooms, banLists); const bot = new Mjolnir(client, managementRoomId, banListRoomId, protectedRooms, banLists);
await bot.start(); await bot.start();
// TODO: Check permissions for mjolnir in protected rooms
// TODO: Complain about permission changes in protected rooms (including after power levels change) // TODO: Complain about permission changes in protected rooms (including after power levels change)
LogService.info("index", "Bot started!") LogService.info("index", "Bot started!")