From e634c3c5020b491005fb8a7be946f75075639171 Mon Sep 17 00:00:00 2001 From: Gnuxie <50846879+Gnuxie@users.noreply.github.com> Date: Mon, 15 Apr 2024 23:33:13 +0100 Subject: [PATCH] Add PolicyChangeNotification protection. (#346) Fixes https://github.com/the-draupnir-project/Draupnir/issues/335. --- .../DefaultEnabledProtectionsMigration.ts | 22 +++ src/protections/DraupnirProtectionsIndex.ts | 1 + src/protections/PolicyChangeNotification.tsx | 135 ++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 src/protections/PolicyChangeNotification.tsx diff --git a/src/protections/DefaultEnabledProtectionsMigration.ts b/src/protections/DefaultEnabledProtectionsMigration.ts index e82be589..5368d839 100644 --- a/src/protections/DefaultEnabledProtectionsMigration.ts +++ b/src/protections/DefaultEnabledProtectionsMigration.ts @@ -5,6 +5,7 @@ import { ActionError,ActionException,ActionExceptionKind,DRAUPNIR_SCHEMA_VERSION_KEY, MjolnirEnabledProtectionsEvent, MjolnirEnabledProtectionsEventType, Ok, SchemedDataManager, Value, findProtection } from "matrix-protection-suite"; import { RedactionSynchronisationProtection } from "./RedactionSynchronisation"; +import { PolicyChangeNotification } from "./PolicyChangeNotification"; export const DefaultEnabledProtectionsMigration = new SchemedDataManager([ async function enableBanPropagationByDefault(input) { @@ -73,5 +74,26 @@ export const DefaultEnabledProtectionsMigration = new SchemedDataManager +// SPDX-FileCopyrightText: 2022 The Matrix.org Foundation C.I.C. +// +// SPDX-License-Identifier: AFL-3.0 AND Apache-2.0 +// +// SPDX-FileAttributionText: +// This modified file incorporates work from mjolnir +// https://github.com/matrix-org/mjolnir +// + +import { AbstractProtection, ActionResult, Logger, MatrixRoomReference, Ok, PolicyListRevision, PolicyRoomManager, PolicyRoomRevisionIssuer, PolicyRuleChange, ProtectedRoomsSet, ProtectionDescription, StringRoomID, UnknownSettings, describeProtection, isError } from "matrix-protection-suite"; +import { DraupnirProtection } from "./Protection"; +import { Draupnir } from "../Draupnir"; +import { DocumentNode } from "../commands/interface-manager/DeadDocument"; +import { JSXFactory } from "../commands/interface-manager/JSXFactory"; +import { renderMentionPill, renderRoomPill } from "../commands/interface-manager/MatrixHelpRenderer"; +import { renderMatrixAndSend } from "../commands/interface-manager/DeadDocumentMatrix"; + +const log = new Logger('PolicyChangeNotification'); + +export type PolicyChangeNotificationCapabilitites = {}; + +export type PolicyChangeNotificationProtectionDescription = ProtectionDescription< + Draupnir, + UnknownSettings, + PolicyChangeNotificationCapabilitites +>; + +type ChangesByRoomID = Map; + +export class PolicyChangeNotification + extends AbstractProtection + implements DraupnirProtection { + + constructor( + description: PolicyChangeNotificationProtectionDescription, + capabilities: PolicyChangeNotificationCapabilitites, + protectedRoomsSet: ProtectedRoomsSet, + private readonly draupnir: Draupnir, + ) { + super(description, capabilities, protectedRoomsSet, [], []); + } + + public async handlePolicyChange(revision: PolicyListRevision, changes: PolicyRuleChange[]): Promise> { + if (changes.length === 0) { + return Ok(undefined); + } + const changesByList: ChangesByRoomID = new Map(); + for (const change of changes) { + const entry = changesByList.get(change.event.room_id); + if (entry === undefined) { + changesByList.set(change.event.room_id, [change]); + } else { + entry.push(change); + } + } + const groupedChanges = await groupRulesByIssuer(this.draupnir.policyRoomManager, changesByList); + if (isError(groupedChanges)) { + return groupedChanges; + } + try { + await renderMatrixAndSend( + {renderGroupedChanges(groupedChanges.ok)}, + this.draupnir.managementRoomID, + undefined, + this.draupnir.client + ); + } catch (e) { + log.error(`couldn't send change to management room`, e); + } + return Ok(undefined); + } +} + +type GroupedChange = { + issuer: PolicyRoomRevisionIssuer, + changes: PolicyRuleChange[], +} + +async function groupRulesByIssuer(policyRoomManager: PolicyRoomManager, changesByList: ChangesByRoomID): Promise> { + const groupedChanges: GroupedChange[] = [] + for (const [roomID, changes] of changesByList) { + const issuer = await policyRoomManager.getPolicyRoomRevisionIssuer(MatrixRoomReference.fromRoomID(roomID)); + if (isError(issuer)) { + return issuer; + } else { + groupedChanges.push({ + issuer: issuer.ok, + changes: changes + }) + } + } + return Ok(groupedChanges); +} + +function renderListChange(change: PolicyRuleChange): DocumentNode { + return +
  • + {renderMentionPill(change.sender, change.sender)} {change.changeType} + {change.rule.kind} ({change.rule.recommendation}) + {change.rule.entity} ({change.rule.reason}) +
  • +
    +} + +function renderListChanges({ issuer, changes }: GroupedChange): DocumentNode { + return + {renderRoomPill(issuer.room)} (shortcode: {issuer.currentRevision.shortcode ?? 'no shortcode'}) + updated with {changes.length} {changes.length === 1 ? 'change' : 'changes'}: +
      {changes.map(renderListChange)}
    +
    +} + +function renderGroupedChanges(groupedChanges: GroupedChange[]): DocumentNode { + return + {groupedChanges.map(renderListChanges)} + +} + +describeProtection({ + name: PolicyChangeNotification.name, + description: 'Provides notification of policy changes from watched lists.', + capabilityInterfaces: {}, + defaultCapabilities: {}, + factory(description, protectedRoomsSet, draupnir, capabilities, _settings) { + return Ok( + new PolicyChangeNotification( + description, + capabilities, + protectedRoomsSet, + draupnir + ) + ); + } +});