Skip to content

Commit

Permalink
Ignore functional members in admin rooms (#1782)
Browse files Browse the repository at this point in the history
* Support functional member filtering out of admin rooms.

* tidyup

* Add test

* lint

* changelog

* Add ignoreFunctionalMembersInAdminRooms to schema

* Add sample config change
  • Loading branch information
Half-Shot authored Apr 8, 2024
1 parent ef5eb44 commit 42af2f9
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog.d/1782.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add option to ignore "functional members" when checking if an admin room contains two users.
4 changes: 3 additions & 1 deletion config.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,8 @@ ircService:
longReplyTemplate: "<$NICK> \"$ORIGINAL\" <- $REPLY"
# how much time needs to pass between the reply and the original message to switch to the long format
shortReplyTresholdSeconds: 300
# Ignore users mentioned in a io.element.functional_members state event when checking admin room membership
ignoreFunctionalMembersInAdminRooms: false

# Maximum number of montly active users, beyond which the bridge gets blocked (both ways)
# RMAUlimit: 100
Expand Down Expand Up @@ -701,4 +703,4 @@ database:
# # The Redis URI to connect to
# redisUrl: redis://user:password@host:port/dbnum
# # Should the connections persist after the bridge successfully shuts down?
# persistConnectionsOnShutdown: true
# persistConnectionsOnShutdown: true
2 changes: 2 additions & 0 deletions config.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ properties:
type: "string"
shortReplyTresholdSeconds:
type: "integer"
ignoreFunctionalMembersInAdminRooms:
type: "boolean"
ircHandler:
type: "object"
properties:
Expand Down
81 changes: 81 additions & 0 deletions spec/integ/admin-rooms.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,87 @@ describe("Admin rooms", function() {
);
});

it("mx bot should be NOT kicked when there are > 2 users in room and functional members are present", async () => {

const intent = env.clientMock._intent(botUserId);
const sdk = intent.underlyingClient;
sdk.getRoomState.and.callFake(
function (roomId) {
expect(roomId).toBe(adminRoomId, 'Room state returned should be for admin room');
return Promise.resolve([
{
content: {membership: "join"},
type: "m.room.member",
state_key: "@bot:fake"
},
{
content: {membership: "join"},
type: "m.room.member",
state_key: "@user1:fake"
},
{
content: {membership: "join"},
type: "m.room.member",
state_key: "@user2:fake"
}, {
type: "io.element.functional_members",
state_key: "",
content: {
service_members: ["@user2:fake"]
}
}
]);
}
);

intent.leaveRoom.and.callFake(function(roomId) {
fail('Bot should not leave room');
});

let sentMessage = true;
sdk.sendEvent.and.callFake((roomId, type, content) => {
expect(roomId).toBe(adminRoomId, 'Bot did not send message to admin room');
sentMessage = true;
return Promise.resolve({});
});

await env.mockAppService._trigger("type:m.room.member", {
content: {
membership: "join",
},
state_key: botUserId,
sender: "@user1:localhost",
room_id: adminRoomId,
type: "m.room.member"
});

await env.mockAppService._trigger("type:m.room.member", {
content: {
membership: "join",
},
state_key: botUserId,
sender: "@user2:localhost",
room_id: adminRoomId,
type: "m.room.member"
});

// trigger the bot to leave
await env.mockAppService._trigger("type:m.room.message", {
content: {
body: "!help",
msgtype: "m.text"
},
sender: "@user2:localhost",
room_id: adminRoomId,
type: "m.room.message"
}).then(
() => {
expect(sentMessage).toBe(true);
},
(err) => {console.log(err)}
);
});

it("mx bot should NOT be kicked when there are 2 users in room and a message is sent", async () => {
const intent = env.clientMock._intent(botUserId);
const sdk = intent.underlyingClient;
Expand Down
3 changes: 3 additions & 0 deletions spec/util/test-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
"ircHandler": {
"powerLevelGracePeriodMs": 500
},
"matrixHandler": {
"ignoreFunctionalMembersInAdminRooms": true
},
"servers": {
"irc.example": {
"port": 6667,
Expand Down
22 changes: 21 additions & 1 deletion src/bridge/MatrixHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ async function reqHandler(req: BridgeRequest, promise: PromiseLike<unknown>|void
const MSG_PMS_DISABLED = "[Bridge] Sorry, PMs are disabled on this bridge.";
const MSG_PMS_DISABLED_FEDERATION = "[Bridge] Sorry, PMs are disabled on this bridge over federation.";

const FUNCTIONAL_MEMBERS_EVENT = "io.element.functional_members";

interface FunctionalMembersEventContent {
service_members: string[];
}

export interface MatrixHandlerConfig {
/* Number of events to store in memory for use in replies. */
eventCacheSize: number;
Expand All @@ -51,6 +57,9 @@ export interface MatrixHandlerConfig {
longReplyTemplate: string;
// Format of the text explaining why a message is truncated and pastebinned
truncatedMessageTemplate: string;
// Ignore io.element.functional_members members joining admin rooms.
// See https://github.com/vector-im/element-meta/blob/develop/spec/functional_members.md
ignoreFunctionalMembersInAdminRooms: boolean;
}

export const DEFAULTS: MatrixHandlerConfig = {
Expand All @@ -60,6 +69,7 @@ export const DEFAULTS: MatrixHandlerConfig = {
shortReplyTemplate: "$NICK: $REPLY",
longReplyTemplate: "<$NICK> \"$ORIGINAL\" <- $REPLY",
truncatedMessageTemplate: "(full message at <$URL>)",
ignoreFunctionalMembersInAdminRooms: false,
};

export interface MatrixEventInvite {
Expand Down Expand Up @@ -277,8 +287,18 @@ export class MatrixHandler {
(m.content as {membership: string}).membership === "join"
);

let functionalMembers = this.config.ignoreFunctionalMembersInAdminRooms &&
((
this.memberTracker?.getState(adminRoom.getId(), FUNCTIONAL_MEMBERS_EVENT, "") as StateLookupEvent|null
)?.content as FunctionalMembersEventContent)?.service_members || [];

if (!Array.isArray(functionalMembers)) {
// Guard against invalid types.
functionalMembers = [];
}

// If an admin room has more than 2 people in it, kick the bot out
if (members.length > 2) {
if (members.filter(m => !functionalMembers.includes(m.state_key)).length > 2) {
req.log.error(
`onAdminMessage: admin room has ${members.length}` +
` users instead of just 2; bot will leave`
Expand Down

0 comments on commit 42af2f9

Please sign in to comment.