diff --git a/.changeset/fast-years-battle.md b/.changeset/fast-years-battle.md new file mode 100644 index 00000000..1ca6aaa9 --- /dev/null +++ b/.changeset/fast-years-battle.md @@ -0,0 +1,6 @@ +--- +"draupnir": minor +--- + +Draupnir will crash at startup if it does not have the ability to send state +events to the mangement room. diff --git a/.changeset/gentle-rockets-punch.md b/.changeset/gentle-rockets-punch.md new file mode 100644 index 00000000..225119c8 --- /dev/null +++ b/.changeset/gentle-rockets-punch.md @@ -0,0 +1,5 @@ +--- +"@the-draupnir-project/matrix-protection-suite": minor +--- + +Fix method names around room v12 privileged creators to no longer have typos diff --git a/apps/draupnir/src/Draupnir.ts b/apps/draupnir/src/Draupnir.ts index a9fc30cc..7bfbd529 100644 --- a/apps/draupnir/src/Draupnir.ts +++ b/apps/draupnir/src/Draupnir.ts @@ -81,6 +81,7 @@ import { sendMatrixEventsFromDeadDocument, } from "@the-draupnir-project/mps-interface-adaptor"; import { TimelineRedactionQueue } from "./queues/TimelineRedactionQueue"; +import { ResultError } from "@gnuxie/typescript-result"; const log = new Logger("Draupnir"); @@ -325,6 +326,21 @@ export class Draupnir implements Client, MatrixAdaptorContext { if (isError(managementRoomProtectResult)) { return managementRoomProtectResult; } + // Check that Draupnir has the ability to send state events to the management + // room. Which is important because Draupnir users state events to persist + // protection settings, including the draupnir news feed position. + if (!managementRoomDetail.isDraupnirUserPowered(clientUserID)) { + const errorText = `${clientUserID} doesn't have the power level required to send state events in the management room. Please make the Draupnir user an administrator.`; + await Task( + clientPlatform + .toRoomMessageSender() + .sendMessage(managementRoomDetail.managementRoomID, { + body: errorText, + msgtype: "m.notice", + }) + ); + return ResultError.Result(errorText); + } return Ok(draupnir); } diff --git a/apps/draupnir/src/appservice/AppServiceDraupnirManager.ts b/apps/draupnir/src/appservice/AppServiceDraupnirManager.ts index dd86434d..9883a966 100644 --- a/apps/draupnir/src/appservice/AppServiceDraupnirManager.ts +++ b/apps/draupnir/src/appservice/AppServiceDraupnirManager.ts @@ -458,7 +458,7 @@ export async function makeManagementRoom( ); } const isRoomVersionWithPrivilidgedCreators = - RoomVersionMirror.isVersionWithPrivilidgedCreators( + RoomVersionMirror.isVersionWithPrivilegedCreators( capabilities.ok.capabilities["m.room_versions"].default ); return await roomCreator.createRoom({ diff --git a/apps/draupnir/src/managementroom/ManagementRoomDetail.ts b/apps/draupnir/src/managementroom/ManagementRoomDetail.ts index c56c73f0..e78acd6c 100644 --- a/apps/draupnir/src/managementroom/ManagementRoomDetail.ts +++ b/apps/draupnir/src/managementroom/ManagementRoomDetail.ts @@ -10,13 +10,19 @@ import { import { JoinRulesEvent, Membership, + PowerLevelPermission, + PowerLevelsEvent, + PowerLevelsMirror, + RoomCreateEvent, RoomMembershipRevisionIssuer, RoomStateRevisionIssuer, + RoomVersionMirror, } from "matrix-protection-suite"; export interface ManagementRoomDetail { isRoomPublic(): boolean; isModerator(userID: StringUserID): boolean; + isDraupnirUserPowered(draupnirUserID: StringUserID): boolean; managementRoom: MatrixRoomID; managementRoomID: StringRoomID; } @@ -52,4 +58,32 @@ export class StandardManagementRoomDetail implements ManagementRoomDetail { public get managementRoomID(): StringRoomID { return this.managementRoom.toRoomIDOrAlias(); } + + public isDraupnirUserPowered(draupnirUserID: StringUserID): boolean { + const powerLevelEvent = + this.stateIssuer.currentRevision.getStateEvent( + "m.room.power_levels", + "" + ); + + const createEvent = + this.stateIssuer.currentRevision.getStateEvent( + "m.room.create", + "" + ); + if (powerLevelEvent === undefined || createEvent === undefined) { + throw new TypeError("Unable to fetch management room state"); + } + if ( + PowerLevelsMirror.isUserAbleToUse( + draupnirUserID, + PowerLevelPermission.StateDefault, + powerLevelEvent.content + ) || + RoomVersionMirror.isUserAPrivilegedCreator(draupnirUserID, createEvent) + ) { + return true; + } + return false; + } } diff --git a/apps/draupnir/src/protections/ProtectedRooms/WatchReplacementPolicyRooms.tsx b/apps/draupnir/src/protections/ProtectedRooms/WatchReplacementPolicyRooms.tsx index 00fb4c47..04653486 100644 --- a/apps/draupnir/src/protections/ProtectedRooms/WatchReplacementPolicyRooms.tsx +++ b/apps/draupnir/src/protections/ProtectedRooms/WatchReplacementPolicyRooms.tsx @@ -89,7 +89,7 @@ function renderPrivilegedUsers(revision: RoomStateRevision): DocumentNode { if (createEvent === undefined) { throw new TypeError("Mate can't find create event in the room"); } - const privilegedCreators = RoomVersionMirror.priviligedCreators(createEvent); + const privilegedCreators = RoomVersionMirror.privilegedCreators(createEvent); return (
diff --git a/apps/draupnir/test/integration/manualLaunchScript.ts b/apps/draupnir/test/integration/manualLaunchScript.ts index 14f17130..05cc0ee2 100644 --- a/apps/draupnir/test/integration/manualLaunchScript.ts +++ b/apps/draupnir/test/integration/manualLaunchScript.ts @@ -26,6 +26,7 @@ void (async () => { config.roomStateBackingStore.enabled ?? false, }), allowSafeMode: true, + deleteManagementRoomAliasOnStart: false, }); await draupnirClient()?.start(); await toggle.encryptionInitialized(); diff --git a/apps/draupnir/test/integration/mjolnirSetupUtils.ts b/apps/draupnir/test/integration/mjolnirSetupUtils.ts index aa98198d..429f6d42 100644 --- a/apps/draupnir/test/integration/mjolnirSetupUtils.ts +++ b/apps/draupnir/test/integration/mjolnirSetupUtils.ts @@ -143,10 +143,12 @@ export async function makeBotModeToggle( stores, eraseAccountData, allowSafeMode, + deleteManagementRoomAliasOnStart: deleteAlias, }: { stores: TopLevelStores; eraseAccountData?: boolean; allowSafeMode?: boolean; + deleteManagementRoomAliasOnStart?: boolean; } = { stores: { dispose() {} } } ): Promise { await configureMjolnir(config); @@ -173,10 +175,12 @@ export async function makeBotModeToggle( config.homeserverUrl, await client.getUserId() ); - - await client - .deleteRoomAlias(config.managementRoom) - .catch((_: unknown) => undefined); + // defaults to true + if (deleteAlias === undefined || deleteAlias) { + await client + .deleteRoomAlias(config.managementRoom) + .catch((_: unknown) => undefined); + } await ensureAliasedRoomExists(client, config.managementRoom); const toggle = await DraupnirBotModeToggle.create( client, diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/RoomStateManagerFactory.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/RoomStateManagerFactory.ts index 8742eaf8..ac20f8df 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/RoomStateManagerFactory.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/ClientManagement/RoomStateManagerFactory.ts @@ -259,7 +259,7 @@ export class RoomStateManagerFactory { if (createEvent === undefined) { return false; } - return RoomVersionMirror.isUserAPrivilidgedCreator(editor, createEvent); + return RoomVersionMirror.isUserAPrivilegedCreator(editor, createEvent); }) .map((issuer) => issuer.room.toRoomIDOrAlias()); const editableRoomIDs = this.policyRoomIssuers diff --git a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/PolicyList/PolicyListManager.ts b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/PolicyList/PolicyListManager.ts index 4fa2fc80..1c418284 100644 --- a/packages/matrix-protection-suite-for-matrix-bot-sdk/src/PolicyList/PolicyListManager.ts +++ b/packages/matrix-protection-suite-for-matrix-bot-sdk/src/PolicyList/PolicyListManager.ts @@ -111,7 +111,7 @@ export class BotSDKPolicyRoomManager implements PolicyRoomManager { ); } const isRoomVersionWithPrivilidgedCreators = - RoomVersionMirror.isVersionWithPrivilidgedCreators( + RoomVersionMirror.isVersionWithPrivilegedCreators( clientCapabilities.ok.capabilities["m.room_versions"].default ); const powerLevels: RoomCreateOptions["power_level_content_override"] = { diff --git a/packages/matrix-protection-suite/src/Client/PowerLevelsMirror.ts b/packages/matrix-protection-suite/src/Client/PowerLevelsMirror.ts index 40fdf2c8..587e80a2 100644 --- a/packages/matrix-protection-suite/src/Client/PowerLevelsMirror.ts +++ b/packages/matrix-protection-suite/src/Client/PowerLevelsMirror.ts @@ -11,6 +11,8 @@ export enum PowerLevelPermission { Invite = "invite", Kick = "kick", Redact = "redact", + EventsDefault = "events_default", + StateDefault = "state_default", } export type MissingPermissionsChange = { @@ -56,9 +58,9 @@ export const PowerLevelsMirror = Object.freeze({ content?: PowerLevelsEventContent ): boolean { const userLevel = this.getUserPowerLevel(who, content); - const defaultPowerLevel = + const defaultPermissionLevel = permission === PowerLevelPermission.Invite ? 0 : 50; - const permissionLevel = content?.[permission] ?? defaultPowerLevel; + const permissionLevel = content?.[permission] ?? defaultPermissionLevel; return userLevel >= permissionLevel; }, isUserAbleToSendEvent( @@ -68,7 +70,7 @@ export const PowerLevelsMirror = Object.freeze({ ): boolean { return ( this.getUserPowerLevel(who, content) >= - this.getStatePowerLevel(eventType, content) + this.getEventPowerLevel(eventType, content) ); }, missingPermissions( @@ -166,7 +168,7 @@ export const PowerLevelsMirror = Object.freeze({ isNewlyAddedRoom?: boolean; } ): MissingPermissionsChange { - if (RoomVersionMirror.isUserAPrivilidgedCreator(userID, createEvent)) { + if (RoomVersionMirror.isUserAPrivilegedCreator(userID, createEvent)) { return { missingStatePermissions: [], missingPermissions: [], diff --git a/packages/matrix-protection-suite/src/MatrixTypes/CreateRoom.ts b/packages/matrix-protection-suite/src/MatrixTypes/CreateRoom.ts index 678953fe..63776f69 100644 --- a/packages/matrix-protection-suite/src/MatrixTypes/CreateRoom.ts +++ b/packages/matrix-protection-suite/src/MatrixTypes/CreateRoom.ts @@ -172,11 +172,11 @@ export const RoomCreateEvent = Type.Intersect([ }), ]); -// FIXME: SHouldn't the prividliged creators function return a result error? +// FIXME: SHouldn't the privileged creators function return a result error? // i think so, but it just depends how the permission calculation system // uses it and whether it supports feeding errors back. export const RoomVersionMirror = Object.freeze({ - isVersionWithPrivilidgedCreators(versionSpecifier: string): boolean { + isVersionWithPrivilegedCreators(versionSpecifier: string): boolean { const integerResult = (() => { try { return Ok(parseInt(versionSpecifier, 10)); @@ -191,11 +191,11 @@ export const RoomVersionMirror = Object.freeze({ return false; // unknown room version. } if (integerResult.ok >= 12) { - return true; // versions below 12 and abovehave privilidged creators. + return true; // versions 12 and above have privileged creators. } return false; }, - isUserAPrivilidgedCreator( + isUserAPrivilegedCreator( userID: StringUserID, creationEvent: RoomCreateEvent ): boolean { @@ -203,7 +203,7 @@ export const RoomVersionMirror = Object.freeze({ return false; } if ( - !this.isVersionWithPrivilidgedCreators(creationEvent.content.room_version) + !this.isVersionWithPrivilegedCreators(creationEvent.content.room_version) ) { return false; } @@ -215,10 +215,10 @@ export const RoomVersionMirror = Object.freeze({ } return false; }, - priviligedCreators(creationEvent: RoomCreateEvent): StringUserID[] { + privilegedCreators(creationEvent: RoomCreateEvent): StringUserID[] { if ( creationEvent.content.room_version === undefined || - !this.isVersionWithPrivilidgedCreators(creationEvent.content.room_version) + !this.isVersionWithPrivilegedCreators(creationEvent.content.room_version) ) { return [creationEvent.sender]; } diff --git a/packages/matrix-protection-suite/src/Protection/ProtectedRoomsSet.ts b/packages/matrix-protection-suite/src/Protection/ProtectedRoomsSet.ts index 606ba267..5c00a13d 100644 --- a/packages/matrix-protection-suite/src/Protection/ProtectedRoomsSet.ts +++ b/packages/matrix-protection-suite/src/Protection/ProtectedRoomsSet.ts @@ -228,7 +228,7 @@ export class StandardProtectedRoomsSet implements ProtectedRoomsSet { previousPowerLevels: PowerLevelsEventContent | undefined ): void { // prividliged creators never change and always have permission. - if (RoomVersionMirror.isUserAPrivilidgedCreator(this.userID, createEvent)) { + if (RoomVersionMirror.isUserAPrivilegedCreator(this.userID, createEvent)) { return; } const missingPermissionsInfo: ProtectionPermissionsChange[] = [];