WIP: Part deux

This commit is contained in:
David Teller 2022-07-27 12:48:27 +02:00
parent 829e1bd0aa
commit 570845a7ec
8 changed files with 51 additions and 51 deletions

View File

@ -27,7 +27,7 @@ import {
TextualMessageEventContent TextualMessageEventContent
} from "matrix-bot-sdk"; } from "matrix-bot-sdk";
import { ALL_RULE_TYPES as ALL_BAN_LIST_RULE_TYPES, RULE_ROOM, RULE_SERVER, RULE_USER } from "./models/ListRule"; import { ALL_RULE_TYPES as ALL_BAN_LIST_RULE_TYPES, RULE_ROOM, RULE_SERVER, RULE_USER } from "./models/PolicyRule";
import { applyServerAcls } from "./actions/ApplyAcl"; import { applyServerAcls } from "./actions/ApplyAcl";
import { RoomUpdateError } from "./models/RoomUpdateError"; import { RoomUpdateError } from "./models/RoomUpdateError";
import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler"; import { COMMAND_PREFIX, handleCommand } from "./commands/CommandHandler";
@ -50,7 +50,7 @@ import RuleServer from "./models/RuleServer";
import { RoomMemberManager } from "./RoomMembers"; import { RoomMemberManager } from "./RoomMembers";
import { ProtectedRoomActivityTracker } from "./queues/ProtectedRoomActivityTracker"; import { ProtectedRoomActivityTracker } from "./queues/ProtectedRoomActivityTracker";
import { ThrottlingQueue } from "./queues/ThrottlingQueue"; import { ThrottlingQueue } from "./queues/ThrottlingQueue";
import PolicyList, { ListRuleChange } from "./models/PolicyList"; import PolicyList, { PolicyRuleChange } from "./models/PolicyList";
const levelToFn = { const levelToFn = {
[LogLevel.DEBUG.toString()]: LogService.debug, [LogLevel.DEBUG.toString()]: LogService.debug,
@ -1052,7 +1052,7 @@ export class Mjolnir {
* @param ignoreSelf Whether to exclude changes that have been made by Mjolnir. * @param ignoreSelf Whether to exclude changes that have been made by Mjolnir.
* @returns true if the message was sent, false if it wasn't (because there there were no changes to report). * @returns true if the message was sent, false if it wasn't (because there there were no changes to report).
*/ */
private async printBanlistChanges(changes: ListRuleChange[], list: PolicyList, ignoreSelf = false): Promise<boolean> { private async printBanlistChanges(changes: PolicyRuleChange[], list: PolicyList, ignoreSelf = false): Promise<boolean> {
if (ignoreSelf) { if (ignoreSelf) {
const sender = await this.client.getUserId(); const sender = await this.client.getUserId();
changes = changes.filter(change => change.sender !== sender); changes = changes.filter(change => change.sender !== sender);

View File

@ -16,7 +16,7 @@ limitations under the License.
import { RichReply } from "matrix-bot-sdk"; import { RichReply } from "matrix-bot-sdk";
import { Mjolnir } from "../Mjolnir"; import { Mjolnir } from "../Mjolnir";
import { EntityType } from "../models/ListRule"; import { EntityType } from "../models/PolicyRule";
import { htmlEscape } from "../utils"; import { htmlEscape } from "../utils";
/** /**

View File

@ -16,7 +16,7 @@ limitations under the License.
import { Mjolnir } from "../Mjolnir"; import { Mjolnir } from "../Mjolnir";
import { RichReply } from "matrix-bot-sdk"; import { RichReply } from "matrix-bot-sdk";
import { EntityType, Recommendation } from "../models/ListRule"; import { EntityType, Recommendation } from "../models/PolicyRule";
// !mjolnir import <room ID> <shortcode> // !mjolnir import <room ID> <shortcode>
export async function execImportCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { export async function execImportCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {

View File

@ -17,7 +17,7 @@ limitations under the License.
import { Mjolnir } from "../Mjolnir"; import { Mjolnir } from "../Mjolnir";
import PolicyList from "../models/PolicyList"; import PolicyList from "../models/PolicyList";
import { extractRequestError, LogLevel, LogService, MatrixGlob, RichReply } from "matrix-bot-sdk"; import { extractRequestError, LogLevel, LogService, MatrixGlob, RichReply } from "matrix-bot-sdk";
import { Recommendation, RULE_ROOM, RULE_SERVER, RULE_USER, USER_RULE_TYPES } from "../models/ListRule"; import { Recommendation, RULE_ROOM, RULE_SERVER, RULE_USER, USER_RULE_TYPES } from "../models/PolicyRule";
import config from "../config"; import config from "../config";
import { DEFAULT_LIST_EVENT_TYPE } from "./SetDefaultBanListCommand"; import { DEFAULT_LIST_EVENT_TYPE } from "./SetDefaultBanListCommand";

View File

@ -16,7 +16,7 @@ limitations under the License.
import { extractRequestError, LogService, MatrixClient, UserID } from "matrix-bot-sdk"; import { extractRequestError, LogService, MatrixClient, UserID } from "matrix-bot-sdk";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { ALL_RULE_TYPES, EntityType, ListRule, Recommendation, ROOM_RULE_TYPES, RULE_ROOM, RULE_SERVER, RULE_USER, SERVER_RULE_TYPES, USER_RULE_TYPES } from "./ListRule"; import { ALL_RULE_TYPES, EntityType, PolicyRule, Recommendation, ROOM_RULE_TYPES, RULE_ROOM, RULE_SERVER, RULE_USER, SERVER_RULE_TYPES, USER_RULE_TYPES } from "./PolicyRule";
export const SHORTCODE_EVENT_TYPE = "org.matrix.mjolnir.shortcode"; export const SHORTCODE_EVENT_TYPE = "org.matrix.mjolnir.shortcode";
@ -26,7 +26,7 @@ export enum ChangeType {
Modified = "MODIFIED" Modified = "MODIFIED"
} }
export interface ListRuleChange { export interface PolicyRuleChange {
readonly changeType: ChangeType, readonly changeType: ChangeType,
/** /**
* State event that caused the change. * State event that caused the change.
@ -43,7 +43,7 @@ export interface ListRuleChange {
* The current rule represented by the event. * The current rule represented by the event.
* If the rule has been removed, then this will show what the rule was. * If the rule has been removed, then this will show what the rule was.
*/ */
readonly rule: ListRule, readonly rule: PolicyRule,
/** /**
* The previous state that has been changed. Only (and always) provided when the change type is `ChangeType.Removed` or `Modified`. * The previous state that has been changed. Only (and always) provided when the change type is `ChangeType.Removed` or `Modified`.
* This will be a copy of the same event as `event` when a redaction has occurred and this will show its unredacted state. * This will be a copy of the same event as `event` when a redaction has occurred and this will show its unredacted state.
@ -53,8 +53,8 @@ export interface ListRuleChange {
declare interface PolicyList { declare interface PolicyList {
// PolicyList.update is emitted when the PolicyList has pulled new rules from Matrix and informs listeners of any changes. // PolicyList.update is emitted when the PolicyList has pulled new rules from Matrix and informs listeners of any changes.
on(event: 'PolicyList.update', listener: (list: PolicyList, changes: ListRuleChange[]) => void): this on(event: 'PolicyList.update', listener: (list: PolicyList, changes: PolicyRuleChange[]) => void): this
emit(event: 'PolicyList.update', list: PolicyList, changes: ListRuleChange[]): boolean emit(event: 'PolicyList.update', list: PolicyList, changes: PolicyRuleChange[]): boolean
// PolicyList.batch is emitted when the PolicyList has created a batch from the events provided by `updateForEvent`. // PolicyList.batch is emitted when the PolicyList has created a batch from the events provided by `updateForEvent`.
on(event: 'PolicyList.batch', listener: (list: PolicyList) => void): this on(event: 'PolicyList.batch', listener: (list: PolicyList) => void): this
emit(event: 'PolicyList.batch', list: PolicyList): boolean emit(event: 'PolicyList.batch', list: PolicyList): boolean
@ -118,10 +118,10 @@ class PolicyList extends EventEmitter {
/** /**
* Return all the active rules of a given kind. * Return all the active rules of a given kind.
* @param kind e.g. RULE_SERVER (m.policy.rule.server). Rule types are always normalised when they are interned into the PolicyList. * @param kind e.g. RULE_SERVER (m.policy.rule.server). Rule types are always normalised when they are interned into the PolicyList.
* @returns The active ListRules for the ban list of that kind. * @returns The active PolicyRules for the ban list of that kind.
*/ */
private rulesOfKind(kind: string): ListRule[] { private rulesOfKind(kind: string): PolicyRule[] {
const rules: ListRule[] = [] const rules: PolicyRule[] = []
const stateKeyMap = this.state.get(kind); const stateKeyMap = this.state.get(kind);
if (stateKeyMap) { if (stateKeyMap) {
for (const event of stateKeyMap.values()) { for (const event of stateKeyMap.values()) {
@ -146,19 +146,19 @@ class PolicyList extends EventEmitter {
}); });
} }
public get serverRules(): ListRule[] { public get serverRules(): PolicyRule[] {
return this.rulesOfKind(RULE_SERVER); return this.rulesOfKind(RULE_SERVER);
} }
public get userRules(): ListRule[] { public get userRules(): PolicyRule[] {
return this.rulesOfKind(RULE_USER); return this.rulesOfKind(RULE_USER);
} }
public get roomRules(): ListRule[] { public get roomRules(): PolicyRule[] {
return this.rulesOfKind(RULE_ROOM); return this.rulesOfKind(RULE_ROOM);
} }
public get allRules(): ListRule[] { public get allRules(): PolicyRule[] {
return [...this.serverRules, ...this.userRules, ...this.roomRules]; return [...this.serverRules, ...this.userRules, ...this.roomRules];
} }
@ -169,7 +169,7 @@ class PolicyList extends EventEmitter {
* @param entity The entity to test e.g. the user id, server name or a room id. * @param entity The entity to test e.g. the user id, server name or a room id.
* @returns All of the rules that match this entity. * @returns All of the rules that match this entity.
*/ */
public rulesMatchingEntity(entity: string, ruleKind?: string): ListRule[] { public rulesMatchingEntity(entity: string, ruleKind?: string): PolicyRule[] {
const ruleTypeOf: (entityPart: string) => string = (entityPart: string) => { const ruleTypeOf: (entityPart: string) => string = (entityPart: string) => {
if (ruleKind) { if (ruleKind) {
return ruleKind; return ruleKind;
@ -233,8 +233,8 @@ class PolicyList extends EventEmitter {
* and updating the model to reflect the room. * and updating the model to reflect the room.
* @returns A description of any rules that were added, modified or removed from the list as a result of this update. * @returns A description of any rules that were added, modified or removed from the list as a result of this update.
*/ */
public async updateList(): Promise<ListRuleChange[]> { public async updateList(): Promise<PolicyRuleChange[]> {
let changes: ListRuleChange[] = []; let changes: PolicyRuleChange[] = [];
const state = await this.client.getRoomState(this.roomId); const state = await this.client.getRoomState(this.roomId);
for (const event of state) { for (const event of state) {
@ -313,11 +313,11 @@ class PolicyList extends EventEmitter {
changeType, event, sender, rule: previousState.unsigned.rule, changeType, event, sender, rule: previousState.unsigned.rule,
...previousState ? { previousState } : {} ...previousState ? { previousState } : {}
}); });
// Event has no content and cannot be parsed as a ListRule. // Event has no content and cannot be parsed as a PolicyRule.
continue; continue;
} }
// It's a rule - parse it // It's a rule - parse it
const rule = ListRule.parse(event); const rule = PolicyRule.parse(event);
if (!rule) { if (!rule) {
// Invalid/unknown rule, just skip it. // Invalid/unknown rule, just skip it.
continue; continue;

View File

@ -51,9 +51,9 @@ export enum Recommendation {
/// The rule specifies an "opinion", as a number in [-100, +100], /// The rule specifies an "opinion", as a number in [-100, +100],
/// where -100 represents a user who is considered absolutely toxic /// where -100 represents a user who is considered absolutely toxic
/// by whoever issued this ListRule and +100 represents a user who /// by whoever issued this PolicyRule and +100 represents a user who
/// is considered absolutely absolutely perfect by whoever issued /// is considered absolutely absolutely perfect by whoever issued
/// this ListRule. /// this PolicyRule.
Opinion = "org.matrix.msc3845.opinion", Opinion = "org.matrix.msc3845.opinion",
} }
@ -81,7 +81,7 @@ export const OPINION_MAX = +100;
/** /**
* Representation of a rule within a Policy List. * Representation of a rule within a Policy List.
*/ */
export abstract class ListRule { export abstract class PolicyRule {
/** /**
* A glob for `entity`. * A glob for `entity`.
*/ */
@ -115,12 +115,12 @@ export abstract class ListRule {
} }
/** /**
* Validate and parse an event into a ListRule. * Validate and parse an event into a PolicyRule.
* *
* @param event An *untrusted* event. * @param event An *untrusted* event.
* @returns null if the ListRule is invalid or not recognized by Mjölnir. * @returns null if the PolicyRule is invalid or not recognized by Mjölnir.
*/ */
public static parse(event: {type: string, content: any}): ListRule | null { public static parse(event: {type: string, content: any}): PolicyRule | null {
// Parse common fields. // Parse common fields.
// If a field is ill-formed, discard the rule. // If a field is ill-formed, discard the rule.
const content = event['content']; const content = event['content'];
@ -155,17 +155,17 @@ export abstract class ListRule {
// From this point, we may need specific fields. // From this point, we may need specific fields.
if (RECOMMENDATION_BAN_VARIANTS.includes(recommendation)) { if (RECOMMENDATION_BAN_VARIANTS.includes(recommendation)) {
return new ListRuleBan(entity, reason, kind); return new PolicyRuleBan(entity, reason, kind);
} else if (RECOMMENDATION_OPINION_VARIANTS.includes(recommendation)) { } else if (RECOMMENDATION_OPINION_VARIANTS.includes(recommendation)) {
let opinion = content['opinion']; let opinion = content['opinion'];
if (!Number.isInteger(opinion)) { if (!Number.isInteger(opinion)) {
return null; return null;
} }
return new ListRuleOpinion(entity, reason, kind, opinion); return new PolicyRuleOpinion(entity, reason, kind, opinion);
} else { } else {
// As long as the `recommendation` is defined, we assume // As long as the `recommendation` is defined, we assume
// that the rule is correct, just unknown. // that the rule is correct, just unknown.
return new ListRuleUnknown(entity, reason, kind, content); return new PolicyRuleUnknown(entity, reason, kind, content);
} }
} }
} }
@ -173,7 +173,7 @@ export abstract class ListRule {
/** /**
* A rule representing a "ban". * A rule representing a "ban".
*/ */
export class ListRuleBan extends ListRule { export class PolicyRuleBan extends PolicyRule {
constructor( constructor(
/** /**
* The entity covered by this rule, e.g. a glob user ID, a room ID, a server domain. * The entity covered by this rule, e.g. a glob user ID, a room ID, a server domain.
@ -195,7 +195,7 @@ export class ListRuleBan extends ListRule {
/** /**
* A rule representing an "opinion" * A rule representing an "opinion"
*/ */
export class ListRuleOpinion extends ListRule { export class PolicyRuleOpinion extends PolicyRule {
constructor( constructor(
/** /**
* The entity covered by this rule, e.g. a glob user ID, a room ID, a server domain. * The entity covered by this rule, e.g. a glob user ID, a room ID, a server domain.
@ -229,7 +229,7 @@ export class ListRuleOpinion extends ListRule {
/** /**
* Any list rule that we do not understand. * Any list rule that we do not understand.
*/ */
export class ListRuleUnknown extends ListRule { export class PolicyRuleUnknown extends PolicyRule {
constructor( constructor(
/** /**
* The entity covered by this rule, e.g. a glob user ID, a room ID, a server domain. * The entity covered by this rule, e.g. a glob user ID, a room ID, a server domain.

View File

@ -13,10 +13,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import BanList, { ChangeType, ListRuleChange } from "./PolicyList" import BanList, { ChangeType, PolicyRuleChange } from "./PolicyList"
import * as crypto from "crypto"; import * as crypto from "crypto";
import { LogService } from "matrix-bot-sdk"; import { LogService } from "matrix-bot-sdk";
import { EntityType, ListRule } from "./ListRule"; import { EntityType, PolicyRule } from "./PolicyRule";
import PolicyList from "./PolicyList"; import PolicyList from "./PolicyList";
export const USER_MAY_INVITE = 'user_may_invite'; export const USER_MAY_INVITE = 'user_may_invite';
@ -149,10 +149,10 @@ export default class RuleServer {
} }
/** /**
* Update the rule server to reflect a ListRule change. * Update the rule server to reflect a PolicyRule change.
* @param change A ListRuleChange sourced from a BanList. * @param change A PolicyRuleChange sourced from a BanList.
*/ */
private applyRuleChange(change: ListRuleChange): void { private applyRuleChange(change: PolicyRuleChange): void {
if (change.changeType === ChangeType.Added) { if (change.changeType === ChangeType.Added) {
const eventRules = new EventRules(change.event.event_id, change.event.room_id, toRuleServerFormat(change.rule), this.currentToken); const eventRules = new EventRules(change.event.event_id, change.event.room_id, toRuleServerFormat(change.rule), this.currentToken);
this.addEventRules(eventRules); this.addEventRules(eventRules);
@ -208,9 +208,9 @@ export default class RuleServer {
* Process the changes that have been made to a BanList. * Process the changes that have been made to a BanList.
* This will ususally be called as a callback from `BanList.onChange`. * This will ususally be called as a callback from `BanList.onChange`.
* @param banList The BanList that the changes happened in. * @param banList The BanList that the changes happened in.
* @param changes An array of ListRuleChanges. * @param changes An array of PolicyRuleChanges.
*/ */
private update(banList: BanList, changes: ListRuleChange[]) { private update(banList: BanList, changes: PolicyRuleChange[]) {
if (changes.length > 0) { if (changes.length > 0) {
this.nextToken(); this.nextToken();
changes.forEach(this.applyRuleChange, this); changes.forEach(this.applyRuleChange, this);
@ -257,11 +257,11 @@ export default class RuleServer {
} }
/** /**
* Convert a ListRule into the format that can be served by the rule server. * Convert a PolicyRule into the format that can be served by the rule server.
* @param policyRule A ListRule to convert. * @param policyRule A PolicyRule to convert.
* @returns An array of rules that can be served from the rule server. * @returns An array of rules that can be served from the rule server.
*/ */
function toRuleServerFormat(policyRule: ListRule): RuleServerRule[] { function toRuleServerFormat(policyRule: PolicyRule): RuleServerRule[] {
function makeLiteral(literal: string) { function makeLiteral(literal: string) {
return { literal } return { literal }
} }

View File

@ -3,11 +3,11 @@ import { strict as assert } from "assert";
import config from "../../src/config"; import config from "../../src/config";
import { newTestUser } from "./clientHelper"; import { newTestUser } from "./clientHelper";
import { LogService, MatrixClient, Permalinks, UserID } from "matrix-bot-sdk"; import { LogService, MatrixClient, Permalinks, UserID } from "matrix-bot-sdk";
import PolicyList, { ChangeType, ListRuleChange } from "../../src/models/PolicyList"; import PolicyList, { ChangeType, PolicyRuleChange } from "../../src/models/PolicyList";
import { ServerAcl } from "../../src/models/ServerAcl"; import { ServerAcl } from "../../src/models/ServerAcl";
import { getFirstReaction } from "./commands/commandUtils"; import { getFirstReaction } from "./commands/commandUtils";
import { getMessagesByUserIn } from "../../src/utils"; import { getMessagesByUserIn } from "../../src/utils";
import { ALL_RULE_TYPES, RULE_SERVER, RULE_USER, SERVER_RULE_TYPES } from "../../src/models/ListRule"; import { ALL_RULE_TYPES, RULE_SERVER, RULE_USER, SERVER_RULE_TYPES } from "../../src/models/PolicyRule";
/** /**
* Create a policy rule in a policy room. * Create a policy rule in a policy room.
@ -40,7 +40,7 @@ describe("Test: Updating the PolicyList", function() {
// Test adding a new rule // Test adding a new rule
await createPolicyRule(mjolnir, banListId, RULE_USER, '@added:localhost:9999', ''); await createPolicyRule(mjolnir, banListId, RULE_USER, '@added:localhost:9999', '');
let changes: ListRuleChange[] = await banList.updateList(); let changes: PolicyRuleChange[] = await banList.updateList();
assert.equal(changes.length, 1, 'There should only be one change'); assert.equal(changes.length, 1, 'There should only be one change');
assert.equal(changes[0].changeType, ChangeType.Added); assert.equal(changes[0].changeType, ChangeType.Added);
assert.equal(changes[0].sender, await mjolnir.getUserId()); assert.equal(changes[0].sender, await mjolnir.getUserId());
@ -187,7 +187,7 @@ describe("Test: Updating the PolicyList", function() {
for (let i = 0; i < ALL_RULE_TYPES.length; i++) { for (let i = 0; i < ALL_RULE_TYPES.length; i++) {
await createPolicyRule(mjolnir, banListId, ALL_RULE_TYPES[i], `*${i}*`, ''); await createPolicyRule(mjolnir, banListId, ALL_RULE_TYPES[i], `*${i}*`, '');
} }
let changes: ListRuleChange[] = await banList.updateList(); let changes: PolicyRuleChange[] = await banList.updateList();
assert.equal(changes.length, ALL_RULE_TYPES.length); assert.equal(changes.length, ALL_RULE_TYPES.length);
assert.equal(banList.allRules.length, ALL_RULE_TYPES.length); assert.equal(banList.allRules.length, ALL_RULE_TYPES.length);
}) })
@ -199,7 +199,7 @@ describe('Test: We do not respond to recommendations other than m.ban in the ban
const banListId = await mjolnir.createRoom(); const banListId = await mjolnir.createRoom();
const banList = new PolicyList(banListId, banListId, mjolnir); const banList = new PolicyList(banListId, banListId, mjolnir);
await createPolicyRule(mjolnir, banListId, RULE_SERVER, 'exmaple.org', '', { recommendation: 'something that is not m.ban' }); await createPolicyRule(mjolnir, banListId, RULE_SERVER, 'exmaple.org', '', { recommendation: 'something that is not m.ban' });
let changes: ListRuleChange[] = await banList.updateList(); let changes: PolicyRuleChange[] = await banList.updateList();
assert.equal(changes.length, 1, 'There should only be one change'); assert.equal(changes.length, 1, 'There should only be one change');
assert.equal(changes[0].changeType, ChangeType.Added); assert.equal(changes[0].changeType, ChangeType.Added);
assert.equal(changes[0].sender, await mjolnir.getUserId()); assert.equal(changes[0].sender, await mjolnir.getUserId());
@ -219,7 +219,7 @@ describe('Test: We will not be able to ban ourselves via ACL.', function() {
await createPolicyRule(mjolnir, banListId, RULE_SERVER, 'evil.com', ''); await createPolicyRule(mjolnir, banListId, RULE_SERVER, 'evil.com', '');
await createPolicyRule(mjolnir, banListId, RULE_SERVER, '*', ''); await createPolicyRule(mjolnir, banListId, RULE_SERVER, '*', '');
// We should still intern the matching rules rule. // We should still intern the matching rules rule.
let changes: ListRuleChange[] = await banList.updateList(); let changes: PolicyRuleChange[] = await banList.updateList();
assert.equal(banList.serverRules.length, 3); assert.equal(banList.serverRules.length, 3);
// But when we construct an ACL, we should be safe. // But when we construct an ACL, we should be safe.
const acl = new ServerAcl(serverName) const acl = new ServerAcl(serverName)