Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Appservice convenience #47

Merged
merged 3 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/appservice/AccessControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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<void> {
await this.accessControlList.createPolicy(EntityType.RULE_USER, Recommendation.Allow, mxid);
}

public async remove(mxid: string): Promise<void> {
await this.accessControlList.unbanEntity(EntityType.RULE_USER, mxid);
}
}
4 changes: 2 additions & 2 deletions src/appservice/AppService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand Down
54 changes: 54 additions & 0 deletions src/appservice/bot/AccessCommands.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Copyright (C) 2022 Gnuxie <[email protected]>
* 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<CommandResult<void>> {
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<CommandResult<void>> {
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,
});
1 change: 1 addition & 0 deletions src/appservice/bot/AppserviceCommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export type AppserviceBaseExecutor = (this: AppserviceContext, ...args: any[]) =

import '../../commands/interface-manager/MatrixPresentations';
import './ListCommand';
import './AccessCommands';
import { AppserviceBotEmitter } from './AppserviceBotEmitter';


Expand Down
2 changes: 1 addition & 1 deletion src/appservice/config/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 1 addition & 2 deletions src/appservice/config/config.harness.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 0 additions & 2 deletions src/appservice/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
29 changes: 22 additions & 7 deletions src/models/PolicyList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
// '@' 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<void> {
// '@' 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<void> {
await this.createPolicy(ruleType, Recommendation.Ban, entity, {
reason: reason || '<no reason supplied>',
});
this.updateForEvent(event_id);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion test/appservice/utils/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function readTestConfig(): IConfig {
export async function setupHarness(): Promise<MjolnirAppService> {
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");
}

Expand Down