2021-11-19 11:44:48 -05:00
import { strict as assert } from "assert" ;
2022-05-03 07:36:53 -04:00
import { newTestUser } from "./clientHelper" ;
2022-02-15 08:51:20 -05:00
import { LogService , MatrixClient , Permalinks , UserID } from "matrix-bot-sdk" ;
2022-07-26 15:47:26 -04:00
import PolicyList , { ChangeType , ListRuleChange } from "../../src/models/PolicyList" ;
2022-05-03 07:36:53 -04:00
import { ServerAcl } from "../../src/models/ServerAcl" ;
import { getFirstReaction } from "./commands/commandUtils" ;
2022-02-15 08:51:20 -05:00
import { getMessagesByUserIn } from "../../src/utils" ;
2022-08-09 06:29:27 -04:00
import { Mjolnir } from "../../src/Mjolnir" ;
2022-07-26 15:47:26 -04:00
import { ALL_RULE_TYPES , RULE_SERVER , RULE_USER , SERVER_RULE_TYPES } from "../../src/models/ListRule" ;
2021-11-19 11:44:48 -05:00
/ * *
* Create a policy rule in a policy room .
* @param client A matrix client that is logged in
* @param policyRoomId The room id to add the policy to .
* @param policyType The type of policy to add e . g . m . policy . rule . user . ( Use RULE_USER though ) .
* @param entity The entity to ban e . g . @foo : example . org
* @param reason A reason for the rule e . g . 'Wouldn' t stop posting spam links '
2022-02-07 12:02:06 -05:00
* @param template The template to use for the policy rule event .
2021-11-19 11:44:48 -05:00
* @returns The event id of the newly created policy rule .
* /
2022-07-26 15:47:26 -04:00
async function createPolicyRule ( client : MatrixClient , policyRoomId : string , policyType : string , entity : string , reason : string , template = { recommendation : 'm.ban' } ) {
2021-11-19 11:44:48 -05:00
return await client . sendStateEvent ( policyRoomId , policyType , ` rule: ${ entity } ` , {
entity ,
reason ,
2022-02-07 12:02:06 -05:00
. . . template ,
2021-11-19 11:44:48 -05:00
} ) ;
}
2022-07-26 15:47:26 -04:00
describe ( "Test: Updating the PolicyList" , function ( ) {
it ( "Calculates what has changed correctly." , async function ( ) {
2021-11-19 11:44:48 -05:00
this . timeout ( 10000 ) ;
2022-08-09 06:29:27 -04:00
const mjolnir : Mjolnir = this . mjolnir !
2022-08-16 10:51:18 -04:00
const moderator = await newTestUser ( this . config . homeserverUrl , { name : { contains : "moderator" } } ) ;
2022-08-09 06:29:27 -04:00
const banListId = await mjolnir . client . createRoom ( { invite : [ await moderator . getUserId ( ) ] } ) ;
const banList = new PolicyList ( banListId , banListId , mjolnir . client ) ;
2022-08-18 08:09:03 -04:00
await mjolnir . client . setUserPowerLevel ( await moderator . getUserId ( ) , banListId , 100 ) ;
2021-11-19 11:44:48 -05:00
2021-11-22 10:41:12 -05:00
assert . equal ( banList . allRules . length , 0 ) ;
2021-11-19 11:44:48 -05:00
// Test adding a new rule
2022-08-09 06:29:27 -04:00
await createPolicyRule ( mjolnir . client , banListId , RULE_USER , '@added:localhost:9999' , '' ) ;
2021-11-19 11:44:48 -05:00
let changes : ListRuleChange [ ] = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 , 'There should only be one change' ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Added ) ;
2022-08-09 06:29:27 -04:00
assert . equal ( changes [ 0 ] . sender , await mjolnir . client . getUserId ( ) ) ;
2021-11-22 10:41:12 -05:00
assert . equal ( banList . userRules . length , 1 ) ;
assert . equal ( banList . allRules . length , 1 ) ;
2021-11-19 11:44:48 -05:00
// Test modifiying a rule
2022-08-09 06:29:27 -04:00
let originalEventId = await createPolicyRule ( mjolnir . client , banListId , RULE_USER , '@modified:localhost:9999' , '' ) ;
2021-11-19 11:44:48 -05:00
await banList . updateList ( ) ;
2022-08-09 06:29:27 -04:00
let modifyingEventId = await createPolicyRule ( mjolnir . client , banListId , RULE_USER , '@modified:localhost:9999' , 'modified reason' ) ;
2021-11-19 11:44:48 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Modified ) ;
assert . equal ( changes [ 0 ] . previousState [ 'event_id' ] , originalEventId , 'There should be a previous state event for a modified rule' ) ;
2021-11-22 10:41:12 -05:00
assert . equal ( changes [ 0 ] . event [ 'event_id' ] , modifyingEventId ) ;
2022-08-09 06:29:27 -04:00
let modifyingAgainEventId = await createPolicyRule ( mjolnir . client , banListId , RULE_USER , '@modified:localhost:9999' , 'modified again' ) ;
2021-11-22 10:41:12 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Modified ) ;
assert . equal ( changes [ 0 ] . previousState [ 'event_id' ] , modifyingEventId , 'There should be a previous state event for a modified rule' ) ;
assert . equal ( changes [ 0 ] . event [ 'event_id' ] , modifyingAgainEventId ) ;
assert . equal ( banList . userRules . length , 2 , 'There should be two rules, one for @modified:localhost:9999 and one for @added:localhost:9999' ) ;
2021-11-19 11:44:48 -05:00
// Test redacting a rule
2022-08-09 06:29:27 -04:00
const redactThis = await createPolicyRule ( mjolnir . client , banListId , RULE_USER , '@redacted:localhost:9999' , '' ) ;
2021-11-19 11:44:48 -05:00
await banList . updateList ( ) ;
2021-11-22 10:41:12 -05:00
assert . equal ( banList . userRules . filter ( r = > r . entity === '@redacted:localhost:9999' ) . length , 1 ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . client . redactEvent ( banListId , redactThis ) ;
2021-11-19 11:44:48 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Removed ) ;
assert . equal ( changes [ 0 ] . event [ 'event_id' ] , redactThis , 'Should show the new version of the event with redacted content' ) ;
assert . equal ( Object . keys ( changes [ 0 ] . event [ 'content' ] ) . length , 0 , 'Should show the new version of the event with redacted content' ) ;
assert . notEqual ( Object . keys ( changes [ 0 ] . previousState [ 'content' ] ) , 0 , 'Should have a copy of the unredacted state' ) ;
assert . notEqual ( changes [ 0 ] . rule , undefined , 'The previous rule should be present' ) ;
2021-11-22 10:41:12 -05:00
assert . equal ( banList . userRules . filter ( r = > r . entity === '@redacted:localhost:9999' ) . length , 0 , 'The rule should be removed.' ) ;
2021-11-19 11:44:48 -05:00
// Test soft redaction of a rule
const softRedactedEntity = '@softredacted:localhost:9999'
2022-08-09 06:29:27 -04:00
await createPolicyRule ( mjolnir . client , banListId , RULE_USER , softRedactedEntity , '' ) ;
2021-11-19 11:44:48 -05:00
await banList . updateList ( ) ;
2021-11-22 10:41:12 -05:00
assert . equal ( banList . userRules . filter ( r = > r . entity === softRedactedEntity ) . length , 1 ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . client . sendStateEvent ( banListId , RULE_USER , ` rule: ${ softRedactedEntity } ` , { } ) ;
2021-11-19 11:44:48 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Removed ) ;
assert . equal ( Object . keys ( changes [ 0 ] . event [ 'content' ] ) . length , 0 , 'Should show the new version of the event with redacted content' ) ;
assert . notEqual ( Object . keys ( changes [ 0 ] . previousState [ 'content' ] ) , 0 , 'Should have a copy of the unredacted state' ) ;
assert . notEqual ( changes [ 0 ] . rule , undefined , 'The previous rule should be present' ) ;
2021-11-22 10:41:12 -05:00
assert . equal ( banList . userRules . filter ( r = > r . entity === softRedactedEntity ) . length , 0 , 'The rule should have been removed' ) ;
2021-11-19 11:44:48 -05:00
// Now test a double soft redaction just to make sure stuff doesn't explode
2022-08-09 06:29:27 -04:00
await mjolnir . client . sendStateEvent ( banListId , RULE_USER , ` rule: ${ softRedactedEntity } ` , { } ) ;
2021-11-19 11:44:48 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 0 , "It shouldn't detect a double soft redaction as a change, it should be seen as adding an invalid rule." ) ;
2021-11-22 10:41:12 -05:00
assert . equal ( banList . userRules . filter ( r = > r . entity === softRedactedEntity ) . length , 0 , 'The rule should have been removed' ) ;
// Test that different (old) rule types will be modelled as the latest event type.
2022-08-09 06:29:27 -04:00
originalEventId = await createPolicyRule ( mjolnir . client , banListId , 'org.matrix.mjolnir.rule.user' , '@old:localhost:9999' , '' ) ;
2021-11-22 10:41:12 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Added ) ;
assert . equal ( banList . userRules . filter ( r = > r . entity === '@old:localhost:9999' ) . length , 1 ) ;
2022-08-09 06:29:27 -04:00
modifyingEventId = await createPolicyRule ( mjolnir . client , banListId , 'm.room.rule.user' , '@old:localhost:9999' , 'modified reason' ) ;
2021-11-22 10:41:12 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Modified ) ;
assert . equal ( changes [ 0 ] . event [ 'event_id' ] , modifyingEventId ) ;
assert . equal ( changes [ 0 ] . previousState [ 'event_id' ] , originalEventId , 'There should be a previous state event for a modified rule' ) ;
assert . equal ( banList . userRules . filter ( r = > r . entity === '@old:localhost:9999' ) . length , 1 ) ;
2022-08-09 06:29:27 -04:00
modifyingAgainEventId = await createPolicyRule ( mjolnir . client , banListId , RULE_USER , '@old:localhost:9999' , 'changes again' ) ;
2021-11-22 10:41:12 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Modified ) ;
assert . equal ( changes [ 0 ] . event [ 'event_id' ] , modifyingAgainEventId ) ;
assert . equal ( changes [ 0 ] . previousState [ 'event_id' ] , modifyingEventId , 'There should be a previous state event for a modified rule' ) ;
assert . equal ( banList . userRules . filter ( r = > r . entity === '@old:localhost:9999' ) . length , 1 ) ;
} )
2022-07-26 15:47:26 -04:00
it ( "Will remove rules with old types when they are 'soft redacted' with a different but more recent event type." , async function ( ) {
2021-11-22 10:41:12 -05:00
this . timeout ( 3000 ) ;
2022-08-09 06:29:27 -04:00
const mjolnir : Mjolnir = this . mjolnir !
2022-08-16 10:51:18 -04:00
const moderator = await newTestUser ( this . config . homeserverUrl , { name : { contains : "moderator" } } ) ;
2022-08-09 06:29:27 -04:00
const banListId = await mjolnir . client . createRoom ( { invite : [ await moderator . getUserId ( ) ] } ) ;
const banList = new PolicyList ( banListId , banListId , mjolnir . client ) ;
2022-08-18 08:09:03 -04:00
await mjolnir . client . setUserPowerLevel ( await moderator . getUserId ( ) , banListId , 100 ) ;
2021-11-22 10:41:12 -05:00
const entity = '@old:localhost:9999' ;
2022-08-09 06:29:27 -04:00
let originalEventId = await createPolicyRule ( mjolnir . client , banListId , 'm.room.rule.user' , entity , '' ) ;
2021-11-22 10:41:12 -05:00
let changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Added ) ;
assert . equal ( banList . userRules . filter ( rule = > rule . entity === entity ) . length , 1 , 'There should be a rule stored that we just added...' )
2022-08-09 06:29:27 -04:00
let softRedactingEventId = await mjolnir . client . sendStateEvent ( banListId , RULE_USER , ` rule: ${ entity } ` , { } ) ;
2021-11-22 10:41:12 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Removed ) ;
assert . equal ( changes [ 0 ] . event [ 'event_id' ] , softRedactingEventId ) ;
assert . equal ( changes [ 0 ] . previousState [ 'event_id' ] , originalEventId , 'There should be a previous state event for a modified rule' ) ;
assert . equal ( banList . userRules . filter ( rule = > rule . entity === entity ) . length , 0 , 'The rule should no longer be stored.' ) ;
} )
2022-07-26 15:47:26 -04:00
it ( "A rule of the most recent type won't be deleted when an old rule is deleted for the same entity." , async function ( ) {
2022-08-09 06:29:27 -04:00
const mjolnir : Mjolnir = this . mjolnir !
2022-08-16 10:51:18 -04:00
const moderator = await newTestUser ( this . config . homeserverUrl , { name : { contains : "moderator" } } ) ;
2022-08-09 06:29:27 -04:00
const banListId = await mjolnir . client . createRoom ( { invite : [ await moderator . getUserId ( ) ] } ) ;
const banList = new PolicyList ( banListId , banListId , mjolnir . client ) ;
2022-08-18 08:09:03 -04:00
await mjolnir . client . setUserPowerLevel ( await moderator . getUserId ( ) , banListId , 100 ) ;
2021-11-22 10:41:12 -05:00
const entity = '@old:localhost:9999' ;
2022-08-09 06:29:27 -04:00
let originalEventId = await createPolicyRule ( mjolnir . client , banListId , 'm.room.rule.user' , entity , '' ) ;
2021-11-22 10:41:12 -05:00
let changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Added ) ;
assert . equal ( banList . userRules . filter ( rule = > rule . entity === entity ) . length , 1 , 'There should be a rule stored that we just added...' )
2022-08-09 06:29:27 -04:00
let updatedEventId = await createPolicyRule ( mjolnir . client , banListId , RULE_USER , entity , '' ) ;
2021-11-22 10:41:12 -05:00
changes = await banList . updateList ( ) ;
// If in the future you change this and it fails, it's really subjective whether this constitutes a modification, since the only thing that has changed
// is the rule type. The actual content is identical.
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Modified ) ;
assert . equal ( changes [ 0 ] . event [ 'event_id' ] , updatedEventId ) ;
assert . equal ( changes [ 0 ] . previousState [ 'event_id' ] , originalEventId , 'There should be a previous state event for a modified rule' ) ;
assert . equal ( banList . userRules . filter ( rule = > rule . entity === entity ) . length , 1 , 'Only the latest version of the rule gets returned.' ) ;
// Now we delete the old version of the rule without consequence.
2022-08-09 06:29:27 -04:00
await mjolnir . client . sendStateEvent ( banListId , 'm.room.rule.user' , ` rule: ${ entity } ` , { } ) ;
2021-11-22 10:41:12 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 0 ) ;
assert . equal ( banList . userRules . filter ( rule = > rule . entity === entity ) . length , 1 , 'The rule should still be active.' ) ;
// And we can still delete the new version of the rule.
2022-08-09 06:29:27 -04:00
let softRedactingEventId = await mjolnir . client . sendStateEvent ( banListId , RULE_USER , ` rule: ${ entity } ` , { } ) ;
2021-11-22 10:41:12 -05:00
changes = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Removed ) ;
assert . equal ( changes [ 0 ] . event [ 'event_id' ] , softRedactingEventId ) ;
assert . equal ( changes [ 0 ] . previousState [ 'event_id' ] , updatedEventId , 'There should be a previous state event for a modified rule' ) ;
assert . equal ( banList . userRules . filter ( rule = > rule . entity === entity ) . length , 0 , 'The rule should no longer be stored.' ) ;
2021-11-19 11:44:48 -05:00
} )
2022-08-09 06:29:27 -04:00
it ( 'Test: PolicyList Supports all entity types.' , async function ( ) {
const mjolnir : Mjolnir = this . mjolnir !
const banListId = await mjolnir . client . createRoom ( ) ;
const banList = new PolicyList ( banListId , banListId , mjolnir . client ) ;
2022-02-07 12:02:06 -05:00
for ( let i = 0 ; i < ALL_RULE_TYPES . length ; i ++ ) {
2022-08-09 06:29:27 -04:00
await createPolicyRule ( mjolnir . client , banListId , ALL_RULE_TYPES [ i ] , ` * ${ i } * ` , '' ) ;
2022-02-07 12:02:06 -05:00
}
let changes : ListRuleChange [ ] = await banList . updateList ( ) ;
assert . equal ( changes . length , ALL_RULE_TYPES . length ) ;
assert . equal ( banList . allRules . length , ALL_RULE_TYPES . length ) ;
} )
2021-11-19 11:44:48 -05:00
} ) ;
2022-02-07 12:02:06 -05:00
2022-08-09 06:29:27 -04:00
describe ( 'Test: We do not respond to recommendations other than m.ban in the PolicyList' , function ( ) {
2022-07-26 15:47:26 -04:00
it ( 'Will not respond to a rule that has a different recommendation to m.ban (or the unstable equivalent).' , async function ( ) {
2022-08-09 06:29:27 -04:00
const mjolnir : Mjolnir = this . mjolnir !
const banListId = await mjolnir . client . createRoom ( ) ;
const banList = new PolicyList ( banListId , banListId , mjolnir . client ) ;
await createPolicyRule ( mjolnir . client , banListId , RULE_SERVER , 'exmaple.org' , '' , { recommendation : 'something that is not m.ban' } ) ;
2022-02-07 12:02:06 -05:00
let changes : ListRuleChange [ ] = await banList . updateList ( ) ;
assert . equal ( changes . length , 1 , 'There should only be one change' ) ;
assert . equal ( changes [ 0 ] . changeType , ChangeType . Added ) ;
2022-08-09 06:29:27 -04:00
assert . equal ( changes [ 0 ] . sender , await mjolnir . client . getUserId ( ) ) ;
2022-02-07 12:02:06 -05:00
// We really don't want things that aren't m.ban to end up being accessible in these APIs.
2022-07-26 15:47:26 -04:00
assert . equal ( banList . serverRules . length , 0 , ` We should have an empty serverRules, got ${ JSON . stringify ( banList . serverRules ) } ` ) ;
assert . equal ( banList . allRules . length , 0 , ` We should have an empty allRules, got ${ JSON . stringify ( banList . allRules ) } ` ) ;
2022-02-07 12:02:06 -05:00
} )
} )
2022-07-26 15:47:26 -04:00
describe ( 'Test: We will not be able to ban ourselves via ACL.' , function ( ) {
it ( 'We do not ban ourselves when we put ourselves into the policy list.' , async function ( ) {
2022-08-09 06:29:27 -04:00
const mjolnir : Mjolnir = this . mjolnir
const serverName = new UserID ( await mjolnir . client . getUserId ( ) ) . domain ;
const banListId = await mjolnir . client . createRoom ( ) ;
const banList = new PolicyList ( banListId , banListId , mjolnir . client ) ;
await createPolicyRule ( mjolnir . client , banListId , RULE_SERVER , serverName , '' ) ;
await createPolicyRule ( mjolnir . client , banListId , RULE_SERVER , 'evil.com' , '' ) ;
await createPolicyRule ( mjolnir . client , banListId , RULE_SERVER , '*' , '' ) ;
2022-02-07 12:02:06 -05:00
// We should still intern the matching rules rule.
let changes : ListRuleChange [ ] = await banList . updateList ( ) ;
assert . equal ( banList . serverRules . length , 3 ) ;
// But when we construct an ACL, we should be safe.
const acl = new ServerAcl ( serverName )
changes . forEach ( change = > acl . denyServer ( change . rule . entity ) ) ;
assert . equal ( acl . safeAclContent ( ) . deny . length , 1 ) ;
assert . equal ( acl . literalAclContent ( ) . deny . length , 3 ) ;
} )
} )
2022-02-15 08:51:20 -05:00
2022-07-26 15:47:26 -04:00
describe ( 'Test: ACL updates will batch when rules are added in succession.' , function ( ) {
it ( 'Will batch ACL updates if we spam rules into a PolicyList' , async function ( ) {
2022-08-09 06:29:27 -04:00
const mjolnir : Mjolnir = this . mjolnir !
const serverName : string = new UserID ( await mjolnir . client . getUserId ( ) ) . domain
2022-08-16 10:51:18 -04:00
const moderator = await newTestUser ( this . config . homeserverUrl , { name : { contains : "moderator" } } ) ;
2022-08-18 08:09:03 -04:00
await moderator . joinRoom ( mjolnir . managementRoomId ) ;
2022-08-09 06:29:27 -04:00
const mjolnirId = await mjolnir . client . getUserId ( ) ;
2022-02-15 08:51:20 -05:00
// Setup some protected rooms so we can check their ACL state later.
const protectedRooms : string [ ] = [ ] ;
2022-08-15 12:20:12 -04:00
for ( let i = 0 ; i < 5 ; i ++ ) {
2022-07-26 15:47:26 -04:00
const room = await moderator . createRoom ( { invite : [ mjolnirId ] } ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . client . joinRoom ( room ) ;
2022-02-15 08:51:20 -05:00
await moderator . setUserPowerLevel ( mjolnirId , room , 100 ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . addProtectedRoom ( room ) ;
2022-02-15 08:51:20 -05:00
protectedRooms . push ( room ) ;
}
// If a previous test hasn't cleaned up properly, these rooms will be populated by bogus ACLs at this point.
2022-08-09 06:29:27 -04:00
await mjolnir . syncLists ( ) ;
2022-02-15 08:51:20 -05:00
await Promise . all ( protectedRooms . map ( async room = > {
// We're going to need timeline pagination I'm afraid.
2022-08-09 06:29:27 -04:00
const roomAcl = await mjolnir . client . getRoomStateEvent ( room , "m.room.server_acl" , "" ) ;
2022-02-15 08:51:20 -05:00
assert . equal ( roomAcl ? . deny ? . length ? ? 0 , 0 , 'There should be no entries in the deny ACL.' ) ;
} ) ) ;
// Flood the watched list with banned servers, which should prompt Mjolnir to update server ACL in protected rooms.
const banListId = await moderator . createRoom ( { invite : [ mjolnirId ] } ) ;
2022-08-18 08:09:03 -04:00
await mjolnir . client . joinRoom ( banListId ) ;
await mjolnir . watchList ( Permalinks . forRoom ( banListId ) ) ;
2022-02-15 08:51:20 -05:00
const acl = new ServerAcl ( serverName ) . denyIpAddresses ( ) . allowServer ( "*" ) ;
2022-08-09 05:57:38 -04:00
const evilServerCount = 200 ;
for ( let i = 0 ; i < evilServerCount ; i ++ ) {
2022-02-15 08:51:20 -05:00
const badServer = ` ${ i } .evil.com ` ;
acl . denyServer ( badServer ) ;
await createPolicyRule ( moderator , banListId , RULE_SERVER , badServer , ` Rule # ${ i } ` ) ;
// Give them a bit of a spread over time.
await new Promise ( resolve = > setTimeout ( resolve , 5 ) ) ;
}
// We do this because it should force us to wait until all the ACL events have been applied.
// Even if that does mean the last few events will not go through batching...
2022-08-09 06:29:27 -04:00
await mjolnir . syncLists ( ) ;
2022-02-15 08:51:20 -05:00
2022-08-09 05:57:38 -04:00
// At this point we check that the state within Mjolnir is internally consistent, this is just because debugging the following
// is a pita.
const list : PolicyList = this . mjolnir . policyLists [ 0 ] ! ;
assert . equal ( list . serverRules . length , evilServerCount , ` There should be ${ evilServerCount } rules in here ` ) ;
2022-02-15 08:51:20 -05:00
// Check each of the protected rooms for ACL events and make sure they were batched and are correct.
await Promise . all ( protectedRooms . map ( async room = > {
2022-08-09 06:29:27 -04:00
const roomAcl = await mjolnir . client . getRoomStateEvent ( room , "m.room.server_acl" , "" ) ;
2022-02-15 08:51:20 -05:00
if ( ! acl . matches ( roomAcl ) ) {
assert . fail ( ` Room ${ room } doesn't have the correct ACL: ${ JSON . stringify ( roomAcl , null , 2 ) } ` )
}
let aclEventCount = 0 ;
2022-08-09 06:29:27 -04:00
await getMessagesByUserIn ( mjolnir . client , mjolnirId , room , 100 , events = > {
2022-02-15 08:51:20 -05:00
events . forEach ( event = > event . type === 'm.room.server_acl' ? aclEventCount += 1 : null ) ;
} ) ;
2022-07-26 15:47:26 -04:00
LogService . debug ( 'PolicyListTest' , ` aclEventCount: ${ aclEventCount } ` ) ;
2022-02-15 08:51:20 -05:00
// If there's less than two then it means the ACL was updated by this test calling `this.mjolnir!.syncLists()`
// and not the listener that detects changes to ban lists (that we want to test!).
2022-02-24 09:27:53 -05:00
// It used to be 10, but it was too low, 30 seems better for CI.
assert . equal ( aclEventCount < 30 && aclEventCount > 2 , true , 'We should have sent less than 30 ACL events to each room because they should be batched' )
2022-02-15 08:51:20 -05:00
} ) ) ;
} )
} )
2022-02-21 11:51:14 -05:00
2022-07-26 15:47:26 -04:00
describe ( 'Test: unbaning entities via the PolicyList.' , function ( ) {
2022-02-21 11:51:14 -05:00
afterEach ( function ( ) { this . moderator ? . stop ( ) ; } ) ;
2022-07-26 15:47:26 -04:00
it ( 'Will remove rules that have legacy types' , async function ( ) {
2022-08-09 06:29:27 -04:00
const mjolnir : Mjolnir = this . mjolnir !
const serverName : string = new UserID ( await mjolnir . client . getUserId ( ) ) . domain
2022-08-16 10:51:18 -04:00
const moderator = await newTestUser ( this . config . homeserverUrl , { name : { contains : "moderator" } } ) ;
2022-02-21 11:51:14 -05:00
this . moderator = moderator ;
2022-08-15 07:54:35 -04:00
await moderator . joinRoom ( mjolnir . managementRoomId ) ;
2022-08-09 06:29:27 -04:00
const mjolnirId = await mjolnir . client . getUserId ( ) ;
2022-02-21 11:51:14 -05:00
// We'll make 1 protected room to test ACLs in.
2022-08-15 12:20:12 -04:00
const protectedRoom = await moderator . createRoom ( { invite : [ mjolnirId ] } ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . client . joinRoom ( protectedRoom ) ;
2022-02-21 11:51:14 -05:00
await moderator . setUserPowerLevel ( mjolnirId , protectedRoom , 100 ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . addProtectedRoom ( protectedRoom ) ;
2022-02-21 11:51:14 -05:00
// If a previous test hasn't cleaned up properly, these rooms will be populated by bogus ACLs at this point.
2022-08-09 06:29:27 -04:00
await mjolnir . syncLists ( ) ;
2022-08-15 07:54:35 -04:00
// If this is not present, then it means the room isn't being protected, which is really bad.
2022-08-09 06:29:27 -04:00
const roomAcl = await mjolnir . client . getRoomStateEvent ( protectedRoom , "m.room.server_acl" , "" ) ;
2022-02-21 11:51:14 -05:00
assert . equal ( roomAcl ? . deny ? . length ? ? 0 , 0 , 'There should be no entries in the deny ACL.' ) ;
2022-07-26 15:47:26 -04:00
// Create some legacy rules on a PolicyList.
2022-02-21 11:51:14 -05:00
const banListId = await moderator . createRoom ( { invite : [ mjolnirId ] } ) ;
2022-08-09 06:29:27 -04:00
await moderator . setUserPowerLevel ( await mjolnir . client . getUserId ( ) , banListId , 100 ) ;
2022-07-26 15:47:26 -04:00
await moderator . sendStateEvent ( banListId , 'org.matrix.mjolnir.shortcode' , '' , { shortcode : "unban-test" } ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . client . joinRoom ( banListId ) ;
2022-08-15 07:54:35 -04:00
await mjolnir . watchList ( Permalinks . forRoom ( banListId ) ) ;
2022-02-21 11:51:14 -05:00
// we use this to compare changes.
2022-07-26 15:47:26 -04:00
const banList = new PolicyList ( banListId , banListId , moderator ) ;
2022-02-21 11:51:14 -05:00
// we need two because we need to test the case where an entity has all rule types in the list
// and another one that only has one (so that we would hit 404 while looking up state)
const olderBadServer = "old.evil.com"
const newerBadServer = "new.evil.com"
await Promise . all ( SERVER_RULE_TYPES . map ( type = > createPolicyRule ( moderator , banListId , type , olderBadServer , 'gregg rulz ok' ) ) ) ;
await createPolicyRule ( moderator , banListId , RULE_SERVER , newerBadServer , 'this is bad sort it out.' ) ;
// Wait for the ACL event to be applied to our protected room.
await this . mjolnir ! . syncLists ( ) ;
await banList . updateList ( ) ;
// rules are normalized, that's why there should only be 2.
assert . equal ( banList . allRules . length , 2 ) ;
// Check that we have setup our test properly and therefore evil.com is banned.
const acl = new ServerAcl ( serverName ) . denyIpAddresses ( ) . allowServer ( "*" ) . denyServer ( olderBadServer ) . denyServer ( newerBadServer ) ;
2022-08-09 06:29:27 -04:00
const protectedAcl = await mjolnir . client . getRoomStateEvent ( protectedRoom , "m.room.server_acl" , "" ) ;
2022-02-21 11:51:14 -05:00
if ( ! acl . matches ( protectedAcl ) ) {
assert . fail ( ` Room ${ protectedRoom } doesn't have the correct ACL: ${ JSON . stringify ( roomAcl , null , 2 ) } ` ) ;
}
// Now unban the servers, we will go via the unban command for completeness sake.
try {
await moderator . start ( ) ;
for ( const server of [ olderBadServer , newerBadServer ] ) {
2022-08-15 07:54:35 -04:00
await getFirstReaction ( moderator , mjolnir . managementRoomId , '✅' , async ( ) = > {
return await moderator . sendMessage ( mjolnir . managementRoomId , { msgtype : 'm.text' , body : ` !mjolnir unban unban-test server ${ server } ` } ) ;
2022-02-21 11:51:14 -05:00
} ) ;
}
} finally {
moderator . stop ( ) ;
}
// Wait for mjolnir to sync protected rooms to update ACL.
2022-08-15 07:54:35 -04:00
await mjolnir . syncLists ( ) ;
2022-02-21 11:51:14 -05:00
// Confirm that the server is unbanned.
await banList . updateList ( ) ;
assert . equal ( banList . allRules . length , 0 ) ;
2022-08-09 06:29:27 -04:00
const aclAfter = await mjolnir . client . getRoomStateEvent ( protectedRoom , "m.room.server_acl" , "" ) ;
2022-02-21 11:51:14 -05:00
assert . equal ( aclAfter . deny . length , 0 , 'Should be no servers denied anymore' ) ;
} )
} )
2022-05-03 07:36:53 -04:00
2022-07-26 15:47:26 -04:00
describe ( 'Test: should apply bans to the most recently active rooms first' , function ( ) {
it ( 'Applies bans to the most recently active rooms first' , async function ( ) {
2022-07-06 09:20:25 -04:00
this . timeout ( 180000 )
2022-08-09 06:29:27 -04:00
const mjolnir : Mjolnir = this . mjolnir !
const serverName : string = new UserID ( await mjolnir . client . getUserId ( ) ) . domain
2022-08-16 10:51:18 -04:00
const moderator = await newTestUser ( this . config . homeserverUrl , { name : { contains : "moderator" } } ) ;
2022-08-18 08:09:03 -04:00
await moderator . joinRoom ( mjolnir . managementRoomId ) ;
2022-08-09 06:29:27 -04:00
const mjolnirId = await mjolnir . client . getUserId ( ) ;
2022-05-03 07:36:53 -04:00
// Setup some protected rooms so we can check their ACL state later.
const protectedRooms : string [ ] = [ ] ;
for ( let i = 0 ; i < 10 ; i ++ ) {
2022-07-26 15:47:26 -04:00
const room = await moderator . createRoom ( { invite : [ mjolnirId ] } ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . client . joinRoom ( room ) ;
2022-05-03 07:36:53 -04:00
await moderator . setUserPowerLevel ( mjolnirId , room , 100 ) ;
2022-08-09 06:29:27 -04:00
await mjolnir . addProtectedRoom ( room ) ;
2022-05-03 07:36:53 -04:00
protectedRooms . push ( room ) ;
}
// If a previous test hasn't cleaned up properly, these rooms will be populated by bogus ACLs at this point.
2022-08-09 06:29:27 -04:00
await mjolnir . syncLists ( ) ;
2022-05-03 07:36:53 -04:00
await Promise . all ( protectedRooms . map ( async room = > {
2022-08-09 06:29:27 -04:00
const roomAcl = await mjolnir . client . getRoomStateEvent ( room , "m.room.server_acl" , "" ) . catch ( e = > e . statusCode === 404 ? { deny : [ ] } : Promise . reject ( e ) ) ;
2022-05-03 07:36:53 -04:00
assert . equal ( roomAcl ? . deny ? . length ? ? 0 , 0 , 'There should be no entries in the deny ACL.' ) ;
} ) ) ;
// Flood the watched list with banned servers, which should prompt Mjolnir to update server ACL in protected rooms.
const banListId = await moderator . createRoom ( { invite : [ mjolnirId ] } ) ;
2022-08-18 08:09:03 -04:00
await mjolnir . client . joinRoom ( banListId ) ;
await mjolnir . watchList ( Permalinks . forRoom ( banListId ) ) ;
2022-05-03 07:36:53 -04:00
2022-08-09 06:29:27 -04:00
await mjolnir . syncLists ( ) ;
2022-05-03 07:36:53 -04:00
// shuffle protected rooms https://stackoverflow.com/a/12646864, we do this so we can create activity "randomly" in them.
for ( let i = protectedRooms . length - 1 ; i > 0 ; i -- ) {
const j = Math . floor ( Math . random ( ) * ( i + 1 ) ) ;
[ protectedRooms [ i ] , protectedRooms [ j ] ] = [ protectedRooms [ j ] , protectedRooms [ i ] ] ;
}
// create some activity in the same order.
for ( const roomId of protectedRooms . slice ( ) . reverse ( ) ) {
2022-08-09 06:29:27 -04:00
await mjolnir . client . sendMessage ( roomId , { body : ` activity ` , msgtype : 'm.text' } ) ;
2022-05-03 07:36:53 -04:00
await new Promise ( resolve = > setTimeout ( resolve , 100 ) ) ;
}
// check the rooms are in the expected order
for ( let i = 0 ; i < protectedRooms . length ; i ++ ) {
2022-08-09 06:29:27 -04:00
assert . equal ( mjolnir . protectedRoomsByActivity ( ) [ i ] , protectedRooms [ i ] ) ;
2022-05-03 07:36:53 -04:00
}
const badServer = ` evil.com ` ;
// just ban one server
const acl = new ServerAcl ( serverName ) . denyIpAddresses ( ) . allowServer ( "*" ) . denyServer ( badServer ) ;
await createPolicyRule ( moderator , banListId , RULE_SERVER , badServer , ` Rule ${ badServer } ` ) ;
// Wait until all the ACL events have been applied.
2022-08-09 06:29:27 -04:00
await mjolnir . syncLists ( ) ;
2022-05-03 07:36:53 -04:00
for ( let i = 0 ; i < protectedRooms . length ; i ++ ) {
2022-08-09 06:29:27 -04:00
assert . equal ( mjolnir . protectedRoomsByActivity ( ) [ i ] , protectedRooms . at ( - i - 1 ) ) ;
2022-05-03 07:36:53 -04:00
}
// Check that the most recently active rooms got the ACL update first.
let last_event_ts = 0 ;
for ( const roomId of protectedRooms ) {
2022-07-26 15:47:26 -04:00
let roomAclEvent : null | any ;
2022-05-03 07:36:53 -04:00
// Can't be the best way to get the whole event, but ok.
2022-08-09 06:29:27 -04:00
await getMessagesByUserIn ( mjolnir . client , mjolnirId , roomId , 1 , events = > roomAclEvent = events [ 0 ] ) ;
2022-05-03 07:36:53 -04:00
const roomAcl = roomAclEvent ! . content ;
if ( ! acl . matches ( roomAcl ) ) {
assert . fail ( ` Room ${ roomId } doesn't have the correct ACL: ${ JSON . stringify ( roomAcl , null , 2 ) } ` )
}
assert . equal ( roomAclEvent . origin_server_ts > last_event_ts , true , ` This room was more recently active so should have the more recent timestamp ` ) ;
last_event_ts = roomAclEvent . origin_server_ts ;
}
} )
} )