diff --git a/src/appservice/AccessControl.ts b/src/appservice/AccessControl.ts index 4d64f897..4156a703 100644 --- a/src/appservice/AccessControl.ts +++ b/src/appservice/AccessControl.ts @@ -28,6 +28,7 @@ limitations under the License. import { Bridge } from "matrix-appservice-bridge"; import { Permalinks } from "../commands/interface-manager/Permalinks"; import AccessControlUnit, { EntityAccess } from "../models/AccessControlUnit"; +import { EntityType, Recommendation } from "../models/ListRule"; import PolicyList from "../models/PolicyList"; /** @@ -74,4 +75,12 @@ export class AccessControl { public getUserAccess(mxid: string): EntityAccess { return this.accessControlUnit.getAccessForUser(mxid, "CHECK_SERVER"); } + + public async allow(mxid: string): Promise { + await this.accessControlList.createPolicy(EntityType.RULE_USER, Recommendation.Allow, mxid); + } + + public async remove(mxid: string): Promise { + await this.accessControlList.unbanEntity(EntityType.RULE_USER, mxid); + } } diff --git a/src/appservice/AppService.ts b/src/appservice/AppService.ts index 97351fa8..f2687cc8 100644 --- a/src/appservice/AppService.ts +++ b/src/appservice/AppService.ts @@ -52,7 +52,7 @@ export class MjolnirAppService { public readonly config: IConfig, public readonly bridge: Bridge, public readonly mjolnirManager: MjolnirManager, - private readonly accessControl: AccessControl, + public readonly accessControl: AccessControl, private readonly dataStore: DataStore, ) { this.api = new Api(config.homeserver.url, mjolnirManager); @@ -81,7 +81,7 @@ export class MjolnirAppService { suppressEcho: false, }); await bridge.initialise(); - const accessControlListId = await bridge.getBot().getClient().resolveRoom(config.accessControlList); + const accessControlListId = await bridge.getBot().getClient().resolveRoom(config.adminRoom); const accessControl = await AccessControl.setupAccessControl(accessControlListId, bridge); const mjolnirManager = await MjolnirManager.makeMjolnirManager(dataStore, bridge, accessControl); const appService = new MjolnirAppService( diff --git a/src/appservice/bot/AccessCommands.tsx b/src/appservice/bot/AccessCommands.tsx new file mode 100644 index 00000000..25f96d3a --- /dev/null +++ b/src/appservice/bot/AccessCommands.tsx @@ -0,0 +1,54 @@ +/** + * Copyright (C) 2022 Gnuxie + * All rights reserved. + */ + +import { defineInterfaceCommand, findTableCommand } from "../../commands/interface-manager/InterfaceCommand"; +import { findPresentationType, parameters, ParsedKeywords } from "../../commands/interface-manager/ParameterParsing"; +import { CommandResult } from "../../commands/interface-manager/Validation"; +import { AppserviceContext } from "./AppserviceCommandHandler"; +import { UserID } from "matrix-bot-sdk"; +import { defineMatrixInterfaceAdaptor } from "../../commands/interface-manager/MatrixInterfaceAdaptor"; +import { tickCrossRenderer } from "../../commands/interface-manager/MatrixHelpRenderer"; + +defineInterfaceCommand({ + designator: ["allow"], + table: "appservice bot", + parameters: parameters([ + { + name: 'user', + acceptor: findPresentationType('UserID'), + } + ]), + command: async function (this: AppserviceContext, _keywords: ParsedKeywords, user: UserID): Promise> { + await this.appservice.accessControl.allow(user.toString()); + return CommandResult.Ok(undefined); + }, + summary: "Allow a user to provision themselves a draupnir using the appservice." +}) + +defineMatrixInterfaceAdaptor({ + interfaceCommand: findTableCommand("appservice bot", "allow"), + renderer: tickCrossRenderer, +}); + +defineInterfaceCommand({ + designator: ["remove"], + table: "appservice bot", + parameters: parameters([ + { + name: 'user', + acceptor: findPresentationType('UserID'), + } + ]), + command: async function (this: AppserviceContext, _keywords: ParsedKeywords, user: UserID): Promise> { + await this.appservice.accessControl.remove(user.toString()); + return CommandResult.Ok(undefined); + }, + summary: "Stop a user from using any provisioned draupnir in the appservice." +}) + +defineMatrixInterfaceAdaptor({ + interfaceCommand: findTableCommand("appservice bot", "remove"), + renderer: tickCrossRenderer, +}); diff --git a/src/appservice/bot/AppserviceCommandHandler.ts b/src/appservice/bot/AppserviceCommandHandler.ts index 6fdcdac3..2853a745 100644 --- a/src/appservice/bot/AppserviceCommandHandler.ts +++ b/src/appservice/bot/AppserviceCommandHandler.ts @@ -22,6 +22,7 @@ export type AppserviceBaseExecutor = (this: AppserviceContext, ...args: any[]) = import '../../commands/interface-manager/MatrixPresentations'; import './ListCommand'; +import './AccessCommands'; import { AppserviceBotEmitter } from './AppserviceBotEmitter'; diff --git a/src/appservice/config/config.example.yaml b/src/appservice/config/config.example.yaml index d35b1c58..f44bbe44 100644 --- a/src/appservice/config/config.example.yaml +++ b/src/appservice/config/config.example.yaml @@ -12,7 +12,7 @@ db: # A room you have created that scopes who can access the appservice. # See docs/access_control.md -accessControlList: "#access-control-list:localhost:9999" +adminRoom: "#draupnir-admin:localhost:9999" # This is a web api that the widget connects to in order to interact with the appservice. webAPI: diff --git a/src/appservice/config/config.harness.yaml b/src/appservice/config/config.harness.yaml index c548e703..d94c9fb6 100644 --- a/src/appservice/config/config.harness.yaml +++ b/src/appservice/config/config.harness.yaml @@ -7,8 +7,7 @@ db: engine: "postgres" connectionString: "postgres://mjolnir-tester:mjolnir-test@localhost:8083/mjolnir-test-db" -accessControlList: "#access-control-list:localhost:9999" -adminRoom: "#access-control-list:localhost:9999" +adminRoom: "#draupnir-admin:localhost:9999" webAPI: port: 9001 diff --git a/src/appservice/config/config.ts b/src/appservice/config/config.ts index c872e874..4d68c103 100644 --- a/src/appservice/config/config.ts +++ b/src/appservice/config/config.ts @@ -46,8 +46,6 @@ export interface IConfig { webAPI: { port: number }, - /** A policy room for controlling access to the appservice -- just replace with mangement room tbh bloody hell m8 */ - accessControlList: string, /** The admin room for the appservice bot. Not called managementRoom like mjolnir on purpose, so they're not mixed in code somehow. */ adminRoom: string, /** configuration for matrix-appservice-bridge's Logger */ diff --git a/src/models/PolicyList.ts b/src/models/PolicyList.ts index e298a378..e888c838 100644 --- a/src/models/PolicyList.ts +++ b/src/models/PolicyList.ts @@ -314,21 +314,36 @@ export class PolicyList extends EventEmitter { } } + /** + * Create a new policy rule in the list. + * @param entityType The type of entity the rule applies to e.g. RULE_USER for users. + * @param recommendation The recommendation e.g. `Recommendation.Ban` that the policy represents. + * @param entity The entity to be added to the policy list. + * @param additionalProperties Any other properties to embed in the rule such as a reason. + * @returns The event id of the policy. + */ + public async createPolicy(entityType: EntityType, recommendation: Recommendation, entity: string, additionalProperties = {}): Promise { + // '@' at the beginning of state keys is reserved. + const stateKey = entityType === RULE_USER ? '_' + entity.substring(1) : entity; + const eventId = await this.client.sendStateEvent(this.roomId, entityType, stateKey, { + recommendation, + entity, + ...additionalProperties + }); + this.updateForEvent(eventId); + return eventId; + } + /** * Ban an entity with Recommendation.Ban from the list. * @param ruleType The type of rule e.g. RULE_USER. * @param entity The entity to ban. * @param reason A reason we are banning them. */ - public async banEntity(ruleType: string, entity: string, reason?: string): Promise { - // '@' at the beginning of state keys is reserved. - const stateKey = ruleType === RULE_USER ? '_' + entity.substring(1) : entity; - const event_id = await this.client.sendStateEvent(this.roomId, ruleType, stateKey, { - entity, - recommendation: Recommendation.Ban, + public async banEntity(ruleType: EntityType, entity: string, reason?: string): Promise { + await this.createPolicy(ruleType, Recommendation.Ban, entity, { reason: reason || '', }); - this.updateForEvent(event_id); } /** diff --git a/test/appservice/utils/harness.ts b/test/appservice/utils/harness.ts index 00ad2ba3..4acde6b8 100644 --- a/test/appservice/utils/harness.ts +++ b/test/appservice/utils/harness.ts @@ -13,7 +13,7 @@ export function readTestConfig(): IConfig { export async function setupHarness(): Promise { const config = readTestConfig(); const utilityUser = await newTestUser(config.homeserver.url, { name: { contains: "utility" }}); - await ensureAliasedRoomExists(utilityUser, config.accessControlList); + await ensureAliasedRoomExists(utilityUser, config.adminRoom); return await MjolnirAppService.run(9000, config, "mjolnir-registration.yaml"); }