Update server ACLs when they change

This commit is contained in:
Travis Ralston 2019-09-27 14:26:57 -06:00
parent 23c6c20768
commit d32ad18f3a
7 changed files with 231 additions and 7 deletions

48
src/actions/ApplyAcl.ts Normal file
View File

@ -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<RoomUpdateError[]> {
// 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 : '<no message>')});
}
}
return errors;
}

View File

@ -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 += "<b>Subscribed lists:</b><br><ul>";
text += "Subscribed lists:\n";
for (const list of lists) {
html += `<li><a href="${list.roomRef}">${list.roomId}</a> (${list.rules.length} rules)</li>`;
text += `${list.roomRef} (${list.rules.length} rules)\n`;
const ruleInfo = `rules: ${list.serverRules.length} servers, ${list.userRules.length} users, ${list.roomRules.length} rooms`;
html += `<li><a href="${list.roomRef}">${list.roomId}</a> (${ruleInfo})</li>`;
text += `${list.roomRef} (${ruleInfo})\n`;
}
html += "</ul>";
@ -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 += `<font color="#ff0000"><b>${errors.length} errors updating protected rooms!</b></font><br /><ul>`;
text += `${errors.length} errors updating protected rooms!\n`;
for (const error of errors) {
html += `<li><a href="https://matrix.to/#/${error.roomId}">${error.roomId}</a> - ${error.errorMessage}</li>`;
text += `${error.roomId} - ${error.errorMessage}\n`;
}
html += "</ul>";
} else {
html += `<font color="#00ff00"><b>Updated all protected rooms with new rules successfully.</b></font>`;
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);
}

View File

@ -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));
}
}

View File

@ -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);
}

View File

@ -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;
}

79
src/models/ServerAcl.ts Normal file
View File

@ -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<string> = new Set<string>();
private deniedServers: Set<string> = new Set<string>();
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<string>(globs);
return this;
}
public denyServer(glob: string): ServerAcl {
this.deniedServers.add(glob);
return this;
}
public setDeniedServers(globs: string[]): ServerAcl {
this.deniedServers = new Set<string>(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,
};
}
}

23
src/utils.ts Normal file
View File

@ -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<T>(set: Set<T>): T[] {
const arr: T[] = [];
for (const v of set) {
arr.push(v);
}
return arr;
}