mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
Don't spam protection warnings, and ensure the user is redacted
We now always prioritize redaction over ban to ensure that the user gets removed from our rooms. This also means that the second image posted by a spammer is redacted after join. This commit also improves the messaging a bit.
This commit is contained in:
parent
2e3bc5287c
commit
f9e3c33935
@ -25,6 +25,7 @@ import { logMessage } from "./LogProxy";
|
||||
import ErrorCache, { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "./ErrorCache";
|
||||
import { IProtection } from "./protections/IProtection";
|
||||
import { PROTECTIONS } from "./protections/protections";
|
||||
import { AutomaticRedactionQueue } from "./queues/AutomaticRedactionQueue";
|
||||
|
||||
export const STATE_NOT_STARTED = "not_started";
|
||||
export const STATE_CHECKING_PERMISSIONS = "checking_permissions";
|
||||
@ -40,6 +41,7 @@ export class Mjolnir {
|
||||
private localpart: string;
|
||||
private currentState: string = STATE_NOT_STARTED;
|
||||
private protections: IProtection[] = [];
|
||||
private redactionQueue = new AutomaticRedactionQueue();
|
||||
|
||||
constructor(
|
||||
public readonly client: MatrixClient,
|
||||
@ -89,6 +91,10 @@ export class Mjolnir {
|
||||
return this.protections;
|
||||
}
|
||||
|
||||
public get redactionHandler(): AutomaticRedactionQueue {
|
||||
return this.redactionQueue;
|
||||
}
|
||||
|
||||
public start() {
|
||||
return this.client.start().then(async () => {
|
||||
this.currentState = STATE_CHECKING_PERMISSIONS;
|
||||
@ -395,12 +401,18 @@ export class Mjolnir {
|
||||
try {
|
||||
await protection.handleEvent(this, roomId, event);
|
||||
} catch (e) {
|
||||
const eventPermalink = Permalinks.forEvent(roomId, event['event_id']);
|
||||
LogService.error("Mjolnir", "Error handling protection: " + protection.name);
|
||||
LogService.error("Mjolnir", "Failed event: " + eventPermalink);
|
||||
LogService.error("Mjolnir", e);
|
||||
await this.client.sendNotice(config.managementRoom, "There was an error processing an event through a protection - see log for details.");
|
||||
await this.client.sendNotice(config.managementRoom, "There was an error processing an event through a protection - see log for details. Event: " + eventPermalink);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the event handlers - we always run this after protections so that the protections
|
||||
// can flag the event for redaction.
|
||||
await this.redactionQueue.handleEvent(roomId, event, this.client);
|
||||
|
||||
if (event['type'] === 'm.room.power_levels' && event['state_key'] === '') {
|
||||
// power levels were updated - recheck permissions
|
||||
ErrorCache.resetError(roomId, ERROR_KIND_PERMISSION);
|
||||
|
@ -24,7 +24,8 @@ const TIMESTAMP_THRESHOLD = 30000; // 30s out of phase
|
||||
|
||||
export class BasicFlooding implements IProtection {
|
||||
|
||||
public lastEvents: { [roomId: string]: { [userId: string]: { originServerTs: number, eventId: string }[] } } = {};
|
||||
private lastEvents: { [roomId: string]: { [userId: string]: { originServerTs: number, eventId: string }[] } } = {};
|
||||
private recentlyBanned: string[] = [];
|
||||
|
||||
constructor() {
|
||||
}
|
||||
@ -55,12 +56,21 @@ export class BasicFlooding implements IProtection {
|
||||
}
|
||||
|
||||
if (messageCount >= MAX_PER_MINUTE) {
|
||||
await logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`);
|
||||
await mjolnir.client.banUser(event['sender'], roomId, "spam");
|
||||
// Prioritize redaction over ban - we can always keep redacting what the user said.
|
||||
|
||||
if (this.recentlyBanned.includes(event['sender'])) return; // already handled (will be redacted)
|
||||
mjolnir.redactionHandler.addUser(event['sender']);
|
||||
this.recentlyBanned.push(event['sender']); // flag to reduce spam
|
||||
|
||||
// Redact all the things the user said too
|
||||
for (const eventId of forUser.map(e => e.eventId)) {
|
||||
await mjolnir.client.redactEvent(roomId, eventId, "spam");
|
||||
}
|
||||
|
||||
await logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`);
|
||||
await mjolnir.client.banUser(event['sender'], roomId, "spam");
|
||||
|
||||
// Free up some memory now that we're ready to handle it elsewhere
|
||||
forUser = forRoom[event['sender']] = []; // reset the user's list
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,8 @@ import { logMessage } from "../LogProxy";
|
||||
|
||||
export class FirstMessageIsImage implements IProtection {
|
||||
|
||||
public justJoined: { [roomId: string]: string[] } = {};
|
||||
private justJoined: { [roomId: string]: string[] } = {};
|
||||
private recentlyBanned: string[] = [];
|
||||
|
||||
constructor() {
|
||||
}
|
||||
@ -55,9 +56,17 @@ export class FirstMessageIsImage implements IProtection {
|
||||
const formattedBody = content['formatted_body'] || '';
|
||||
const isMedia = msgtype === 'm.image' || msgtype === 'm.video' || formattedBody.toLowerCase().includes('<img');
|
||||
if (isMedia && this.justJoined[roomId].includes(event['sender'])) {
|
||||
await logMessage(LogLevel.WARN, "FirstMessageIsImage", `Banning ${event['sender']} for posting an image as the first thing after joining.`);
|
||||
await mjolnir.client.banUser(event['sender'], roomId, "spam");
|
||||
// Prioritize redaction over ban because we can always keep redacting the user's messages
|
||||
|
||||
if (this.recentlyBanned.includes(event['sender'])) return; // already handled (will be redacted)
|
||||
mjolnir.redactionHandler.addUser(event['sender']);
|
||||
this.recentlyBanned.push(event['sender']); // flag to reduce spam
|
||||
|
||||
// Redact the event
|
||||
await mjolnir.client.redactEvent(roomId, event['event_id'], "spam");
|
||||
|
||||
await logMessage(LogLevel.WARN, "FirstMessageIsImage", `Banning ${event['sender']} for posting an image as the first thing after joining in ${roomId}.`);
|
||||
await mjolnir.client.banUser(event['sender'], roomId, "spam");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,9 @@ import { Mjolnir } from "../Mjolnir";
|
||||
|
||||
/**
|
||||
* Represents a protection mechanism of sorts. Protections are intended to be
|
||||
* event-based (ie: X messages in a period of time, or posting X events)
|
||||
* event-based (ie: X messages in a period of time, or posting X events).
|
||||
*
|
||||
* Protections are guaranteed to be run before redaction handlers.
|
||||
*/
|
||||
export interface IProtection {
|
||||
readonly name: string;
|
||||
|
46
src/queues/AutomaticRedactionQueue.ts
Normal file
46
src/queues/AutomaticRedactionQueue.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import { LogLevel, LogService, MatrixClient, Permalinks } from "matrix-bot-sdk";
|
||||
import { logMessage } from "../LogProxy";
|
||||
|
||||
export class AutomaticRedactionQueue {
|
||||
private usersToRedact: Set<string> = new Set<string>();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public addUser(userId: string) {
|
||||
this.usersToRedact.add(userId);
|
||||
}
|
||||
|
||||
public isUserQueued(userId: string): boolean {
|
||||
return this.usersToRedact.has(userId);
|
||||
}
|
||||
|
||||
public async handleEvent(roomId: string, event: any, mjolnirClient: MatrixClient) {
|
||||
if (this.isUserQueued(event['sender'])) {
|
||||
const permalink = Permalinks.forEvent(roomId, event['event_id']);
|
||||
try {
|
||||
LogService.info("AutomaticRedactionQueue", `Redacting event because the user is listed as bad: ${permalink}`)
|
||||
await mjolnirClient.redactEvent(roomId, event['event_id']);
|
||||
} catch (e) {
|
||||
logMessage(LogLevel.WARN, "AutomaticRedactionQueue", `Unable to redact message: ${permalink}`);
|
||||
LogService.warn("AutomaticRedactionQueue", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user