mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
Add commands to suspend/unsuspend user (#506)
This commit is contained in:
parent
033d84cc70
commit
2f6120eca9
@ -35,6 +35,7 @@ modules:
|
||||
|
||||
|
||||
homeserver:
|
||||
docker: element-hq/synapse:latest
|
||||
# Basic configuration.
|
||||
server_name: localhost:9999
|
||||
public_baseurl: http://localhost:9999
|
||||
@ -76,6 +77,10 @@ homeserver:
|
||||
per_second: 10000
|
||||
burst_count: 10000
|
||||
|
||||
experimental_features:
|
||||
msc3823_account_suspension: true
|
||||
|
||||
|
||||
# Creating a few users simplifies testing.
|
||||
users:
|
||||
- localname: admin
|
||||
|
@ -479,6 +479,19 @@ export class Mjolnir {
|
||||
return await this.client.doRequest("POST", endpoint);
|
||||
}
|
||||
|
||||
public async suspendSynapseUser(userId: string): Promise<any> {
|
||||
const endpoint = `/_synapse/admin/v1/suspend/${userId}`;
|
||||
const body = {"suspend": true}
|
||||
return await this.client.doRequest("PUT", endpoint, null, body);
|
||||
}
|
||||
|
||||
public async unsuspendSynapseUser(userId: string): Promise<any> {
|
||||
const endpoint = `/_synapse/admin/v1/suspend/${userId}`;
|
||||
const body = {"suspend": false}
|
||||
return await this.client.doRequest("PUT", endpoint, null, body);
|
||||
}
|
||||
|
||||
|
||||
public async shutdownSynapseRoom(roomId: string, message?: string): Promise<any> {
|
||||
const endpoint = `/_synapse/admin/v1/rooms/${roomId}`;
|
||||
return await this.client.doRequest("DELETE", endpoint, null, {
|
||||
|
@ -43,6 +43,8 @@ import { execMakeRoomAdminCommand } from "./MakeRoomAdminCommand";
|
||||
import { parse as tokenize } from "shell-quote";
|
||||
import { execSinceCommand } from "./SinceCommand";
|
||||
import { execSetupProtectedRoom } from "./SetupDecentralizedReportingCommand";
|
||||
import {execSuspendCommand} from "./SuspendCommand";
|
||||
import {execUnsuspendCommand} from "./UnsuspendCommand";
|
||||
|
||||
|
||||
export const COMMAND_PREFIX = "!mjolnir";
|
||||
@ -128,6 +130,10 @@ export async function handleCommand(roomId: string, event: { content: { body: st
|
||||
return await execKickCommand(roomId, event, mjolnir, parts);
|
||||
} else if (parts[1] === 'make' && parts[2] === 'admin' && parts.length > 3) {
|
||||
return await execMakeRoomAdminCommand(roomId, event, mjolnir, parts);
|
||||
} else if (parts[1] === 'suspend' && parts.length > 2) {
|
||||
return await execSuspendCommand(roomId, event, mjolnir, parts);
|
||||
} else if (parts[1] === 'unsuspend' && parts.length > 2) {
|
||||
return await execUnsuspendCommand(roomId, event, mjolnir, parts)
|
||||
} else {
|
||||
// Help menu
|
||||
const menu = "" +
|
||||
@ -170,7 +176,9 @@ export async function handleCommand(roomId: string, event: { content: { body: st
|
||||
"!mjolnir shutdown room <room alias/ID> [message] - Uses the bot's account to shut down a room, preventing access to the room on this server\n" +
|
||||
"!mjolnir powerlevel <user ID> <power level> [room alias/ID] - Sets the power level of the user in the specified room (or all protected rooms)\n" +
|
||||
"!mjolnir make admin <room alias> [user alias/ID] - Make the specified user or the bot itself admin of the room\n" +
|
||||
"!mjolnir help - This menu\n";
|
||||
"!mjolnir suspend <user ID> - Suspend the specified user" +
|
||||
"!mjolnir unsuspend <user ID> - Unsuspend the specified user" +
|
||||
"!mjolnir help - This menu\n"
|
||||
const html = `<b>Mjolnir help:</b><br><pre><code>${htmlEscape(menu)}</code></pre>`;
|
||||
const text = `Mjolnir help:\n${menu}`;
|
||||
const reply = RichReply.createFor(roomId, event, text, html);
|
||||
|
38
src/commands/SuspendCommand.ts
Normal file
38
src/commands/SuspendCommand.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2024 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 {Mjolnir} from "../Mjolnir";
|
||||
import {RichReply} from "matrix-bot-sdk";
|
||||
|
||||
export async function execSuspendCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
|
||||
const target = parts[2];
|
||||
|
||||
const isAdmin = await mjolnir.isSynapseAdmin();
|
||||
if (!isAdmin) {
|
||||
const message = "I am not a Synapse administrator, or the endpoint is blocked";
|
||||
const reply = RichReply.createFor(roomId, event, message, message);
|
||||
reply['msgtype'] = "m.notice";
|
||||
await mjolnir.client.sendMessage(roomId, reply);
|
||||
return;
|
||||
}
|
||||
|
||||
await mjolnir.suspendSynapseUser(target);
|
||||
const msg = `User ${target} has been suspended.`
|
||||
const confirmation = RichReply.createFor(roomId, event, msg, msg);
|
||||
confirmation['msgtype'] = "m.notice";
|
||||
await mjolnir.client.sendMessage(roomId, confirmation)
|
||||
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
|
||||
}
|
38
src/commands/UnsuspendCommand.ts
Normal file
38
src/commands/UnsuspendCommand.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright 2024 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 {Mjolnir} from "../Mjolnir";
|
||||
import {RichReply} from "matrix-bot-sdk";
|
||||
|
||||
export async function execUnsuspendCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
|
||||
const target = parts[2];
|
||||
|
||||
const isAdmin = await mjolnir.isSynapseAdmin();
|
||||
if (!isAdmin) {
|
||||
const message = "I am not a Synapse administrator, or the endpoint is blocked";
|
||||
const reply = RichReply.createFor(roomId, event, message, message);
|
||||
reply['msgtype'] = "m.notice";
|
||||
mjolnir.client.sendMessage(roomId, reply);
|
||||
return;
|
||||
}
|
||||
|
||||
await mjolnir.unsuspendSynapseUser(target);
|
||||
const msg = `User ${target}'s suspension has been reversed.`
|
||||
const confirmation = RichReply.createFor(roomId, event, msg, msg);
|
||||
confirmation['msgtype'] = "m.notice";
|
||||
mjolnir.client.sendMessage(roomId, confirmation)
|
||||
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
|
||||
}
|
91
test/integration/commands/suspendCommandTest.ts
Normal file
91
test/integration/commands/suspendCommandTest.ts
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2024 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 {newTestUser} from "../clientHelper";
|
||||
import {strict as assert} from "assert";
|
||||
import { MatrixClient, RoomCreateOptions } from "matrix-bot-sdk";
|
||||
import { read as configRead } from "../../../src/config";
|
||||
|
||||
describe("Test: suspend/unsuspend command", function () {
|
||||
let admin: MatrixClient;
|
||||
let badUser: MatrixClient;
|
||||
const config = configRead()
|
||||
this.beforeEach(async () => {
|
||||
admin = await newTestUser(config.homeserverUrl, { name: { contains: "suspend-command" }});
|
||||
await admin.start();
|
||||
badUser = await newTestUser(config.homeserverUrl, {name: { contains: "bad-user"}})
|
||||
await badUser.start();
|
||||
})
|
||||
this.afterEach(async function () {
|
||||
admin.stop();
|
||||
badUser.stop();
|
||||
})
|
||||
|
||||
it("Mjolnir asks synapse to suspend and unsuspend a user", async function() {
|
||||
this.timeout(20000);
|
||||
await admin.joinRoom(this.mjolnir.managementRoomId);
|
||||
const roomOption: RoomCreateOptions = {preset: "public_chat"}
|
||||
const room = await admin.createRoom(roomOption);
|
||||
await badUser.joinRoom(room)
|
||||
await admin.joinRoom(room)
|
||||
const badUserID = await badUser.getUserId()
|
||||
|
||||
let reply = new Promise(async (resolve, reject) => {
|
||||
await admin.sendMessage(this.mjolnir.managementRoomId, {msgtype: "m.text", body: `!mjolnir suspend ${badUserID}`});
|
||||
admin.on('room.event', (roomId, event) => {
|
||||
if (
|
||||
roomId === this.mjolnir.managementRoomId
|
||||
&& event?.type === "m.room.message"
|
||||
&& event.sender === this.mjolnir.client.userId
|
||||
&& event.content?.body.endsWith(`User ${badUserID} has been suspended.`)
|
||||
) {
|
||||
resolve(event);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await reply
|
||||
try {
|
||||
await badUser.sendMessage(room, {msgtype: "m.text", body: `testing`})
|
||||
assert.fail("Bad user successfully sent message.")
|
||||
}
|
||||
catch (error) {
|
||||
assert.match(error.message, /ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED/i, )
|
||||
}
|
||||
|
||||
let reply2 = new Promise(async (resolve, reject) => {
|
||||
await admin.sendMessage(this.mjolnir.managementRoomId, {msgtype: "m.text", body: `!mjolnir unsuspend ${badUserID}`});
|
||||
admin.on('room.event', (roomId, event) => {
|
||||
if (
|
||||
roomId === this.mjolnir.managementRoomId
|
||||
&& event?.type === "m.room.message"
|
||||
&& event.sender === this.mjolnir.client.userId
|
||||
&& event.content?.body.endsWith(`User ${badUserID}'s suspension has been reversed.`)
|
||||
) {
|
||||
resolve(event);
|
||||
}
|
||||
});
|
||||
});
|
||||
await reply2
|
||||
|
||||
try {
|
||||
await badUser.sendMessage(room, {msgtype: "m.text", body: `testing`});
|
||||
}
|
||||
catch (error) {
|
||||
assert.fail("Unable to send message, account not successfully unsuspended.")
|
||||
}
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user