mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
support compound consequences, switch WordList to consequences (#351)
This commit is contained in:
parent
89b7ec1a18
commit
f108935d07
@ -35,7 +35,7 @@ import { applyUserBans } from "./actions/ApplyBan";
|
|||||||
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
|
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
|
||||||
import { Protection } from "./protections/IProtection";
|
import { Protection } from "./protections/IProtection";
|
||||||
import { PROTECTIONS } from "./protections/protections";
|
import { PROTECTIONS } from "./protections/protections";
|
||||||
import { ConsequenceType, Consequence } from "./protections/consequence";
|
import { Consequence } from "./protections/consequence";
|
||||||
import { ProtectionSettingValidationError } from "./protections/ProtectionSettings";
|
import { ProtectionSettingValidationError } from "./protections/ProtectionSettings";
|
||||||
import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue";
|
import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue";
|
||||||
import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue";
|
import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue";
|
||||||
@ -959,36 +959,37 @@ export class Mjolnir {
|
|||||||
await this.printBanlistChanges(changes, policyList, true);
|
await this.printBanlistChanges(changes, policyList, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleConsequence(protection: Protection, roomId: string, eventId: string, sender: string, consequence: Consequence) {
|
private async handleConsequences(protection: Protection, roomId: string, eventId: string, sender: string, consequences: Consequence[]) {
|
||||||
switch (consequence.type) {
|
for (const consequence of consequences) {
|
||||||
case ConsequenceType.alert:
|
try {
|
||||||
break;
|
if (consequence.name === "alert") {
|
||||||
case ConsequenceType.redact:
|
/* take no additional action, just print the below message to management room */
|
||||||
await this.client.redactEvent(roomId, eventId, "abuse detected");
|
} else if (consequence.name === "ban") {
|
||||||
break;
|
|
||||||
case ConsequenceType.ban:
|
|
||||||
await this.client.banUser(sender, roomId, "abuse detected");
|
await this.client.banUser(sender, roomId, "abuse detected");
|
||||||
break;
|
} else if (consequence.name === "redact") {
|
||||||
|
await this.client.redactEvent(roomId, eventId, "abuse detected");
|
||||||
|
} else {
|
||||||
|
throw new Error(`unknown consequence ${consequence.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = `protection ${protection.name} enacting ${ConsequenceType[consequence.type]}`
|
let message = `protection ${protection.name} enacting`
|
||||||
|
+ ` ${consequence.name}`
|
||||||
+ ` against ${htmlEscape(sender)}`
|
+ ` against ${htmlEscape(sender)}`
|
||||||
+ ` in ${htmlEscape(roomId)}`;
|
+ ` in ${htmlEscape(roomId)}`
|
||||||
if (consequence.reason !== undefined) {
|
+ ` (reason: ${htmlEscape(consequence.reason)})`;
|
||||||
// even though internally-sourced, there's no promise that `consequence.reason`
|
|
||||||
// will never have user-supplied information, so escape it
|
|
||||||
message += ` (reason: ${htmlEscape(consequence.reason)})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.client.sendMessage(this.managementRoomId, {
|
await this.client.sendMessage(this.managementRoomId, {
|
||||||
msgtype: "m.notice",
|
msgtype: "m.notice",
|
||||||
body: message,
|
body: message,
|
||||||
[CONSEQUENCE_EVENT_DATA]: {
|
[CONSEQUENCE_EVENT_DATA]: {
|
||||||
who: sender,
|
who: sender,
|
||||||
room: roomId,
|
room: roomId,
|
||||||
type: ConsequenceType[consequence.type]
|
types: [consequence.name],
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
await this.logMessage(LogLevel.ERROR, "handleConsequences", `Failed to enact ${consequence.name} consequence: ${e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleEvent(roomId: string, event: any) {
|
private async handleEvent(roomId: string, event: any) {
|
||||||
@ -1018,9 +1019,9 @@ export class Mjolnir {
|
|||||||
|
|
||||||
// Iterate all the enabled protections
|
// Iterate all the enabled protections
|
||||||
for (const protection of this.enabledProtections) {
|
for (const protection of this.enabledProtections) {
|
||||||
let consequence: Consequence | undefined = undefined;
|
let consequences: Consequence[] | undefined = undefined;
|
||||||
try {
|
try {
|
||||||
consequence = await protection.handleEvent(this, roomId, event);
|
consequences = await protection.handleEvent(this, roomId, event);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const eventPermalink = Permalinks.forEvent(roomId, event['event_id']);
|
const eventPermalink = Permalinks.forEvent(roomId, event['event_id']);
|
||||||
LogService.error("Mjolnir", "Error handling protection: " + protection.name);
|
LogService.error("Mjolnir", "Error handling protection: " + protection.name);
|
||||||
@ -1030,8 +1031,8 @@ export class Mjolnir {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consequence !== undefined) {
|
if (consequences !== undefined) {
|
||||||
await this.handleConsequence(protection, roomId, event["event_id"], event["sender"], consequence);
|
await this.handleConsequences(protection, roomId, event["event_id"], event["sender"], consequences);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export abstract class Protection {
|
|||||||
* Handle a single event from a protected room, to decide if we need to
|
* Handle a single event from a protected room, to decide if we need to
|
||||||
* respond to it
|
* respond to it
|
||||||
*/
|
*/
|
||||||
async handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise<Consequence | any> {
|
async handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise<Consequence[] | any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Protection } from "./IProtection";
|
import { Protection } from "./IProtection";
|
||||||
|
import { ConsequenceBan, ConsequenceRedact } from "./consequence";
|
||||||
import { Mjolnir } from "../Mjolnir";
|
import { Mjolnir } from "../Mjolnir";
|
||||||
import { LogLevel, LogService } from "matrix-bot-sdk";
|
import { LogLevel, LogService } from "matrix-bot-sdk";
|
||||||
import { isTrueJoinEvent } from "../utils";
|
import { isTrueJoinEvent } from "../utils";
|
||||||
@ -95,21 +96,14 @@ export class WordList extends Protection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the test
|
if (!message) {
|
||||||
if (message && this.badWords!.test(message)) {
|
return;
|
||||||
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Banning ${event['sender']} for word list violation in ${roomId}.`);
|
|
||||||
if (!mjolnir.config.noop) {
|
|
||||||
await mjolnir.client.banUser(event['sender'], roomId, "Word list violation");
|
|
||||||
} else {
|
|
||||||
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redact the event
|
const matches = message.match(this.badWords!);
|
||||||
if (!mjolnir.config.noop) {
|
if (matches) {
|
||||||
await mjolnir.client.redactEvent(roomId, event['event_id'], "spam");
|
const reason = `bad word: ${matches[0]}`;
|
||||||
} else {
|
return [new ConsequenceBan(reason), new ConsequenceRedact(reason)];
|
||||||
await mjolnir.logMessage(LogLevel.WARN, "WordList", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`, roomId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,44 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
* Distinct individual actions that can be caused as a result of detected abuse
|
|
||||||
*/
|
|
||||||
export enum ConsequenceType {
|
|
||||||
// effectively a no-op. just tell the management room
|
|
||||||
alert,
|
|
||||||
// redact the event that triggered this consequence
|
|
||||||
redact,
|
|
||||||
// ban the user that sent the event that triggered this consequence
|
|
||||||
ban
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Consequence {
|
export class Consequence {
|
||||||
/*
|
/*
|
||||||
* Action to take upon detection of abuse and an optional explanation of the detection
|
* A requested action to take against a user after detected abuse
|
||||||
*
|
*
|
||||||
* @param type Action to take
|
* @param name The name of the consequence being requested
|
||||||
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
||||||
* this will be HTML escaped before printing, just in case it has user-provided data
|
* this will be HTML escaped before printing, just in case it has user-provided data
|
||||||
*/
|
*/
|
||||||
constructor(public readonly type: ConsequenceType, public readonly reason?: string) {}
|
constructor(public name: string, public reason: string) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConsequenceAlert extends Consequence {
|
||||||
|
/*
|
||||||
|
* Request an alert to be created after detected abuse
|
||||||
|
*
|
||||||
|
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
||||||
|
* this will be HTML escaped before printing, just in case it has user-provided data
|
||||||
|
*/
|
||||||
|
constructor(reason: string) {
|
||||||
|
super("alert", reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ConsequenceRedact extends Consequence {
|
||||||
|
/*
|
||||||
|
* Request a message redaction after detected abuse
|
||||||
|
*
|
||||||
|
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
||||||
|
* this will be HTML escaped before printing, just in case it has user-provided data
|
||||||
|
*/
|
||||||
|
constructor(reason: string) {
|
||||||
|
super("redact", reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ConsequenceBan extends Consequence {
|
||||||
|
/*
|
||||||
|
* Request a ban after detected abuse
|
||||||
|
*
|
||||||
|
* @param reason Brief explanation of why we're taking an action, printed to management room.
|
||||||
|
* this will be HTML escaped before printing, just in case it has user-provided data
|
||||||
|
*/
|
||||||
|
constructor(reason: string) {
|
||||||
|
super("ban", reason);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { Mjolnir } from "../../src/Mjolnir";
|
|||||||
import { IProtection } from "../../src/protections/IProtection";
|
import { IProtection } from "../../src/protections/IProtection";
|
||||||
import { newTestUser, noticeListener } from "./clientHelper";
|
import { newTestUser, noticeListener } from "./clientHelper";
|
||||||
import { matrixClient, mjolnir } from "./mjolnirSetupUtils";
|
import { matrixClient, mjolnir } from "./mjolnirSetupUtils";
|
||||||
import { ConsequenceType, Consequence } from "../../src/protections/consequence";
|
import { ConsequenceBan, ConsequenceRedact } from "../../src/protections/consequence";
|
||||||
|
|
||||||
describe("Test: standard consequences", function() {
|
describe("Test: standard consequences", function() {
|
||||||
let badUser;
|
let badUser;
|
||||||
@ -33,7 +33,7 @@ describe("Test: standard consequences", function() {
|
|||||||
settings = { };
|
settings = { };
|
||||||
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
||||||
if (event.content.body === "ngmWkF") {
|
if (event.content.body === "ngmWkF") {
|
||||||
return new Consequence(ConsequenceType.redact, "asd");
|
return [new ConsequenceRedact("asd")];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -77,7 +77,7 @@ describe("Test: standard consequences", function() {
|
|||||||
settings = { };
|
settings = { };
|
||||||
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
||||||
if (event.content.body === "7Uga3d") {
|
if (event.content.body === "7Uga3d") {
|
||||||
return new Consequence(ConsequenceType.ban, "asd");
|
return [new ConsequenceBan("asd")];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -124,7 +124,7 @@ describe("Test: standard consequences", function() {
|
|||||||
settings = { };
|
settings = { };
|
||||||
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
handleEvent = async (mjolnir: Mjolnir, roomId: string, event: any) => {
|
||||||
if (event.content.body === "8HUnwb") {
|
if (event.content.body === "8HUnwb") {
|
||||||
return new Consequence(ConsequenceType.ban, "asd");
|
return [new ConsequenceBan("asd")];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user