2019-09-27 17:15:10 -04:00
/ *
2021-07-01 17:11:27 -04:00
Copyright 2019 - 2021 The Matrix . org Foundation C . I . C .
2019-09-27 17:15:10 -04:00
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 .
* /
2021-07-01 17:11:27 -04:00
import {
extractRequestError ,
LogLevel ,
LogService ,
2021-09-27 10:52:28 -04:00
MembershipEvent ,
2021-07-01 17:11:27 -04:00
} from "matrix-bot-sdk" ;
2021-10-07 08:42:08 -04:00
2022-09-29 09:49:09 -04:00
import { ALL_RULE_TYPES as ALL_BAN_LIST_RULE_TYPES } from "./models/ListRule" ;
2019-09-27 17:15:10 -04:00
import { COMMAND_PREFIX , handleCommand } from "./commands/CommandHandler" ;
2021-09-14 09:36:53 -04:00
import { UnlistedUserRedactionQueue } from "./queues/UnlistedUserRedactionQueue" ;
2022-02-02 07:43:05 -05:00
import { htmlEscape } from "./utils" ;
2021-11-09 07:15:49 -05:00
import { ReportManager } from "./report/ReportManager" ;
2022-07-04 10:06:36 -04:00
import { ReportPoller } from "./report/ReportPoller" ;
2021-10-07 08:42:08 -04:00
import { WebAPIs } from "./webapis/WebAPIs" ;
2021-10-22 04:47:05 -04:00
import RuleServer from "./models/RuleServer" ;
2022-06-08 05:49:43 -04:00
import { ThrottlingQueue } from "./queues/ThrottlingQueue" ;
2022-12-07 12:00:05 -05:00
import { getDefaultConfig , IConfig } from "./config" ;
2022-12-21 13:32:27 -05:00
import { PolicyListManager } from "./models/PolicyList" ;
2022-10-13 12:19:32 -04:00
import { ProtectedRoomsSet } from "./ProtectedRoomsSet" ;
2022-09-29 09:49:09 -04:00
import ManagementRoomOutput from "./ManagementRoomOutput" ;
import { ProtectionManager } from "./protections/ProtectionManager" ;
import { RoomMemberManager } from "./RoomMembers" ;
2022-10-14 07:30:26 -04:00
import ProtectedRoomsConfig from "./ProtectedRoomsConfig" ;
2022-12-06 12:17:40 -05:00
import { MatrixEmitter , MatrixSendClient } from "./MatrixEmitter" ;
2023-01-05 02:37:54 -05:00
import { OpenMetrics } from "./webapis/OpenMetrics" ;
2022-02-15 10:44:41 -05:00
2019-10-04 23:22:18 -04:00
export const STATE_NOT_STARTED = "not_started" ;
export const STATE_CHECKING_PERMISSIONS = "checking_permissions" ;
export const STATE_SYNCING = "syncing" ;
export const STATE_RUNNING = "running" ;
2022-07-04 10:06:36 -04:00
/ * *
* Synapse will tell us where we last got to on polling reports , so we need
* to store that for pagination on further polls
* /
export const REPORT_POLL_EVENT_TYPE = "org.matrix.mjolnir.report_poll" ;
2019-10-08 15:58:31 -04:00
2019-09-27 17:15:10 -04:00
export class Mjolnir {
2019-09-27 18:04:08 -04:00
private displayName : string ;
private localpart : string ;
2019-10-04 23:22:18 -04:00
private currentState : string = STATE_NOT_STARTED ;
2022-03-07 05:34:25 -05:00
public readonly roomJoins : RoomMemberManager ;
2021-09-15 06:06:03 -04:00
/ * *
* This is for users who are not listed on a watchlist ,
* but have been flagged by the automatic spam detection as suispicous
* /
2021-09-14 09:36:53 -04:00
private unlistedUserRedactionQueue = new UnlistedUserRedactionQueue ( ) ;
2022-10-14 07:30:26 -04:00
private protectedRoomsConfig : ProtectedRoomsConfig ;
2022-10-13 12:19:32 -04:00
public readonly protectedRoomsTracker : ProtectedRoomsSet ;
2021-10-07 08:42:08 -04:00
private webapis : WebAPIs ;
2023-01-05 02:37:54 -05:00
private openMetrics : OpenMetrics ;
2022-06-08 05:49:43 -04:00
public taskQueue : ThrottlingQueue ;
2022-08-09 05:57:38 -04:00
/ * *
2022-09-29 09:49:09 -04:00
* Reporting back to the management room .
2022-08-09 05:57:38 -04:00
* /
2022-09-29 09:49:09 -04:00
public readonly managementRoomOutput : ManagementRoomOutput ;
2022-07-04 10:06:36 -04:00
/ *
* Config - enabled polling of reports in Synapse , so Mjolnir can react to reports
* /
private reportPoller? : ReportPoller ;
2022-09-29 09:49:09 -04:00
/ * *
* Store the protections being used by Mjolnir .
* /
public readonly protectionManager : ProtectionManager ;
/ * *
* Handle user reports from the homeserver .
* /
public readonly reportManager : ReportManager ;
2022-12-21 13:32:27 -05:00
public readonly policyListManager : PolicyListManager ;
2021-09-27 10:52:28 -04:00
/ * *
* Adds a listener to the client that will automatically accept invitations .
2022-12-06 12:17:40 -05:00
* @param { MatrixSendClient } client
2021-09-27 10:52:28 -04:00
* @param options By default accepts invites from anyone .
* @param { string } options . managementRoom The room to report ignored invitations to if ` recordIgnoredInvites ` is true .
* @param { boolean } options . recordIgnoredInvites Whether to report invites that will be ignored to the ` managementRoom ` .
* @param { boolean } options . autojoinOnlyIfManager Whether to only accept an invitation by a user present in the ` managementRoom ` .
2022-08-17 05:05:23 -04:00
* @param { string } options . acceptInvitesFromSpace A space of users to accept invites from , ignores invites form users not in this space .
2021-09-27 10:52:28 -04:00
* /
2022-12-06 12:17:40 -05:00
private static addJoinOnInviteListener ( mjolnir : Mjolnir , client : MatrixSendClient , options : { [ key : string ] : any } ) {
mjolnir . matrixEmitter . on ( "room.invite" , async ( roomId : string , inviteEvent : any ) = > {
2021-09-27 10:52:28 -04:00
const membershipEvent = new MembershipEvent ( inviteEvent ) ;
const reportInvite = async ( ) = > {
if ( ! options . recordIgnoredInvites ) return ; // Nothing to do
2022-01-17 11:24:12 -05:00
await client . sendMessage ( mjolnir . managementRoomId , {
2021-09-27 10:52:28 -04:00
msgtype : "m.text" ,
body : ` ${ membershipEvent . sender } has invited me to ${ roomId } but the config prevents me from accepting the invitation. `
+ ` If you would like this room protected, use "!mjolnir rooms add ${ roomId } " so I can accept the invite. ` ,
format : "org.matrix.custom.html" ,
formatted_body : ` ${ htmlEscape ( membershipEvent . sender ) } has invited me to ${ htmlEscape ( roomId ) } but the config prevents me from `
+ ` accepting the invitation. If you would like this room protected, use <code>!mjolnir rooms add ${ htmlEscape ( roomId ) } </code> `
+ ` so I can accept the invite. ` ,
} ) ;
} ;
if ( options . autojoinOnlyIfManager ) {
2022-01-17 11:24:12 -05:00
const managers = await client . getJoinedRoomMembers ( mjolnir . managementRoomId ) ;
2021-09-27 10:52:28 -04:00
if ( ! managers . includes ( membershipEvent . sender ) ) return reportInvite ( ) ; // ignore invite
2023-01-05 05:12:48 -05:00
} else if ( options . acceptInvitesFromSpace ) {
2022-08-17 05:05:23 -04:00
const spaceId = await client . resolveRoom ( options . acceptInvitesFromSpace ) ;
const spaceUserIds = await client . getJoinedRoomMembers ( spaceId )
. catch ( async e = > {
if ( e . body ? . errcode === "M_FORBIDDEN" ) {
2022-09-29 09:49:09 -04:00
await mjolnir . managementRoomOutput . logMessage ( LogLevel . ERROR , 'Mjolnir' , ` Mjolnir is not in the space configured for acceptInvitesFromSpace, did you invite it? ` ) ;
2022-08-17 05:05:23 -04:00
await client . joinRoom ( spaceId ) ;
return await client . getJoinedRoomMembers ( spaceId ) ;
} else {
return Promise . reject ( e ) ;
}
} ) ;
if ( ! spaceUserIds . includes ( membershipEvent . sender ) ) return reportInvite ( ) ; // ignore invite
2021-09-27 10:52:28 -04:00
}
return client . joinRoom ( roomId ) ;
} ) ;
}
/ * *
* Create a new Mjolnir instance from a client and the options in the configuration file , ready to be started .
2022-12-06 12:17:40 -05:00
* @param { MatrixSendClient } client The client for Mjolnir to use .
2021-09-27 10:52:28 -04:00
* @returns A new Mjolnir instance that can be started without further setup .
* /
2022-12-06 12:17:40 -05:00
static async setupMjolnirFromConfig ( client : MatrixSendClient , matrixEmitter : MatrixEmitter , config : IConfig ) : Promise < Mjolnir > {
2022-12-07 12:00:05 -05:00
if ( ! config . autojoinOnlyIfManager && config . acceptInvitesFromSpace === getDefaultConfig ( ) . acceptInvitesFromSpace ) {
2023-01-05 05:12:48 -05:00
throw new TypeError ( "`autojoinOnlyIfManager` has been disabled but you have not set `acceptInvitesFromSpace`. Please make it empty to accept invites from everywhere or give it a namespace alias or room id." ) ;
2022-12-07 12:00:05 -05:00
}
2021-09-27 10:52:28 -04:00
const joinedRooms = await client . getJoinedRooms ( ) ;
// Ensure we're also in the management room
LogService . info ( "index" , "Resolving management room..." ) ;
const managementRoomId = await client . resolveRoom ( config . managementRoom ) ;
if ( ! joinedRooms . includes ( managementRoomId ) ) {
2022-01-17 11:24:12 -05:00
await client . joinRoom ( config . managementRoom ) ;
2021-09-27 10:52:28 -04:00
}
2021-10-22 04:47:05 -04:00
const ruleServer = config . web . ruleServer ? new RuleServer ( ) : null ;
2022-12-21 13:32:27 -05:00
const mjolnir = new Mjolnir ( client , await client . getUserId ( ) , matrixEmitter , managementRoomId , config , ruleServer ) ;
2022-09-29 09:49:09 -04:00
await mjolnir . managementRoomOutput . logMessage ( LogLevel . INFO , "index" , "Mjolnir is starting up. Use !mjolnir to query status." ) ;
2022-01-17 11:24:12 -05:00
Mjolnir . addJoinOnInviteListener ( mjolnir , client , config ) ;
return mjolnir ;
2021-09-27 10:52:28 -04:00
}
2019-09-27 17:15:10 -04:00
constructor (
2022-12-06 12:17:40 -05:00
public readonly client : MatrixSendClient ,
2022-09-29 09:49:09 -04:00
private readonly clientUserId : string ,
2022-12-06 12:17:40 -05:00
public readonly matrixEmitter : MatrixEmitter ,
2022-01-17 11:24:12 -05:00
public readonly managementRoomId : string ,
2022-08-09 06:29:27 -04:00
public readonly config : IConfig ,
2021-10-22 04:47:05 -04:00
// Combines the rules from ban lists so they can be served to a homeserver module or another consumer.
2022-07-26 15:47:26 -04:00
public readonly ruleServer : RuleServer | null ,
2019-09-27 17:15:10 -04:00
) {
2022-10-14 07:30:26 -04:00
this . protectedRoomsConfig = new ProtectedRoomsConfig ( client ) ;
2022-12-21 13:32:27 -05:00
this . policyListManager = new PolicyListManager ( this ) ;
2020-02-18 19:06:27 -05:00
2021-10-07 08:42:08 -04:00
// Setup bot.
2022-12-06 12:17:40 -05:00
matrixEmitter . on ( "room.event" , this . handleEvent . bind ( this ) ) ;
2019-09-27 17:15:10 -04:00
2022-12-06 12:17:40 -05:00
matrixEmitter . on ( "room.message" , async ( roomId , event ) = > {
2022-01-17 11:24:12 -05:00
if ( roomId !== this . managementRoomId ) return ;
2019-09-27 17:15:10 -04:00
if ( ! event [ 'content' ] ) return ;
const content = event [ 'content' ] ;
2019-09-27 18:04:08 -04:00
if ( content [ 'msgtype' ] === "m.text" && content [ 'body' ] ) {
2020-02-12 17:27:27 -05:00
const prefixes = [
COMMAND_PREFIX ,
this . localpart + ":" ,
this . displayName + ":" ,
await client . getUserId ( ) + ":" ,
2020-02-18 15:46:31 -05:00
this . localpart + " " ,
this . displayName + " " ,
await client . getUserId ( ) + " " ,
2020-02-12 17:27:27 -05:00
. . . config . commands . additionalPrefixes . map ( p = > ` ! ${ p } ` ) ,
. . . config . commands . additionalPrefixes . map ( p = > ` ${ p } : ` ) ,
2020-02-18 15:46:31 -05:00
. . . config . commands . additionalPrefixes . map ( p = > ` ${ p } ` ) ,
2020-02-12 17:27:27 -05:00
. . . config . commands . additionalPrefixes ,
] ;
if ( config . commands . allowNoPrefix ) prefixes . push ( "!" ) ;
2021-06-14 09:43:50 -04:00
const prefixUsed = prefixes . find ( p = > content [ 'body' ] . toLowerCase ( ) . startsWith ( p . toLowerCase ( ) ) ) ;
2019-09-27 22:07:16 -04:00
if ( ! prefixUsed ) return ;
// rewrite the event body to make the prefix uniform (in case the bot has spaces in its display name)
2020-02-12 17:27:27 -05:00
let restOfBody = content [ 'body' ] . substring ( prefixUsed . length ) ;
if ( ! restOfBody . startsWith ( " " ) ) restOfBody = ` ${ restOfBody } ` ;
event [ 'content' ] [ 'body' ] = COMMAND_PREFIX + restOfBody ;
2019-11-06 20:46:49 -05:00
LogService . info ( "Mjolnir" , ` Command being run by ${ event [ 'sender' ] } : ${ event [ 'content' ] [ 'body' ] } ` ) ;
2019-09-27 18:04:08 -04:00
2023-04-03 16:03:46 -04:00
client . sendReadReceipt ( roomId , event [ 'event_id' ] ) . catch ( ( e : any ) = > {
LogService . warn ( "Mjolnir" , "Error sending read receipt: " , e ) ;
} ) ;
2019-09-27 17:15:10 -04:00
return handleCommand ( roomId , event , this ) ;
}
} ) ;
2019-09-27 18:04:08 -04:00
2022-12-06 12:17:40 -05:00
matrixEmitter . on ( "room.join" , ( roomId : string , event : any ) = > {
2020-01-21 17:19:03 -05:00
LogService . info ( "Mjolnir" , ` Joined ${ roomId } ` ) ;
return this . resyncJoinedRooms ( ) ;
} ) ;
2022-12-06 12:17:40 -05:00
matrixEmitter . on ( "room.leave" , ( roomId : string , event : any ) = > {
2020-01-21 17:19:03 -05:00
LogService . info ( "Mjolnir" , ` Left ${ roomId } ` ) ;
return this . resyncJoinedRooms ( ) ;
} ) ;
2019-09-27 18:04:08 -04:00
client . getUserId ( ) . then ( userId = > {
this . localpart = userId . split ( ':' ) [ 0 ] . substring ( 1 ) ;
return client . getUserProfile ( userId ) ;
} ) . then ( profile = > {
if ( profile [ 'displayname' ] ) {
this . displayName = profile [ 'displayname' ] ;
}
2020-01-21 15:43:36 -05:00
} ) ;
2021-10-07 08:42:08 -04:00
// Setup Web APIs
console . log ( "Creating Web APIs" ) ;
2022-09-29 09:49:09 -04:00
this . reportManager = new ReportManager ( this ) ;
this . webapis = new WebAPIs ( this . reportManager , this . config , this . ruleServer ) ;
2022-07-04 10:06:36 -04:00
if ( config . pollReports ) {
2022-09-29 09:49:09 -04:00
this . reportPoller = new ReportPoller ( this , this . reportManager ) ;
2022-07-04 10:06:36 -04:00
}
2023-01-05 02:37:54 -05:00
this . openMetrics = new OpenMetrics ( this . config ) ;
2022-03-07 05:34:25 -05:00
// Setup join/leave listener
2022-12-06 12:17:40 -05:00
this . roomJoins = new RoomMemberManager ( this . matrixEmitter ) ;
2022-06-08 05:49:43 -04:00
this . taskQueue = new ThrottlingQueue ( this , config . backgroundDelayMS ) ;
2022-09-29 09:49:09 -04:00
this . protectionManager = new ProtectionManager ( this ) ;
this . managementRoomOutput = new ManagementRoomOutput ( managementRoomId , client , config ) ;
2023-01-05 02:36:35 -05:00
this . protectedRoomsTracker = new ProtectedRoomsSet (
client ,
clientUserId ,
managementRoomId ,
this . managementRoomOutput ,
this . protectionManager ,
config ) ;
2019-09-27 17:15:10 -04:00
}
2019-10-04 23:22:18 -04:00
public get state ( ) : string {
return this . currentState ;
}
2021-09-15 06:06:03 -04:00
/ * *
2021-09-16 06:46:44 -04:00
* Returns the handler to flag a user for redaction , removing any future messages that they send .
* Typically this is used by the flooding or image protection on users that have not been banned from a list yet .
* It cannot used to redact any previous messages the user has sent , in that cas you should use the ` EventRedactionQueue ` .
2021-09-15 06:06:03 -04:00
* /
2021-09-14 09:36:53 -04:00
public get unlistedUserRedactionHandler ( ) : UnlistedUserRedactionQueue {
return this . unlistedUserRedactionQueue ;
2019-12-09 21:15:51 -05:00
}
2021-10-07 08:42:08 -04:00
/ * *
* Start Mjölnir .
* /
public async start() {
2023-01-05 02:37:54 -05:00
LogService . info ( "Mjolnir" , "Starting Mjolnir instance" ) ;
2021-10-07 08:42:08 -04:00
try {
// Start the web server.
console . log ( "Starting web server" ) ;
await this . webapis . start ( ) ;
2022-07-04 10:06:36 -04:00
if ( this . reportPoller ) {
let reportPollSetting : { from : number } = { from : 0 } ;
try {
reportPollSetting = await this . client . getAccountData ( REPORT_POLL_EVENT_TYPE ) ;
} catch ( err ) {
if ( err . body ? . errcode !== "M_NOT_FOUND" ) {
throw err ;
} else {
2022-09-29 09:49:09 -04:00
this . managementRoomOutput . logMessage ( LogLevel . INFO , "Mjolnir@startup" , "report poll setting does not exist yet" ) ;
2022-07-04 10:06:36 -04:00
}
}
this . reportPoller . start ( reportPollSetting . from ) ;
}
2023-01-05 02:37:54 -05:00
await this . openMetrics . start ( ) ;
2021-10-07 08:42:08 -04:00
// Load the state.
2019-10-04 23:22:34 -04:00
this . currentState = STATE_CHECKING_PERMISSIONS ;
2020-02-18 19:06:27 -05:00
2022-09-29 09:49:09 -04:00
await this . managementRoomOutput . logMessage ( LogLevel . DEBUG , "Mjolnir@startup" , "Loading protected rooms..." ) ;
2022-10-14 07:30:26 -04:00
await this . protectedRoomsConfig . loadProtectedRoomsFromConfig ( this . config ) ;
await this . protectedRoomsConfig . loadProtectedRoomsFromAccountData ( ) ;
this . protectedRoomsConfig . getExplicitlyProtectedRooms ( ) . forEach ( this . protectRoom , this ) ;
2022-11-22 10:34:50 -05:00
// We have to build the policy lists before calling `resyncJoinedRooms` otherwise mjolnir will try to protect
// every policy list we are already joined to, as mjolnir will not be able to distinguish them from normal rooms.
2022-12-21 13:32:27 -05:00
await this . policyListManager . init ( ) ;
2022-11-22 10:34:50 -05:00
await this . resyncJoinedRooms ( false ) ;
2022-09-29 09:49:09 -04:00
await this . protectionManager . start ( ) ;
2020-02-18 19:06:27 -05:00
2022-08-09 06:29:27 -04:00
if ( this . config . verifyPermissionsOnStartup ) {
2022-09-29 09:49:09 -04:00
await this . managementRoomOutput . logMessage ( LogLevel . INFO , "Mjolnir@startup" , "Checking permissions..." ) ;
2022-12-08 11:09:55 -05:00
await this . protectedRoomsTracker . verifyPermissions ( ) ;
2020-02-18 19:06:27 -05:00
}
2021-10-07 08:42:08 -04:00
2022-10-14 07:30:26 -04:00
// Start the bot.
2022-12-06 12:17:40 -05:00
await this . matrixEmitter . start ( ) ;
2022-10-14 07:30:26 -04:00
2020-02-18 19:06:27 -05:00
this . currentState = STATE_SYNCING ;
2022-08-09 06:29:27 -04:00
if ( this . config . syncOnStartup ) {
2022-09-29 09:49:09 -04:00
await this . managementRoomOutput . logMessage ( LogLevel . INFO , "Mjolnir@startup" , "Syncing lists..." ) ;
2022-12-08 11:09:55 -05:00
await this . protectedRoomsTracker . syncLists ( ) ;
2019-10-04 23:02:37 -04:00
}
2021-10-07 08:42:08 -04:00
2019-10-04 23:22:34 -04:00
this . currentState = STATE_RUNNING ;
2022-09-29 09:49:09 -04:00
await this . managementRoomOutput . logMessage ( LogLevel . INFO , "Mjolnir@startup" , "Startup complete. Now monitoring rooms." ) ;
2021-10-07 08:42:08 -04:00
} catch ( err ) {
2020-06-12 10:03:08 -04:00
try {
LogService . error ( "Mjolnir" , "Error during startup:" ) ;
2021-07-01 17:11:27 -04:00
LogService . error ( "Mjolnir" , extractRequestError ( err ) ) ;
2022-04-01 11:40:10 -04:00
this . stop ( ) ;
2022-09-29 09:49:09 -04:00
await this . managementRoomOutput . logMessage ( LogLevel . ERROR , "Mjolnir@startup" , "Startup failed due to error - see console" ) ;
2020-06-12 10:03:08 -04:00
} catch ( e ) {
2022-04-01 11:40:10 -04:00
LogService . error ( "Mjolnir" , ` Failed to report startup error to the management room: ${ e } ` ) ;
2020-06-12 10:03:08 -04:00
}
2022-11-30 10:06:02 -05:00
throw err ;
2021-10-07 08:42:08 -04:00
}
2019-09-27 17:15:10 -04:00
}
2021-09-27 10:52:28 -04:00
/ * *
* Stop Mjolnir from syncing and processing commands .
* /
2021-09-22 12:59:11 -04:00
public stop() {
2021-09-30 10:32:20 -04:00
LogService . info ( "Mjolnir" , "Stopping Mjolnir..." ) ;
2022-12-06 12:17:40 -05:00
this . matrixEmitter . stop ( ) ;
2021-10-07 08:42:08 -04:00
this . webapis . stop ( ) ;
2022-07-04 10:06:36 -04:00
this . reportPoller ? . stop ( ) ;
2023-01-05 02:37:54 -05:00
this . openMetrics . stop ( ) ;
2021-09-22 12:59:11 -04:00
}
2022-10-14 15:01:26 -04:00
/ * *
* Rooms that mjolnir is configured to explicitly protect .
* Do not use to access all of the rooms that mjolnir protects .
* FIXME : In future ProtectedRoomsSet on this mjolnir should not be public and should also be accessed via a delegator method .
* /
public get explicitlyProtectedRooms ( ) : string [ ] {
return this . protectedRoomsConfig . getExplicitlyProtectedRooms ( )
}
2022-10-14 07:30:26 -04:00
/ * *
* Explicitly protect this room , adding it to the account data .
* Should NOT be used to protect a room to implement e . g . ` config.protectAllJoinedRooms ` ,
* use ` protectRoom ` instead .
* @param roomId The room to be explicitly protected by mjolnir and persisted in config .
* /
2020-01-21 15:43:36 -05:00
public async addProtectedRoom ( roomId : string ) {
2022-10-14 07:30:26 -04:00
await this . protectedRoomsConfig . addProtectedRoom ( roomId ) ;
this . protectRoom ( roomId ) ;
}
2020-02-18 19:06:27 -05:00
2022-10-14 07:30:26 -04:00
/ * *
* Protect the room , but do not persist it to the account data .
* @param roomId The room to protect .
* /
private protectRoom ( roomId : string ) : void {
this . protectedRoomsTracker . addProtectedRoom ( roomId ) ;
this . roomJoins . addRoom ( roomId ) ;
2020-01-21 15:43:36 -05:00
}
2022-10-14 07:30:26 -04:00
/ * *
* Remove a room from the explicitly protect set of rooms that is persisted to account data .
* Should NOT be used to remove a room that we have left , e . g . when implementing ` config.protectAllJoinedRooms ` ,
* use ` unprotectRoom ` instead .
* @param roomId The room to remove from account data and stop protecting .
* /
2020-01-21 15:43:36 -05:00
public async removeProtectedRoom ( roomId : string ) {
2022-10-14 07:30:26 -04:00
await this . protectedRoomsConfig . removeProtectedRoom ( roomId ) ;
this . unprotectRoom ( roomId ) ;
}
/ * *
* Unprotect a room .
* @param roomId The room to stop protecting .
* /
private unprotectRoom ( roomId : string ) : void {
2022-03-07 05:34:25 -05:00
this . roomJoins . removeRoom ( roomId ) ;
2022-09-29 09:49:09 -04:00
this . protectedRoomsTracker . removeProtectedRoom ( roomId ) ;
2020-01-21 15:43:36 -05:00
}
2022-10-14 07:30:26 -04:00
/ * *
* Resynchronize the protected rooms with rooms that the mjolnir user is joined to .
* This is to implement ` config.protectAllJoinedRooms ` functionality .
* @param withSync Whether to synchronize all protected rooms with the watched policy lists afterwards .
* /
private async resyncJoinedRooms ( withSync = true ) : Promise < void > {
2022-08-09 06:29:27 -04:00
if ( ! this . config . protectAllJoinedRooms ) return ;
2020-01-21 17:19:03 -05:00
2022-10-14 07:30:26 -04:00
// We filter out all policy rooms so that we only protect ones that are
// explicitly protected, so that we don't try to protect lists that we are just watching.
const filterOutManagementAndPolicyRooms = ( roomId : string ) = > {
2022-12-21 13:32:27 -05:00
const policyListIds = this . policyListManager . lists . map ( list = > list . roomId ) ;
2022-10-14 07:30:26 -04:00
return roomId !== this . managementRoomId && ! policyListIds . includes ( roomId ) ;
} ;
const joinedRoomIdsToProtect = new Set ( [
. . . ( await this . client . getJoinedRooms ( ) ) . filter ( filterOutManagementAndPolicyRooms ) ,
// We do this specifically so policy lists that have been explicitly marked as protected
// will be protected.
. . . this . protectedRoomsConfig . getExplicitlyProtectedRooms ( ) ,
] ) ;
const previousRoomIdsProtecting = new Set ( this . protectedRoomsTracker . getProtectedRooms ( ) ) ;
2022-09-29 09:49:09 -04:00
// find every room that we have left (since last time)
2022-10-14 07:30:26 -04:00
for ( const roomId of previousRoomIdsProtecting . keys ( ) ) {
if ( ! joinedRoomIdsToProtect . has ( roomId ) ) {
2022-09-29 09:49:09 -04:00
// Then we have left this room.
2022-10-14 07:30:26 -04:00
this . unprotectRoom ( roomId ) ;
2022-03-07 05:34:25 -05:00
}
2020-01-21 17:19:03 -05:00
}
2022-09-29 09:49:09 -04:00
// find every room that we have joined (since last time).
2022-10-14 07:30:26 -04:00
for ( const roomId of joinedRoomIdsToProtect . keys ( ) ) {
if ( ! previousRoomIdsProtecting . has ( roomId ) ) {
2022-09-29 09:49:09 -04:00
// Then we have joined this room
2022-10-14 07:30:26 -04:00
this . protectRoom ( roomId ) ;
2022-03-07 05:34:25 -05:00
}
2020-01-21 17:19:03 -05:00
}
2020-02-18 19:06:27 -05:00
2020-01-21 17:19:03 -05:00
if ( withSync ) {
2022-12-08 11:09:55 -05:00
await this . protectedRoomsTracker . syncLists ( ) ;
2022-01-25 09:47:50 -05:00
}
}
2019-09-27 17:15:10 -04:00
private async handleEvent ( roomId : string , event : any ) {
2019-11-14 17:59:01 -05:00
// Check for UISI errors
2022-01-17 11:24:12 -05:00
if ( roomId === this . managementRoomId ) {
2019-11-14 17:59:01 -05:00
if ( event [ 'type' ] === 'm.room.message' && event [ 'content' ] && event [ 'content' ] [ 'body' ] ) {
if ( event [ 'content' ] [ 'body' ] === "** Unable to decrypt: The sender's device has not sent us the keys for this message. **" ) {
// UISI
await this . client . unstableApis . addReactionToEvent ( roomId , event [ 'event_id' ] , '⚠' ) ;
await this . client . unstableApis . addReactionToEvent ( roomId , event [ 'event_id' ] , 'UISI' ) ;
await this . client . unstableApis . addReactionToEvent ( roomId , event [ 'event_id' ] , '🚨' ) ;
}
}
}
2019-12-04 21:25:46 -05:00
// Check for updated ban lists before checking protected rooms - the ban lists might be protected
// themselves.
2022-12-21 13:32:27 -05:00
const policyList = this . policyListManager . lists . find ( list = > list . roomId === roomId ) ;
2022-07-26 15:47:26 -04:00
if ( policyList !== undefined ) {
2022-02-15 08:51:20 -05:00
if ( ALL_BAN_LIST_RULE_TYPES . includes ( event [ 'type' ] ) || event [ 'type' ] === 'm.room.redaction' ) {
2022-08-19 08:09:08 -04:00
policyList . updateForEvent ( event . event_id )
2019-12-04 21:25:46 -05:00
}
}
2022-09-29 09:49:09 -04:00
if ( event . sender !== this . clientUserId ) {
this . protectedRoomsTracker . handleEvent ( roomId , event ) ;
2021-11-12 13:35:44 -05:00
}
2019-09-27 17:15:10 -04:00
}
2019-11-14 17:44:13 -05:00
public async isSynapseAdmin ( ) : Promise < boolean > {
try {
const endpoint = ` /_synapse/admin/v1/users/ ${ await this . client . getUserId ( ) } /admin ` ;
const response = await this . client . doRequest ( "GET" , endpoint ) ;
return response [ 'admin' ] ;
} catch ( e ) {
LogService . error ( "Mjolnir" , "Error determining if Mjolnir is a server admin:" ) ;
2021-07-01 17:11:27 -04:00
LogService . error ( "Mjolnir" , extractRequestError ( e ) ) ;
2019-11-14 17:44:13 -05:00
return false ; // assume not
}
}
public async deactivateSynapseUser ( userId : string ) : Promise < any > {
const endpoint = ` /_synapse/admin/v1/deactivate/ ${ userId } ` ;
return await this . client . doRequest ( "POST" , endpoint ) ;
}
2020-02-13 15:56:03 -05:00
2021-07-28 04:56:50 -04:00
public async shutdownSynapseRoom ( roomId : string , message? : string ) : Promise < any > {
const endpoint = ` /_synapse/admin/v1/rooms/ ${ roomId } ` ;
return await this . client . doRequest ( "DELETE" , endpoint , null , {
2020-02-13 15:56:03 -05:00
new_room_user_id : await this . client . getUserId ( ) ,
2021-07-28 04:56:50 -04:00
block : true ,
2021-08-17 11:20:31 -04:00
message : message /* If `undefined`, we'll use Synapse's default message. */
2020-02-13 15:56:03 -05:00
} ) ;
}
2021-09-14 07:17:29 -04:00
2022-03-07 04:14:06 -05:00
/ * *
* Make a user administrator via the Synapse Admin API
* @param roomId the room where the user ( or the bot ) shall be made administrator .
* @param userId optionally specify the user mxID to be made administrator , if not specified the bot mxID will be used .
* @returns The list of errors encountered , for reporting to the management room .
* /
public async makeUserRoomAdmin ( roomId : string , userId? : string ) : Promise < any > {
try {
const endpoint = ` /_synapse/admin/v1/rooms/ ${ roomId } /make_room_admin ` ;
return await this . client . doRequest ( "POST" , endpoint , null , {
user_id : userId || await this . client . getUserId ( ) , /* if not specified make the bot administrator */
} ) ;
} catch ( e ) {
return extractRequestError ( e ) ;
}
}
2019-09-27 17:15:10 -04:00
}
2022-11-30 10:06:02 -05:00