Make rules and rules matching use interface-manager.

This commit is contained in:
gnuxie
2023-02-11 14:20:46 +00:00
committed by Gnuxie
parent 728ed1c0e5
commit 079f0dfcab
3 changed files with 148 additions and 144 deletions
+1 -5
View File
@@ -27,7 +27,6 @@ limitations under the License.
import { Mjolnir } from "../Mjolnir";
import { showJoinsStatus } from "./JoinsCommand";
import { execDumpRulesCommand, execRulesMatchingCommand } from "./DumpRulesCommand";
import { LogService, RichReply } from "matrix-bot-sdk";
import { htmlEscape } from "../utils";
import { execSyncCommand } from "./SyncCommand";
@@ -71,6 +70,7 @@ import "./HijackRoomCommand";
import "./Ban";
import "./Unban";
import "./StatusCommand";
import "./Rules";
export const COMMAND_PREFIX = "!mjolnir";
@@ -85,10 +85,6 @@ export async function handleCommand(roomId: string, event: { content: { body: st
try {
if (parts.length === 1 || parts[1] === 'joins') {
return await showJoinsStatus(roomId, event, mjolnir, parts.slice(/* ["joins"] */ 2));
} else if (parts[1] === 'rules' && parts.length === 4 && parts[2] === 'matching') {
return await execRulesMatchingCommand(roomId, event, mjolnir, parts[3])
} else if (parts[1] === 'rules') {
return await execDumpRulesCommand(roomId, event, mjolnir);
} else if (parts[1] === 'sync') {
return await execSyncCommand(roomId, event, mjolnir);
} else if (parts[1] === 'verify') {
-139
View File
@@ -1,139 +0,0 @@
/**
* Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
* All rights reserved.
*
* This file is modified and is NOT licensed under the Apache License.
* This modified file incorperates work from mjolnir
* https://github.com/matrix-org/mjolnir
* which included the following license notice:
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.
*
* However, this file is modified and the modifications in this file
* are NOT distributed, contributed, committed, or licensed under the Apache License.
*/
import { RichReply } from "matrix-bot-sdk";
import { Mjolnir } from "../Mjolnir";
import { EntityType } from "../models/ListRule";
import { htmlEscape } from "../utils";
/**
* List all of the rules that match a given entity.
* The reason why you want to test against all rules and not just e.g. user or server is because
* there are situations where rules of different types can ban other entities e.g. server ACL can cause users to be banned.
* @param roomId The room the command is from.
* @param event The event containing the command.
* @param mjolnir A mjolnir to fetch the watched lists from.
* @param entity a user, room id or server.
* @returns When a response has been sent to the command.
*/
export async function execRulesMatchingCommand(roomId: string, event: any, mjolnir: Mjolnir, entity: string) {
let html = "";
let text = "";
for (const list of mjolnir.policyListManager.lists) {
const matches = list.rulesMatchingEntity(entity)
if (matches.length === 0) {
continue;
}
const matchesInfo = `Found ${matches.length} ` + (matches.length === 1 ? 'match:' : 'matches:');
const shortcodeInfo = list.listShortcode ? ` (shortcode: ${htmlEscape(list.listShortcode)})` : '';
html += `<a href="${htmlEscape(list.roomRef)}">${htmlEscape(list.roomId)}</a>${shortcodeInfo} ${matchesInfo}<br/><ul>`;
text += `${list.roomRef}${shortcodeInfo} ${matchesInfo}:\n`;
for (const rule of matches) {
// If we know the rule kind, we will give it a readable name, otherwise just use its name.
let ruleKind: string = rule.kind;
switch (ruleKind) {
case EntityType.RULE_USER:
ruleKind = 'user';
break;
case EntityType.RULE_SERVER:
ruleKind = 'server';
break;
case EntityType.RULE_ROOM:
ruleKind = 'room';
break;
}
html += `<li>${htmlEscape(ruleKind)} (<code>${htmlEscape(rule.recommendation ?? "")}</code>): <code>${htmlEscape(rule.entity)}</code> (${htmlEscape(rule.reason)})</li>`;
text += `* ${ruleKind} (${rule.recommendation}): ${rule.entity} (${rule.reason})\n`;
}
html += "</ul>";
}
if (text.length === 0) {
html += `No results for ${htmlEscape(entity)}`;
text += `No results for ${entity}`;
}
const reply = RichReply.createFor(roomId, event, text, html);
reply["msgtype"] = "m.notice";
return mjolnir.client.sendMessage(roomId, reply);
}
// !mjolnir rules
export async function execDumpRulesCommand(roomId: string, event: any, mjolnir: Mjolnir) {
let html = "<b>Rules currently in use:</b><br/>";
let text = "Rules currently in use:\n";
let hasLists = false;
for (const list of mjolnir.policyListManager.lists) {
hasLists = true;
let hasRules = false;
const shortcodeInfo = list.listShortcode ? ` (shortcode: ${list.listShortcode})` : '';
html += `<a href="${list.roomRef}">${list.roomId}</a>${shortcodeInfo}:<br/><ul>`;
text += `${list.roomRef}${shortcodeInfo}:\n`;
for (const rule of list.serverRules) {
hasRules = true;
html += `<li>server (<code>${rule.recommendation}</code>): <code>${htmlEscape(rule.entity)}</code> (${htmlEscape(rule.reason)})</li>`;
text += `* server (${rule.recommendation}): ${rule.entity} (${rule.reason})\n`;
}
for (const rule of list.userRules) {
hasRules = true;
html += `<li>user (<code>${rule.recommendation}</code>): <code>${htmlEscape(rule.entity)}</code> (${htmlEscape(rule.reason)})</li>`;
text += `* user (${rule.recommendation}): ${rule.entity} (${rule.reason})\n`;
}
for (const rule of list.roomRules) {
hasRules = true;
html += `<li>room (<code>${rule.recommendation}</code>): <code>${htmlEscape(rule.entity)}</code> (${htmlEscape(rule.reason)})</li>`;
text += `* room (${rule.recommendation}): ${rule.entity} (${rule.reason})\n`;
}
if (!hasRules) {
html += "<li><i>No rules</i>";
text += "* No rules\n";
}
html += "</ul>";
text += "\n";
}
if (!hasLists) {
html = "No ban lists configured";
text = "No ban lists configured";
}
const reply = RichReply.createFor(roomId, event, text, html);
reply["msgtype"] = "m.notice";
return mjolnir.client.sendMessage(roomId, reply);
}
+147
View File
@@ -0,0 +1,147 @@
/**
* Copyright (C) 2022 Gnuxie <Gnuxie@protonmail.com>
* All rights reserved.
*
* This file is modified and is NOT licensed under the Apache License.
* This modified file incorperates work from mjolnir
* https://github.com/matrix-org/mjolnir
* which included the following license notice:
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.
*
* However, this file is modified and the modifications in this file
* are NOT distributed, contributed, committed, or licensed under the Apache License.
*/
import { MjolnirContext } from "./CommandHandler";
import { renderMatrixAndSend } from "./interface-manager/DeadDocumentMatrix";
import { defineInterfaceCommand, findTableCommand } from "./interface-manager/InterfaceCommand";
import { JSXFactory } from "./interface-manager/JSXFactory";
import { tickCrossRenderer } from "./interface-manager/MatrixHelpRenderer";
import { defineMatrixInterfaceAdaptor } from "./interface-manager/MatrixInterfaceAdaptor";
import { MatrixRoomReference } from "./interface-manager/MatrixRoomReference";
import { findPresentationType, parameters, union } from "./interface-manager/ParameterParsing";
import { CommandResult } from "./interface-manager/Validation";
import { UserID } from "matrix-bot-sdk";
import { MatrixSendClient } from "../MatrixEmitter";
import { ListRule } from "../models/ListRule";
async function renderListMatches(
client: MatrixSendClient, commandRoomId: string, event: any, result: CommandResult<ListMatches[]>
) {
if (result.isErr()) {
return await tickCrossRenderer.call(this, ...arguments);
}
const lists = result.ok;
if (lists.length === 0) {
return await renderMatrixAndSend(
<root>No policy lists configured</root>,
commandRoomId, event, client
)
}
return await renderMatrixAndSend(
<root>
<b>Rules currently in use:</b><br/>
{lists.map(list => renderListRules(list))}
</root>,
commandRoomId, event, client
)
}
function renderListRules(list: ListMatches) {
const renderRuleSummary = (rule: ListRule, entityDescription: string) => {
return <li>
{entityDescription} (<code>{rule.recommendation}</code>): <code>{rule.entity}</code> ({rule.reason})
</li>
};
return <fragment>
<a href={list.roomRef}>{list.roomId}</a>
{list.shortcode ? `(shortcode: ${list.shortcode})` : ''}:<br/>
<ul>
{list.matches.length === 0
? <li><i>No rules</i></li>
: list.matches.map(rule => renderRuleSummary(rule, rule.kind))}
</ul>
</fragment>
}
interface ListMatches {
shortcode: string,
roomRef: string,
roomId: string,
matches: ListRule[]
}
defineInterfaceCommand({
designator: ["rules"],
table: "mjolnir",
parameters: parameters([]),
command: async function (this: MjolnirContext) {
return CommandResult.Ok(
this.mjolnir.policyListManager.lists
.map(list => {
return {
shortcode: list.listShortcode,
roomRef: list.roomRef,
roomId: list.roomId,
matches: list.allRules
}
})
);
},
summary: "Lists the rules currently in use by Mjolnir."
})
defineMatrixInterfaceAdaptor({
interfaceCommand: findTableCommand("mjolnir", "rules"),
renderer: renderListMatches
})
defineInterfaceCommand({
designator: ["rules", "matching"],
table: "mjolnir",
parameters: parameters([
{
name: "entity",
acceptor: union(
findPresentationType("UserID"),
findPresentationType("MatrixRoomReference"),
findPresentationType("string"),
)
}
]),
command: async function (
this: MjolnirContext, _keywords, entity: string|UserID|MatrixRoomReference
): Promise<CommandResult<ListMatches[]>> {
return CommandResult.Ok(
this.mjolnir.policyListManager.lists
.map(list => {
return {
shortcode: list.listShortcode,
roomRef: list.roomRef,
roomId: list.roomId,
matches: list.rulesMatchingEntity(entity.toString())
}
})
);
},
summary: "Lists the rules in use that will match this entity e.g. `!rules matching @foo:example.com` will show all the user and server rules, including globs, that match this user"
})
// I'm pretty sure that both commands could merge and use the same rendeer.
defineMatrixInterfaceAdaptor({
interfaceCommand: findTableCommand("mjolnir", "rules", "matching"),
renderer: renderListMatches
})