Skip to content

Commit

Permalink
Stop the config being global (in almost all contexts). (#334)
Browse files Browse the repository at this point in the history
* Stop the config being global (in almost all contexts).

* make sure unit test has a config

* Make failing word list more visible

* Only use Healthz from index.ts

Not really sure how useful it is anyways?
  • Loading branch information
Gnuxie authored Aug 9, 2022
1 parent 121d4cf commit 21aabc8
Show file tree
Hide file tree
Showing 26 changed files with 179 additions and 188 deletions.
38 changes: 18 additions & 20 deletions src/Mjolnir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,12 @@ import { applyServerAcls } from "./actions/ApplyAcl";
import { RoomUpdateError } from "./models/RoomUpdateError";
import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
import { applyUserBans } from "./actions/ApplyBan";
import config from "./config";
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
import { Protection } from "./protections/IProtection";
import { PROTECTIONS } from "./protections/protections";
import { ConsequenceType, Consequence } from "./protections/consequence";
import { ProtectionSettingValidationError } from "./protections/ProtectionSettings";
import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue";
import { Healthz } from "./health/healthz";
import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue";
import { htmlEscape } from "./utils";
import { ReportManager } from "./report/ReportManager";
Expand All @@ -50,6 +48,7 @@ import RuleServer from "./models/RuleServer";
import { RoomMemberManager } from "./RoomMembers";
import { ProtectedRoomActivityTracker } from "./queues/ProtectedRoomActivityTracker";
import { ThrottlingQueue } from "./queues/ThrottlingQueue";
import { IConfig } from "./config";
import PolicyList, { ListRuleChange } from "./models/PolicyList";

const levelToFn = {
Expand Down Expand Up @@ -162,7 +161,7 @@ export class Mjolnir {
* @param {MatrixClient} client The client for Mjolnir to use.
* @returns A new Mjolnir instance that can be started without further setup.
*/
static async setupMjolnirFromConfig(client: MatrixClient): Promise<Mjolnir> {
static async setupMjolnirFromConfig(client: MatrixClient, config: IConfig): Promise<Mjolnir> {
const policyLists: PolicyList[] = [];
const protectedRooms: { [roomId: string]: string } = {};
const joinedRooms = await client.getJoinedRooms();
Expand All @@ -188,7 +187,7 @@ export class Mjolnir {
}

const ruleServer = config.web.ruleServer ? new RuleServer() : null;
const mjolnir = new Mjolnir(client, managementRoomId, protectedRooms, policyLists, ruleServer);
const mjolnir = new Mjolnir(client, managementRoomId, config, protectedRooms, policyLists, ruleServer);
await mjolnir.logMessage(LogLevel.INFO, "index", "Mjolnir is starting up. Use !mjolnir to query status.");
Mjolnir.addJoinOnInviteListener(mjolnir, client, config);
return mjolnir;
Expand All @@ -197,6 +196,7 @@ export class Mjolnir {
constructor(
public readonly client: MatrixClient,
public readonly managementRoomId: string,
public readonly config: IConfig,
/*
* All the rooms that Mjolnir is protecting and their permalinks.
* If `config.protectAllJoinedRooms` is specified, then `protectedRooms` will be all joined rooms except watched banlists that we can't protect (because they aren't curated by us).
Expand All @@ -208,7 +208,7 @@ export class Mjolnir {
) {
this.explicitlyProtectedRoomIds = Object.keys(this.protectedRooms);

for (const reason of config.automaticallyRedactForReasons) {
for (const reason of this.config.automaticallyRedactForReasons) {
this.automaticRedactionReasons.push(new MatrixGlob(reason.toLowerCase()));
}

Expand Down Expand Up @@ -276,7 +276,7 @@ export class Mjolnir {
console.log("Creating Web APIs");
const reportManager = new ReportManager(this);
reportManager.on("report.new", this.handleReport.bind(this));
this.webapis = new WebAPIs(reportManager, this.ruleServer);
this.webapis = new WebAPIs(reportManager, this.config, this.ruleServer);
if (config.pollReports) {
this.reportPoller = new ReportPoller(this, reportManager);
}
Expand Down Expand Up @@ -356,20 +356,19 @@ export class Mjolnir {
await this.buildWatchedPolicyLists();
this.applyUnprotectedRooms();

if (config.verifyPermissionsOnStartup) {
if (this.config.verifyPermissionsOnStartup) {
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Checking permissions...");
await this.verifyPermissions(config.verboseLogging);
await this.verifyPermissions(this.config.verboseLogging);
}

this.currentState = STATE_SYNCING;
if (config.syncOnStartup) {
if (this.config.syncOnStartup) {
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Syncing lists...");
await this.syncLists(config.verboseLogging);
await this.syncLists(this.config.verboseLogging);
await this.registerProtections();
}

this.currentState = STATE_RUNNING;
Healthz.isHealthy = true;
await this.logMessage(LogLevel.INFO, "Mjolnir@startup", "Startup complete. Now monitoring rooms.");
} catch (err) {
try {
Expand Down Expand Up @@ -399,14 +398,13 @@ export class Mjolnir {
if (!additionalRoomIds) additionalRoomIds = [];
if (!Array.isArray(additionalRoomIds)) additionalRoomIds = [additionalRoomIds];

if (config.RUNTIME.client && (config.verboseLogging || LogLevel.INFO.includes(level))) {
if (this.config.verboseLogging || LogLevel.INFO.includes(level)) {
let clientMessage = message;
if (level === LogLevel.WARN) clientMessage = `⚠ | ${message}`;
if (level === LogLevel.ERROR) clientMessage = `‼ | ${message}`;

const client = config.RUNTIME.client;
const managementRoomId = await client.resolveRoom(config.managementRoom);
const roomIds = [managementRoomId, ...additionalRoomIds];
const client = this.client;
const roomIds = [this.managementRoomId, ...additionalRoomIds];

let evContent: TextualMessageEventContent = {
body: message,
Expand All @@ -418,7 +416,7 @@ export class Mjolnir {
evContent = await replaceRoomIdsWithPills(this, clientMessage, new Set(roomIds), "m.notice");
}

await client.sendMessage(managementRoomId, evContent);
await client.sendMessage(this.managementRoomId, evContent);
}

levelToFn[level.toString()](module, message);
Expand All @@ -443,7 +441,7 @@ export class Mjolnir {
const rooms = (additionalProtectedRooms?.rooms ?? []);
rooms.push(roomId);
await this.client.setAccountData(PROTECTED_ROOMS_EVENT_TYPE, { rooms: rooms });
await this.syncLists(config.verboseLogging);
await this.syncLists(this.config.verboseLogging);
}

public async removeProtectedRoom(roomId: string) {
Expand All @@ -465,7 +463,7 @@ export class Mjolnir {
}

private async resyncJoinedRooms(withSync = true) {
if (!config.protectAllJoinedRooms) return;
if (!this.config.protectAllJoinedRooms) return;

const joinedRoomIds = (await this.client.getJoinedRooms()).filter(r => r !== this.managementRoomId);
const oldRoomIdsSet = new Set(this.protectedJoinedRoomIds);
Expand All @@ -491,7 +489,7 @@ export class Mjolnir {
this.applyUnprotectedRooms();

if (withSync) {
await this.syncLists(config.verboseLogging);
await this.syncLists(this.config.verboseLogging);
}
}

Expand Down Expand Up @@ -718,7 +716,7 @@ export class Mjolnir {
}

public async warnAboutUnprotectedPolicyListRoom(roomId: string) {
if (!config.protectAllJoinedRooms) return; // doesn't matter
if (!this.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", ""));
Expand Down
5 changes: 2 additions & 3 deletions src/actions/ApplyAcl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import PolicyList from "../models/PolicyList";
import { ServerAcl } from "../models/ServerAcl";
import { RoomUpdateError } from "../models/RoomUpdateError";
import { Mjolnir } from "../Mjolnir";
import config from "../config";
import { LogLevel, UserID } from "matrix-bot-sdk";
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";

Expand Down Expand Up @@ -57,7 +56,7 @@ async function _applyServerAcls(lists: PolicyList[], roomIds: string[], mjolnir:
mjolnir.logMessage(LogLevel.WARN, "ApplyAcl", `Mjölnir has detected and removed an ACL that would exclude itself. Please check the ACL lists.`);
}

if (config.verboseLogging) {
if (mjolnir.config.verboseLogging) {
// We specifically use sendNotice to avoid having to escape HTML
await mjolnir.client.sendNotice(mjolnir.managementRoomId, `Constructed server ACL:\n${JSON.stringify(finalAcl, null, 2)}`);
}
Expand All @@ -80,7 +79,7 @@ async function _applyServerAcls(lists: PolicyList[], roomIds: string[], mjolnir:
// We specifically use sendNotice to avoid having to escape HTML
await mjolnir.logMessage(LogLevel.DEBUG, "ApplyAcl", `Applying ACL in ${roomId}`, roomId);

if (!config.noop) {
if (!mjolnir.config.noop) {
await mjolnir.client.sendStateEvent(roomId, "m.room.server_acl", "", finalAcl);
} else {
await mjolnir.logMessage(LogLevel.WARN, "ApplyAcl", `Tried to apply ACL in ${roomId} but Mjolnir is running in no-op mode`, roomId);
Expand Down
5 changes: 2 additions & 3 deletions src/actions/ApplyBan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
import PolicyList from "../models/PolicyList";
import { RoomUpdateError } from "../models/RoomUpdateError";
import { Mjolnir } from "../Mjolnir";
import config from "../config";
import { LogLevel } from "matrix-bot-sdk";
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../ErrorCache";

Expand All @@ -38,7 +37,7 @@ export async function applyUserBans(lists: PolicyList[], roomIds: string[], mjol

let members: { userId: string, membership: string }[];

if (config.fasterMembershipChecks) {
if (mjolnir.config.fasterMembershipChecks) {
const memberIds = await mjolnir.client.getJoinedRoomMembers(roomId);
members = memberIds.map(u => {
return { userId: u, membership: "join" };
Expand All @@ -64,7 +63,7 @@ export async function applyUserBans(lists: PolicyList[], roomIds: string[], mjol
// We specifically use sendNotice to avoid having to escape HTML
await mjolnir.logMessage(LogLevel.INFO, "ApplyBan", `Banning ${member.userId} in ${roomId} for: ${userRule.reason}`, roomId);

if (!config.noop) {
if (!mjolnir.config.noop) {
await mjolnir.client.banUser(member.userId, roomId, userRule.reason);
if (mjolnir.automaticRedactGlobs.find(g => g.test(userRule.reason.toLowerCase()))) {
mjolnir.queueRedactUserMessagesIn(member.userId, roomId);
Expand Down
5 changes: 2 additions & 3 deletions src/commands/KickCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ limitations under the License.

import { Mjolnir } from "../Mjolnir";
import { LogLevel, MatrixGlob, RichReply } from "matrix-bot-sdk";
import config from "../config";

// !mjolnir kick <user|filter> [room] [reason]
export async function execKickCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
Expand All @@ -30,7 +29,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln
parts.pop();
}

if (config.commands.confirmWildcardBan && /[*?]/.test(glob) && !force) {
if (mjolnir.config.commands.confirmWildcardBan && /[*?]/.test(glob) && !force) {
let replyMessage = "Wildcard bans require an addition `--force` argument to confirm";
const reply = RichReply.createFor(roomId, event, replyMessage, replyMessage);
reply["msgtype"] = "m.notice";
Expand Down Expand Up @@ -60,7 +59,7 @@ export async function execKickCommand(roomId: string, event: any, mjolnir: Mjoln
if (kickRule.test(victim)) {
await mjolnir.logMessage(LogLevel.DEBUG, "KickCommand", `Removing ${victim} in ${protectedRoomId}`, protectedRoomId);

if (!config.noop) {
if (!mjolnir.config.noop) {
try {
await mjolnir.taskQueue.push(async () => {
return mjolnir.client.kickUser(victim, protectedRoomId, reason);
Expand Down
3 changes: 1 addition & 2 deletions src/commands/MakeRoomAdminCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import config from "../config";
import { Mjolnir } from "../Mjolnir";
import { RichReply } from "matrix-bot-sdk";

// !mjolnir make admin <room> [<user ID>]
export async function execMakeRoomAdminCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const isAdmin = await mjolnir.isSynapseAdmin();
if (!config.admin?.enableMakeRoomAdminCommand || !isAdmin) {
if (!mjolnir.config.admin?.enableMakeRoomAdminCommand || !isAdmin) {
const message = "Either the command is disabled or I am not running as homeserver administrator.";
const reply = RichReply.createFor(roomId, event, message, message);
reply['msgtype'] = "m.notice";
Expand Down
7 changes: 3 additions & 4 deletions src/commands/UnbanBanCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { Mjolnir } from "../Mjolnir";
import PolicyList from "../models/PolicyList";
import { extractRequestError, LogLevel, LogService, MatrixGlob, RichReply } from "matrix-bot-sdk";
import { Recommendation, RULE_ROOM, RULE_SERVER, RULE_USER, USER_RULE_TYPES } from "../models/ListRule";
import config from "../config";
import { DEFAULT_LIST_EVENT_TYPE } from "./SetDefaultBanListCommand";

interface Arguments {
Expand Down Expand Up @@ -95,7 +94,7 @@ export async function parseArguments(roomId: string, event: any, mjolnir: Mjolni
else if (!ruleType) replyMessage = "Please specify the type as either 'user', 'room', or 'server'";
else if (!entity) replyMessage = "No entity found";

if (config.commands.confirmWildcardBan && /[*?]/.test(entity) && !force) {
if (mjolnir.config.commands.confirmWildcardBan && /[*?]/.test(entity) && !force) {
replyMessage = "Wildcard bans require an additional `--force` argument to confirm";
}

Expand Down Expand Up @@ -150,7 +149,7 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol
if (rule.test(victim)) {
await mjolnir.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Unbanning ${victim} in ${protectedRoomId}`, protectedRoomId);

if (!config.noop) {
if (!mjolnir.config.noop) {
await mjolnir.client.unbanUser(victim, protectedRoomId);
} else {
await mjolnir.logMessage(LogLevel.WARN, "UnbanBanCommand", `Attempted to unban ${victim} in ${protectedRoomId} but Mjolnir is running in no-op mode`, protectedRoomId);
Expand All @@ -163,7 +162,7 @@ export async function execUnbanCommand(roomId: string, event: any, mjolnir: Mjol

if (unbannedSomeone) {
await mjolnir.logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Syncing lists to ensure no users were accidentally unbanned`);
await mjolnir.syncLists(config.verboseLogging);
await mjolnir.syncLists(mjolnir.config.verboseLogging);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { MatrixClient } from "matrix-bot-sdk";
// The object is magically generated by external lib `config`
// from the file specified by `NODE_ENV`, e.g. production.yaml
// or harness.yaml.
interface IConfig {
export interface IConfig {
homeserverUrl: string;
rawHomeserverUrl: string;
accessToken: string;
Expand Down
3 changes: 2 additions & 1 deletion src/health/healthz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import config from "../config";
import * as http from "http";
import { LogService } from "matrix-bot-sdk";
// allowed to use the global configuration since this is only intended to be used by `src/index.ts`.
import config from '../config';

export class Healthz {
private static healthCode: number;
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,14 @@ if (config.health.healthz.enabled) {
patchMatrixClient();
config.RUNTIME.client = client;

bot = await Mjolnir.setupMjolnirFromConfig(client);
bot = await Mjolnir.setupMjolnirFromConfig(client, config);
} catch (err) {
console.error(`Failed to setup mjolnir from the config ${config.dataPath}: ${err}`);
throw err;
}
try {
await bot.start();
Healthz.isHealthy = true;
} catch (err) {
console.error(`Mjolnir failed to start: ${err}`);
throw err;
Expand Down
5 changes: 2 additions & 3 deletions src/protections/BasicFlooding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { Protection } from "./IProtection";
import { NumberProtectionSetting } from "./ProtectionSettings";
import { Mjolnir } from "../Mjolnir";
import { LogLevel, LogService } from "matrix-bot-sdk";
import config from "../config";

// if this is exceeded, we'll ban the user for spam and redact their messages
export const DEFAULT_MAX_PER_MINUTE = 10;
Expand Down Expand Up @@ -64,7 +63,7 @@ export class BasicFlooding extends Protection {

if (messageCount >= this.settings.maxPerMinute.value) {
await mjolnir.logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`, roomId);
if (!config.noop) {
if (!mjolnir.config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "spam");
} else {
await mjolnir.logMessage(LogLevel.WARN, "BasicFlooding", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
Expand All @@ -75,7 +74,7 @@ export class BasicFlooding extends Protection {
this.recentlyBanned.push(event['sender']); // flag to reduce spam

// Redact all the things the user said too
if (!config.noop) {
if (!mjolnir.config.noop) {
for (const eventId of forUser.map(e => e.eventId)) {
await mjolnir.client.redactEvent(roomId, eventId, "spam");
}
Expand Down
5 changes: 2 additions & 3 deletions src/protections/FirstMessageIsImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
import { Protection } from "./IProtection";
import { Mjolnir } from "../Mjolnir";
import { LogLevel, LogService } from "matrix-bot-sdk";
import config from "../config";
import { isTrueJoinEvent } from "../utils";

export class FirstMessageIsImage extends Protection {
Expand Down Expand Up @@ -58,7 +57,7 @@ export class FirstMessageIsImage extends Protection {
const isMedia = msgtype === 'm.image' || msgtype === 'm.video' || formattedBody.toLowerCase().includes('<img');
if (isMedia && this.justJoined[roomId].includes(event['sender'])) {
await mjolnir.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Banning ${event['sender']} for posting an image as the first thing after joining in ${roomId}.`);
if (!config.noop) {
if (!mjolnir.config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "spam");
} else {
await mjolnir.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
Expand All @@ -69,7 +68,7 @@ export class FirstMessageIsImage extends Protection {
this.recentlyBanned.push(event['sender']); // flag to reduce spam

// Redact the event
if (!config.noop) {
if (!mjolnir.config.noop) {
await mjolnir.client.redactEvent(roomId, event['event_id'], "spam");
} else {
await mjolnir.logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
Expand Down
Loading

0 comments on commit 21aabc8

Please sign in to comment.