Skip to content

Commit

Permalink
Use admin api for redactions if possible (#538)
Browse files Browse the repository at this point in the history
* clairfy room variable

* use admin api for redactions if possible

* tests

* refactor to handle queued redactions

* pull out functions

* lint

* use const
  • Loading branch information
H-Shay authored Oct 10, 2024
1 parent 943f878 commit aa7e8d4
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 25 deletions.
3 changes: 3 additions & 0 deletions src/Mjolnir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,11 +351,14 @@ export class Mjolnir {
}

this.currentState = STATE_RUNNING;

await this.managementRoomOutput.logMessage(
LogLevel.INFO,
"Mjolnir@startup",
"Startup complete. Now monitoring rooms.",
);
// update protected rooms set
this.protectedRoomsTracker.isAdmin = await this.isSynapseAdmin();
} catch (err) {
try {
LogService.error("Mjolnir", "Error during startup:");
Expand Down
7 changes: 6 additions & 1 deletion src/ProtectedRoomsSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ export class ProtectedRoomsSet {
/** The last revision we used to sync protected rooms. */ Revision
>();

/**
* whether the mjolnir instance is server admin
*/
public isAdmin = false;

constructor(
private readonly client: MatrixSendClient,
private readonly clientUserId: string,
Expand All @@ -126,7 +131,7 @@ export class ProtectedRoomsSet {
* @param roomId The room we want to redact them in.
*/
public redactUser(userId: string, roomId: string) {
this.eventRedactionQueue.add(new RedactUserInRoom(userId, roomId));
this.eventRedactionQueue.add(new RedactUserInRoom(userId, roomId, this.isAdmin));
}

/**
Expand Down
10 changes: 6 additions & 4 deletions src/commands/RedactCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import { Permalinks } from "@vector-im/matrix-bot-sdk";
// !mjolnir redact <user ID> [room alias] [limit]
export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const userId = parts[2];
let roomAlias: string | null = null;

let targetRoom: string | null = null;
let limit = Number.parseInt(parts.length > 3 ? parts[3] : "", 10); // default to NaN for later
if (parts.length > 3 && isNaN(limit)) {
roomAlias = await mjolnir.client.resolveRoom(parts[3]);
targetRoom = await mjolnir.client.resolveRoom(parts[3]);
if (parts.length > 4) {
limit = Number.parseInt(parts[4], 10);
}
Expand All @@ -49,8 +50,9 @@ export async function execRedactCommand(roomId: string, event: any, mjolnir: Mjo
return;
}

const targetRoomIds = roomAlias ? [roomAlias] : mjolnir.protectedRoomsTracker.getProtectedRooms();
await redactUserMessagesIn(mjolnir.client, mjolnir.managementRoomOutput, userId, targetRoomIds, limit);
const targetRoomIds = targetRoom ? [targetRoom] : mjolnir.protectedRoomsTracker.getProtectedRooms();
const isAdmin = await mjolnir.isSynapseAdmin();
await redactUserMessagesIn(mjolnir.client, mjolnir.managementRoomOutput, userId, targetRoomIds, isAdmin, limit);

await mjolnir.client.unstableApis.addReactionToEvent(roomId, event["event_id"], "✅");
await mjolnir.client.redactEvent(roomId, processingReactionId, "done processing");
Expand Down
6 changes: 4 additions & 2 deletions src/queues/EventRedactionQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export interface QueuedRedaction {
export class RedactUserInRoom implements QueuedRedaction {
userId: string;
roomId: string;
isAdmin: boolean;

constructor(userId: string, roomId: string) {
constructor(userId: string, roomId: string, isAdmin: boolean) {
this.userId = userId;
this.roomId = roomId;
this.isAdmin = isAdmin;
}

public async redact(client: MatrixClient, managementRoom: ManagementRoomOutput) {
Expand All @@ -54,7 +56,7 @@ export class RedactUserInRoom implements QueuedRedaction {
"Mjolnir",
`Redacting events from ${this.userId} in room ${this.roomId}.`,
);
await redactUserMessagesIn(client, managementRoom, this.userId, [this.roomId]);
await redactUserMessagesIn(client, managementRoom, this.userId, [this.roomId], this.isAdmin);
}

public redactionEqual(redaction: QueuedRedaction): boolean {
Expand Down
93 changes: 76 additions & 17 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { LogLevel, LogService, MatrixGlob, getRequestFn, setRequestFn } from "@vector-im/matrix-bot-sdk";
import {
LogLevel,
LogService,
MatrixGlob,
getRequestFn,
setRequestFn,
extractRequestError,
} from "@vector-im/matrix-bot-sdk";
import { ClientRequest, IncomingMessage } from "http";
import { default as parseDuration } from "parse-duration";
import * as Sentry from "@sentry/node";
import * as _ from "@sentry/tracing"; // Performing the import activates tracing.
import { collectDefaultMetrics, Counter, Histogram, register } from "prom-client";

import ManagementRoomOutput from "./ManagementRoomOutput";
import { IHealthConfig } from "./config";
import { MatrixSendClient } from "./MatrixEmitter";
import ManagementRoomOutput from "./ManagementRoomOutput";

// Define a few aliases to simplify parsing durations.

Expand Down Expand Up @@ -67,27 +74,33 @@ export function isTrueJoinEvent(event: any): boolean {
return membership === "join" && prevMembership !== "join";
}

/**
* Redact a user's messages in a set of rooms.
* See `getMessagesByUserIn`.
*
* @param client Client to redact the messages with.
* @param managementRoom Management room to log messages back to.
* @param userIdOrGlob A mxid or a glob which is applied to the whole sender field of events in the room, which will be redacted if they match.
* See `MatrixGlob` in matrix-bot-sdk.
* @param targetRoomIds Rooms to redact the messages from.
* @param limit The number of messages to redact from most recent first. If the limit is reached then no further messages will be redacted.
* @param noop Whether to operate in noop mode.
*/
export async function redactUserMessagesIn(
async function adminRedactUserMessagesIn(
client: MatrixSendClient,
managementRoom: ManagementRoomOutput,
userId: string,
targetRooms: string[],
limit = 1000,
) {
const body = { limit: limit, rooms: targetRooms };
const redactEndpoint = `/_synapse/admin/v1/user/${userId}/redact`;
const response = await client.doRequest("GET", redactEndpoint, null, body);
const redactID = response["redact_id"];
await managementRoom.logMessage(
LogLevel.INFO,
"utils#redactUserMessagesIn",
`Successfully requested redaction, ID for task is ${redactID}`,
);
}

async function botRedactUserMessagesIn(
client: MatrixSendClient,
managementRoom: ManagementRoomOutput,
userIdOrGlob: string,
targetRoomIds: string[],
targetRooms: string[],
limit = 1000,
noop = false,
) {
for (const targetRoomId of targetRoomIds) {
for (const targetRoomId of targetRooms) {
await managementRoom.logMessage(
LogLevel.DEBUG,
"utils#redactUserMessagesIn",
Expand Down Expand Up @@ -127,6 +140,52 @@ export async function redactUserMessagesIn(
}
}

/**
* Redact a user's messages in a set of rooms.
* See `getMessagesByUserIn`.
*
* @param client Client to redact the messages with.
* @param managementRoom Management room to log messages back to.
* @param userIdOrGlob A mxid or a glob which is applied to the whole sender field of events in the room, which will be redacted if they match.
* See `MatrixGlob` in matrix-bot-sdk.
* @param targetRoomIds Rooms to redact the messages from.
* @param isAdmin whether the bot is server admin
* @param limit The number of messages to redact from most recent first. If the limit is reached then no further messages will be redacted.
* @param noop Whether to operate in noop mode.
*/

export async function redactUserMessagesIn(
client: MatrixSendClient,
managementRoom: ManagementRoomOutput,
userIdOrGlob: string,
targetRoomIds: string[],
isAdmin: boolean,
limit = 1000,
noop = false,
) {
const usingGlob = userIdOrGlob.includes("*");
// if admin use the Admin API, but admin endpoint does not support globs
if (isAdmin && !usingGlob) {
try {
await adminRedactUserMessagesIn(client, managementRoom, userIdOrGlob, targetRoomIds, limit);
} catch (e) {
LogService.error(
"utils#redactUserMessagesIn",
`Error using admin API to redact messages: ${extractRequestError(e)}`,
);
await managementRoom.logMessage(
LogLevel.ERROR,
"utils#redactUserMessagesIn",
`Error using admin API to redact messages for user ${userIdOrGlob}, please check logs for more info - falling
back to non-admin redaction process.`,
);
await botRedactUserMessagesIn(client, managementRoom, userIdOrGlob, targetRoomIds, limit, noop);
}
} else {
await botRedactUserMessagesIn(client, managementRoom, userIdOrGlob, targetRoomIds, limit, noop);
}
}

/**
* Gets all the events sent by a user (or users if using wildcards) in a given room ID, since
* the time they joined.
Expand Down
Loading

0 comments on commit aa7e8d4

Please sign in to comment.