Skip to content

Commit

Permalink
Add PolicyChangeNotification protection. (#346)
Browse files Browse the repository at this point in the history
Fixes #335.
  • Loading branch information
Gnuxie authored Apr 15, 2024
1 parent 6ca5b63 commit e634c3c
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 0 deletions.
22 changes: 22 additions & 0 deletions src/protections/DefaultEnabledProtectionsMigration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MjolnirEnabledProtectionsEvent>([
async function enableBanPropagationByDefault(input) {
Expand Down Expand Up @@ -73,5 +74,26 @@ export const DefaultEnabledProtectionsMigration = new SchemedDataManager<Mjolnir
enabled: [...enabledProtections],
[DRAUPNIR_SCHEMA_VERSION_KEY]: 3,
});
},
async function enablePolicyChangeNotification(input) {
if (!Value.Check(MjolnirEnabledProtectionsEvent, input)) {
return ActionError.Result(
`The data for ${MjolnirEnabledProtectionsEventType} is corrupted.`
);
}
const enabledProtections = new Set(input.enabled);
const protection = findProtection(PolicyChangeNotification.name);
if (protection === undefined) {
const message = `Cannot find the ${PolicyChangeNotification.name} protection`;
return ActionException.Result(message, {
exception: new TypeError(message),
exceptionKind: ActionExceptionKind.Unknown
});
}
enabledProtections.add(protection.name);
return Ok({
enabled: [...enabledProtections],
[DRAUPNIR_SCHEMA_VERSION_KEY]: 4,
});
}
]);
1 change: 1 addition & 0 deletions src/protections/DraupnirProtectionsIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import './JoinWaveShortCircuit';
import './RedactionSynchronisation';
import './MessageIsMedia';
import './MessageIsVoice';
import './PolicyChangeNotification';
import './TrustedReporters';
import './WordList';

Expand Down
135 changes: 135 additions & 0 deletions src/protections/PolicyChangeNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-FileCopyrightText: 2022 - 2024 Gnuxie <[email protected]>
// SPDX-FileCopyrightText: 2022 The Matrix.org Foundation C.I.C.
//
// SPDX-License-Identifier: AFL-3.0 AND Apache-2.0
//
// SPDX-FileAttributionText: <text>
// This modified file incorporates work from mjolnir
// https://github.com/matrix-org/mjolnir
// </text>

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<string>,
PolicyChangeNotificationCapabilitites
>;

type ChangesByRoomID = Map<StringRoomID, PolicyRuleChange[]>;

export class PolicyChangeNotification
extends AbstractProtection<PolicyChangeNotificationProtectionDescription>
implements DraupnirProtection<PolicyChangeNotificationProtectionDescription> {

constructor(
description: PolicyChangeNotificationProtectionDescription,
capabilities: PolicyChangeNotificationCapabilitites,
protectedRoomsSet: ProtectedRoomsSet,
private readonly draupnir: Draupnir,
) {
super(description, capabilities, protectedRoomsSet, [], []);
}

public async handlePolicyChange(revision: PolicyListRevision, changes: PolicyRuleChange[]): Promise<ActionResult<void>> {
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(
<root>{renderGroupedChanges(groupedChanges.ok)}</root>,
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<ActionResult<GroupedChange[]>> {
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 <fragment>
<li>
{renderMentionPill(change.sender, change.sender)} <code>{change.changeType}</code> &#32;
{change.rule.kind} (<code>{change.rule.recommendation}</code>) &#32;
<code>{change.rule.entity}</code> ({change.rule.reason})
</li>
</fragment>
}

function renderListChanges({ issuer, changes }: GroupedChange): DocumentNode {
return <fragment>
{renderRoomPill(issuer.room)} (shortcode: {issuer.currentRevision.shortcode ?? 'no shortcode'}) &#32;
updated with {changes.length} {changes.length === 1 ? 'change' : 'changes'}:
<ul>{changes.map(renderListChange)}</ul>
</fragment>
}

function renderGroupedChanges(groupedChanges: GroupedChange[]): DocumentNode {
return <fragment>
{groupedChanges.map(renderListChanges)}
</fragment>
}

describeProtection<PolicyChangeNotificationCapabilitites, Draupnir>({
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
)
);
}
});

0 comments on commit e634c3c

Please sign in to comment.