<commit_before>import { Request } from "express";
import { IrcBridge } from "../bridge/IrcBridge";
import { Defer } from "../promiseutil";
import { ConfigValidator, MatrixRoom, MatrixUser } from "matrix-appservice-bridge";
import Bluebird from "bluebird";
import { IrcRoom } from "../models/IrcRoom";
import { IrcAction } from "../models/IrcAction";
import { BridgeRequest, BridgeRequestData } from "../models/BridgeRequest";
import { ProvisionRequest } from "./ProvisionRequest";
import logging, { RequestLogger } from "../logging";
import * as promiseutil from "../promiseutil";
import * as express from "express";
import { IrcServer } from "../irc/IrcServer";
import { IrcUser } from "../models/IrcUser";
import { GetNicksResponseOperators } from "../irc/BridgedClient";
import { BridgeStateSyncer } from "../bridge/BridgeStateSyncer";

const log = logging("Provisioner");

const matrixRoomIdValidation = {
    "type": "string",
    "pattern": "^!.*:.*$"
};

const validationProperties = {
    "matrix_room_id" : matrixRoomIdValidation,
    "remote_room_channel" : {
        "type": "string",
        "pattern": "^([#+&]|(![A-Z0-9]{5}))[^\\s:,]+$"
    },
    "remote_room_server" : {
        "type": "string",
        "pattern": "^[a-z\\.0-9:-]+$"
    },
    "op_nick" : {
        "type": "string"
    },
    "key" : {
        "type": "string"
    },
    "user_id" : {
        "type": "string"
    }
};

interface PendingRequest {
    userId: string;
    defer: Defer<unknown>;
    log: RequestLogger;
}

export class Provisioner {
    // Cache bot clients so as not to create duplicates
    private botClients: {} = {};
    private pendingRequests: {
        [domain: string]: {
            [nick: string]: PendingRequest;
        };
    } = {};
    private linkValidator: ConfigValidator;
    private queryLinkValidator: ConfigValidator;
    private unlinkValidator: ConfigValidator;
    private roomIdValidator: ConfigValidator;
    constructor(private ircBridge: IrcBridge, private enabled: boolean, private requestTimeoutSeconds: number) {
        this.linkValidator = new ConfigValidator({
            "type": "object",
            "properties": validationProperties,
            "required": [
                "matrix_room_id",
                "remote_room_channel",
                "remote_room_server",
                "op_nick",
                "user_id"
            ]
        });
        this.queryLinkValidator = new ConfigValidator({
            "type": "object",
            "properties": validationProperties,
            "required": [
                "remote_room_channel",
                "remote_room_server"
            ]
        });
        this.unlinkValidator = new ConfigValidator({
            "type": "object",
            "properties": validationProperties,
            "required": [
                "matrix_room_id",
                "remote_room_channel",
                "remote_room_server",
                "user_id"
            ]
        });
        this.roomIdValidator = new ConfigValidator({
            "type": "object",
            "properties": {
                "matrix_room_id" : matrixRoomIdValidation
            }
        });

        if (enabled) {
            log.info("Starting provisioning...");
        }
        else {
            log.info("Provisioning disabled.");
        }

        const appservice = this.ircBridge.getAppServiceBridge().appService;
        // TODO: Make app public
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const app = ((appservice as any).app as express.Router);

        if (enabled && !(app.use && app.get && app.post)) {
            throw new Error('Could not start provisioning API');
        }

        // Disable all provision endpoints by not calling 'next' and returning an error instead
        if (!enabled) {
            app.use((req, res, next) => {
                if (this.isProvisionRequest(req)) {
                    res.header("Access-Control-Allow-Origin", "*");
                    res.header("Access-Control-Allow-Headers",
                        "Origin, X-Requested-With, Content-Type, Accept");
                    res.status(500);
                    res.json({error : 'Provisioning is not enabled.'});
                }
                else {
                    next();
                }
            });
        }

        app.use((req, res, next) => {
            // Deal with CORS (temporarily for s-web)
            if (this.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
            }
            if (!this.ircBridge.getAppServiceBridge().requestCheckToken(req)) {
                res.status(403).send({
                    errcode: "M_FORBIDDEN",
                    error: "Bad token supplied"
                });
                return;
            }
            next();
        });

        app.post("/_matrix/provision/link",
            this.createProvisionEndpoint(this.requestLink, 'requestLink')
        );

        app.post("/_matrix/provision/unlink",
            this.createProvisionEndpoint(this.unlink, 'unlink')
        );

        app.get("/_matrix/provision/listlinks/:roomId",
            this.createProvisionEndpoint(this.listings, 'listings')
        );

        app.post("/_matrix/provision/querylink",
            this.createProvisionEndpoint(this.queryLink, 'queryLink')
        );

        app.get("/_matrix/provision/querynetworks",
            this.createProvisionEndpoint(this.queryNetworks, 'queryNetworks')
        );

        app.get("/_matrix/provision/limits",
            this.createProvisionEndpoint(this.getLimits, 'limits')
        );

        if (enabled) {
            log.info("Provisioning started");
        }
    }

