mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 13:17:08 +00:00
Drop provisioning API.
This commit is contained in:
parent
ab918bd293
commit
fc9233ad0a
@ -26,7 +26,6 @@ import { UserNotificationsEvent } from "./Notifications/UserNotificationWatcher"
|
||||
import { UserTokenStore } from "./tokens/UserTokenStore";
|
||||
import * as GitHubWebhookTypes from "@octokit/webhooks-types";
|
||||
import { Logger } from "matrix-appservice-bridge";
|
||||
import { Provisioner } from "./provisioning/provisioner";
|
||||
import { JiraProvisionerRouter } from "./jira/Router";
|
||||
import { GitHubProvisionerRouter } from "./github/Router";
|
||||
import { OAuthRequest } from "./WebhookTypes";
|
||||
@ -56,7 +55,6 @@ export class Bridge {
|
||||
private adminRooms: Map<string, AdminRoom> = new Map();
|
||||
private feedReader?: FeedReader;
|
||||
private houndReader?: HoundReader;
|
||||
private provisioningApi?: Provisioner;
|
||||
private replyProcessor = new RichRepliesPreprocessor(true);
|
||||
|
||||
private ready = false;
|
||||
@ -135,34 +133,6 @@ export class Bridge {
|
||||
this.github,
|
||||
);
|
||||
|
||||
if (this.config.provisioning) {
|
||||
const routers = [];
|
||||
if (this.config.jira) {
|
||||
routers.push({
|
||||
route: "/v1/jira",
|
||||
router: new JiraProvisionerRouter(this.config.jira, this.tokenStore).getRouter(),
|
||||
});
|
||||
this.connectionManager.registerProvisioningConnection(JiraProjectConnection);
|
||||
}
|
||||
if (this.config.github && this.github) {
|
||||
routers.push({
|
||||
route: "/v1/github",
|
||||
router: new GitHubProvisionerRouter(this.config.github, this.tokenStore, this.github).getRouter(),
|
||||
});
|
||||
this.connectionManager.registerProvisioningConnection(GitHubRepoConnection);
|
||||
}
|
||||
if (this.config.generic) {
|
||||
this.connectionManager.registerProvisioningConnection(GenericHookConnection);
|
||||
}
|
||||
this.provisioningApi = new Provisioner(
|
||||
this.config.provisioning,
|
||||
this.connectionManager,
|
||||
this.botUsersManager,
|
||||
this.as,
|
||||
routers,
|
||||
);
|
||||
}
|
||||
|
||||
this.as.on("query.room", async (roomAlias, cb) => {
|
||||
try {
|
||||
cb(await this.onQueryRoom(roomAlias));
|
||||
@ -775,9 +745,6 @@ export class Bridge {
|
||||
);
|
||||
|
||||
}
|
||||
if (this.provisioningApi) {
|
||||
this.listener.bindResource('provisioning', this.provisioningApi.expressRouter);
|
||||
}
|
||||
if (this.config.metrics?.enabled) {
|
||||
this.listener.bindResource('metrics', Metrics.expressRouter);
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import { CommentProcessor } from "./CommentProcessor";
|
||||
import { ConnectionDeclaration, ConnectionDeclarations, GenericHookConnection, GitHubDiscussionConnection, GitHubDiscussionSpace, GitHubIssueConnection,
|
||||
GitHubProjectConnection, GitHubRepoConnection, GitHubUserSpace, GitLabIssueConnection, GitLabRepoConnection, IConnection, IConnectionState, JiraProjectConnection } from "./Connections";
|
||||
import { FigmaFileConnection, FeedConnection } from "./Connections";
|
||||
import { GetConnectionTypeResponseItem } from "./provisioning/api";
|
||||
import { GetConnectionTypeResponseItem } from "./Widgets/api";
|
||||
import { GitLabClient } from "./Gitlab/Client";
|
||||
import { GithubInstance } from "./github/GithubInstance";
|
||||
import { IBridgeStorageProvider } from "./Stores/StorageProvider";
|
||||
|
@ -6,7 +6,7 @@ import { Logger } from "matrix-appservice-bridge";
|
||||
import { BaseConnection } from "./BaseConnection";
|
||||
import markdown from "markdown-it";
|
||||
import { Connection, ProvisionConnectionOpts } from "./IConnection";
|
||||
import { GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { GetConnectionsResponseItem } from "../Widgets/api";
|
||||
import { readFeed, sanitizeHtml } from "../libRs";
|
||||
import UserAgent from "../UserAgent";
|
||||
import { retry, retryMatrixErrorFilter } from "../PromiseUtil";
|
||||
|
@ -7,7 +7,7 @@ import { MatrixEvent } from "../MatrixEvent";
|
||||
import { Appservice, Intent, StateEvent } from "matrix-bot-sdk";
|
||||
import { ApiError, ErrCode } from "../api";
|
||||
import { BaseConnection } from "./BaseConnection";
|
||||
import { GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { GetConnectionsResponseItem } from "../Widgets/api";
|
||||
import { BridgeConfigGenericWebhooks } from "../config/Config";
|
||||
import { ensureUserIsInRoom } from "../IntentUtils";
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
@ -4,7 +4,7 @@ import { CommentProcessor } from "../CommentProcessor";
|
||||
import { FormatUtil, LooseMinimalGitHubRepo } from "../FormatUtil";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { Connection, IConnection, IConnectionState, InstantiateConnectionOpts, ProvisionConnectionOpts } from "./IConnection";
|
||||
import { GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { GetConnectionsResponseItem } from "../Widgets/api";
|
||||
import { IssuesOpenedEvent, IssuesReopenedEvent, IssuesEditedEvent, PullRequestOpenedEvent, IssuesClosedEvent, PullRequestClosedEvent,
|
||||
PullRequestReadyForReviewEvent, PullRequestReviewSubmittedEvent, ReleasePublishedEvent, ReleaseCreatedEvent,
|
||||
IssuesLabeledEvent, IssuesUnlabeledEvent, WorkflowRunCompletedEvent, IssueCommentCreatedEvent, PushEvent
|
||||
|
@ -8,7 +8,7 @@ import { BridgeConfigGitLab, GitLabInstance } from "../config/Config";
|
||||
import { IGitlabMergeRequest, IGitlabProject, IGitlabUser, IGitLabWebhookMREvent, IGitLabWebhookNoteEvent, IGitLabWebhookPushEvent, IGitLabWebhookReleaseEvent, IGitLabWebhookTagPushEvent, IGitLabWebhookWikiPageEvent } from "../Gitlab/WebhookTypes";
|
||||
import { CommandConnection } from "./CommandConnection";
|
||||
import { Connection, IConnection, IConnectionState, InstantiateConnectionOpts, ProvisionConnectionOpts } from "./IConnection";
|
||||
import { ConnectionWarning, GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { ConnectionWarning, GetConnectionsResponseItem } from "../Widgets/api";
|
||||
import { ErrCode, ApiError, ValidatorApiError } from "../api"
|
||||
import { AccessLevel, SerializedGitlabDiscussionThreads } from "../Gitlab/Types";
|
||||
import Ajv, { JSONSchemaType } from "ajv";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MatrixEvent, MatrixMessageContent } from "../MatrixEvent";
|
||||
import { IssuesOpenedEvent, IssuesEditedEvent } from "@octokit/webhooks-types";
|
||||
import { ConnectionWarning, GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { ConnectionWarning, GetConnectionsResponseItem } from "../Widgets/api";
|
||||
import { Appservice, Intent, IRichReplyMetadata, StateEvent } from "matrix-bot-sdk";
|
||||
import { BridgeConfig, BridgePermissionLevel } from "../config/Config";
|
||||
import { UserTokenStore } from "../tokens/UserTokenStore";
|
||||
|
@ -13,7 +13,7 @@ import { UserTokenStore } from "../tokens/UserTokenStore";
|
||||
import { CommandError, NotLoggedInError } from "../errors";
|
||||
import { ApiError, ErrCode } from "../api";
|
||||
import JiraApi from "jira-client";
|
||||
import { GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { GetConnectionsResponseItem } from "../Widgets/api";
|
||||
import { BridgeConfigJira } from "../config/Config";
|
||||
import { HookshotJiraApi } from "../jira/Client";
|
||||
import { GrantChecker } from "../grants/GrantCheck";
|
||||
|
@ -5,8 +5,8 @@ import { errorMiddleware } from "./api";
|
||||
|
||||
// Appserices can't be handled yet because the bot-sdk maintains control of it.
|
||||
// See https://github.com/turt2live/matrix-bot-sdk/issues/191
|
||||
export type ResourceName = "webhooks"|"widgets"|"metrics"|"provisioning";
|
||||
export const ResourceTypeArray: ResourceName[] = ["webhooks","widgets","metrics","provisioning"];
|
||||
export type ResourceName = "webhooks"|"widgets"|"metrics";
|
||||
export const ResourceTypeArray: ResourceName[] = ["webhooks","widgets","metrics"];
|
||||
import { Handlers } from "@sentry/node";
|
||||
export interface BridgeConfigListener {
|
||||
bindAddress?: string;
|
||||
|
@ -8,7 +8,7 @@ import { ProvisioningApi, ProvisioningRequest } from "matrix-appservice-bridge";
|
||||
import { IBridgeStorageProvider } from "../Stores/StorageProvider";
|
||||
import { ConnectionManager } from "../ConnectionManager";
|
||||
import BotUsersManager, {BotUser} from "../Managers/BotUsersManager";
|
||||
import { assertUserPermissionsInRoom, GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { assertUserPermissionsInRoom, GetConnectionsResponseItem } from "./api";
|
||||
import { Appservice, PowerLevelsEvent } from "matrix-bot-sdk";
|
||||
import { GithubInstance } from '../github/GithubInstance';
|
||||
import { AllowedTokenTypes, TokenType, UserTokenStore } from '../tokens/UserTokenStore';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { GetConnectionsResponseItem } from "../provisioning/api";
|
||||
import { GetConnectionsResponseItem } from "./api";
|
||||
|
||||
export interface BridgeRoomStateGitHub {
|
||||
enabled: boolean;
|
||||
|
@ -434,12 +434,6 @@ export interface BridgeConfigServiceBot {
|
||||
service: string;
|
||||
}
|
||||
|
||||
export interface BridgeConfigProvisioning {
|
||||
bindAddress?: string;
|
||||
port?: number;
|
||||
secret: string;
|
||||
}
|
||||
|
||||
export interface BridgeConfigMetrics {
|
||||
enabled: boolean;
|
||||
bindAddress?: string;
|
||||
@ -472,7 +466,6 @@ export interface BridgeConfigRoot {
|
||||
metrics?: BridgeConfigMetrics;
|
||||
passFile: string;
|
||||
permissions?: BridgeConfigActorPermission[];
|
||||
provisioning?: BridgeConfigProvisioning;
|
||||
queue?: BridgeConfigQueue;
|
||||
sentry?: BridgeConfigSentry;
|
||||
serviceBots?: BridgeConfigServiceBot[];
|
||||
@ -524,8 +517,6 @@ export class BridgeConfig {
|
||||
public readonly serviceBots?: BridgeConfigServiceBot[];
|
||||
@configKey("EXPERIMENTAL support for complimentary widgets", true)
|
||||
public readonly widgets?: BridgeWidgetConfig;
|
||||
@configKey("Provisioning API for integration managers", true)
|
||||
public readonly provisioning?: BridgeConfigProvisioning;
|
||||
@configKey("Prometheus metrics support", true)
|
||||
public readonly metrics?: BridgeConfigMetrics;
|
||||
|
||||
@ -559,7 +550,6 @@ export class BridgeConfig {
|
||||
this.jira = configData.jira && new BridgeConfigJira(configData.jira);
|
||||
this.generic = configData.generic && new BridgeConfigGenericWebhooks(configData.generic);
|
||||
this.feeds = configData.feeds && new BridgeConfigFeeds(configData.feeds);
|
||||
this.provisioning = configData.provisioning;
|
||||
this.passFile = configData.passFile ?? "./passkey.pem";
|
||||
this.bot = configData.bot;
|
||||
this.serviceBots = configData.serviceBots;
|
||||
@ -627,7 +617,11 @@ export class BridgeConfig {
|
||||
}
|
||||
|
||||
if ('goNebMigrator' in configData) {
|
||||
log.warn(`The GoNEB migrator has been removed from this release. You should remove the 'goNebMigrator' from your config.`);
|
||||
log.warn(`The GoNEB migrator has been removed from Hookshot. You should remove the 'goNebMigrator' from your config.`);
|
||||
}
|
||||
|
||||
if ('provisioning' in configData) {
|
||||
log.warn(`The provisioning API has been removed from Hookshot. You should remove the 'provisioning' from your config.`);
|
||||
}
|
||||
|
||||
// Listeners is a bit special
|
||||
@ -650,15 +644,6 @@ export class BridgeConfig {
|
||||
})
|
||||
}
|
||||
|
||||
if (this.provisioning?.port) {
|
||||
this.listeners.push({
|
||||
resources: ['provisioning'],
|
||||
port: this.provisioning.port,
|
||||
bindAddress: this.provisioning.bindAddress,
|
||||
})
|
||||
log.warn("The `provisioning` configuration still specifies a port/bindAddress. This should be moved to the `listeners` config.");
|
||||
}
|
||||
|
||||
if (this.metrics?.port) {
|
||||
this.listeners.push({
|
||||
resources: ['metrics'],
|
||||
|
@ -126,9 +126,6 @@ export const DefaultConfigRoot: BridgeConfigRoot = {
|
||||
pollTimeoutSeconds: 30,
|
||||
pollConcurrency: 4,
|
||||
},
|
||||
provisioning: {
|
||||
secret: "!secretToken"
|
||||
},
|
||||
metrics: {
|
||||
enabled: true,
|
||||
},
|
||||
@ -141,7 +138,7 @@ export const DefaultConfigRoot: BridgeConfigRoot = {
|
||||
{
|
||||
port: 9001,
|
||||
bindAddress: '127.0.0.1',
|
||||
resources: ['metrics', 'provisioning'],
|
||||
resources: ['metrics'],
|
||||
},
|
||||
{
|
||||
port: 9002,
|
||||
|
@ -1,295 +0,0 @@
|
||||
Provisioning API for matrix-hookshot
|
||||
-----------------------------
|
||||
|
||||
# Overview
|
||||
|
||||
This document describes how to integrate with `matrix-hookshot`'s provisioning API.
|
||||
|
||||
Requests made to the bridge must be against the API listener defined in the config under `provisioning`, not
|
||||
the appservice or webhook listeners.
|
||||
|
||||
Requests should always be authenticated with the secret given in the config, inside the `Authorization` header.
|
||||
Requests being made on behalf of users (most provisioning APIs) should include the userId as a query parameter.
|
||||
|
||||
```
|
||||
GET /v1/health?userId=%40Half-Shot%3Ahalf-shot.uk
|
||||
Authorization: Bearer secret
|
||||
```
|
||||
|
||||
APIs are versioned independently so two endpoints on the latest version may not always have the same version.
|
||||
|
||||
# APIs
|
||||
|
||||
## GET /v1/health
|
||||
|
||||
Request the status of the provisioning API.
|
||||
|
||||
### Response
|
||||
|
||||
```
|
||||
HTTP 200
|
||||
{}
|
||||
```
|
||||
|
||||
Any other response should be considered a failed request (e.g. 404, 502 etc).
|
||||
|
||||
## GET /v1/connectiontypes
|
||||
|
||||
Request the connection types enabled for this bridge.
|
||||
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"JiraProject": {
|
||||
"type": "JiraProject", // The name of the connection
|
||||
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type for the connection
|
||||
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
|
||||
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## GET /v1/{roomId}/connections
|
||||
|
||||
Request the connections for a given room. The `{roomId}` parameter is the target Matrix room.
|
||||
|
||||
### Response
|
||||
|
||||
```json5
|
||||
[{
|
||||
"type": "JiraProject", // The name of the connection
|
||||
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
|
||||
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
|
||||
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
|
||||
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
|
||||
"config": {
|
||||
// ... connection specific details, can be configured.
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
|
||||
## GET /v1/{roomId}/connections/{id}
|
||||
|
||||
Request details of a single connection. The `{roomId}` parameter is the target Matrix room.
|
||||
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"type": "JiraProject", // The name of the connection
|
||||
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
|
||||
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
|
||||
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
|
||||
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
|
||||
"config": {
|
||||
// ... connection specific details, can be configured.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## PUT /v1/{roomId}/connections/{type}
|
||||
|
||||
Create a new connection of a given type. The type refers to the `eventType` (`IConnection.CanonicalEventType`). The `{roomId}` parameter is the target Matrix room.
|
||||
|
||||
The body of the request is the configuration for the connection, which will be the "ConnectionState" interface for each connection.
|
||||
|
||||
|
||||
### Request body
|
||||
```json5
|
||||
{
|
||||
// ... connection specific details, can be configured.
|
||||
}
|
||||
```
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"type": "JiraProject", // The name of the connection
|
||||
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
|
||||
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
|
||||
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
|
||||
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
|
||||
"config": {
|
||||
// ... connection specific details, can be configured.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## PATCH /v1/{roomId}/connections/{id}
|
||||
|
||||
Update a connection's configuration. The `id` refers to the `id` returned in the GET response.
|
||||
|
||||
The body of the request is the configuration for the connection, which will be the "ConnectionState" interface for each connection.
|
||||
|
||||
### Request body
|
||||
```json5
|
||||
{
|
||||
// ... connection specific details, can be configured.
|
||||
}
|
||||
```
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
"type": "JiraProject", // The name of the connection
|
||||
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
|
||||
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
|
||||
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty
|
||||
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
|
||||
"config": {
|
||||
// ... connection specific details, can be configured.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## DELETE /v1/{roomId}/connections/{id}
|
||||
|
||||
Delete a connection. The `id` refers to the `id` returned in the GET response.
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"ok": true
|
||||
}
|
||||
```
|
||||
|
||||
# Service specific APIs
|
||||
|
||||
Some services have specific APIs for additional functionality, like OAuth.
|
||||
|
||||
## GitHub
|
||||
|
||||
|
||||
### GET /v1/github/oauth?userId={userId}
|
||||
|
||||
|
||||
Request an OAuth url for the given user. Once the user has completed the steps in the OAuth process,
|
||||
the bridge will be granted access.
|
||||
|
||||
### Response
|
||||
|
||||
```json5
|
||||
[{
|
||||
"user_url": "https://github.com/login/oauth/authorize?...",
|
||||
"org_url": "https://github.com/apps/matrix-bridge/installations/new",
|
||||
}]
|
||||
```
|
||||
|
||||
### GET /v1/github/account?userId={userId}
|
||||
|
||||
Request the status of the users account. This will return a `loggedIn` value to determine if the
|
||||
bridge has a GitHub identity stored for the user, and any organisations they have access to.
|
||||
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"loggedIn": true,
|
||||
"organisations":[{
|
||||
"name": "half-shot",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### GET /v1/github/orgs/{orgName}/repositories?userId={userId}&page={page}&perPage={perPage}
|
||||
|
||||
Request a list of all repositories a user is a member of in the given org. The `owner` and `name` value of a repository can be given to create a new GitHub connection.
|
||||
|
||||
This request is paginated, and `page` sets the page (defaults to `1`) while `perPage` (defaults to `10`) sets the number of entries per page.
|
||||
|
||||
This request can be retried until the number of entries is less than the value of `perPage`.
|
||||
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"loggedIn": true,
|
||||
"repositories":[{
|
||||
"name": "matrix-hookshot",
|
||||
"owner": "matrix-org",
|
||||
"fullName": "matrix-org/matrix-hookshot",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4",
|
||||
"description": "A bridge between Matrix and multiple project management services, such as GitHub, GitLab and JIRA. "
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### GET /v1/github/repositories?userId={userId}&page={page}&perPage={perPage}
|
||||
|
||||
Request a list of all repositories a user is a member of (including those not belonging to an org). The `owner` and `name` value of a repository can be given to create a new GitHub connection.
|
||||
|
||||
If the user has only allowed a subset of repositories to be bridged, `changeSelectionUrl` will be defined and can be used to expand the search query.
|
||||
|
||||
This request is paginated, and `page` sets the page (defaults to `1`) while `perPage` (defaults to `10`) sets the number of entries per page.
|
||||
|
||||
This request can be retried until the number of entries is less than the value of `perPage`.
|
||||
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"loggedIn": true,
|
||||
"changeSelectionUrl": "https://github.com/settings/installations/12345",
|
||||
"repositories":[{
|
||||
"name": "matrix-hookshot",
|
||||
"owner": "matrix-org",
|
||||
"fullName": "matrix-org/matrix-hookshot",
|
||||
"avatarUrl": "https://avatars.githubusercontent.com/u/8418310?v=4",
|
||||
"description": "A bridge between Matrix and multiple project management services, such as GitHub, GitLab and JIRA. "
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## JIRA
|
||||
|
||||
|
||||
### GET /v1/jira/oauth?userId={userId}
|
||||
|
||||
|
||||
Request an OAuth url for the given user. Once the user has completed the steps in the OAuth process,
|
||||
the bridge will be granted access.
|
||||
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"url": "https://auth.atlassian.com/authorize?..."
|
||||
}
|
||||
```
|
||||
|
||||
### GET /v1/jira/account?userId={userId}
|
||||
|
||||
|
||||
Request the status of the users account. This will return a `loggedIn` value to determine if the
|
||||
bridge has a JIRA identity stored for the user, and any instances they have access to. Note that if a
|
||||
user does not have access to an instance, they can authenticate again to gain access to it (if they are able
|
||||
to consent).
|
||||
### Response
|
||||
|
||||
```json5
|
||||
{
|
||||
"loggedIn": true,
|
||||
"instances":[{
|
||||
"name": "acme",
|
||||
"url": "https://acme.atlassian.net"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### GET /v1/jira/instances/{instanceName}/projects?userId={userId}
|
||||
|
||||
Request a list of all projects a user can see in a given instance. The `url` value of a project can be given to create
|
||||
a new JIRA connection.
|
||||
### Response
|
||||
|
||||
```json5
|
||||
[{
|
||||
"key": "PLAY",
|
||||
"name": "Jira Playground",
|
||||
"url": "https://acme.atlassian.net/projects/PLAY"
|
||||
}]
|
||||
```
|
@ -1,209 +0,0 @@
|
||||
import { BridgeConfigProvisioning } from "../config/Config";
|
||||
import { Router, default as express, NextFunction, Request, Response } from "express";
|
||||
import { ConnectionManager } from "../ConnectionManager";
|
||||
import { Logger } from "matrix-appservice-bridge";
|
||||
import { assertUserPermissionsInRoom, GetConnectionsResponseItem, GetConnectionTypeResponseItem } from "./api";
|
||||
import { ApiError, ErrCode } from "../api";
|
||||
import { Appservice } from "matrix-bot-sdk";
|
||||
import Metrics from "../Metrics";
|
||||
import BotUsersManager from "../Managers/BotUsersManager";
|
||||
|
||||
const log = new Logger("Provisioner");
|
||||
|
||||
// Simple validator
|
||||
const ROOM_ID_VALIDATOR = /!.+:.+/;
|
||||
const USER_ID_VALIDATOR = /@.+:.+/;
|
||||
|
||||
|
||||
export class Provisioner {
|
||||
public readonly expressRouter: Router = Router();
|
||||
constructor(
|
||||
private readonly config: BridgeConfigProvisioning,
|
||||
private readonly connMan: ConnectionManager,
|
||||
private readonly botUsersManager: BotUsersManager,
|
||||
private readonly as: Appservice,
|
||||
additionalRoutes: {route: string, router: Router}[]) {
|
||||
if (!this.config.secret) {
|
||||
throw Error('Missing secret in provisioning config');
|
||||
}
|
||||
this.expressRouter.use("/v1", (req, _res, next) => {
|
||||
Metrics.provisioningHttpRequest.inc({path: req.path, method: req.method});
|
||||
next();
|
||||
});
|
||||
this.expressRouter.get("/v1/health", this.getHealth);
|
||||
this.expressRouter.use("/v1", this.checkAuth.bind(this));
|
||||
this.expressRouter.use(express.json());
|
||||
// Room Routes
|
||||
this.expressRouter.get(
|
||||
"/v1/connectiontypes",
|
||||
this.getConnectionTypes.bind(this),
|
||||
);
|
||||
this.expressRouter.use("/v1", this.checkUserId.bind(this));
|
||||
additionalRoutes.forEach(route => {
|
||||
this.expressRouter.use(route.route, route.router);
|
||||
});
|
||||
this.expressRouter.get<{roomId: string}, unknown, unknown, {userId: string}>(
|
||||
"/v1/:roomId/connections",
|
||||
this.checkRoomId.bind(this),
|
||||
(...args) => this.checkUserPermission("read", ...args),
|
||||
this.getConnections.bind(this),
|
||||
);
|
||||
this.expressRouter.get<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>(
|
||||
"/v1/:roomId/connections/:connectionId",
|
||||
this.checkRoomId.bind(this),
|
||||
(...args) => this.checkUserPermission("read", ...args),
|
||||
this.getConnection.bind(this),
|
||||
);
|
||||
this.expressRouter.put<{roomId: string, type: string}, unknown, Record<string, unknown>, {userId: string}>(
|
||||
"/v1/:roomId/connections/:type",
|
||||
this.checkRoomId.bind(this),
|
||||
(...args) => this.checkUserPermission("write", ...args),
|
||||
this.putConnection.bind(this),
|
||||
);
|
||||
this.expressRouter.patch<{roomId: string, connectionId: string}, unknown, Record<string, unknown>, {userId: string}>(
|
||||
"/v1/:roomId/connections/:connectionId",
|
||||
this.checkRoomId.bind(this),
|
||||
(...args) => this.checkUserPermission("write", ...args),
|
||||
this.patchConnection.bind(this),
|
||||
);
|
||||
this.expressRouter.delete<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>(
|
||||
"/v1/:roomId/connections/:connectionId",
|
||||
this.checkRoomId.bind(this),
|
||||
(...args) => this.checkUserPermission("write", ...args),
|
||||
this.deleteConnection.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
private checkAuth(req: Request, _res: Response, next: NextFunction) {
|
||||
if (req.headers.authorization === `Bearer ${this.config.secret}`) {
|
||||
return next();
|
||||
}
|
||||
throw new ApiError("Unauthorized", ErrCode.BadToken);
|
||||
}
|
||||
|
||||
private checkRoomId(req: Request<{roomId: string}>, _res: Response, next: NextFunction) {
|
||||
if (!req.params.roomId || !ROOM_ID_VALIDATOR.exec(req.params.roomId)) {
|
||||
throw new ApiError("Invalid roomId", ErrCode.BadValue);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
private checkUserId(req: Request, _res: Response, next: NextFunction) {
|
||||
if (typeof req.query.userId !== "string" || !USER_ID_VALIDATOR.exec(req.query.userId)) {
|
||||
throw new ApiError("Invalid userId", ErrCode.BadValue);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
private async checkUserPermission(requiredPermission: "read"|"write", req: Request<{roomId: string}, unknown, unknown, {userId: string}>, res: Response, next: NextFunction) {
|
||||
const userId = req.query.userId;
|
||||
const roomId = req.params.roomId;
|
||||
|
||||
const botUser = this.botUsersManager.getBotUserInRoom(roomId);
|
||||
if (!botUser) {
|
||||
throw new ApiError("Bot is not joined to the room.", ErrCode.NotInRoom);
|
||||
}
|
||||
|
||||
try {
|
||||
await assertUserPermissionsInRoom(userId, roomId, requiredPermission, botUser.intent);
|
||||
next();
|
||||
} catch (ex) {
|
||||
next(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private getHealth(_req: Request, res: Response) {
|
||||
return res.send({})
|
||||
}
|
||||
|
||||
private getConnectionTypes(_req: Request, res: Response<Record<string, GetConnectionTypeResponseItem>>) {
|
||||
return res.send(this.connMan.enabledForProvisioning);
|
||||
}
|
||||
|
||||
private getConnections(req: Request<{roomId: string}>, res: Response<GetConnectionsResponseItem[]>) {
|
||||
const connections = this.connMan.getAllConnectionsForRoom(req.params.roomId);
|
||||
const details = connections.map(c => c.getProvisionerDetails?.()).filter(c => !!c) as GetConnectionsResponseItem[];
|
||||
return res.send(details);
|
||||
}
|
||||
|
||||
private getConnection(req: Request<{roomId: string, connectionId: string}>, res: Response<GetConnectionsResponseItem>) {
|
||||
const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId);
|
||||
if (!connection) {
|
||||
throw new ApiError("Connection does not exist.", ErrCode.NotFound);
|
||||
}
|
||||
if (!connection.getProvisionerDetails) {
|
||||
throw new ApiError("Connection type does not support updates.", ErrCode.UnsupportedOperation);
|
||||
}
|
||||
return res.send(connection.getProvisionerDetails());
|
||||
}
|
||||
|
||||
private async putConnection(req: Request<{roomId: string, type: string}, unknown, Record<string, unknown>, {userId: string}>, res: Response<GetConnectionsResponseItem>, next: NextFunction) {
|
||||
const roomId = req.params.roomId;
|
||||
const userId = req.query.userId;
|
||||
const eventType = req.params.type;
|
||||
const connectionType = this.connMan.getConnectionTypeForEventType(eventType);
|
||||
if (!connectionType) {
|
||||
throw new ApiError("Unknown event type", ErrCode.NotFound);
|
||||
}
|
||||
const serviceType = connectionType.ServiceCategory;
|
||||
|
||||
// Need to figure out which connections are available
|
||||
try {
|
||||
if (!req.body || typeof req.body !== "object") {
|
||||
throw new ApiError("A JSON body must be provided", ErrCode.BadValue);
|
||||
}
|
||||
this.connMan.validateCommandPrefix(roomId, req.body);
|
||||
|
||||
const botUser = this.botUsersManager.getBotUserInRoom(roomId, serviceType);
|
||||
if (!botUser) {
|
||||
throw new ApiError("Bot is not joined to the room.", ErrCode.NotInRoom);
|
||||
}
|
||||
|
||||
const result = await this.connMan.provisionConnection(roomId, botUser.intent, userId, connectionType, req.body);
|
||||
if (!result.connection.getProvisionerDetails) {
|
||||
throw new Error('Connection supported provisioning but not getProvisionerDetails');
|
||||
}
|
||||
|
||||
res.send({
|
||||
...result.connection.getProvisionerDetails(true),
|
||||
warning: result.warning,
|
||||
});
|
||||
} catch (ex) {
|
||||
log.error(`Failed to create connection for ${roomId}`, ex);
|
||||
return next(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async patchConnection(req: Request<{roomId: string, connectionId: string}, unknown, Record<string, unknown>, {userId: string}>, res: Response<GetConnectionsResponseItem>, next: NextFunction) {
|
||||
try {
|
||||
const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId);
|
||||
if (!connection) {
|
||||
return next(new ApiError("Connection does not exist.", ErrCode.NotFound));
|
||||
}
|
||||
if (!connection.provisionerUpdateConfig || !connection.getProvisionerDetails) {
|
||||
return next(new ApiError("Connection type does not support updates.", ErrCode.UnsupportedOperation));
|
||||
}
|
||||
this.connMan.validateCommandPrefix(req.params.roomId, req.body, connection);
|
||||
await connection.provisionerUpdateConfig(req.query.userId, req.body);
|
||||
res.send(connection.getProvisionerDetails(true));
|
||||
} catch (ex) {
|
||||
next(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async deleteConnection(req: Request<{roomId: string, connectionId: string}>, res: Response<{ok: true}>, next: NextFunction) {
|
||||
try {
|
||||
const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId);
|
||||
if (!connection) {
|
||||
return next(new ApiError("Connection does not exist.", ErrCode.NotFound));
|
||||
}
|
||||
if (!connection.onRemove) {
|
||||
return next(new ApiError("Connection does not support removal.", ErrCode.UnsupportedOperation));
|
||||
}
|
||||
await this.connMan.purgeConnection(req.params.roomId, req.params.connectionId);
|
||||
res.send({ok: true});
|
||||
} catch (ex) {
|
||||
return next(ex);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user