-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Incorperate Mjolnir's new report-to-moderator features #8
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,7 +129,7 @@ export class Mjolnir { | |
if (options.autojoinOnlyIfManager) { | ||
const managers = await client.getJoinedRoomMembers(mjolnir.managementRoomId); | ||
if (!managers.includes(membershipEvent.sender)) return reportInvite(); // ignore invite | ||
} else { | ||
} else if (options.acceptInvitesFromSpace) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check why this was changed. doesn't look good |
||
const spaceId = await client.resolveRoom(options.acceptInvitesFromSpace); | ||
const spaceUserIds = await client.getJoinedRoomMembers(spaceId) | ||
.catch(async e => { | ||
|
@@ -154,7 +154,7 @@ export class Mjolnir { | |
*/ | ||
static async setupMjolnirFromConfig(client: MatrixSendClient, matrixEmitter: MatrixEmitter, config: IConfig): Promise<Mjolnir> { | ||
if (!config.autojoinOnlyIfManager && config.acceptInvitesFromSpace === getDefaultConfig().acceptInvitesFromSpace) { | ||
throw new TypeError("`autojoinOnlyIfManager` has been disabled, yet no space has been provided for `acceptInvitesFromSpace`."); | ||
throw new TypeError("`autojoinOnlyIfManager` has been disabled but you have not set `acceptInvitesFromSpace`. Please make it empty to accept invites from everywhere or give it a namespace alias or room id."); | ||
} | ||
const policyLists: PolicyList[] = []; | ||
const joinedRooms = await client.getJoinedRooms(); | ||
|
@@ -257,8 +257,13 @@ export class Mjolnir { | |
this.protectionManager = new ProtectionManager(this); | ||
|
||
this.managementRoomOutput = new ManagementRoomOutput(managementRoomId, client, config); | ||
const protections = new ProtectionManager(this); | ||
this.protectedRoomsTracker = new ProtectedRoomsSet(client, clientUserId, managementRoomId, this.managementRoomOutput, protections, config); | ||
this.protectedRoomsTracker = new ProtectedRoomsSet( | ||
client, | ||
clientUserId, | ||
managementRoomId, | ||
this.managementRoomOutput, | ||
this.protectionManager, | ||
config); | ||
} | ||
|
||
public get lists(): PolicyList[] { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -115,12 +115,6 @@ export class ProtectedRoomsSet { | |
private readonly clientUserId: string, | ||
private readonly managementRoomId: string, | ||
private readonly managementRoomOutput: ManagementRoomOutput, | ||
/** | ||
* The protection manager is only used to verify the permissions | ||
* that the protection manager requires are correct for this set of rooms. | ||
* The protection manager is not really compatible with this abstraction yet | ||
* because of a direct dependency on the protection manager in Mjolnir commands. | ||
*/ | ||
Comment on lines
-118
to
-123
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why was this deleted, is the comment still true? |
||
private readonly protectionManager: ProtectionManager, | ||
private readonly config: IConfig, | ||
) { | ||
|
@@ -275,11 +269,13 @@ export class ProtectedRoomsSet { | |
} | ||
this.protectedRooms.add(roomId); | ||
this.protectedRoomActivityTracker.addProtectedRoom(roomId); | ||
this.protectionManager.addProtectedRoom(roomId); | ||
} | ||
|
||
public removeProtectedRoom(roomId: string): void { | ||
this.protectedRoomActivityTracker.removeProtectedRoom(roomId); | ||
this.protectedRooms.delete(roomId); | ||
this.protectionManager.removeProtectedRoom(roomId); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { Mjolnir } from "../Mjolnir"; | ||
import { LogLevel } from "matrix-bot-sdk"; | ||
|
||
const EVENT_MODERATED_BY = "org.matrix.msc3215.room.moderation.moderated_by"; | ||
const EVENT_MODERATOR_OF = "org.matrix.msc3215.room.moderation.moderator_of"; | ||
|
||
// !mjolnir rooms setup <room alias/ID> reporting | ||
export async function execSetupProtectedRoom(commandRoomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. upgrade this to a new style command |
||
// For the moment, we only accept a subcommand `reporting`. | ||
if (parts[4] !== 'reporting') { | ||
await mjolnir.client.sendNotice(commandRoomId, "Invalid subcommand for `rooms setup <room alias/ID> subcommand`, expected one of \"reporting\""); | ||
await mjolnir.client.unstableApis.addReactionToEvent(commandRoomId, event['event_id'], '❌'); | ||
return; | ||
} | ||
const protectedRoomId = await mjolnir.client.joinRoom(parts[3]); | ||
|
||
try { | ||
const userId = await mjolnir.client.getUserId(); | ||
|
||
// A backup of the previous state in case we need to rollback. | ||
let previousState: /* previous content */ any | /* there was no previous content */ null; | ||
try { | ||
previousState = await mjolnir.client.getRoomStateEvent(protectedRoomId, EVENT_MODERATED_BY, EVENT_MODERATED_BY); | ||
} catch (ex) { | ||
previousState = null; | ||
} | ||
|
||
// Setup protected room -> moderation room link. | ||
// We do this before the other one to be able to fail early if we do not have a sufficient | ||
// powerlevel. | ||
let eventId = await mjolnir.client.sendStateEvent(protectedRoomId, EVENT_MODERATED_BY, EVENT_MODERATED_BY, { | ||
room_id: commandRoomId, | ||
user_id: userId, | ||
}); | ||
|
||
try { | ||
// Setup moderation room -> protected room. | ||
await mjolnir.client.sendStateEvent(commandRoomId, EVENT_MODERATOR_OF, protectedRoomId, { | ||
user_id: userId, | ||
}); | ||
} catch (ex) { | ||
// If the second `sendStateEvent` fails, we could end up with a room half setup, which | ||
// is bad. Attempt to rollback. | ||
try { | ||
if (previousState) { | ||
await mjolnir.client.sendStateEvent(protectedRoomId, EVENT_MODERATED_BY, EVENT_MODERATED_BY, previousState); | ||
} else { | ||
await mjolnir.client.redactEvent(protectedRoomId, eventId, "Rolling back incomplete MSC3215 setup"); | ||
} | ||
} finally { | ||
// Ignore second exception | ||
throw ex; | ||
} | ||
} | ||
|
||
} catch (ex) { | ||
mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "execSetupProtectedRoom", ex.message); | ||
await mjolnir.client.unstableApis.addReactionToEvent(commandRoomId, event['event_id'], '❌'); | ||
} | ||
await mjolnir.client.unstableApis.addReactionToEvent(commandRoomId, event['event_id'], '✅'); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
Copyright 2023 Element. | ||
|
||
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 } from "matrix-bot-sdk"; | ||
import { Mjolnir } from "../Mjolnir"; | ||
import { Protection } from "./IProtection"; | ||
|
||
/* | ||
An implementation of per decentralized abuse reports, as per | ||
https://github.com/Yoric/matrix-doc/blob/aristotle/proposals/3215-towards-decentralized-moderation.md | ||
*/ | ||
|
||
const EVENT_MODERATED_BY = "org.matrix.msc3215.room.moderation.moderated_by"; | ||
const EVENT_MODERATOR_OF = "org.matrix.msc3215.room.moderation.moderator_of"; | ||
|
||
/** | ||
* Setup decentralized abuse reports in protected rooms. | ||
*/ | ||
export class LocalAbuseReports extends Protection { | ||
settings: { }; | ||
public readonly name = "LocalAbuseReports"; | ||
public readonly description = "Enables MSC3215-compliant web clients to send abuse reports to the moderator instead of the homeserver admin"; | ||
readonly requiredStatePermissions = [EVENT_MODERATED_BY]; | ||
|
||
/** | ||
* A new room has been added to the list of rooms to protect with this protection. | ||
*/ | ||
async startProtectingRoom(mjolnir: Mjolnir, protectedRoomId: string) { | ||
try { | ||
const userId = await mjolnir.client.getUserId(); | ||
|
||
// Fetch the previous state of the room, to avoid overwriting any existing setup. | ||
let previousState: /* previous content */ any | /* there was no previous content */ null; | ||
try { | ||
previousState = await mjolnir.client.getRoomStateEvent(protectedRoomId, EVENT_MODERATED_BY, EVENT_MODERATED_BY); | ||
} catch (ex) { | ||
previousState = null; | ||
} | ||
if (previousState && previousState["room_id"] && previousState["user_id"]) { | ||
if (previousState["room_id"] === mjolnir.managementRoomId && previousState["user_id"] === userId) { | ||
// The room is already setup, do nothing. | ||
return; | ||
} else { | ||
// There is a setup already, but it's not for us. Don't overwrite it. | ||
let protectedRoomAliasOrId = await mjolnir.client.getPublishedAlias(protectedRoomId) || protectedRoomId; | ||
mjolnir.managementRoomOutput.logMessage(LogLevel.INFO, "LocalAbuseReports", `Room ${protectedRoomAliasOrId} is already setup for decentralized abuse reports with bot ${previousState["user_id"]} and room ${previousState["room_id"]}, not overwriting automatically. To overwrite, use command \`!mjolnir rooms setup ${protectedRoomId} reporting\``); | ||
return; | ||
} | ||
} | ||
|
||
// Setup protected room -> moderation room link. | ||
// We do this before the other one to be able to fail early if we do not have a sufficient | ||
// powerlevel. | ||
let eventId; | ||
try { | ||
eventId = await mjolnir.client.sendStateEvent(protectedRoomId, EVENT_MODERATED_BY, EVENT_MODERATED_BY, { | ||
room_id: mjolnir.managementRoomId, | ||
user_id: userId, | ||
}); | ||
} catch (ex) { | ||
mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "LocalAbuseReports", `Could not autoset protected room -> moderation room link: ${ex.message}. To set it manually, use command \`!mjolnir rooms setup ${protectedRoomId} reporting\``); | ||
return; | ||
} | ||
|
||
try { | ||
// Setup moderation room -> protected room. | ||
await mjolnir.client.sendStateEvent(mjolnir.managementRoomId, EVENT_MODERATOR_OF, protectedRoomId, { | ||
user_id: userId, | ||
}); | ||
} catch (ex) { | ||
// If the second `sendStateEvent` fails, we could end up with a room half setup, which | ||
// is bad. Attempt to rollback. | ||
mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "LocalAbuseReports", `Could not autoset moderation room -> protected room link: ${ex.message}. To set it manually, use command \`!mjolnir rooms setup ${protectedRoomId} reporting\``); | ||
try { | ||
await mjolnir.client.redactEvent(protectedRoomId, eventId, "Rolling back incomplete MSC3215 setup"); | ||
} finally { | ||
// Ignore second exception, propagate first. | ||
throw ex; | ||
} | ||
} | ||
} catch (ex) { | ||
mjolnir.managementRoomOutput.logMessage(LogLevel.ERROR, "LocalAbuseReports", ex.message); | ||
} | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. newline please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably shouldn't be part of the PR, it's about testing Mjolnir for all