    private createProvisionEndpoint(fn: (req: ProvisionRequest) => unknown, fnName: string) {
        return async (req: express.Request, res: express.Response) => {
            const pReq = new ProvisionRequest(req, fnName);
            pReq.log.info(
                'New provisioning request: ' + JSON.stringify(req.body) +
                ' params: ' + JSON.stringify(req.params)
            );
            try {
                let result = await fn.call(this, pReq);
                if (!result) {
                    result = {};
                }
                pReq.log.info(`Sending result: ${JSON.stringify(result)}`);
                res.json(result);
            }
            catch (err) {
                res.status(500).json({error: err.message});
                pReq.log.error(err.stack);
                throw err;
            }
        }
    }

    private isProvisionRequest(req: Request) {
        return req.url === '/_matrix/provision/unlink' ||<commit_msg>Fix provisioner access to endpoint<commit_after>import { Request } from "express";
import { IrcBridge } from "../bridge/IrcBridge";
import { Defer } from "../promiseutil";
import { ConfigValidator, MatrixRoom, MatrixUser } from "matrix-appservice-bridge";
import Bluebird from "bluebird";
import { IrcRoom } from "../models/IrcRoom";
import { IrcAction } from "../models/IrcAction";
import { BridgeRequest, BridgeRequestData } from "../models/BridgeRequest";
import { ProvisionRequest } from "./ProvisionRequest";
import logging, { RequestLogger } from "../logging";
import * as promiseutil from "../promiseutil";
import * as express from "express";
import { IrcServer } from "../irc/IrcServer";
import { IrcUser } from "../models/IrcUser";
import { GetNicksResponseOperators } from "../irc/BridgedClient";
import { BridgeStateSyncer } from "../bridge/BridgeStateSyncer";

const log = logging("Provisioner");

const matrixRoomIdValidation = {
    "type": "string",
    "pattern": "^!.*:.*$"
};

const validationProperties = {
    "matrix_room_id" : matrixRoomIdValidation,
    "remote_room_channel" : {
        "type": "string",
        "pattern": "^([#+&]|(![A-Z0-9]{5}))[^\\s:,]+$"
    },
    "remote_room_server" : {
        "type": "string",
        "pattern": "^[a-z\\.0-9:-]+$"
    },
    "op_nick" : {
        "type": "string"
    },
    "key" : {
        "type": "string"
    },
    "user_id" : {
        "type": "string"
    }
};

interface PendingRequest {
    userId: string;
    defer: Defer<unknown>;
    log: RequestLogger;
}

export class Provisioner {
    // Cache bot clients so as not to create duplicates
    private botClients: {} = {};
    private pendingRequests: {
        [domain: string]: {
            [nick: string]: PendingRequest;
        };
    } = {};
    private linkValidator: ConfigValidator;
    private queryLinkValidator: ConfigValidator;
    private unlinkValidator: ConfigValidator;
    private roomIdValidator: ConfigValidator;
    constructor(private ircBridge: IrcBridge, private enabled: boolean, private requestTimeoutSeconds: number) {
        this.linkValidator = new ConfigValidator({
            "type": "object",
            "properties": validationProperties,
            "required": [
                "matrix_room_id",
                "remote_room_channel",
                "remote_room_server",
                "op_nick",
                "user_id"
            ]
        });
        this.queryLinkValidator = new ConfigValidator({
            "type": "object",
            "properties": validationProperties,
            "required": [
                "remote_room_channel",
                "remote_room_server"
            ]
        });
        this.unlinkValidator = new ConfigValidator({
            "type": "object",
            "properties": validationProperties,
            "required": [
                "matrix_room_id",
                "remote_room_channel",
                "remote_room_server",
                "user_id"
            ]
        });
        this.roomIdValidator = new ConfigValidator({
            "type": "object",
            "properties": {
                "matrix_room_id" : matrixRoomIdValidation
            }
        });

        if (enabled) {
            log.info("Starting provisioning...");
        }
        else {
            log.info("Provisioning disabled.");
        }

        const appservice = this.ircBridge.getAppServiceBridge().appService;
        const app = appservice?.expressApp;

        // Disable all provision endpoints by not calling 'next' and returning an error instead
        if (!enabled) {
            if (app) {
                app.use((req, res, next) => {
                    if (this.isProvisionRequest(req)) {
                        res.header("Access-Control-Allow-Origin", "*");
                        res.header("Access-Control-Allow-Headers",
                            "Origin, X-Requested-With, Content-Type, Accept");
                        res.status(500);
                        res.json({error : 'Provisioning is not enabled.'});
                    }
                    else {
                        next();
                    }
                });
            }
            return;
        }

        if (!app) {
            throw new Error('Could not start provisioning API');
        }

        app.use((req, res, next) => {
            // Deal with CORS (temporarily for s-web)
            if (this.isProvisionRequest(req)) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers",
                    "Origin, X-Requested-With, Content-Type, Accept");
            }
            if (!this.ircBridge.getAppServiceBridge().requestCheckToken(req)) {
                res.status(403).send({
                    errcode: "M_FORBIDDEN",
                    error: "Bad token supplied"
                });
                return;
            }
            next();
        });

        app.post("/_matrix/provision/link",
            this.createProvisionEndpoint(this.requestLink, 'requestLink')
        );

        app.post("/_matrix/provision/unlink",
            this.createProvisionEndpoint(this.unlink, 'unlink')
        );

        app.get("/_matrix/provision/listlinks/:roomId",
            this.createProvisionEndpoint(this.listings, 'listings')
        );

        app.post("/_matrix/provision/querylink",
            this.createProvisionEndpoint(this.queryLink, 'queryLink')
        );

        app.get("/_matrix/provision/querynetworks",
            this.createProvisionEndpoint(this.queryNetworks, 'queryNetworks')
        );

        app.get("/_matrix/provision/limits",
            this.createProvisionEndpoint(this.getLimits, 'limits')
        );

        log.info("Provisioning started");
    }

    private createProvisionEndpoint(fn: (req: ProvisionRequest) => unknown, fnName: string) {
        return async (req: express.Request, res: express.Response) => {
            const pReq = new ProvisionRequest(req, fnName);
            pReq.log.info(
                'New provisioning request: ' + JSON.stringify(req.body) +
                ' params: ' + JSON.stringify(req.params)
            );
            try {
                let result = await fn.call(this, pReq);
                if (!result) {
                    result = {};
                }
                pReq.log.info(`Sending result: ${JSON.stringify(result)}`);
                res.json(result);
            }
            catch (err) {
                res.status(500).json({error: err.message});
                pReq.log.error(err.stack);
                throw err;
            }
        }
    }

    private isProvisionRequest(req: Request) {
        return req.url === '/_matrix/provision/unlink' ||
{'fraction_non_alphanumeric': 0.13222352596069228, 'fraction_numerical': 0.002053388090349076, 'mean_word_length': 1.801931374563386, 'pattern_counts': {'":': 40, '<': 5, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4099787', 'n_tokens_mistral': 3775, 'n_tokens_neox': 3616, 'n_words': 720}