mirror of https://github.com/boxyhq/jackson.git
871 lines
28 KiB
TypeScript
871 lines
28 KiB
TypeScript
import * as dbutils from '../db/utils';
|
|
import * as metrics from '../opentelemetry/metrics';
|
|
import {
|
|
GetConfigQuery,
|
|
GetConnectionsQuery,
|
|
DelConnectionsQuery,
|
|
IConnectionAPIController,
|
|
Storable,
|
|
SAMLSSOConnectionWithEncodedMetadata,
|
|
SAMLSSOConnectionWithRawMetadata,
|
|
OIDCSSOConnectionWithDiscoveryUrl,
|
|
OIDCSSOConnectionWithMetadata,
|
|
JacksonOption,
|
|
SAMLSSORecord,
|
|
OIDCSSORecord,
|
|
GetIDPEntityIDBody,
|
|
IEventController,
|
|
UpdateSAMLConnectionParams,
|
|
UpdateOIDCConnectionParams,
|
|
GetByProductParams,
|
|
Index,
|
|
} from '../typings';
|
|
import { JacksonError } from './error';
|
|
import { IndexNames, appID, transformConnections, transformConnection, isConnectionActive } from './utils';
|
|
import oidcConnection from './connection/oidc';
|
|
import samlConnection from './connection/saml';
|
|
import { OryController } from '../ee/ory/ory';
|
|
|
|
export class ConnectionAPIController implements IConnectionAPIController {
|
|
private connectionStore: Storable;
|
|
private opts: JacksonOption;
|
|
private eventController: IEventController;
|
|
private oryController: OryController;
|
|
|
|
constructor({ connectionStore, opts, eventController, oryController }) {
|
|
this.connectionStore = connectionStore;
|
|
this.opts = opts;
|
|
this.eventController = eventController;
|
|
this.oryController = oryController;
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* definitions:
|
|
* Connection:
|
|
* type: object
|
|
* example:
|
|
* {
|
|
* "idpMetadata": {
|
|
* "sso": {
|
|
* "postUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxsso/saml",
|
|
* "redirectUrl": "https://dev-20901260.okta.com/app/dev-20901260_jacksonnext_1/xxxxxxxxxxxsso/saml"
|
|
* },
|
|
* "entityID": "http://www.okta.com/xxxxxxxxxxxxx",
|
|
* "thumbprint": "Eo+eUi3UM3XIMkFFtdVK3yJ5vO9f7YZdasdasdad",
|
|
* "loginType": "idp",
|
|
* "provider": "okta.com"
|
|
* },
|
|
* "defaultRedirectUrl": "https://hoppscotch.io/",
|
|
* "redirectUrl": ["https://hoppscotch.io/"],
|
|
* "tenant": "hoppscotch.io",
|
|
* "product": "API Engine",
|
|
* "name": "Hoppscotch-SP",
|
|
* "description": "SP for hoppscotch.io",
|
|
* "clientID": "Xq8AJt3yYAxmXizsCWmUBDRiVP1iTC8Y/otnvFIMitk",
|
|
* "clientSecret": "00e3e11a3426f97d8000000738300009130cd45419c5943",
|
|
* "deactivated": false
|
|
* }
|
|
* validationErrorsPost:
|
|
* description: Please provide rawMetadata or encodedRawMetadata | Please provide a defaultRedirectUrl | Please provide redirectUrl | redirectUrl is invalid | Exceeded maximum number of allowed redirect urls | defaultRedirectUrl is invalid | Please provide tenant | Please provide product | Please provide a friendly name | Description should not exceed 100 characters | Strategy: xxxx not supported | Please provide the clientId from OpenID Provider | Please provide the clientSecret from OpenID Provider | Please provide the discoveryUrl for the OpenID Provider
|
|
*
|
|
* parameters:
|
|
* nameParamPost:
|
|
* name: name
|
|
* description: Name/identifier for the connection
|
|
* type: string
|
|
* in: formData
|
|
* labelParamPost:
|
|
* name: label
|
|
* description: An internal label to identify the connection
|
|
* type: string
|
|
* in: formData
|
|
* descriptionParamPost:
|
|
* name: description
|
|
* description: A short description for the connection not more than 100 characters
|
|
* type: string
|
|
* in: formData
|
|
* encodedRawMetadataParamPost:
|
|
* name: encodedRawMetadata
|
|
* description: Base64 encoding of the XML metadata
|
|
* in: formData
|
|
* type: string
|
|
* rawMetadataParamPost:
|
|
* name: rawMetadata
|
|
* description: Raw XML metadata
|
|
* in: formData
|
|
* type: string
|
|
* metadataUrlParamPost:
|
|
* name: metadataUrl
|
|
* description: URL containing raw XML metadata
|
|
* in: formData
|
|
* type: string
|
|
* defaultRedirectUrlParamPost:
|
|
* name: defaultRedirectUrl
|
|
* description: The redirect URL to use in the IdP login flow
|
|
* in: formData
|
|
* required: true
|
|
* type: string
|
|
* redirectUrlParamPost:
|
|
* name: redirectUrl
|
|
* description: JSON encoded array containing a list of allowed redirect URLs
|
|
* in: formData
|
|
* required: true
|
|
* type: string
|
|
* tenantParamPost:
|
|
* name: tenant
|
|
* description: Tenant
|
|
* in: formData
|
|
* required: true
|
|
* type: string
|
|
* productParamPost:
|
|
* name: product
|
|
* description: Product
|
|
* in: formData
|
|
* required: true
|
|
* type: string
|
|
* oidcDiscoveryUrlPost:
|
|
* name: oidcDiscoveryUrl
|
|
* description: well-known URL where the OpenID Provider configuration is exposed
|
|
* in: formData
|
|
* type: string
|
|
* oidcMetadataPost:
|
|
* name: oidcMetadata
|
|
* description: metadata (JSON) for the OpenID Provider in the absence of discoveryUrl
|
|
* in: formData
|
|
* type: string
|
|
* oidcClientIdPost:
|
|
* name: oidcClientId
|
|
* description: clientId of the application set up on the OpenID Provider
|
|
* in: formData
|
|
* type: string
|
|
* oidcClientSecretPost:
|
|
* name: oidcClientSecret
|
|
* description: clientSecret of the application set up on the OpenID Provider
|
|
* in: formData
|
|
* type: string
|
|
* sortOrder:
|
|
* name: sortOrder
|
|
* description: Indicate the position of the connection in the IdP selection screen
|
|
* in: formData
|
|
* type: number
|
|
* required: false
|
|
* /api/v1/sso:
|
|
* post:
|
|
* summary: Create SSO connection
|
|
* operationId: create-sso-connection
|
|
* tags: [Single Sign On]
|
|
* produces:
|
|
* - application/json
|
|
* consumes:
|
|
* - application/x-www-form-urlencoded
|
|
* - application/json
|
|
* parameters:
|
|
* - $ref: '#/parameters/nameParamPost'
|
|
* - $ref: '#/parameters/labelParamPost'
|
|
* - $ref: '#/parameters/descriptionParamPost'
|
|
* - $ref: '#/parameters/encodedRawMetadataParamPost'
|
|
* - $ref: '#/parameters/rawMetadataParamPost'
|
|
* - $ref: '#/parameters/metadataUrlParamPost'
|
|
* - $ref: '#/parameters/defaultRedirectUrlParamPost'
|
|
* - $ref: '#/parameters/redirectUrlParamPost'
|
|
* - $ref: '#/parameters/tenantParamPost'
|
|
* - $ref: '#/parameters/productParamPost'
|
|
* - $ref: '#/parameters/oidcDiscoveryUrlPost'
|
|
* - $ref: '#/parameters/oidcMetadataPost'
|
|
* - $ref: '#/parameters/oidcClientIdPost'
|
|
* - $ref: '#/parameters/oidcClientSecretPost'
|
|
* - $ref: '#/parameters/sortOrder'
|
|
* responses:
|
|
* 200:
|
|
* description: Success
|
|
* schema:
|
|
* $ref: '#/definitions/Connection'
|
|
* 400:
|
|
* $ref: '#/definitions/validationErrorsPost'
|
|
* 401:
|
|
* description: Unauthorized
|
|
*/
|
|
public async createSAMLConnection(
|
|
body: SAMLSSOConnectionWithEncodedMetadata | SAMLSSOConnectionWithRawMetadata
|
|
): Promise<SAMLSSORecord> {
|
|
metrics.increment('createConnection');
|
|
|
|
const connection = await samlConnection.create(body, this.connectionStore, this.oryController);
|
|
|
|
await this.eventController.notify('sso.created', connection);
|
|
|
|
return connection;
|
|
}
|
|
|
|
// For backwards compatibility
|
|
public async config(
|
|
...args: Parameters<ConnectionAPIController['createSAMLConnection']>
|
|
): Promise<SAMLSSORecord> {
|
|
return this.createSAMLConnection(...args);
|
|
}
|
|
|
|
public async createOIDCConnection(
|
|
body: OIDCSSOConnectionWithDiscoveryUrl | OIDCSSOConnectionWithMetadata
|
|
): Promise<OIDCSSORecord> {
|
|
metrics.increment('createConnection');
|
|
|
|
if (!this.opts.oidcPath) {
|
|
throw new JacksonError('Please set OpenID response handler path (oidcPath) on Jackson', 500);
|
|
}
|
|
|
|
const connection = await oidcConnection.create(body, this.connectionStore, this.oryController);
|
|
|
|
await this.eventController.notify('sso.created', connection);
|
|
|
|
return connection;
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* definitions:
|
|
* validationErrorsPatch:
|
|
* description: Please provide clientID | Please provide clientSecret | clientSecret mismatch | Tenant/Product config mismatch with IdP metadata | Description should not exceed 100 characters| redirectUrl is invalid | Exceeded maximum number of allowed redirect urls | defaultRedirectUrl is invalid | Tenant/Product config mismatch with OIDC Provider metadata
|
|
* parameters:
|
|
* clientIDParamPatch:
|
|
* name: clientID
|
|
* description: Client ID for the connection
|
|
* type: string
|
|
* in: formData
|
|
* required: true
|
|
* clientSecretParamPatch:
|
|
* name: clientSecret
|
|
* description: Client Secret for the connection
|
|
* type: string
|
|
* in: formData
|
|
* required: true
|
|
* nameParamPatch:
|
|
* name: name
|
|
* description: Name/identifier for the connection
|
|
* type: string
|
|
* in: formData
|
|
* labelParamPatch:
|
|
* name: label
|
|
* description: An internal label to identify the connection
|
|
* type: string
|
|
* in: formData
|
|
* descriptionParamPatch:
|
|
* name: description
|
|
* description: A short description for the connection not more than 100 characters
|
|
* type: string
|
|
* in: formData
|
|
* encodedRawMetadataParamPatch:
|
|
* name: encodedRawMetadata
|
|
* description: Base64 encoding of the XML metadata
|
|
* in: formData
|
|
* type: string
|
|
* rawMetadataParamPatch:
|
|
* name: rawMetadata
|
|
* description: Raw XML metadata
|
|
* in: formData
|
|
* type: string
|
|
* metadataUrlParamPatch:
|
|
* name: metadataUrl
|
|
* description: URL containing raw XML metadata
|
|
* in: formData
|
|
* type: string
|
|
* oidcDiscoveryUrlPatch:
|
|
* name: oidcDiscoveryUrl
|
|
* description: well-known URL where the OpenID Provider configuration is exposed
|
|
* in: formData
|
|
* type: string
|
|
* oidcMetadataPatch:
|
|
* name: oidcMetadata
|
|
* description: metadata (JSON) for the OpenID Provider in the absence of discoveryUrl
|
|
* in: formData
|
|
* type: string
|
|
* oidcClientIdPatch:
|
|
* name: oidcClientId
|
|
* description: clientId of the application set up on the OpenID Provider
|
|
* in: formData
|
|
* type: string
|
|
* oidcClientSecretPatch:
|
|
* name: oidcClientSecret
|
|
* description: clientSecret of the application set up on the OpenID Provider
|
|
* in: formData
|
|
* type: string
|
|
* defaultRedirectUrlParamPatch:
|
|
* name: defaultRedirectUrl
|
|
* description: The redirect URL to use in the IdP login flow
|
|
* in: formData
|
|
* type: string
|
|
* redirectUrlParamPatch:
|
|
* name: redirectUrl
|
|
* description: JSON encoded array containing a list of allowed redirect URLs
|
|
* in: formData
|
|
* type: string
|
|
* tenantParamPatch:
|
|
* name: tenant
|
|
* description: Tenant
|
|
* in: formData
|
|
* required: true
|
|
* type: string
|
|
* productParamPatch:
|
|
* name: product
|
|
* description: Product
|
|
* in: formData
|
|
* required: true
|
|
* type: string
|
|
* deactivatedParamPatch:
|
|
* name: deactivated
|
|
* description: Connection status
|
|
* in: formData
|
|
* required: false
|
|
* type: boolean
|
|
* sortOrderParamPatch:
|
|
* name: sortOrder
|
|
* description: Indicate the position of the connection in the IdP selection screen
|
|
* in: formData
|
|
* type: number
|
|
* required: false
|
|
* /api/v1/sso:
|
|
* patch:
|
|
* summary: Update SSO Connection
|
|
* operationId: update-sso-connection
|
|
* tags: [Single Sign On]
|
|
* consumes:
|
|
* - application/json
|
|
* - application/x-www-form-urlencoded
|
|
* parameters:
|
|
* - $ref: '#/parameters/clientIDParamPatch'
|
|
* - $ref: '#/parameters/clientSecretParamPatch'
|
|
* - $ref: '#/parameters/nameParamPatch'
|
|
* - $ref: '#/parameters/labelParamPatch'
|
|
* - $ref: '#/parameters/descriptionParamPatch'
|
|
* - $ref: '#/parameters/encodedRawMetadataParamPatch'
|
|
* - $ref: '#/parameters/rawMetadataParamPatch'
|
|
* - $ref: '#/parameters/metadataUrlParamPatch'
|
|
* - $ref: '#/parameters/oidcDiscoveryUrlPatch'
|
|
* - $ref: '#/parameters/oidcMetadataPatch'
|
|
* - $ref: '#/parameters/oidcClientIdPatch'
|
|
* - $ref: '#/parameters/oidcClientSecretPatch'
|
|
* - $ref: '#/parameters/defaultRedirectUrlParamPatch'
|
|
* - $ref: '#/parameters/redirectUrlParamPatch'
|
|
* - $ref: '#/parameters/tenantParamPatch'
|
|
* - $ref: '#/parameters/productParamPatch'
|
|
* - $ref: '#/parameters/deactivatedParamPatch'
|
|
* - $ref: '#/parameters/sortOrderParamPatch'
|
|
* responses:
|
|
* 204:
|
|
* description: Success
|
|
* 400:
|
|
* $ref: '#/definitions/validationErrorsPatch'
|
|
* 401:
|
|
* description: Unauthorized
|
|
* 500:
|
|
* description: Please set OpenID response handler path (oidcPath) on Jackson
|
|
*/
|
|
public async updateSAMLConnection(body: UpdateSAMLConnectionParams): Promise<void> {
|
|
const connection = await samlConnection.update(
|
|
body,
|
|
this.connectionStore,
|
|
this.getConnections.bind(this),
|
|
this.oryController
|
|
);
|
|
|
|
if ('deactivated' in body) {
|
|
if (isConnectionActive(connection)) {
|
|
await this.eventController.notify('sso.activated', connection);
|
|
} else {
|
|
await this.eventController.notify('sso.deactivated', connection);
|
|
}
|
|
}
|
|
}
|
|
|
|
// For backwards compatibility
|
|
public async updateConfig(
|
|
...args: Parameters<ConnectionAPIController['updateSAMLConnection']>
|
|
): Promise<void> {
|
|
await this.updateSAMLConnection(...args);
|
|
}
|
|
|
|
public async updateOIDCConnection(body: UpdateOIDCConnectionParams): Promise<void> {
|
|
if (!this.opts.oidcPath) {
|
|
throw new JacksonError('Please set OpenID response handler path (oidcPath) on Jackson', 500);
|
|
}
|
|
|
|
const connection = await oidcConnection.update(
|
|
body,
|
|
this.connectionStore,
|
|
this.getConnections.bind(this),
|
|
this.oryController
|
|
);
|
|
|
|
if ('deactivated' in body) {
|
|
if (isConnectionActive(connection)) {
|
|
await this.eventController.notify('sso.activated', connection);
|
|
} else {
|
|
await this.eventController.notify('sso.deactivated', connection);
|
|
}
|
|
}
|
|
}
|
|
|
|
public getIDPEntityID(body: GetIDPEntityIDBody): string {
|
|
const tenant = 'tenant' in body ? body.tenant : undefined;
|
|
const product = 'product' in body ? body.product : undefined;
|
|
if (!tenant || !product) {
|
|
throw new JacksonError('Please provide `tenant` and `product`.', 400);
|
|
} else {
|
|
return `${this.opts.samlAudience}/${appID(tenant, product)}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* parameters:
|
|
* tenantParamGet:
|
|
* in: query
|
|
* name: tenant
|
|
* type: string
|
|
* description: Tenant
|
|
* productParamGet:
|
|
* in: query
|
|
* name: product
|
|
* type: string
|
|
* description: Product
|
|
* clientIDParamGet:
|
|
* in: query
|
|
* name: clientID
|
|
* type: string
|
|
* description: Client ID
|
|
* strategyParamGet:
|
|
* in: query
|
|
* name: strategy
|
|
* type: string
|
|
* description: Strategy which can help to filter connections with tenant/product query
|
|
* sortParamGet:
|
|
* in: query
|
|
* name: sort
|
|
* type: string
|
|
* description: If present, the connections will be sorted by `sortOrder`. It won't consider if pagination is used.
|
|
* definitions:
|
|
* Connection:
|
|
* type: object
|
|
* properties:
|
|
* clientID:
|
|
* type: string
|
|
* description: Connection clientID
|
|
* clientSecret:
|
|
* type: string
|
|
* description: Connection clientSecret
|
|
* name:
|
|
* type: string
|
|
* description: Connection name
|
|
* label:
|
|
* type: string
|
|
* description: Connection label
|
|
* description:
|
|
* type: string
|
|
* description: Connection description
|
|
* redirectUrl:
|
|
* type: string
|
|
* description: A list of allowed redirect URLs
|
|
* defaultRedirectUrl:
|
|
* type: string
|
|
* description: The redirect URL to use in the IdP login flow
|
|
* tenant:
|
|
* type: string
|
|
* description: Connection tenant
|
|
* product:
|
|
* type: string
|
|
* description: Connection product
|
|
* idpMetadata:
|
|
* type: object
|
|
* description: SAML IdP metadata
|
|
* oidcProvider:
|
|
* type: object
|
|
* description: OIDC IdP metadata
|
|
* deactivated:
|
|
* type: boolean
|
|
* description: Connection status
|
|
* sortOrder:
|
|
* type: number
|
|
* description: Connection sort order
|
|
* responses:
|
|
* '200Get':
|
|
* description: Success
|
|
* schema:
|
|
* type: array
|
|
* items:
|
|
* $ref: '#/definitions/Connection'
|
|
* '400Get':
|
|
* description: Please provide `clientID` or `tenant` and `product`.
|
|
* '401Get':
|
|
* description: Unauthorized
|
|
* /api/v1/sso:
|
|
* get:
|
|
* summary: Get SSO Connections
|
|
* parameters:
|
|
* - $ref: '#/parameters/tenantParamGet'
|
|
* - $ref: '#/parameters/productParamGet'
|
|
* - $ref: '#/parameters/clientIDParamGet'
|
|
* - $ref: '#/parameters/strategyParamGet'
|
|
* - $ref: '#/parameters/sortParamGet'
|
|
* operationId: get-connections
|
|
* tags: [Single Sign On]
|
|
* responses:
|
|
* '200':
|
|
* $ref: '#/responses/200Get'
|
|
* '400':
|
|
* $ref: '#/responses/400Get'
|
|
* '401':
|
|
* $ref: '#/responses/401Get'
|
|
*/
|
|
public async getConnections(body: GetConnectionsQuery): Promise<Array<SAMLSSORecord | OIDCSSORecord>> {
|
|
const clientID = 'clientID' in body ? body.clientID : undefined;
|
|
const tenant = 'tenant' in body ? body.tenant : undefined;
|
|
const product = 'product' in body ? body.product : undefined;
|
|
const strategy = 'strategy' in body ? body.strategy : undefined;
|
|
const entityId = 'entityId' in body ? body.entityId : undefined;
|
|
|
|
metrics.increment('getConnections');
|
|
|
|
let connections: (SAMLSSORecord | OIDCSSORecord)[] | null = null;
|
|
|
|
// Fetch connections by entityId
|
|
if (entityId) {
|
|
const result = await this.connectionStore.getByIndex({
|
|
name: IndexNames.EntityID,
|
|
value: entityId,
|
|
});
|
|
|
|
if (!result || typeof result !== 'object') {
|
|
connections = [];
|
|
} else {
|
|
connections = result.data;
|
|
}
|
|
}
|
|
|
|
// Fetch connections by clientID
|
|
else if (clientID) {
|
|
const result = await this.connectionStore.get(clientID);
|
|
|
|
if (!result || typeof result !== 'object') {
|
|
connections = [];
|
|
} else {
|
|
connections = [result];
|
|
}
|
|
}
|
|
|
|
// Fetch connections by multiple tenants
|
|
else if (tenant && product && Array.isArray(tenant)) {
|
|
const tenants = tenant.filter((t) => t).filter((t, i, a) => a.indexOf(t) === i);
|
|
|
|
const result = await Promise.all(
|
|
tenants.map(async (t) =>
|
|
this.connectionStore.getByIndex({
|
|
name: IndexNames.TenantProduct,
|
|
value: dbutils.keyFromParts(t, product),
|
|
})
|
|
)
|
|
);
|
|
|
|
if (!result || !result.length) {
|
|
connections = [];
|
|
} else {
|
|
connections = result.flatMap((r) => r.data);
|
|
}
|
|
}
|
|
|
|
// Fetch connections by tenant and product
|
|
else if (tenant && product && !Array.isArray(tenant)) {
|
|
const result = await this.connectionStore.getByIndex({
|
|
name: IndexNames.TenantProduct,
|
|
value: dbutils.keyFromParts(tenant, product),
|
|
});
|
|
|
|
if (!result || !result.data.length) {
|
|
connections = [];
|
|
} else {
|
|
connections = result.data;
|
|
}
|
|
|
|
// Filter connections by strategy
|
|
if (connections && connections.length > 0 && strategy) {
|
|
connections = connections.filter((connection) => {
|
|
if (strategy === 'saml') {
|
|
return 'idpMetadata' in connection;
|
|
}
|
|
|
|
if (strategy === 'oidc') {
|
|
return 'oidcProvider' in connection;
|
|
}
|
|
|
|
return false;
|
|
});
|
|
}
|
|
}
|
|
|
|
if (connections) {
|
|
const sort = 'sort' in body ? body.sort : false;
|
|
|
|
if (sort) {
|
|
connections.sort((a, b) => (b.sortOrder || 0) - (a.sortOrder || 0));
|
|
}
|
|
|
|
return transformConnections(connections);
|
|
}
|
|
|
|
throw new JacksonError('Please provide `clientID` or `tenant` and `product`.', 400);
|
|
}
|
|
|
|
public async getConfig(body: GetConfigQuery): Promise<SAMLSSORecord | Record<string, never>> {
|
|
const clientID = 'clientID' in body ? body.clientID : undefined;
|
|
const tenant = 'tenant' in body ? body.tenant : undefined;
|
|
const product = 'product' in body ? body.product : undefined;
|
|
|
|
metrics.increment('getConnections');
|
|
|
|
if (clientID) {
|
|
const samlConfig = await this.connectionStore.get(clientID);
|
|
|
|
return samlConfig || {};
|
|
}
|
|
|
|
if (tenant && product) {
|
|
const samlConfigs = (
|
|
await this.connectionStore.getByIndex({
|
|
name: IndexNames.TenantProduct,
|
|
value: dbutils.keyFromParts(tenant, product),
|
|
})
|
|
).data;
|
|
|
|
if (!samlConfigs || !samlConfigs.length) {
|
|
return {};
|
|
}
|
|
|
|
return { ...samlConfigs[0] };
|
|
}
|
|
|
|
throw new JacksonError('Please provide `clientID` or `tenant` and `product`.', 400);
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* parameters:
|
|
* clientIDDel:
|
|
* name: clientID
|
|
* in: query
|
|
* type: string
|
|
* description: Client ID
|
|
* clientSecretDel:
|
|
* name: clientSecret
|
|
* in: query
|
|
* type: string
|
|
* description: Client Secret
|
|
* tenantDel:
|
|
* name: tenant
|
|
* in: query
|
|
* type: string
|
|
* description: Tenant
|
|
* productDel:
|
|
* name: product
|
|
* in: query
|
|
* type: string
|
|
* description: Product
|
|
* strategyDel:
|
|
* name: strategy
|
|
* in: query
|
|
* type: string
|
|
* description: Strategy which can help to filter connections with tenant/product query
|
|
* /api/v1/sso:
|
|
* delete:
|
|
* parameters:
|
|
* - $ref: '#/parameters/clientIDDel'
|
|
* - $ref: '#/parameters/clientSecretDel'
|
|
* - $ref: '#/parameters/tenantDel'
|
|
* - $ref: '#/parameters/productDel'
|
|
* - $ref: '#/parameters/strategyDel'
|
|
* summary: Delete SSO Connections
|
|
* operationId: delete-sso-connection
|
|
* tags: [Single Sign On]
|
|
* responses:
|
|
* '200':
|
|
* description: Success
|
|
* '400':
|
|
* description: clientSecret mismatch | Please provide `clientID` and `clientSecret` or `tenant` and `product`.
|
|
* '401':
|
|
* description: Unauthorized
|
|
*/
|
|
public async deleteConnections(body: DelConnectionsQuery): Promise<void> {
|
|
const clientID = 'clientID' in body ? body.clientID : undefined;
|
|
const clientSecret = 'clientSecret' in body ? body.clientSecret : undefined;
|
|
const tenant = 'tenant' in body ? body.tenant : undefined;
|
|
const product = 'product' in body ? body.product : undefined;
|
|
const strategy = 'strategy' in body ? body.strategy : undefined;
|
|
|
|
metrics.increment('deleteConnections');
|
|
|
|
if (clientID && clientSecret) {
|
|
const connection = await this.connectionStore.get(clientID);
|
|
|
|
if (!connection) {
|
|
return;
|
|
}
|
|
|
|
if (connection.clientSecret === clientSecret) {
|
|
await this.connectionStore.delete(clientID);
|
|
await this.eventController.notify('sso.deleted', transformConnection(connection));
|
|
} else {
|
|
throw new JacksonError('clientSecret mismatch', 400);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (tenant && product) {
|
|
const connections = (
|
|
await this.connectionStore.getByIndex({
|
|
name: IndexNames.TenantProduct,
|
|
value: dbutils.keyFromParts(tenant, product),
|
|
})
|
|
).data;
|
|
|
|
if (!connections || !connections.length) {
|
|
return;
|
|
}
|
|
|
|
// filter if strategy is passed
|
|
const filteredConnections = strategy
|
|
? connections.filter((connection) => {
|
|
if (strategy === 'saml') {
|
|
if (connection.idpMetadata) {
|
|
return true;
|
|
}
|
|
}
|
|
if (strategy === 'oidc') {
|
|
if (connection.oidcProvider) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
})
|
|
: connections;
|
|
|
|
for (const conf of transformConnections(filteredConnections)) {
|
|
await this.connectionStore.delete(conf.clientID);
|
|
await this.eventController.notify('sso.deleted', conf);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
throw new JacksonError('Please provide `clientID` and `clientSecret` or `tenant` and `product`.', 400);
|
|
}
|
|
|
|
public async deleteConfig(body: DelConnectionsQuery): Promise<void> {
|
|
await this.deleteConnections({ ...body, strategy: 'saml' });
|
|
}
|
|
|
|
/**
|
|
* @swagger
|
|
* parameters:
|
|
* productParamGet:
|
|
* in: query
|
|
* name: product
|
|
* type: string
|
|
* description: Product
|
|
* required: true
|
|
* definitions:
|
|
* Connection:
|
|
* type: object
|
|
* properties:
|
|
* clientID:
|
|
* type: string
|
|
* description: Connection clientID
|
|
* clientSecret:
|
|
* type: string
|
|
* description: Connection clientSecret
|
|
* name:
|
|
* type: string
|
|
* description: Connection name
|
|
* description:
|
|
* type: string
|
|
* description: Connection description
|
|
* redirectUrl:
|
|
* type: string
|
|
* description: A list of allowed redirect URLs
|
|
* defaultRedirectUrl:
|
|
* type: string
|
|
* description: The redirect URL to use in the IdP login flow
|
|
* tenant:
|
|
* type: string
|
|
* description: Connection tenant
|
|
* product:
|
|
* type: string
|
|
* description: Connection product
|
|
* idpMetadata:
|
|
* type: object
|
|
* description: SAML IdP metadata
|
|
* oidcProvider:
|
|
* type: object
|
|
* description: OIDC IdP metadata
|
|
* responses:
|
|
* '200GetByProduct':
|
|
* description: Success
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: array
|
|
* items:
|
|
* $ref: '#/definitions/Connection'
|
|
* pageToken:
|
|
* type: string
|
|
* description: token for pagination
|
|
* '400Get':
|
|
* description: Please provide a `product`.
|
|
* '401Get':
|
|
* description: Unauthorized
|
|
* /api/v1/sso/product:
|
|
* get:
|
|
* summary: Get SSO Connections by product
|
|
* parameters:
|
|
* - $ref: '#/parameters/productParamGet'
|
|
* - $ref: '#/parameters/pageOffset'
|
|
* - $ref: '#/parameters/pageLimit'
|
|
* - $ref: '#/parameters/pageToken'
|
|
* operationId: get-connections-by-product
|
|
* tags: [Single Sign On]
|
|
* responses:
|
|
* '200':
|
|
* $ref: '#/responses/200GetByProduct'
|
|
* '400':
|
|
* $ref: '#/responses/400Get'
|
|
* '401':
|
|
* $ref: '#/responses/401Get'
|
|
*/
|
|
public async getConnectionsByProduct(
|
|
body: GetByProductParams
|
|
): Promise<{ data: (SAMLSSORecord | OIDCSSORecord)[]; pageToken?: string }> {
|
|
const { product, pageOffset, pageLimit, pageToken } = body;
|
|
|
|
if (!product) {
|
|
throw new JacksonError('Please provide a `product`.', 400);
|
|
}
|
|
|
|
const connections = await this.connectionStore.getByIndex(
|
|
{
|
|
name: IndexNames.Product,
|
|
value: product,
|
|
},
|
|
pageOffset,
|
|
pageLimit,
|
|
pageToken
|
|
);
|
|
|
|
return { data: transformConnections(connections.data), pageToken };
|
|
}
|
|
|
|
public async getCount(idx?: Index) {
|
|
return await this.connectionStore.getCount(idx);
|
|
}
|
|
}
|