mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-09-16 07:42:36 +00:00
Rework RoomUpdateErrors to be CommandErrors or CommandExceptions
https://github.com/Gnuxie/Draupnir/pull/93/
This commit is contained in:
+26
-27
@@ -25,14 +25,15 @@ limitations under the License.
|
||||
* are NOT distributed, contributed, committed, or licensed under the Apache License.
|
||||
*/
|
||||
|
||||
import { LogLevel, LogService, MatrixGlob, UserID } from "matrix-bot-sdk";
|
||||
import { LogLevel, MatrixGlob, UserID } from "matrix-bot-sdk";
|
||||
import { CommandExceptionKind } from "./commands/interface-manager/CommandException";
|
||||
import { IConfig } from "./config";
|
||||
import ManagementRoomOutput from "./ManagementRoomOutput";
|
||||
import { MatrixSendClient } from "./MatrixEmitter";
|
||||
import AccessControlUnit, { Access } from "./models/AccessControlUnit";
|
||||
import { RULE_ROOM, RULE_SERVER, RULE_USER } from "./models/ListRule";
|
||||
import PolicyList, { ListRuleChange, Revision } from "./models/PolicyList";
|
||||
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION, printActionResult, RoomUpdateError } from "./models/RoomUpdateError";
|
||||
import { printActionResult, IRoomUpdateError, RoomUpdateException } from "./models/RoomUpdateError";
|
||||
import { ProtectionManager } from "./protections/ProtectionManager";
|
||||
import { EventRedactionQueue, RedactUserInRoom } from "./queues/EventRedactionQueue";
|
||||
import { ProtectedRoomActivityTracker } from "./queues/ProtectedRoomActivityTracker";
|
||||
@@ -181,7 +182,7 @@ export class ProtectedRoomsSet {
|
||||
* @param roomId Limit processing to one room only, otherwise process redactions for all rooms.
|
||||
* @returns The list of errors encountered, for reporting to the management room.
|
||||
*/
|
||||
public async processRedactionQueue(roomId?: string): Promise<RoomUpdateError[]> {
|
||||
public async processRedactionQueue(roomId?: string): Promise<IRoomUpdateError[]> {
|
||||
return await this.eventRedactionQueue.process(this.client, this.managementRoomOutput, roomId);
|
||||
}
|
||||
|
||||
@@ -290,7 +291,7 @@ export class ProtectedRoomsSet {
|
||||
* @param {string[]} roomIds The room IDs to apply the ACLs in.
|
||||
* @param {Mjolnir} mjolnir The Mjolnir client to apply the ACLs with.
|
||||
*/
|
||||
private async applyServerAcls(lists: PolicyList[], roomIds: string[]): Promise<RoomUpdateError[]> {
|
||||
private async applyServerAcls(lists: PolicyList[], roomIds: string[]): Promise<IRoomUpdateError[]> {
|
||||
// we need to provide mutual exclusion so that we do not have requests updating the m.room.server_acl event
|
||||
// finish out of order and therefore leave the room out of sync with the policy lists.
|
||||
if (this.config.disableServerACL) {
|
||||
@@ -303,7 +304,7 @@ export class ProtectedRoomsSet {
|
||||
});
|
||||
}
|
||||
|
||||
private async _applyServerAcls(lists: PolicyList[], roomIds: string[]): Promise<RoomUpdateError[]> {
|
||||
private async _applyServerAcls(lists: PolicyList[], roomIds: string[]): Promise<IRoomUpdateError[]> {
|
||||
const serverName: string = new UserID(await this.client.getUserId()).domain;
|
||||
|
||||
// Construct a server ACL first
|
||||
@@ -315,7 +316,7 @@ export class ProtectedRoomsSet {
|
||||
await this.client.sendNotice(this.managementRoomId, `Constructed server ACL:\n${JSON.stringify(finalAcl, null, 2)}`);
|
||||
}
|
||||
|
||||
const errors: RoomUpdateError[] = [];
|
||||
const errors: IRoomUpdateError[] = [];
|
||||
for (const roomId of roomIds) {
|
||||
try {
|
||||
await this.managementRoomOutput.logMessage(LogLevel.DEBUG, "ApplyAcl", `Checking ACLs for ${roomId}`, roomId);
|
||||
@@ -340,8 +341,8 @@ export class ProtectedRoomsSet {
|
||||
}
|
||||
} catch (e) {
|
||||
const message = e.message || (e.body ? e.body.error : '<no message>');
|
||||
const kind = message && message.includes("You don't have permission to post that to the room") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL;
|
||||
errors.push({ roomId, errorMessage: message, errorKind: kind });
|
||||
const kind = message && message.includes("You don't have permission to post that to the room") ? CommandExceptionKind.Known : CommandExceptionKind.Unknown;
|
||||
errors.push(new RoomUpdateException(roomId, kind, e, message))
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
@@ -353,17 +354,18 @@ export class ProtectedRoomsSet {
|
||||
* @param {string[]} roomIds The room IDs to apply the bans in.
|
||||
* @param {Mjolnir} mjolnir The Mjolnir client to apply the bans with.
|
||||
*/
|
||||
private async applyUserBans(roomIds: string[]): Promise<RoomUpdateError[]> {
|
||||
private async applyUserBans(roomIds: string[]): Promise<IRoomUpdateError[]> {
|
||||
// We can only ban people who are not already banned, and who match the rules.
|
||||
const errors: RoomUpdateError[] = [];
|
||||
const errors: IRoomUpdateError[] = [];
|
||||
|
||||
const addErrorToReport = (roomId: string, e: any) => {
|
||||
const message = e.message || (e.body ? e.body.error : '<no message>');
|
||||
errors.push({
|
||||
errors.push(new RoomUpdateException(
|
||||
roomId,
|
||||
errorMessage: message,
|
||||
errorKind: message && message.includes("You don't have permission to ban") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL,
|
||||
});
|
||||
message && message.includes("You don't have permission to ban") ? CommandExceptionKind.Known : CommandExceptionKind.Unknown,
|
||||
e,
|
||||
message
|
||||
));
|
||||
};
|
||||
|
||||
for (const roomId of roomIds) {
|
||||
@@ -461,28 +463,25 @@ export class ProtectedRoomsSet {
|
||||
}
|
||||
|
||||
private async printActionResult(
|
||||
errors: RoomUpdateError[],
|
||||
errors: IRoomUpdateError[],
|
||||
renderOptions: { title?: string, noErrorsText?: string }
|
||||
): Promise<void> {
|
||||
printActionResult(this.client, this.managementRoomId, errors, renderOptions);
|
||||
}
|
||||
|
||||
public async unbanUser(user: string): Promise<RoomUpdateError[]> {
|
||||
const errors: RoomUpdateError[] = [];
|
||||
public async unbanUser(user: string): Promise<IRoomUpdateError[]> {
|
||||
const errors: IRoomUpdateError[] = [];
|
||||
for (const room of this.protectedRoomActivityTracker.protectedRoomsByActivity()) {
|
||||
try {
|
||||
await this.client.unbanUser(user, room);
|
||||
} catch (e) {
|
||||
// FIXME: We need to know if `info` is acceptable or not.
|
||||
// Technically these kinds of errors are expected to occur,
|
||||
// so not sure if it warrants reporting as ERROR.
|
||||
LogService.info('ProtectedRoomSet', `Unable to unban a user ${user} from ${room}`, e);
|
||||
const message = e.message || (e.body ? e.body.error : '<no message>');
|
||||
errors.push({
|
||||
roomId: room,
|
||||
errorMessage: message,
|
||||
errorKind: message && message.includes("You don't have permission to ban") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL,
|
||||
});
|
||||
errors.push(new RoomUpdateException(
|
||||
room,
|
||||
message && message.includes("You don't have permission to ban") ? CommandExceptionKind.Known : CommandExceptionKind.Unknown,
|
||||
e,
|
||||
message
|
||||
));
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
@@ -493,7 +492,7 @@ export class ProtectedRoomsSet {
|
||||
}
|
||||
|
||||
public async verifyPermissions() {
|
||||
const errors: RoomUpdateError[] = [];
|
||||
const errors: IRoomUpdateError[] = [];
|
||||
for (const roomId of this.protectedRooms) {
|
||||
errors.push(...(await this.protectionManager.verifyPermissionsIn(roomId)));
|
||||
}
|
||||
|
||||
@@ -26,24 +26,48 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
import { UserID } from "matrix-bot-sdk";
|
||||
import { CommandException, CommandExceptionKind } from "../commands/interface-manager/CommandException";
|
||||
import { DocumentNode } from "../commands/interface-manager/DeadDocument";
|
||||
import { renderMatrixAndSend } from "../commands/interface-manager/DeadDocumentMatrix";
|
||||
import { JSXFactory } from "../commands/interface-manager/JSXFactory";
|
||||
import { Permalinks } from "../commands/interface-manager/Permalinks";
|
||||
import { CommandError, CommandResult } from "../commands/interface-manager/Validation";
|
||||
import { MatrixSendClient } from "../MatrixEmitter";
|
||||
|
||||
export const ERROR_KIND_PERMISSION = "permission";
|
||||
export const ERROR_KIND_FATAL = "fatal";
|
||||
|
||||
export interface RoomUpdateError {
|
||||
roomId: string;
|
||||
errorMessage: string;
|
||||
errorKind: string;
|
||||
export interface IRoomUpdateError extends CommandError {
|
||||
readonly roomId: string,
|
||||
}
|
||||
|
||||
function renderErrorItem(error: RoomUpdateError, viaServers: string[]): DocumentNode {
|
||||
export class PermissionError extends CommandError implements IRoomUpdateError {
|
||||
constructor(
|
||||
public readonly roomId: string,
|
||||
message: string
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export class RoomUpdateException extends CommandException implements IRoomUpdateError {
|
||||
roomId: string;
|
||||
|
||||
constructor(public readonly: string, ...args: ConstructorParameters<typeof CommandException>) {
|
||||
super(...args);
|
||||
}
|
||||
|
||||
public static Result<Ok>(
|
||||
message: string,
|
||||
options: {
|
||||
exception: Error,
|
||||
exceptionKind: CommandExceptionKind,
|
||||
roomId: string
|
||||
}): CommandResult<Ok, RoomUpdateException> {
|
||||
return CommandResult.Err(new RoomUpdateException(options.roomId, options.exceptionKind, options.exception, message));
|
||||
}
|
||||
}
|
||||
|
||||
function renderErrorItem(error: IRoomUpdateError, viaServers: string[]): DocumentNode {
|
||||
return <li>
|
||||
<a href={Permalinks.forRoom(error.roomId, viaServers)}>{error.roomId}</a> - {error.errorMessage}
|
||||
<a href={Permalinks.forRoom(error.roomId, viaServers)}>{error.roomId}</a> - {error.message}
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -58,7 +82,7 @@ function renderErrorItem(error: RoomUpdateError, viaServers: string[]): Document
|
||||
*/
|
||||
export async function renderActionResult(
|
||||
client: MatrixSendClient,
|
||||
errors: RoomUpdateError[],
|
||||
errors: IRoomUpdateError[],
|
||||
{ title = 'There were errors updating protected rooms.', noErrorsText = 'Done updating rooms - no errors.'}: { title?: string, noErrorsText?: string } = {}
|
||||
): Promise<DocumentNode> {
|
||||
if (errors.length === 0) {
|
||||
@@ -97,7 +121,7 @@ export async function renderActionResult(
|
||||
export async function printActionResult(
|
||||
client: MatrixSendClient,
|
||||
roomId: string,
|
||||
errors: RoomUpdateError[],
|
||||
errors: IRoomUpdateError[],
|
||||
renderOptions: { title?: string, noErrorsText?: string } = {}
|
||||
): Promise<void> {
|
||||
await renderMatrixAndSend(
|
||||
|
||||
@@ -40,7 +40,8 @@ import { MatrixRoomReference } from "../commands/interface-manager/MatrixRoomRef
|
||||
import { findPolicyListFromRoomReference } from "../commands/Ban";
|
||||
import PolicyList from "../models/PolicyList";
|
||||
import { renderListRules } from "../commands/Rules";
|
||||
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION, printActionResult, RoomUpdateError } from "../models/RoomUpdateError";
|
||||
import { printActionResult, IRoomUpdateError, RoomUpdateException } from "../models/RoomUpdateError";
|
||||
import { CommandExceptionKind } from "../commands/interface-manager/CommandException";
|
||||
|
||||
const BAN_PROPAGATION_PROMPT_LISTENER = 'ge.applied-langua.ge.draupnir.ban_propagation';
|
||||
const UNBAN_PROPAGATION_PROMPT_LISTENER = 'ge.applied-langua.ge.draupnir.unban_propagation';
|
||||
@@ -157,19 +158,20 @@ async function banReactionListener(this: ListenerContext, key: string, item: unk
|
||||
}
|
||||
}
|
||||
|
||||
async function unbanFromAllLists(mjolnir: Mjolnir, user: string): Promise<RoomUpdateError[]> {
|
||||
const errors: RoomUpdateError[] = [];
|
||||
async function unbanFromAllLists(mjolnir: Mjolnir, user: string): Promise<IRoomUpdateError[]> {
|
||||
const errors: IRoomUpdateError[] = [];
|
||||
for (const list of mjolnir.policyListManager.lists) {
|
||||
try {
|
||||
await list.unbanEntity(RULE_USER, user);
|
||||
} catch (e) {
|
||||
LogService.info('BanPropagation', `Could not unban ${user} from ${list.roomRef}`, e);
|
||||
const message = e.message || (e.body ? e.body.error : '<no message>');
|
||||
errors.push({
|
||||
roomId: list.roomId,
|
||||
errorMessage: message,
|
||||
errorKind: message.includes("You don't have permission") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL
|
||||
})
|
||||
errors.push(new RoomUpdateException(
|
||||
list.roomId,
|
||||
message.includes("You don't have permission") ? CommandExceptionKind.Known : CommandExceptionKind.Unknown,
|
||||
e,
|
||||
message
|
||||
));
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
|
||||
@@ -39,11 +39,11 @@ import { LogLevel, LogService } from "matrix-bot-sdk";
|
||||
import { ProtectionSettingValidationError } from "./ProtectionSettings";
|
||||
import { Consequence } from "./consequence";
|
||||
import { htmlEscape } from "../utils";
|
||||
import { ERROR_KIND_FATAL, ERROR_KIND_PERMISSION } from "../models/RoomUpdateError";
|
||||
import { RoomUpdateError } from "../models/RoomUpdateError";
|
||||
import { IRoomUpdateError, PermissionError, RoomUpdateException } from "../models/RoomUpdateError";
|
||||
import { BanPropagation } from "./BanPropagation";
|
||||
import { MatrixDataManager, RawSchemedData, SchemaMigration, SCHEMA_VERSION_KEY } from "../models/MatrixDataManager";
|
||||
import { Permalinks } from "../commands/interface-manager/Permalinks";
|
||||
import { CommandExceptionKind } from "../commands/interface-manager/CommandException";
|
||||
|
||||
const PROTECTIONS: Protection[] = [
|
||||
new FirstMessageIsImage(),
|
||||
@@ -384,8 +384,8 @@ export class ProtectionManager {
|
||||
return new Set(this.enabledProtections.map((p) => p.requiredStatePermissions).flat())
|
||||
}
|
||||
|
||||
public async verifyPermissionsIn(roomId: string): Promise<RoomUpdateError[]> {
|
||||
const errors: RoomUpdateError[] = [];
|
||||
public async verifyPermissionsIn(roomId: string): Promise<IRoomUpdateError[]> {
|
||||
const errors: IRoomUpdateError[] = [];
|
||||
const additionalPermissions = this.requiredProtectionPermissions();
|
||||
|
||||
try {
|
||||
@@ -413,35 +413,21 @@ export class ProtectionManager {
|
||||
const userLevel = plDefault(users[ownUserId], usersDefault);
|
||||
const aclLevel = plDefault(events["m.room.server_acl"], stateDefault);
|
||||
|
||||
// Wants: ban, kick, redact, m.room.server_acl
|
||||
const addErrorToReport = (message: string) => {
|
||||
errors.push(new PermissionError(roomId, message))
|
||||
}
|
||||
|
||||
if (userLevel < ban) {
|
||||
errors.push({
|
||||
roomId,
|
||||
errorMessage: `Missing power level for bans: ${userLevel} < ${ban}`,
|
||||
errorKind: ERROR_KIND_PERMISSION,
|
||||
});
|
||||
addErrorToReport(`Missing power level for bans: ${userLevel} < ${ban}`);
|
||||
}
|
||||
if (userLevel < kick) {
|
||||
errors.push({
|
||||
roomId,
|
||||
errorMessage: `Missing power level for kicks: ${userLevel} < ${kick}`,
|
||||
errorKind: ERROR_KIND_PERMISSION,
|
||||
});
|
||||
addErrorToReport(`Missing power level for kicks: ${userLevel} < ${kick}`);
|
||||
}
|
||||
if (userLevel < redact) {
|
||||
errors.push({
|
||||
roomId,
|
||||
errorMessage: `Missing power level for redactions: ${userLevel} < ${redact}`,
|
||||
errorKind: ERROR_KIND_PERMISSION,
|
||||
});
|
||||
addErrorToReport(`Missing power level for redactions: ${userLevel} < ${redact}`);
|
||||
}
|
||||
if (!this.mjolnir.config.disableServerACL && userLevel < aclLevel) {
|
||||
errors.push({
|
||||
roomId,
|
||||
errorMessage: `Missing power level for server ACLs: ${userLevel} < ${aclLevel}`,
|
||||
errorKind: ERROR_KIND_PERMISSION,
|
||||
});
|
||||
addErrorToReport(`Missing power level for server ACLs: ${userLevel} < ${aclLevel}`);
|
||||
}
|
||||
|
||||
// Wants: Additional permissions
|
||||
@@ -450,24 +436,15 @@ export class ProtectionManager {
|
||||
const permLevel = plDefault(events[additionalPermission], stateDefault);
|
||||
|
||||
if (userLevel < permLevel) {
|
||||
errors.push({
|
||||
roomId,
|
||||
errorMessage: `Missing power level for "${additionalPermission}" state events: ${userLevel} < ${permLevel}`,
|
||||
errorKind: ERROR_KIND_PERMISSION,
|
||||
});
|
||||
addErrorToReport(`Missing power level for "${additionalPermission}" state events: ${userLevel} < ${permLevel}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise OK
|
||||
} catch (e) {
|
||||
LogService.error("Mjolnir", e);
|
||||
errors.push({
|
||||
roomId,
|
||||
errorMessage: e.message || (e.body ? e.body.error : '<no message>'),
|
||||
errorKind: ERROR_KIND_FATAL,
|
||||
});
|
||||
const message = `Unexpected error when attempting to verify the permissions in ${roomId}`;
|
||||
errors.push(new RoomUpdateException(roomId, CommandExceptionKind.Unknown, e, message));
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@ limitations under the License.
|
||||
* are NOT distributed, contributed, committed, or licensed under the Apache License.
|
||||
*/
|
||||
import { LogLevel, MatrixClient } from "matrix-bot-sdk"
|
||||
import { ERROR_KIND_FATAL } from "../models/RoomUpdateError";
|
||||
import { RoomUpdateError } from "../models/RoomUpdateError";
|
||||
import { IRoomUpdateError, RoomUpdateException } from "../models/RoomUpdateError";
|
||||
import { redactUserMessagesIn } from "../utils";
|
||||
import ManagementRoomOutput from "../ManagementRoomOutput";
|
||||
import { MatrixSendClient } from "../MatrixEmitter";
|
||||
import { CommandExceptionKind } from "../commands/interface-manager/CommandException";
|
||||
|
||||
export interface QueuedRedaction {
|
||||
/** The room which the redaction will take place in. */
|
||||
@@ -119,25 +119,21 @@ export class EventRedactionQueue {
|
||||
* @param limitToRoomId If the roomId is provided, only redactions for that room will be processed.
|
||||
* @returns A description of any errors encountered by each QueuedRedaction that was processed.
|
||||
*/
|
||||
public async process(client: MatrixSendClient, managementRoom: ManagementRoomOutput, limitToRoomId?: string): Promise<RoomUpdateError[]> {
|
||||
const errors: RoomUpdateError[] = [];
|
||||
public async process(client: MatrixSendClient, managementRoom: ManagementRoomOutput, limitToRoomId?: string): Promise<IRoomUpdateError[]> {
|
||||
const errors: IRoomUpdateError[] = [];
|
||||
const redact = async (currentBatch: QueuedRedaction[]) => {
|
||||
for (const redaction of currentBatch) {
|
||||
try {
|
||||
await redaction.redact(client, managementRoom);
|
||||
} catch (e) {
|
||||
let roomError: RoomUpdateError;
|
||||
if (e.roomId && e.errorMessage && e.errorKind) {
|
||||
roomError = e;
|
||||
} else {
|
||||
const message = e.message || (e.body ? e.body.error : '<no message>');
|
||||
roomError = {
|
||||
roomId: redaction.roomId,
|
||||
errorMessage: message,
|
||||
errorKind: ERROR_KIND_FATAL,
|
||||
};
|
||||
}
|
||||
errors.push(roomError);
|
||||
const message = e.message || (e.body ? e.body.error : '<no message>');
|
||||
const error = new RoomUpdateException(
|
||||
redaction.roomId,
|
||||
CommandExceptionKind.Unknown,
|
||||
e,
|
||||
message
|
||||
);
|
||||
errors.push(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user