diff --git a/src/actions/ApplyAcl.ts b/src/actions/ApplyAcl.ts new file mode 100644 index 0000000..17452f9 --- /dev/null +++ b/src/actions/ApplyAcl.ts @@ -0,0 +1,48 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +import BanList from "../models/BanList"; +import { MatrixClient } from "matrix-bot-sdk"; +import { ServerAcl } from "../models/ServerAcl"; +import { RoomUpdateError } from "../models/RoomUpdateError"; + +/** + * Applies the server ACLs represented by the ban lists to the provided rooms, returning the + * room IDs that could not be updated and their error. + * @param {BanList[]} lists The lists to construct ACLs from. + * @param {string[]} roomIds The room IDs to apply the ACLs in. + * @param {MatrixClient} client The Matrix client to apply the ACLs with. + */ +export async function applyServerAcls(lists: BanList[], roomIds: string[], client: MatrixClient): Promise { + // Construct a server ACL first + const acl = new ServerAcl().denyIpAddresses().allowServer("*"); + for (const list of lists) { + for (const rule of list.serverRules) { + acl.denyServer(rule.entity); + } + } + + const errors: RoomUpdateError[] = []; + for (const roomId of roomIds) { + try { + await client.sendStateEvent(roomId, "m.room.server_acl", "", acl.safeAclContent()); + } catch (e) { + errors.push({roomId, errorMessage: e.message || (e.body ? e.body.error : '')}); + } + } + + return errors; +} diff --git a/src/index.ts b/src/index.ts index 7d8cc26..70f14ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,9 @@ import { SimpleFsStorageProvider } from "matrix-bot-sdk"; import config from "./config"; -import BanList, { ALL_RULE_TYPES } from "./BanList"; +import BanList, { ALL_RULE_TYPES } from "./models/BanList"; +import { applyServerAcls } from "./actions/ApplyAcl"; +import { RoomUpdateError } from "./models/RoomUpdateError"; LogService.setLogger(new RichConsoleLogger()); @@ -44,8 +46,10 @@ client.on("room.event", async (roomId, event) => { for (const list of lists) { if (list.roomId !== roomId) continue; await list.updateList(); - // TODO: Re-apply ACLs as needed } + + const errors = await applyServerAcls(lists, await client.getJoinedRooms(), client); + return printActionResult(errors); } else if (event['type'] === "m.room.member") { // TODO: Check membership against ban lists } @@ -106,8 +110,9 @@ async function printStatus(roomId: string) { html += "Subscribed lists:
"; @@ -119,3 +124,29 @@ async function printStatus(roomId: string) { }; return client.sendMessage(roomId, message); } + +async function printActionResult(errors: RoomUpdateError[]) { + let html = ""; + let text = ""; + + if (errors.length > 0) { + html += `${errors.length} errors updating protected rooms!
"; + } else { + html += `Updated all protected rooms with new rules successfully.`; + text += "Updated all protected rooms with new rules successfully"; + } + + const message = { + msgtype: "m.notice", + body: text, + format: "org.matrix.custom.html", + formatted_body: html, + }; + return client.sendMessage(managementRoomId, message); +} diff --git a/src/BanList.ts b/src/models/BanList.ts similarity index 73% rename from src/BanList.ts rename to src/models/BanList.ts index a4a385f..c931f29 100644 --- a/src/BanList.ts +++ b/src/models/BanList.ts @@ -27,11 +27,23 @@ export const SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"] export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES]; export default class BanList { - public rules: ListRule[] = []; + private rules: ListRule[] = []; constructor(public readonly roomId: string, public readonly roomRef, private client: MatrixClient) { } + public get serverRules(): ListRule[] { + return this.rules.filter(r => r.kind === RULE_SERVER); + } + + public get userRules(): ListRule[] { + return this.rules.filter(r => r.kind === RULE_USER); + } + + public get roomRules(): ListRule[] { + return this.rules.filter(r => r.kind === RULE_ROOM); + } + public async updateList() { this.rules = []; @@ -41,6 +53,17 @@ export default class BanList { continue; } + let kind: string = null; + if (USER_RULE_TYPES.includes(event['type'])) { + kind = RULE_USER; + } else if (ROOM_RULE_TYPES.includes(event['type'])) { + kind = RULE_ROOM; + } else if (SERVER_RULE_TYPES.includes(event['type'])) { + kind = RULE_SERVER; + } else { + continue; // invalid/unknown + } + // It's a rule - parse it const content = event['content']; if (!content) continue; @@ -53,7 +76,7 @@ export default class BanList { continue; } - this.rules.push(new ListRule(entity, recommendation, reason)); + this.rules.push(new ListRule(entity, recommendation, reason, kind)); } } diff --git a/src/ListRule.ts b/src/models/ListRule.ts similarity index 94% rename from src/ListRule.ts rename to src/models/ListRule.ts index 69f8128..6a0f018 100644 --- a/src/ListRule.ts +++ b/src/models/ListRule.ts @@ -23,7 +23,7 @@ export class ListRule { private glob: MatrixGlob; - constructor(public readonly entity: string, private action: string, public readonly reason: string) { + constructor(public readonly entity: string, private action: string, public readonly reason: string, public readonly kind: string) { this.glob = new MatrixGlob(entity); } diff --git a/src/models/RoomUpdateError.ts b/src/models/RoomUpdateError.ts new file mode 100644 index 0000000..bf40ccf --- /dev/null +++ b/src/models/RoomUpdateError.ts @@ -0,0 +1,20 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +export interface RoomUpdateError { + roomId: string; + errorMessage: string; +} diff --git a/src/models/ServerAcl.ts b/src/models/ServerAcl.ts new file mode 100644 index 0000000..746438c --- /dev/null +++ b/src/models/ServerAcl.ts @@ -0,0 +1,79 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +import { setToArray } from "../utils"; + +export interface ServerAclContent { + allow: string[]; + deny: string[]; + allow_ip_literals: boolean; +} + +export class ServerAcl { + private allowedServers: Set = new Set(); + private deniedServers: Set = new Set(); + private allowIps = false; + + public allowIpAddresses(): ServerAcl { + this.allowIps = true; + return this; + } + + public denyIpAddresses(): ServerAcl { + this.allowIps = false; + return this; + } + + public allowServer(glob: string): ServerAcl { + this.allowedServers.add(glob); + return this; + } + + public setAllowedServers(globs: string[]): ServerAcl { + this.allowedServers = new Set(globs); + return this; + } + + public denyServer(glob: string): ServerAcl { + this.deniedServers.add(glob); + return this; + } + + public setDeniedServers(globs: string[]): ServerAcl { + this.deniedServers = new Set(globs); + return this; + } + + public literalAclContent(): ServerAclContent { + return { + allow: setToArray(this.allowedServers), + deny: setToArray(this.deniedServers), + allow_ip_literals: this.allowIps, + }; + } + + public safeAclContent(): ServerAclContent { + const allowed = setToArray(this.allowedServers); + if (!allowed || allowed.length === 0) { + allowed.push("*"); // allow everything + } + return { + allow: allowed, + deny: setToArray(this.deniedServers), + allow_ip_literals: this.allowIps, + }; + } +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..6693920 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,23 @@ +/* +Copyright 2019 The Matrix.org Foundation C.I.C. + +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. +*/ + +export function setToArray(set: Set): T[] { + const arr: T[] = []; + for (const v of set) { + arr.push(v); + } + return arr; +}