diff --git a/src/auth/methods.json b/src/auth/methods.json index 774cc93fa..05d36d24f 100644 --- a/src/auth/methods.json +++ b/src/auth/methods.json @@ -17,5 +17,9 @@ "github": { "text": "Github", "description": "" + }, + "digitalcredential": { + "text": "Digital Credential", + "description": "A digital credential that proves your identity and is issued by a trusted authority." } } diff --git a/src/batch/data-rules.js b/src/batch/data-rules.js index 7d91c6274..4a8006ebb 100644 --- a/src/batch/data-rules.js +++ b/src/batch/data-rules.js @@ -1,3 +1,4 @@ + const metadata = { Organization: { query: 'allOrganizations', @@ -18,6 +19,7 @@ const metadata = { name: 'connectExclusiveListCreate', list: 'OrganizationUnit', syncFirst: true, + refKey: 'extForeignKey', }, }, }, @@ -387,7 +389,7 @@ const metadata = { Application: { query: 'allApplications', refKey: 'appId', - sync: ['name', 'description'], + sync: ['name', 'description', 'namespace'], transformations: { owner: { name: 'connectOne', list: 'allUsers', refKey: 'username' }, organization: { @@ -408,19 +410,32 @@ const metadata = { query: 'allProducts', refKey: 'appId', compositeRefKey: ['name', 'namespace'], - sync: ['name', 'description', 'namespace'], + sync: ['name', 'type', 'description', 'namespace', 'organization', 'openapiSpecs'], transformations: { dataset: { name: 'connectOne', list: 'allDatasets', refKey: 'name' }, + openapiSpecs: { name: "toStringDefaultArray" }, environments: { name: 'connectExclusiveListCreate', list: 'Environment', syncFirst: true, refKey: 'appId', }, + organization: { + name: 'connectOne', + list: 'allOrganizations', + refKey: 'name', + }, + }, + validations: { + type: { + type: 'enum', + values: ['app', 'service'], + }, }, example: { name: 'my-new-product', appId: '000000000000', + type: 'service', environments: [ { name: 'dev', @@ -449,6 +464,9 @@ const metadata = { filterByNamespace: true, }, legal: { name: 'connectOne', list: 'allLegals', refKey: 'reference' }, + // exclude "spec" otherwise batch reading will automatically try to read it + // which we don't necessarily want + // spec: { name: 'connectOne', list: 'allBlobs', refKey: 'name' }, credentialIssuer: { name: 'connectOne', list: 'allCredentialIssuers', @@ -530,7 +548,7 @@ const metadata = { mode: { type: 'enum', values: ['auto'] }, clientAuthenticator: { type: 'enum', - values: ['client-secret', 'client-jwt', 'client-jwt-jwks-url'], + values: ['client-secret', 'client-jwt', 'client-jwt-jwks-url', 'client-certificate'], }, environmentDetails: { type: 'entityArray', diff --git a/src/batch/feed-worker.ts b/src/batch/feed-worker.ts index 57086fcab..311031c96 100644 --- a/src/batch/feed-worker.ts +++ b/src/batch/feed-worker.ts @@ -221,7 +221,7 @@ function buildQueryResponse(md: any, children: string[] = undefined): string[] { .slice(); response.push(md.refKey); - logger.debug('[buildQueryResponse] DRAFT (%s) %j', md.query, response); + logger.debug('[buildQueryResponse] DRAFT (%s) (%s) %j', children, md.query, response); if (children) { relationshipFields.forEach((field: string) => { // populate the fields as well @@ -511,6 +511,7 @@ export const syncRecords = async function ( const transformInfo = md.transformations[transformKey]; if (transformInfo.syncFirst) { // handle these children independently first - return a list of IDs + const allIds = await syncListOfRecords( context, transformInfo, diff --git a/src/controllers/ioc/keystoneInjector.ts b/src/controllers/ioc/keystoneInjector.ts index 1da31cf8d..7b621fb33 100644 --- a/src/controllers/ioc/keystoneInjector.ts +++ b/src/controllers/ioc/keystoneInjector.ts @@ -2,6 +2,7 @@ import { Keystone } from '@keystonejs/keystone'; import { injectable } from 'tsyringe'; import { scopes, scopesToRoles } from '../../auth/scope-role-utils'; import { Logger } from '../../logger'; +import { lookupUserByUsername } from '../../services/keystone'; const logger = Logger('controller'); @@ -38,6 +39,49 @@ export class KeystoneService { return this.keystone.createContext({ skipAccessControl: true }); } + public async createContextithUser( + request: any, + skipAccessControl: boolean = false + ) { + const _scopes = scopes(request.user.scope); + + const identityProvider = request.user.identity_provider; + + if (!request.user && !request.user.preferred_username) { + throw new Error( + 'User information is required to create context with user' + ); + } + const tmpCtx = this.keystone.createContext({ + skipAccessControl: true, + }); + const users = await lookupUserByUsername( + tmpCtx, + request.user.preferred_username + ); + if (!users) { + throw new Error(`User ${request.user.preferred_username} not found`); + } + const userId = users[0].id; + + const identity = { + id: null, + name: resolveName(request.user), + username: resolveUsername(request.user), + namespace: request.params.ns || request.params.gatewayId, + roles: JSON.stringify(scopesToRoles(identityProvider, _scopes)), + scopes: _scopes, + userId, + } as any; + logger.debug('identity %j', identity); + const ctx = this.keystone.createContext({ + skipAccessControl, + authentication: { item: identity }, + }); + ctx.req = request; + return ctx; + } + public createContext(request: any, skipAccessControl: boolean = false): any { const _scopes = scopes(request.user.scope); diff --git a/src/controllers/v2/openapi.yaml b/src/controllers/v2/openapi.yaml index 50002bffb..adf5428f9 100644 --- a/src/controllers/v2/openapi.yaml +++ b/src/controllers/v2/openapi.yaml @@ -352,6 +352,7 @@ components: - client-secret - client-jwt - client-jwt-jwks-url + - client-certificate instruction: type: string environmentDetails: @@ -579,6 +580,12 @@ components: type: string enabled: type: boolean + permDataPlane: + type: string + permDomains: + items: + type: string + type: array updatedAt: type: number format: double @@ -586,6 +593,8 @@ components: - name - orgUnit - enabled + - permDataPlane + - permDomains - updatedAt type: object additionalProperties: false @@ -645,21 +654,33 @@ components: type: string name: type: string + type: + type: string + enum: + - app + - service description: type: string namespace: type: string + openapiSpecs: + items: + type: string + type: array dataset: $ref: '#/components/schemas/DraftDatasetRefID' environments: items: $ref: '#/components/schemas/Environment' type: array + organization: + $ref: '#/components/schemas/OrganizationRefID' type: object additionalProperties: false example: name: my-new-product appId: '000000000000' + type: service environments: - name: dev diff --git a/src/controllers/v2/routes.ts b/src/controllers/v2/routes.ts index d141d7077..f09885882 100644 --- a/src/controllers/v2/routes.ts +++ b/src/controllers/v2/routes.ts @@ -232,7 +232,7 @@ const models: TsoaRoute.Models = { "flow": {"dataType":"enum","enums":["client-credentials"]}, "mode": {"dataType":"enum","enums":["auto"]}, "authPlugin": {"dataType":"string"}, - "clientAuthenticator": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["client-secret"]},{"dataType":"enum","enums":["client-jwt"]},{"dataType":"enum","enums":["client-jwt-jwks-url"]}]}, + "clientAuthenticator": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["client-secret"]},{"dataType":"enum","enums":["client-jwt"]},{"dataType":"enum","enums":["client-jwt-jwks-url"]},{"dataType":"enum","enums":["client-certificate"]}]}, "instruction": {"dataType":"string"}, "environmentDetails": {"dataType":"array","array":{"dataType":"refObject","ref":"IssuerEnvironmentConfig"}}, "resourceType": {"dataType":"string"}, @@ -373,6 +373,8 @@ const models: TsoaRoute.Models = { "name": {"dataType":"string","required":true}, "orgUnit": {"dataType":"string","required":true}, "enabled": {"dataType":"boolean","required":true}, + "permDataPlane": {"dataType":"string","required":true}, + "permDomains": {"dataType":"array","array":{"dataType":"string"},"required":true}, "updatedAt": {"dataType":"double","required":true}, }, "additionalProperties": false, @@ -414,10 +416,13 @@ const models: TsoaRoute.Models = { "properties": { "appId": {"dataType":"string"}, "name": {"dataType":"string"}, + "type": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["app"]},{"dataType":"enum","enums":["service"]}]}, "description": {"dataType":"string"}, "namespace": {"dataType":"string"}, + "openapiSpecs": {"dataType":"array","array":{"dataType":"string"}}, "dataset": {"ref":"DraftDatasetRefID"}, "environments": {"dataType":"array","array":{"dataType":"refObject","ref":"Environment"}}, + "organization": {"ref":"OrganizationRefID"}, }, "additionalProperties": false, }, diff --git a/src/controllers/v2/types.ts b/src/controllers/v2/types.ts index c5909c0cc..fac6c5660 100644 --- a/src/controllers/v2/types.ts +++ b/src/controllers/v2/types.ts @@ -263,6 +263,7 @@ export interface Application { appId?: string; // Primary Key name?: string; description?: string; + namespace?: string; owner?: UserRefID; organization?: OrganizationRefID; organizationUnit?: OrganizationUnitRefID; @@ -274,6 +275,7 @@ export interface Application { * @example { * "name": "my-new-product", * "appId": "000000000000", + * "type": "service", * "environments": [ * { * "name": "dev", @@ -288,10 +290,13 @@ export interface Application { export interface Product { appId?: string; // Primary Key name?: string; + type?: "app" | "service"; description?: string; namespace?: string; + openapiSpecs?: string[]; dataset?: DraftDatasetRefID; environments?: Environment[]; + organization?: OrganizationRefID; } @@ -337,7 +342,7 @@ export interface CredentialIssuer { flow?: "client-credentials"; mode?: "auto"; authPlugin?: string; - clientAuthenticator?: "client-secret" | "client-jwt" | "client-jwt-jwks-url"; + clientAuthenticator?: "client-secret" | "client-jwt" | "client-jwt-jwks-url" | "client-certificate"; instruction?: string; environmentDetails?: IssuerEnvironmentConfig[]; resourceType?: string; diff --git a/src/controllers/v3/GatewayServicesController.ts b/src/controllers/v3/GatewayServicesController.ts index eb902cce6..7d8bf894e 100644 --- a/src/controllers/v3/GatewayServicesController.ts +++ b/src/controllers/v3/GatewayServicesController.ts @@ -38,6 +38,7 @@ export class GatewayController extends Controller { @OperationId('publish-gateway-config') @Security('jwt', ['Gateway.Config']) public async put( + @Path() gatewayId: string, @FormField() dryRun: boolean, @UploadedFile() configFile: Express.Multer.File ): Promise { diff --git a/src/controllers/v3/OrgAPISpecController.ts b/src/controllers/v3/OrgAPISpecController.ts new file mode 100644 index 000000000..9c0718d82 --- /dev/null +++ b/src/controllers/v3/OrgAPISpecController.ts @@ -0,0 +1,69 @@ +import { + Controller, + Request, + OperationId, + Put, + Path, + Route, + Security, + Body, + Tags, + Get, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { OrgAPISpecCreateInput } from './types-extra'; +import { Logger } from '../../logger'; +import { gql } from 'graphql-request'; +import { UpdateAPISpec, GetAPISpecsByOrg } from '../../services/workflow/api-specs'; + +const logger = Logger('controllers.OrgAPISpec'); + +@injectable() +@Route('/organizations') +@Tags('Organizations') +export class OrgAPISpecController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + /** + * Update API Specification for a Product Environment + * > `Required Scope:` Namespace.Assign + * + * @summary Manage Access Requests + * @param ns + * @param body + * @param request + */ + @Put('/{org}/api_specs') + @OperationId('organization-put-access-requests') + @Security('jwt', ['Namespace.Assign']) + public async put( + @Path() org: string, + @Body() body: OrgAPISpecCreateInput, + @Request() request: any + ): Promise<{ id: string }> { + const ctx = await this.keystone.createContextithUser(request, true); + + const result = await UpdateAPISpec(ctx, body.specUrl, body.productEnvAppId); + logger.debug('OrgAPISpecController: %j', result); + return { id: result.id }; + } + + @Get('/{org}/api_specs') + @OperationId('organization-get-api-specs') + //@Security('jwt', ['Namespace.Assign']) + public async get( + @Path() org: string, + @Request() request: any + ): Promise<{ prodEnvId: string; spec: string }> { + const ctx = this.keystone.sudo(); + //const ctx = await this.keystone.createContext(request, true); + const result = await GetAPISpecsByOrg(ctx, org); + logger.debug('OrgAPISpecController: %j', result); + return result; + } +} diff --git a/src/controllers/v3/OrgAccessRequestsController.ts b/src/controllers/v3/OrgAccessRequestsController.ts new file mode 100644 index 000000000..756354afd --- /dev/null +++ b/src/controllers/v3/OrgAccessRequestsController.ts @@ -0,0 +1,152 @@ +import { + Controller, + Request, + OperationId, + Put, + Path, + Route, + Security, + Body, + Get, + Tags, + FieldErrors, + ValidateError, + Delete, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { BatchResult } from '../../batch/types'; +import { Product } from './types'; +import { getGwaProductEnvironment, revokeAllConsumerAccess } from '../../services/workflow'; +import { getOrgNamespaces } from '../../services/workflow/get-namespaces'; +import { deleteServiceAccess, getAccessRequestByNamespaceServiceAccess, getAccessRequestsByNamespace } from '../../services/keystone'; +import { OrgAccessRequest, OrgAccessRequestCreateInput } from './types-extra'; +import { OrgAccessRequestCreate } from '../../services/workflow/org-access-request'; +import { Logger } from '../../logger'; +import { gql } from 'graphql-request'; +import { data } from 'msw/lib/types/context'; +import { getAccessRequest } from '../../services/keystone/access-request'; +import { assert } from 'console'; + +const logger = Logger('controllers.OrgAccessReq'); + +@injectable() +@Route('/organizations') +@Tags('Organizations') +export class OrgAccessRequestsController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + /** + * Get Access Requests that are available by API for this organization + * > `Required Scope:` Namespace.Assign + * + * @summary Get Organization Access Requests + */ + @Get('/{org}/access_requests') + @OperationId('organization-access-requests') + @Security('jwt', ['Namespace.Assign']) + public async getRequests( + @Path() org: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.createContext(request, true); + + const prodEnv = await getGwaProductEnvironment(ctx, false); + + const nsList = await getOrgNamespaces(org, prodEnv); + + const records = await getAccessRequestsByNamespace(ctx, nsList.map((n) => n.name)); + // return records + // .map((o) => removeEmpty(o)) + // .map((o) => transformAllRefID(o, ['organization', 'organizationUnit'])) + // .map((o) => + // replaceKey(o, 'gatewayId', 'namespace') + // ); + return records as any + } + + + /** Delete Access Request + * > `Required Scope:` Namespace.Assign + */ + @Delete('/{org}/access_requests/{id}') + @OperationId('organization-delete-access-request') + @Security('jwt', ['Namespace.Assign']) + public async deleteRequest( + @Path() org: string, + @Path() id: string, + @Request() request: any + ): Promise<{}> { + const ctx = this.keystone.createContext(request, true); + + const accessRequest = await getAccessRequest(ctx, id); + + //const ns = accessRequest.productEnvironment.product.namespace; + + const revoke = await deleteServiceAccess(ctx, accessRequest.serviceAccess.id); + logger.debug('Revoke Result %j', revoke); + + return {}; + } + + + /** + * Manage Access Requests for APIs that will appear on the API Directory + * > `Required Scope:` Namespace.Assign + * + * @summary Manage Access Requests + * @param ns + * @param body + * @param request + */ + @Put('/{org}/access_requests') + @OperationId('organization-put-access-requests') + @Security('jwt', ['Namespace.Assign']) + public async put( + @Path() org: string, + @Body() body: OrgAccessRequestCreateInput, + @Request() request: any + ): Promise<{id: string}> { + const ctx = await this.keystone.createContextithUser(request, true); + + body.org = org; + body.userId = ctx.authedItem.userId; + + const result = await this.keystone.executeGraphQL({ + context: ctx, + query: createAccessRequest, + variables: { data: body }, + }); + logger.debug('Result %j', result); + if (result.errors) { + const errors: FieldErrors = {}; + result.errors.forEach((err: any, ind: number) => { + errors[`d${ind}`] = { message: err.message }; + }); + logger.error('%j', result); + throw new ValidateError(errors, 'Unable to create Access Request'); + } + return { + id: result.data.orgCreateAccessRequest.id, + }; + + } +} + +const createAccessRequest = gql` + mutation OrgAccessRequestCreate ($data: OrgAccessRequestCreateInput) { + orgCreateAccessRequest (data: $data) { + application { + appId + } + accessRequest { + id + } + } + } +`; + diff --git a/src/controllers/v3/OrgProductController.ts b/src/controllers/v3/OrgProductController.ts new file mode 100644 index 000000000..912a42f16 --- /dev/null +++ b/src/controllers/v3/OrgProductController.ts @@ -0,0 +1,241 @@ +import { + Controller, + Request, + OperationId, + Put, + Path, + Route, + Security, + Body, + Get, + Tags, + Delete, +} from 'tsoa'; +import { strict as assert } from 'assert'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + syncRecordsThrowErrors, + getRecords, + parseJsonString, + removeEmpty, + removeKeys, + transformAllRefID, + deleteRecord, + replaceKey, +} from '../../batch/feed-worker'; +import { BatchResult } from '../../batch/types'; +import { Dataset, DraftDataset } from './types'; +import { Product } from './types'; +import { ProductCatalog, ProductCatalogOperation } from './types-extra'; +import { gql } from 'graphql-request'; +import { Environment } from '../../services/keystone/types'; +import YAML from 'yaml'; +import { getGwaProductEnvironment } from '../../services/workflow'; +import { NamespaceService } from '../../services/org-groups'; +import { OrgNamespace } from '../../services/org-groups/types'; +import { dynamicallySetEnvironmentDetails } from '../../services/keystone'; + +@injectable() +@Route('/organizations') +@Tags('API Directory (Administration)') +export class OrgProductController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + /** + * Get Products that are available by API across all Organizations + * + * @summary Get Product Catalog + */ + @Get('/{org}/catalog') + @OperationId('organization-products-catalog') + public async getProductCatalog( + @Path() org: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.sudo(); + + const result = await this.keystone.executeGraphQL({ + context: ctx, + query: list, + }); + const envs = result.data.allEnvironments.filter( + (e: Environment) => e.product.organization != null + ); + + const output = envs.map((env: any) => { + const spec = YAML.parse(env.spec?.blob || '{}'); + + + const operations = spec?.paths && Object.keys(spec.paths).map((path) => { + return Object.keys(spec.paths[path]).map((method) => { + const op = spec.paths[path][method]; + return { + operationId: op.operationId, + method: method.toUpperCase(), + path, + summary: op.summary || '', + scopes: (op.security && op.security[0] && op.security[0]['bearer_auth']) ? op.security[0]['bearer_auth'] : [], + }; + }); + }); + + const flattenedOperations = []; + if (operations) { + for (const opList of operations) { + for (const op of opList) { + flattenedOperations.push(op); + } + } + } + + if (env.credentialIssuer != null) { + const envDetails = JSON.parse(dynamicallySetEnvironmentDetails(env.credentialIssuer)); + const credEnv = envDetails.find((e: any) => e.environment === env.name); + + env.credentialIssuer = { + issuerUrl: credEnv?.issuerUrl, + clientId: credEnv?.clientId, + } + } + + return { + appId: env.appId, + name: env.name, + spec: { + title: spec.info?.title || '', + version: spec.info?.version || '', + summary: spec.info?.summary || '', + description: spec.info?.description || '', + operations: flattenedOperations, + }, + credentialIssuer: env.credentialIssuer, + product: { + name: env.product.name, + type: env.product.type, + namespace: env.product.namespace, + organization: { + name: env.product.organization.name, + }, + }, + }; + }); + + const prodEnv = await getGwaProductEnvironment(ctx, false); + const envConfig = prodEnv.issuerEnvConfig; + + const svc = new NamespaceService(envConfig.issuerUrl); + await svc.login(envConfig.clientId, envConfig.clientSecret); + + const promises = output.filter((env:any) => env.product.namespace).map(async (env: any) => { + const nsAttributes = await getNamespaceAttributes( + svc, + env.product.namespace + ); + env.namespace = nsAttributes; + }); + await Promise.all(promises); + return output; + } + + /** + * Get metadata about Datasets that are available by API for this organization + * > `Required Scope:` Dataset.Manage + * + * @summary Get Organization Datasets + */ + @Get('/{org}/products') + @OperationId('organization-products') + @Security('jwt', ['Dataset.Manage']) + public async getProducts( + @Path() org: string, + @Request() request: any + ): Promise { + const ctx = this.keystone.createContext(request); + + const batchClause = { + query: '$org: String', + clause: '{ organization: { name: $org } }', + variables: { org }, + }; + + const records = await getRecords( + ctx, + 'Product', + undefined, + ['environments'], + batchClause + ); + + return records + .map((o) => removeEmpty(o)) + .map((o) => transformAllRefID(o, ['organization', 'organizationUnit'])) + .map((o) => removeKeys(o, ['id'])); + } + + /** + * Manage Products for APIs that will appear on the API Directory + * > `Required Scope:` Namespace.Manage + * + * @summary Manage Products + * @param ns + * @param body + * @param request + */ + @Put('/{org}/gateways/{gatewayId}/products') + @OperationId('organization-put-product') + @Security('jwt', ['Dataset.Manage']) + public async put( + @Path() gatewayId: string, + @Path() org: string, + @Body() body: Product, + @Request() request: any + ): Promise { + // TODO: Make sure namespace is allowed for this org + body['gatewayId'] = gatewayId; + body['organization'] = org; + + return await syncRecordsThrowErrors( + this.keystone.createContext(request, true), + 'Product', + body['appId'], + replaceKey(body, 'gatewayId', 'namespace') + ); + } +} + +const list = gql` + query OrgProductCatalog { + allEnvironments { + appId + name + spec { + blob + } + credentialIssuer { + name + clientId + inheritFrom { + environmentDetails + } + } + product { + name + type + namespace + organization { + name + } + } + } + } +`; + + +async function getNamespaceAttributes(svc: NamespaceService, ns: string) : Promise { + return await svc.getNamespaceOrganizationDetails(ns); +} diff --git a/src/controllers/v3/OrganizationController.ts b/src/controllers/v3/OrganizationController.ts index 1597c3a26..3399913bc 100644 --- a/src/controllers/v3/OrganizationController.ts +++ b/src/controllers/v3/OrganizationController.ts @@ -12,6 +12,8 @@ import { Get, Tags, Post, + FieldErrors, + ValidateError, } from 'tsoa'; import { KeystoneService } from '../ioc/keystoneInjector'; import { inject, injectable } from 'tsyringe'; @@ -24,6 +26,7 @@ import { transformAllRefID, syncRecordsThrowErrors, parseBlobString, + replaceKey, } from '../../batch/feed-worker'; import { GroupAccessService, @@ -41,12 +44,13 @@ import { } from '../../services/org-groups/types'; import { getOrganizations, getOrganizationUnit } from '../../services/keystone'; import { getActivity } from '../../services/keystone/activity'; -import { Activity, Organization } from './types'; +import { Activity, Gateway, Organization } from './types'; import { isParent } from '../../services/org-groups/group-converter-utils'; import { ActivitySummary } from '../../services/keystone/types'; -import { ActivityDetail } from './types-extra'; +import { ActivityDetail, GatewayAdd } from './types-extra'; import { BatchResult } from '../../batch/types'; import { assertEqual } from '../ioc/assert'; +import { gql } from 'graphql-request'; @injectable() @Route('/organizations') @@ -195,6 +199,41 @@ export class OrganizationController extends Controller { return await svc.listAssignedNamespacesByOrg(org); } + /** + * Create a gateway + * + * @summary Create Gateway + * @param ns + * @param request + * @returns + */ + @Post('{org}/gateways') + @OperationId('organization-create-gateway') + @Security('jwt', ['Namespace.Assign']) + public async createGateway( + @Path() org: string, + @Request() request: any, + @Body() vars: GatewayAdd + ): Promise { + const modifiedVars = replaceKey(vars, 'gatewayId', 'name'); + const result = await this.keystone.executeGraphQL({ + context: this.keystone.createContext(request), + query: createNS, + variables: modifiedVars, + }); + if (result.errors) { + const errors: FieldErrors = {}; + result.errors.forEach((err: any, ind: number) => { + errors[`d${ind}`] = { message: err.message }; + }); + throw new ValidateError(errors, 'Unable to create Gateway'); + } + return { + gatewayId: result.data.createNamespace.name, + displayName: result.data.createNamespace.displayName + }; + } + /** * > `Required Scope:` Gateway.Assign */ @@ -306,3 +345,12 @@ export class OrganizationController extends Controller { .map((o) => parseBlobString(o)); } } + +const createNS = gql` + mutation CreateNamespace($name: String, $displayName: String, $org: String, $domains: String, $dataPlane: String) { + createNamespace(name: $name, displayName: $displayName, org: $org, domains: $domains, dataPlane: $dataPlane) { + name + displayName + } + } +`; \ No newline at end of file diff --git a/src/controllers/v3/SDXController.ts b/src/controllers/v3/SDXController.ts new file mode 100644 index 000000000..cb3f99777 --- /dev/null +++ b/src/controllers/v3/SDXController.ts @@ -0,0 +1,66 @@ +import { + Controller, + Request, + OperationId, + Get, + Put, + Path, + Route, + Security, + Body, + Tags, + FormField, + UploadedFile, + ValidateError, +} from 'tsoa'; +import { KeystoneService } from '../ioc/keystoneInjector'; +import { inject, injectable } from 'tsyringe'; +import { + syncRecords, + getRecords, + parseJsonString, + removeEmpty, + removeKeys, +} from '../../batch/feed-worker'; +import { GatewayRoute } from './types'; +import { PublishResult } from './types-extra'; +import { CatalogEntry, GetCatalog } from '../../services/sdx/sdx-catalog'; +import { GetConfigUsingPattern } from '../../services/sdx/gateway-patterns'; +import { assertEqual } from '../ioc/assert'; + +interface GatewayPatternConfigRequest { + pattern: string; + delete?: boolean; + parameters: any; +} + +@injectable() +@Route('/sdx') +@Tags('SDX') +export class SDXController extends Controller { + private keystone: KeystoneService; + constructor(@inject('KeystoneService') private _keystone: KeystoneService) { + super(); + this.keystone = _keystone; + } + + @Put('/{gatewayId}/config-from-pattern') + @OperationId('get-config-from-sdx-pattern') + @Security('jwt', []) + public async put( + @Path() gatewayId: string, + @Body() body: GatewayPatternConfigRequest, + @Request() request: any + ): Promise { + const ctx = this.keystone.createContext(request); + return await GetConfigUsingPattern(ctx, body); + } + + @Get('/catalog') + @OperationId('get-catalog') + @Security('jwt', []) + public async getCatalog(@Request() request: any): Promise { + const ctx = this.keystone.createContext(request); + return await GetCatalog(ctx); + } +} diff --git a/src/controllers/v3/openapi.yaml b/src/controllers/v3/openapi.yaml new file mode 100644 index 000000000..28d2ae36a --- /dev/null +++ b/src/controllers/v3/openapi.yaml @@ -0,0 +1,2409 @@ +components: + examples: {} + headers: {} + parameters: {} + requestBodies: {} + responses: {} + schemas: + DatasetContact: + properties: + name: + type: string + email: + type: string + role: + type: string + enum: + - pointOfContact + nullable: false + type: object + additionalProperties: false + DatasetResource: + properties: + id: + type: string + name: + type: string + format: + type: string + enum: + - openapi-json + - json + url: + type: string + type: object + additionalProperties: false + OrganizationRefID: + type: string + OrganizationUnitRefID: + type: string + Dataset: + properties: + extForeignKey: + type: string + name: + type: string + license_title: + type: string + security_class: + type: string + view_audience: + type: string + download_audience: + type: string + record_publish_date: + type: string + notes: + type: string + title: + type: string + isInCatalog: + type: string + isDraft: + type: string + contacts: + items: + $ref: '#/components/schemas/DatasetContact' + type: array + resources: + items: + $ref: '#/components/schemas/DatasetResource' + type: array + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + organization: + $ref: '#/components/schemas/OrganizationRefID' + organizationUnit: + $ref: '#/components/schemas/OrganizationUnitRefID' + type: object + additionalProperties: false + BatchResult: + properties: + status: + type: number + format: double + result: + type: string + reason: + type: string + id: + type: string + ownedBy: + type: string + childResults: + items: + $ref: '#/components/schemas/BatchResult' + type: array + required: + - status + - result + type: object + additionalProperties: false + DraftDataset: + properties: + name: + type: string + license_title: + type: string + security_class: + type: string + enum: + - HIGH-CABINET + - HIGH-CONFIDENTIAL + - HIGH-SENSITIVITY + - MEDIUM-SENSITIVITY + - MEDIUM-PERSONAL + - LOW-SENSITIVITY + - LOW-PUBLIC + - PUBLIC + - 'PROTECTED A' + - 'PROTECTED B' + - 'PROTECTED C' + view_audience: + type: string + enum: + - Public + - Government + - 'Named users' + - 'Government and Business BCeID' + download_audience: + type: string + enum: + - Public + - Government + - 'Named users' + - 'Government and Business BCeID' + record_publish_date: + type: string + notes: + type: string + title: + type: string + isInCatalog: + type: boolean + isDraft: + type: boolean + contacts: + items: + $ref: '#/components/schemas/DatasetContact' + type: array + resources: + items: + $ref: '#/components/schemas/DatasetResource' + type: array + tags: + items: + type: string + type: array + organization: + $ref: '#/components/schemas/OrganizationRefID' + organizationUnit: + $ref: '#/components/schemas/OrganizationUnitRefID' + type: object + additionalProperties: false + example: + name: my_sample_dataset + license_title: 'Open Government Licence - British Columbia' + security_class: PUBLIC + view_audience: Public + download_audience: Public + record_publish_date: '2017-09-05' + notes: 'Some notes' + title: 'A title about my dataset' + tags: + - tag1 + - tag2 + organization: ministry-of-citizens-services + organizationUnit: databc + Gateway: + properties: + gatewayId: + type: string + displayName: + type: string + type: object + additionalProperties: false + ActivityDetail: + properties: + id: + type: string + message: + type: string + params: + properties: {} + additionalProperties: + type: string + type: object + activityAt: {} + blob: {} + required: + - message + - params + - activityAt + type: object + additionalProperties: false + PublishResult: + properties: + message: + type: string + results: + type: string + error: + type: string + type: object + additionalProperties: false + GatewayServiceRefID: + type: string + GatewayRouteRefID: + type: string + GatewayPlugin: + properties: + extForeignKey: + type: string + name: + type: string + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + config: {} + service: + $ref: '#/components/schemas/GatewayServiceRefID' + route: + $ref: '#/components/schemas/GatewayRouteRefID' + type: object + additionalProperties: false + GatewayRoute: + properties: + extForeignKey: + type: string + name: + type: string + gatewayId: + type: string + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + methods: + items: + type: string + type: array + paths: + items: + type: string + type: array + hosts: + items: + type: string + type: array + service: + $ref: '#/components/schemas/GatewayServiceRefID' + plugins: + items: + $ref: '#/components/schemas/GatewayPlugin' + type: array + type: object + additionalProperties: false + IssuerEnvironmentConfig: + properties: + environment: + type: string + exists: + type: boolean + issuerUrl: + type: string + clientRegistration: + type: string + enum: + - anonymous + - managed + - iat + clientId: + type: string + clientSecret: + type: string + initialAccessToken: + type: string + type: object + additionalProperties: false + example: + environment: dev + issuerUrl: 'https://idp.site/auth/realms/my-realm' + clientRegistration: managed + clientId: a-client-id + clientSecret: a-client-secret + undefinedRefID: + type: string + CredentialIssuer: + properties: + name: + type: string + gatewayId: + type: string + description: + type: string + flow: + type: string + enum: + - client-credentials + nullable: false + mode: + type: string + enum: + - auto + nullable: false + authPlugin: + type: string + clientAuthenticator: + type: string + enum: + - client-secret + - client-jwt + - client-jwt-jwks-url + - client-certificate + instruction: + type: string + environmentDetails: + items: + $ref: '#/components/schemas/IssuerEnvironmentConfig' + type: array + resourceType: + type: string + resourceAccessScope: + type: string + isShared: + type: boolean + apiKeyName: + type: string + availableScopes: + items: + type: string + type: array + resourceScopes: + items: + type: string + type: array + clientRoles: + items: + type: string + type: array + clientMappers: + items: + type: string + type: array + inheritFrom: + $ref: '#/components/schemas/undefinedRefID' + owner: + $ref: '#/components/schemas/undefinedRefID' + type: object + additionalProperties: false + example: + name: my-auth-profile + description: 'Auth connection to my IdP' + flow: client-credentials + clientAuthenticator: client-secret + mode: auto + environmentDetails: [] + owner: janis@gov.bc.ca + OrgAccessRequest: + properties: + id: + type: string + name: + type: string + isApproved: + type: boolean + isIssued: + type: boolean + isComplete: + type: boolean + requestor: + properties: + username: + type: string + name: + type: string + required: + - username + - name + type: object + application: + properties: + namespace: + type: string + appId: + type: string + name: + type: string + required: + - namespace + - appId + - name + type: object + productEnvironment: + properties: + product: + properties: + name: + type: string + openapiSpecs: + items: {type: string} + type: array + namespace: + type: string + required: + - name + - openapiSpecs + - namespace + type: object + flow: + type: string + appId: + type: string + name: + type: string + required: + - product + - flow + - appId + - name + type: object + serviceAccess: + properties: + consumer: + properties: + tags: + items: {type: string} + type: array + username: + type: string + required: + - tags + - username + type: object + id: + type: string + required: + - consumer + - id + type: object + createdAt: {} + required: + - id + - name + - isApproved + - isIssued + - isComplete + - requestor + - application + - productEnvironment + - serviceAccess + - createdAt + type: object + additionalProperties: false + OrgAccessRequestCreateInput: + properties: + org: + type: string + orgMemberId: + type: string + userId: + type: string + consumerProductEnvAppId: + type: string + providerProductEnvAppId: + type: string + businessProcess: + type: string + accessPointDN: + type: string + optionalClientScopes: + items: + type: string + type: array + required: + - orgMemberId + - consumerProductEnvAppId + - providerProductEnvAppId + - businessProcess + - accessPointDN + - optionalClientScopes + type: object + additionalProperties: false + OrganizationUnit: + properties: + extForeignKey: + type: string + name: + type: string + sector: + type: string + title: + type: string + description: + type: string + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + type: object + additionalProperties: false + Organization: + properties: + extForeignKey: + type: string + name: + type: string + sector: + type: string + title: + type: string + description: + type: string + extSource: + type: string + extRecordHash: + type: string + tags: + items: + type: string + type: array + orgUnits: + items: + $ref: '#/components/schemas/OrganizationUnit' + type: array + type: object + additionalProperties: false + GroupPermission: + properties: + resource: + type: string + scopes: + items: + type: string + type: array + required: + - scopes + type: object + additionalProperties: false + GroupRole: + properties: + name: + type: string + permissions: + items: + $ref: '#/components/schemas/GroupPermission' + type: array + required: + - name + - permissions + type: object + additionalProperties: false + GroupAccess: + properties: + name: + type: string + parent: + type: string + roles: + items: + $ref: '#/components/schemas/GroupRole' + type: array + required: + - roles + type: object + additionalProperties: false + UserReference: + properties: + id: + type: string + email: + type: string + type: object + additionalProperties: false + GroupMember: + properties: + member: + $ref: '#/components/schemas/UserReference' + roles: + items: + type: string + type: array + required: + - member + - roles + type: object + additionalProperties: false + GroupMembership: + properties: + name: + type: string + parent: + type: string + members: + items: + $ref: '#/components/schemas/GroupMember' + type: array + type: object + additionalProperties: false + OrgNamespace: + properties: + name: + type: string + orgUnit: + type: string + enabled: + type: boolean + permDataPlane: + type: string + permDomains: + items: + type: string + type: array + updatedAt: + type: number + format: double + required: + - name + - orgUnit + - enabled + - permDataPlane + - permDomains + - updatedAt + type: object + additionalProperties: false + GatewayAdd: + properties: + gatewayId: + type: string + displayName: + type: string + org: + type: string + domains: + type: string + dataPlane: + type: string + type: object + additionalProperties: false + OrgAPISpecCreateInput: + properties: + productEnvAppId: + type: string + specUrl: + type: string + required: + - productEnvAppId + - specUrl + type: object + additionalProperties: false + ProductCatalogOperation: + properties: + operationId: + type: string + method: + type: string + path: + type: string + summary: + type: string + scopes: + items: + type: string + type: array + required: + - operationId + - method + - path + - summary + - scopes + type: object + additionalProperties: false + ProductCatalog: + properties: + appId: + type: string + name: + type: string + spec: + properties: + operations: + items: + $ref: '#/components/schemas/ProductCatalogOperation' + type: array + description: + type: string + version: + type: string + title: + type: string + required: + - operations + - description + - version + - title + type: object + credentialIssuer: + properties: + clientId: + type: string + issuerUrl: + type: string + required: + - clientId + - issuerUrl + type: object + product: + properties: + organization: + properties: + name: + type: string + required: + - name + type: object + type: + type: string + name: + type: string + required: + - organization + - type + - name + type: object + namespace: + properties: + updatedAt: + type: number + format: double + enabled: + type: boolean + permDomains: + items: + type: string + type: array + permDataPlane: + type: string + orgUnit: + type: string + name: + type: string + required: + - updatedAt + - enabled + - permDomains + - permDataPlane + - orgUnit + - name + type: object + required: + - appId + - name + - spec + - product + - namespace + type: object + additionalProperties: false + DraftDatasetRefID: + type: string + LegalRefID: + type: string + CredentialIssuerRefID: + type: string + Environment: + properties: + appId: + type: string + name: + type: string + enum: + - dev + - test + - prod + - sandbox + - other + active: + type: boolean + approval: + type: boolean + flow: + type: string + enum: + - public + - protected-externally + - authorization-code + - client-credentials + - kong-acl-only + - kong-api-key-only + - kong-api-key-acl + additionalDetailsToRequest: + type: string + services: + items: + $ref: '#/components/schemas/GatewayServiceRefID' + type: array + legal: + $ref: '#/components/schemas/LegalRefID' + credentialIssuer: + $ref: '#/components/schemas/CredentialIssuerRefID' + type: object + additionalProperties: false + example: + name: dev + active: false + approval: false + flow: public + appId: '00000000' + Product: + properties: + appId: + type: string + name: + type: string + type: + type: string + enum: + - app + - service + description: + type: string + gatewayId: + type: string + openapiSpecs: + items: + type: string + type: array + dataset: + $ref: '#/components/schemas/DraftDatasetRefID' + environments: + items: + $ref: '#/components/schemas/Environment' + type: array + organization: + $ref: '#/components/schemas/OrganizationRefID' + type: object + additionalProperties: false + example: + name: my-new-product + appId: '000000000000' + type: service + environments: + - + name: dev + active: false + approval: false + flow: public + appId: '00000000' + Record_string.string_: + properties: {} + type: object + description: 'Construct a type with a set of properties K of type T' + GatewayPatternConfig: + properties: + pattern: + type: string + locator: + type: string + parameters: + $ref: '#/components/schemas/Record_string.string_' + required: + - pattern + - locator + - parameters + type: object + additionalProperties: false + CatalogEntry: + properties: + id: + type: string + locator: + type: string + product: + properties: + namespace: + type: string + type: + type: string + name: + type: string + required: + - namespace + - type + - name + type: object + organization: + properties: + orgUnit: + type: string + name: + type: string + required: + - name + type: object + gateway: + properties: + permissions: + properties: + domains: + items: {type: string} + type: array + dataPlane: + items: {type: string} + type: array + required: + - domains + - dataPlane + type: object + name: + type: string + required: + - permissions + - name + type: object + edgeServer: + properties: + dn: + type: string + host: + type: string + required: + - dn + - host + type: object + hasSpec: + type: boolean + required: + - id + - locator + - product + - organization + - gateway + - edgeServer + - hasSpec + type: object + additionalProperties: false + securitySchemes: + jwt: + type: oauth2 + description: 'Authz Client Credential' + flows: + clientCredentials: + tokenUrl: 'https://token_endpoint' + scopes: {} + portal: + type: http + description: 'Authz Portal Login' + scheme: bearer + bearerFormat: JWT + openid: + type: openIdConnect + description: 'OIDC Login' + openIdConnectUrl: 'https://well_known_endpoint' +info: + title: 'APS Directory API' + version: 3.0.0 + description: 'API Services Portal by BC Gov API Programme Services' + license: + name: MIT + contact: + name: 'BC Gov APS' +openapi: 3.0.0 +paths: + '/organizations/{org}/datasets': + get: + operationId: organization-datasets + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/Dataset' + type: array + description: "Get metadata about Datasets that are available by API for this organization\n> `Required Scope:` Dataset.Manage" + summary: 'Get Organization Datasets' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + put: + operationId: put-organization-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Manage metadata about Datasets that are available by API for this organization\n> `Required Scope:` Dataset.Manage" + summary: 'Manage Organization Datasets' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DraftDataset' + '/organizations/{org}/datasets/{name}': + delete: + operationId: delete-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Delete a Dataset\n> `Required Scope:` Dataset.Manage" + summary: 'Delete a dataset' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: name + required: true + schema: + type: string + get: + operationId: get-organization-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Dataset' + description: "Get metadata about a Dataset that are available by API for this organization\n> `Required Scope:` Dataset.Manage" + summary: 'Get Organization Dataset' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: name + required: true + schema: + type: string + /directory: + get: + operationId: directory-list + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - 'API Directory' + security: [] + parameters: [] + '/directory/{id}': + get: + operationId: directory-item + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - 'API Directory' + security: [] + parameters: + - + in: path + name: id + required: true + schema: + type: string + '/gateways/{gatewayId}/datasets': + put: + operationId: put-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Update metadata about a Dataset\n> `Required Scope:` Gateway.Manage" + summary: 'Update Dataset' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DraftDataset' + '/gateways/{gatewayId}/datasets/{name}': + get: + operationId: get-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Dataset' + description: "Get metadata about a Dataset\n> `Required Scope:` Gateway.Manage" + summary: 'Get Dataset' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: name + required: true + schema: + type: string + /routes/availability: + get: + operationId: check-availability + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - 'Service Routes' + security: [] + parameters: + - + in: query + name: serviceName + required: true + schema: + type: string + - + in: query + name: gatewayId + required: true + schema: + type: string + /gateways/report: + get: + operationId: report + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - Gateways + security: + - + jwt: [] + parameters: + - + in: query + name: ids + required: false + schema: + default: '[]' + type: string + /gateways: + get: + operationId: gateway-list + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/Gateway' + type: array + summary: 'List of Gateways available to the user' + tags: + - Gateways + security: + - + jwt: [] + parameters: [] + post: + operationId: create-gateway + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + description: 'Create a gateway' + summary: 'Create Gateway' + tags: + - Gateways + security: + - + jwt: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + '/gateways/{gatewayId}': + get: + operationId: gateway-profile + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + description: "Get details about the gateway, such as permissions for what the gateway is setup with.\n> `Required Scope:` Gateway.Manage" + summary: 'Gateway Summary' + tags: + - Gateways + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + delete: + operationId: delete-gateway + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + description: "Delete the gateway\n> `Required Scope:` Gateway.Manage" + summary: 'Delete Gateway' + tags: + - Gateways + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: query + name: force + required: false + schema: + default: false + type: boolean + '/gateways/{gatewayId}/activity': + get: + operationId: gateway-admin-activity + responses: + '200': + description: 'Activity[]' + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ActivityDetail' + type: array + description: '> `Required Scope:` Gateway.View' + summary: 'Get administration activity for this Gateway' + tags: + - Gateways + security: + - + jwt: + - Namespace.View + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: query + name: first + required: false + schema: + default: 20 + format: double + type: number + - + in: query + name: skip + required: false + schema: + default: 0 + format: double + type: number + '/gateways/{gatewayId}/links': + get: + operationId: get-gateway-links + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + properties: {host: {type: string}} + required: [host] + type: object + type: array + description: "Get a summary of your endpoints\n> `Required Scope:` Gateway.Manage" + summary: 'Get endpoints' + tags: + - Gateways + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + '/gateways/{gatewayId}/directory/{id}': + get: + operationId: get-ns-directory-dataset + responses: + '200': + description: Ok + content: + application/json: + schema: {} + description: "Used primarily for \"Preview Mode\"\nGet a particular Dataset" + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: id + required: true + schema: + type: string + '/gateways/{gatewayId}/directory': + get: + operationId: get-ns-directory + responses: + '200': + description: Ok + content: + application/json: + schema: {} + description: "Used primarily for \"Preview Mode\"\nList the datasets belonging to a particular Gateway" + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + '/gateways/{gatewayId}/services': + put: + operationId: publish-gateway-config + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResult' + tags: + - 'Gateway Services' + security: + - + jwt: + - Gateway.Config + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + dryRun: + type: string + configFile: + type: string + format: binary + required: + - dryRun + - configFile + get: + operationId: get-gateway-routes + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/GatewayRoute' + type: array + description: "Get a summary of your Gateway Services\n> `Required Scope:` Gateway.Manage" + summary: 'Get Gateway Services' + tags: + - 'Gateway Services' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + '/identifiers/{type}': + get: + operationId: GetNewID + responses: + '200': + description: Ok + content: + application/json: + schema: + type: string + tags: + - 'New Identifiers' + security: [] + parameters: + - + in: path + name: type + required: true + schema: + type: string + enum: + - environment + - product + - application + - gateway + '/gateways/{gatewayId}/issuers': + put: + operationId: put-issuer + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Create or Update Authorization Profiles\n> `Required Scope:` CredentialIssuer.Admin" + summary: 'Manage Authorization Profiles' + tags: + - 'Authorization Profiles' + security: + - + jwt: + - CredentialIssuer.Admin + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CredentialIssuer' + get: + operationId: get-issuers + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/CredentialIssuer' + type: array + description: "Get Authorization Profiles setup in this Gateway\n> `Required Scope:` Gateway.Manage" + summary: 'Get Authorization Profiles' + tags: + - 'Authorization Profiles' + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + '/gateways/{gatewayId}/issuers/{name}': + delete: + operationId: delete-issuer + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Delete an Authorization Profile\n> `Required Scope:` CredentialIssuer.Admin" + summary: 'Delete Profile' + tags: + - 'Authorization Profiles' + security: + - + jwt: + - CredentialIssuer.Admin + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: name + required: true + schema: + type: string + '/organizations/{org}/access_requests': + get: + operationId: organization-access-requests + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/OrgAccessRequest' + type: array + description: "Get Access Requests that are available by API for this organization\n> `Required Scope:` Namespace.Assign" + summary: 'Get Organization Access Requests' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + put: + operationId: organization-put-access-requests + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + id: {type: string} + required: + - id + type: object + description: "Manage Access Requests for APIs that will appear on the API Directory\n> `Required Scope:` Namespace.Assign" + summary: 'Manage Access Requests' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OrgAccessRequestCreateInput' + '/organizations/{org}/access_requests/{id}': + delete: + operationId: organization-delete-access-request + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: {} + type: object + description: "Delete Access Request\n> `Required Scope:` Namespace.Assign" + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: id + required: true + schema: + type: string + /organizations: + get: + operationId: organization-list + responses: + '200': + description: Ok + content: + application/json: + schema: + items: {} + type: array + tags: + - Organizations + security: [] + parameters: [] + '/organizations/{org}': + put: + operationId: put-organization + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Create Organization\n> `Required Scope:` GroupAccess.Manage" + summary: 'Create Organizations' + tags: + - Organizations + security: + - + jwt: + - GroupAccess.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Organization' + get: + operationId: organization-units + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - Organizations + security: [] + parameters: + - + in: path + name: org + required: true + schema: + type: string + '/organizations/{org}/roles': + get: + operationId: get-organization-roles + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupAccess' + description: '> `Required Scope:` GroupAccess.Manage' + tags: + - Organizations + security: + - + jwt: + - GroupAccess.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + '/organizations/{org}/access': + get: + operationId: get-organization-access + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/GroupMembership' + description: '> `Required Scope:` GroupAccess.Manage' + tags: + - Organizations + security: + - + jwt: + - GroupAccess.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + put: + operationId: put-organization-access + responses: + '204': + description: 'No content' + description: '> `Required Scope:` GroupAccess.Manage' + tags: + - Organizations + security: + - + jwt: + - GroupAccess.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupMembership' + '/organizations/{org}/gateways': + get: + operationId: organization-gateways + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/OrgNamespace' + type: array + description: '> `Required Scope:` Gateway.Assign' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + post: + operationId: organization-create-gateway + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Gateway' + description: 'Create a gateway' + summary: 'Create Gateway' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GatewayAdd' + '/organizations/{org}/{orgUnit}/gateways/{gatewayId}': + put: + operationId: assign-namespace-to-organization + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + result: {type: string} + required: + - result + type: object + description: '> `Required Scope:` Gateway.Assign' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: orgUnit + required: true + schema: + type: string + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: query + name: enable + required: false + schema: + default: true + type: boolean + delete: + operationId: unassign-namespace-from-organization + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + result: {type: string} + required: + - result + type: object + description: '> `Required Scope:` Gateway.Assign' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: path + name: orgUnit + required: true + schema: + type: string + - + in: path + name: gatewayId + required: true + schema: + type: string + '/organizations/{org}/activity': + get: + operationId: org-gateway-activity + responses: + '200': + description: 'Activity[]' + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ActivityDetail' + type: array + description: '> `Required Scope:` Gateway.Assign' + summary: 'Get administration activity for Gateways associated with this Organization' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + - + in: query + name: first + required: false + schema: + default: 20 + format: double + type: number + - + in: query + name: skip + required: false + schema: + default: 0 + format: double + type: number + '/organizations/{org}/api_specs': + put: + operationId: organization-put-access-requests + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + id: {type: string} + required: + - id + type: object + description: "Update API Specification for a Product Environment\n> `Required Scope:` Namespace.Assign" + summary: 'Manage Access Requests' + tags: + - Organizations + security: + - + jwt: + - Namespace.Assign + parameters: + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/OrgAPISpecCreateInput' + get: + operationId: organization-get-api-specs + responses: + '200': + description: Ok + content: + application/json: + schema: + properties: + spec: {type: string} + prodEnvId: {type: string} + required: + - spec + - prodEnvId + type: object + tags: + - Organizations + security: [] + parameters: + - + in: path + name: org + required: true + schema: + type: string + '/organizations/{org}/catalog': + get: + operationId: organization-products-catalog + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/ProductCatalog' + type: array + description: 'Get Products that are available by API across all Organizations' + summary: 'Get Product Catalog' + tags: + - 'API Directory (Administration)' + security: [] + parameters: + - + in: path + name: org + required: true + schema: + type: string + '/organizations/{org}/products': + get: + operationId: organization-products + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/Dataset' + type: array + description: "Get metadata about Datasets that are available by API for this organization\n> `Required Scope:` Dataset.Manage" + summary: 'Get Organization Datasets' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: org + required: true + schema: + type: string + '/organizations/{org}/gateways/{gatewayId}/products': + put: + operationId: organization-put-product + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Manage Products for APIs that will appear on the API Directory\n> `Required Scope:` Namespace.Manage" + summary: 'Manage Products' + tags: + - 'API Directory (Administration)' + security: + - + jwt: + - Dataset.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: org + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + /roles: + get: + operationId: GetRoles + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - Organizations + security: [] + parameters: [] + '/gateways/{gatewayId}/products': + put: + operationId: put-product + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Manage Products for APIs that will appear on the API Directory\n> `Required Scope:` Namespace.Manage" + summary: 'Manage Products' + tags: + - Products + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Product' + get: + operationId: get-products + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/Product' + type: array + description: "Get Products describing APIs that will appear on the API Directory\n> `Required Scope:` Namespace.Manage" + summary: 'Get Products' + tags: + - Products + security: + - + jwt: + - Namespace.Manage + parameters: [] + '/gateways/{gatewayId}/products/{appId}': + delete: + operationId: delete-product + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/BatchResult' + description: "Delete a Product\n> `Required Scope:` Namespace.Manage" + summary: 'Manage Products' + tags: + - Products + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: appId + required: true + schema: + type: string + '/gateways/{gatewayId}/environments/{appId}': + delete: + operationId: delete-product-environment + responses: + '204': + description: 'No content' + description: "Delete a Product Environment\n> `Required Scope:` Namespace.Manage" + summary: 'Delete a Product Environment' + tags: + - Products + security: + - + jwt: + - Namespace.Manage + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + - + in: path + name: appId + required: true + schema: + type: string + - + in: query + name: force + required: false + schema: + default: false + type: boolean + '/sdx/{gatewayId}/config-from-pattern': + put: + operationId: get-config-from-sdx-pattern + responses: + '200': + description: Ok + content: + application/json: + schema: {} + tags: + - SDX + security: + - + jwt: [] + parameters: + - + in: path + name: gatewayId + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GatewayPatternConfig' + /sdx: + get: + operationId: get-catalog + responses: + '200': + description: Ok + content: + application/json: + schema: + items: + $ref: '#/components/schemas/CatalogEntry' + type: array + tags: + - SDX + security: + - + jwt: [] + parameters: [] +servers: + - + url: /ds/api/v3 +tags: + - + name: 'API Directory' + description: 'Discover all the great BC Government APIs' + - + name: 'API Directory (Administration)' + description: 'Administer datasets on the API Directory' + - + name: Organizations + description: 'Manage organizational access control' + - + name: Gateways + description: 'Get aggregated information about gateways' + - + name: 'Gateway Services' + description: 'View your Gateway Service details' + - + name: Products + description: 'Manage your Products and Environments for publishing to the API Directory' + - + name: 'Authorization Profiles' + description: 'Configure the integration to external Identity Providers' diff --git a/src/controllers/v3/routes.ts b/src/controllers/v3/routes.ts new file mode 100644 index 000000000..234a873dd --- /dev/null +++ b/src/controllers/v3/routes.ts @@ -0,0 +1,2180 @@ +/* tslint:disable */ +/* eslint-disable */ +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { Controller, ValidationService, FieldErrors, ValidateError, TsoaRoute, HttpStatusCodeLiteral, TsoaResponse } from '@tsoa/runtime'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrgDatasetController } from './OrgDatasetController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { DirectoryController } from './DirectoryController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { DatasetController } from './DatasetController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { EndpointsController } from './EndpointsController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { NamespaceController } from './GatewayController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { GatewayDirectoryController } from './GatewayDirectoryController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { GatewayController } from './GatewayServicesController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { IdentifiersController } from './IdentifierController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { IssuerController } from './IssuerController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrgAccessRequestsController } from './OrgAccessRequestsController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrganizationController } from './OrganizationController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrgAPISpecController } from './OrgAPISpecController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrgProductController } from './OrgProductController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { OrgRoleController } from './OrgRoleController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { ProductController } from './ProductController'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { SDXController } from './SDXController'; +import { expressAuthentication } from './../../auth/auth-tsoa'; +// @ts-ignore - no great way to install types from subpackage +const promiseAny = require('promise.any'); +import { iocContainer } from './../ioc'; +import { IocContainer, IocContainerFactory } from '@tsoa/runtime'; +import * as express from 'express'; +const multer = require('multer'); +const upload = multer(); + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +const models: TsoaRoute.Models = { + "DatasetContact": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "email": {"dataType":"string"}, + "role": {"dataType":"enum","enums":["pointOfContact"]}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DatasetResource": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string"}, + "name": {"dataType":"string"}, + "format": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["openapi-json"]},{"dataType":"enum","enums":["json"]}]}, + "url": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrganizationRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrganizationUnitRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Dataset": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "license_title": {"dataType":"string"}, + "security_class": {"dataType":"string"}, + "view_audience": {"dataType":"string"}, + "download_audience": {"dataType":"string"}, + "record_publish_date": {"dataType":"string"}, + "notes": {"dataType":"string"}, + "title": {"dataType":"string"}, + "isInCatalog": {"dataType":"string"}, + "isDraft": {"dataType":"string"}, + "contacts": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetContact"}}, + "resources": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetResource"}}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "organization": {"ref":"OrganizationRefID"}, + "organizationUnit": {"ref":"OrganizationUnitRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BatchResult": { + "dataType": "refObject", + "properties": { + "status": {"dataType":"double","required":true}, + "result": {"dataType":"string","required":true}, + "reason": {"dataType":"string"}, + "id": {"dataType":"string"}, + "ownedBy": {"dataType":"string"}, + "childResults": {"dataType":"array","array":{"dataType":"refObject","ref":"BatchResult"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DraftDataset": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "license_title": {"dataType":"string"}, + "security_class": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["HIGH-CABINET"]},{"dataType":"enum","enums":["HIGH-CONFIDENTIAL"]},{"dataType":"enum","enums":["HIGH-SENSITIVITY"]},{"dataType":"enum","enums":["MEDIUM-SENSITIVITY"]},{"dataType":"enum","enums":["MEDIUM-PERSONAL"]},{"dataType":"enum","enums":["LOW-SENSITIVITY"]},{"dataType":"enum","enums":["LOW-PUBLIC"]},{"dataType":"enum","enums":["PUBLIC"]},{"dataType":"enum","enums":["PROTECTED A"]},{"dataType":"enum","enums":["PROTECTED B"]},{"dataType":"enum","enums":["PROTECTED C"]}]}, + "view_audience": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["Public"]},{"dataType":"enum","enums":["Government"]},{"dataType":"enum","enums":["Named users"]},{"dataType":"enum","enums":["Government and Business BCeID"]}]}, + "download_audience": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["Public"]},{"dataType":"enum","enums":["Government"]},{"dataType":"enum","enums":["Named users"]},{"dataType":"enum","enums":["Government and Business BCeID"]}]}, + "record_publish_date": {"dataType":"string"}, + "notes": {"dataType":"string"}, + "title": {"dataType":"string"}, + "isInCatalog": {"dataType":"boolean"}, + "isDraft": {"dataType":"boolean"}, + "contacts": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetContact"}}, + "resources": {"dataType":"array","array":{"dataType":"refObject","ref":"DatasetResource"}}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "organization": {"ref":"OrganizationRefID"}, + "organizationUnit": {"ref":"OrganizationUnitRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Gateway": { + "dataType": "refObject", + "properties": { + "gatewayId": {"dataType":"string"}, + "displayName": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ActivityDetail": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string"}, + "message": {"dataType":"string","required":true}, + "params": {"dataType":"nestedObjectLiteral","nestedProperties":{},"additionalProperties":{"dataType":"string"},"required":true}, + "activityAt": {"dataType":"any","required":true}, + "blob": {"dataType":"any"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "PublishResult": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string"}, + "results": {"dataType":"string"}, + "error": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayServiceRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayRouteRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayPlugin": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "config": {"dataType":"any"}, + "service": {"ref":"GatewayServiceRefID"}, + "route": {"ref":"GatewayRouteRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayRoute": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "gatewayId": {"dataType":"string"}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "methods": {"dataType":"array","array":{"dataType":"string"}}, + "paths": {"dataType":"array","array":{"dataType":"string"}}, + "hosts": {"dataType":"array","array":{"dataType":"string"}}, + "service": {"ref":"GatewayServiceRefID"}, + "plugins": {"dataType":"array","array":{"dataType":"refObject","ref":"GatewayPlugin"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IssuerEnvironmentConfig": { + "dataType": "refObject", + "properties": { + "environment": {"dataType":"string"}, + "exists": {"dataType":"boolean"}, + "issuerUrl": {"dataType":"string"}, + "clientRegistration": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["anonymous"]},{"dataType":"enum","enums":["managed"]},{"dataType":"enum","enums":["iat"]}]}, + "clientId": {"dataType":"string"}, + "clientSecret": {"dataType":"string"}, + "initialAccessToken": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "undefinedRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CredentialIssuer": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "gatewayId": {"dataType":"string"}, + "description": {"dataType":"string"}, + "flow": {"dataType":"enum","enums":["client-credentials"]}, + "mode": {"dataType":"enum","enums":["auto"]}, + "authPlugin": {"dataType":"string"}, + "clientAuthenticator": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["client-secret"]},{"dataType":"enum","enums":["client-jwt"]},{"dataType":"enum","enums":["client-jwt-jwks-url"]},{"dataType":"enum","enums":["client-certificate"]}]}, + "instruction": {"dataType":"string"}, + "environmentDetails": {"dataType":"array","array":{"dataType":"refObject","ref":"IssuerEnvironmentConfig"}}, + "resourceType": {"dataType":"string"}, + "resourceAccessScope": {"dataType":"string"}, + "isShared": {"dataType":"boolean"}, + "apiKeyName": {"dataType":"string"}, + "availableScopes": {"dataType":"array","array":{"dataType":"string"}}, + "resourceScopes": {"dataType":"array","array":{"dataType":"string"}}, + "clientRoles": {"dataType":"array","array":{"dataType":"string"}}, + "clientMappers": {"dataType":"array","array":{"dataType":"string"}}, + "inheritFrom": {"ref":"undefinedRefID"}, + "owner": {"ref":"undefinedRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrgAccessRequest": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "isApproved": {"dataType":"boolean","required":true}, + "isIssued": {"dataType":"boolean","required":true}, + "isComplete": {"dataType":"boolean","required":true}, + "requestor": {"dataType":"nestedObjectLiteral","nestedProperties":{"username":{"dataType":"string","required":true},"name":{"dataType":"string","required":true}},"required":true}, + "application": {"dataType":"nestedObjectLiteral","nestedProperties":{"namespace":{"dataType":"string","required":true},"appId":{"dataType":"string","required":true},"name":{"dataType":"string","required":true}},"required":true}, + "productEnvironment": {"dataType":"nestedObjectLiteral","nestedProperties":{"product":{"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true},"openapiSpecs":{"dataType":"array","array":{"dataType":"string"},"required":true},"namespace":{"dataType":"string","required":true}},"required":true},"flow":{"dataType":"string","required":true},"appId":{"dataType":"string","required":true},"name":{"dataType":"string","required":true}},"required":true}, + "serviceAccess": {"dataType":"nestedObjectLiteral","nestedProperties":{"consumer":{"dataType":"nestedObjectLiteral","nestedProperties":{"tags":{"dataType":"array","array":{"dataType":"string"},"required":true},"username":{"dataType":"string","required":true}},"required":true},"id":{"dataType":"string","required":true}},"required":true}, + "createdAt": {"dataType":"any","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrgAccessRequestCreateInput": { + "dataType": "refObject", + "properties": { + "org": {"dataType":"string"}, + "orgMemberId": {"dataType":"string","required":true}, + "userId": {"dataType":"string"}, + "consumerProductEnvAppId": {"dataType":"string","required":true}, + "providerProductEnvAppId": {"dataType":"string","required":true}, + "businessProcess": {"dataType":"string","required":true}, + "accessPointDN": {"dataType":"string","required":true}, + "optionalClientScopes": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrganizationUnit": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "sector": {"dataType":"string"}, + "title": {"dataType":"string"}, + "description": {"dataType":"string"}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Organization": { + "dataType": "refObject", + "properties": { + "extForeignKey": {"dataType":"string"}, + "name": {"dataType":"string"}, + "sector": {"dataType":"string"}, + "title": {"dataType":"string"}, + "description": {"dataType":"string"}, + "extSource": {"dataType":"string"}, + "extRecordHash": {"dataType":"string"}, + "tags": {"dataType":"array","array":{"dataType":"string"}}, + "orgUnits": {"dataType":"array","array":{"dataType":"refObject","ref":"OrganizationUnit"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupPermission": { + "dataType": "refObject", + "properties": { + "resource": {"dataType":"string"}, + "scopes": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupRole": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string","required":true}, + "permissions": {"dataType":"array","array":{"dataType":"refObject","ref":"GroupPermission"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupAccess": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "parent": {"dataType":"string"}, + "roles": {"dataType":"array","array":{"dataType":"refObject","ref":"GroupRole"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UserReference": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string"}, + "email": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupMember": { + "dataType": "refObject", + "properties": { + "member": {"ref":"UserReference","required":true}, + "roles": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GroupMembership": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string"}, + "parent": {"dataType":"string"}, + "members": {"dataType":"array","array":{"dataType":"refObject","ref":"GroupMember"}}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrgNamespace": { + "dataType": "refObject", + "properties": { + "name": {"dataType":"string","required":true}, + "orgUnit": {"dataType":"string","required":true}, + "enabled": {"dataType":"boolean","required":true}, + "permDataPlane": {"dataType":"string","required":true}, + "permDomains": {"dataType":"array","array":{"dataType":"string"},"required":true}, + "updatedAt": {"dataType":"double","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayAdd": { + "dataType": "refObject", + "properties": { + "gatewayId": {"dataType":"string"}, + "displayName": {"dataType":"string"}, + "org": {"dataType":"string"}, + "domains": {"dataType":"string"}, + "dataPlane": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "OrgAPISpecCreateInput": { + "dataType": "refObject", + "properties": { + "productEnvAppId": {"dataType":"string","required":true}, + "specUrl": {"dataType":"string","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ProductCatalogOperation": { + "dataType": "refObject", + "properties": { + "operationId": {"dataType":"string","required":true}, + "method": {"dataType":"string","required":true}, + "path": {"dataType":"string","required":true}, + "summary": {"dataType":"string","required":true}, + "scopes": {"dataType":"array","array":{"dataType":"string"},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ProductCatalog": { + "dataType": "refObject", + "properties": { + "appId": {"dataType":"string","required":true}, + "name": {"dataType":"string","required":true}, + "spec": {"dataType":"nestedObjectLiteral","nestedProperties":{"operations":{"dataType":"array","array":{"dataType":"refObject","ref":"ProductCatalogOperation"},"required":true},"description":{"dataType":"string","required":true},"version":{"dataType":"string","required":true},"title":{"dataType":"string","required":true}},"required":true}, + "credentialIssuer": {"dataType":"nestedObjectLiteral","nestedProperties":{"clientId":{"dataType":"string","required":true},"issuerUrl":{"dataType":"string","required":true}}}, + "product": {"dataType":"nestedObjectLiteral","nestedProperties":{"organization":{"dataType":"nestedObjectLiteral","nestedProperties":{"name":{"dataType":"string","required":true}},"required":true},"type":{"dataType":"string","required":true},"name":{"dataType":"string","required":true}},"required":true}, + "namespace": {"dataType":"nestedObjectLiteral","nestedProperties":{"updatedAt":{"dataType":"double","required":true},"enabled":{"dataType":"boolean","required":true},"permDomains":{"dataType":"array","array":{"dataType":"string"},"required":true},"permDataPlane":{"dataType":"string","required":true},"orgUnit":{"dataType":"string","required":true},"name":{"dataType":"string","required":true}},"required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DraftDatasetRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "LegalRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CredentialIssuerRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Environment": { + "dataType": "refObject", + "properties": { + "appId": {"dataType":"string"}, + "name": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["dev"]},{"dataType":"enum","enums":["test"]},{"dataType":"enum","enums":["prod"]},{"dataType":"enum","enums":["sandbox"]},{"dataType":"enum","enums":["other"]}]}, + "active": {"dataType":"boolean"}, + "approval": {"dataType":"boolean"}, + "flow": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["public"]},{"dataType":"enum","enums":["protected-externally"]},{"dataType":"enum","enums":["authorization-code"]},{"dataType":"enum","enums":["client-credentials"]},{"dataType":"enum","enums":["kong-acl-only"]},{"dataType":"enum","enums":["kong-api-key-only"]},{"dataType":"enum","enums":["kong-api-key-acl"]}]}, + "additionalDetailsToRequest": {"dataType":"string"}, + "services": {"dataType":"array","array":{"dataType":"refAlias","ref":"GatewayServiceRefID"}}, + "legal": {"ref":"LegalRefID"}, + "credentialIssuer": {"ref":"CredentialIssuerRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Product": { + "dataType": "refObject", + "properties": { + "appId": {"dataType":"string"}, + "name": {"dataType":"string"}, + "type": {"dataType":"union","subSchemas":[{"dataType":"enum","enums":["app"]},{"dataType":"enum","enums":["service"]}]}, + "description": {"dataType":"string"}, + "gatewayId": {"dataType":"string"}, + "openapiSpecs": {"dataType":"array","array":{"dataType":"string"}}, + "dataset": {"ref":"DraftDatasetRefID"}, + "environments": {"dataType":"array","array":{"dataType":"refObject","ref":"Environment"}}, + "organization": {"ref":"OrganizationRefID"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Record_string.string_": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GatewayPatternConfig": { + "dataType": "refObject", + "properties": { + "pattern": {"dataType":"string","required":true}, + "locator": {"dataType":"string","required":true}, + "parameters": {"ref":"Record_string.string_","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CatalogEntry": { + "dataType": "refObject", + "properties": { + "id": {"dataType":"string","required":true}, + "locator": {"dataType":"string","required":true}, + "product": {"dataType":"nestedObjectLiteral","nestedProperties":{"namespace":{"dataType":"string","required":true},"type":{"dataType":"string","required":true},"name":{"dataType":"string","required":true}},"required":true}, + "organization": {"dataType":"nestedObjectLiteral","nestedProperties":{"orgUnit":{"dataType":"string"},"name":{"dataType":"string","required":true}},"required":true}, + "gateway": {"dataType":"nestedObjectLiteral","nestedProperties":{"permissions":{"dataType":"nestedObjectLiteral","nestedProperties":{"domains":{"dataType":"array","array":{"dataType":"string"},"required":true},"dataPlane":{"dataType":"array","array":{"dataType":"string"},"required":true}},"required":true},"name":{"dataType":"string","required":true}},"required":true}, + "edgeServer": {"dataType":"nestedObjectLiteral","nestedProperties":{"dn":{"dataType":"string","required":true},"host":{"dataType":"string","required":true}},"required":true}, + "hasSpec": {"dataType":"boolean","required":true}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +}; +const validationService = new ValidationService(models); + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +export function RegisterRoutes(app: express.Router) { + // ########################################################################################################### + // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look + // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa + // ########################################################################################################### + app.get('/ds/api/v3/organizations/:org/datasets', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgDatasetController_getDatasets(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgDatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDatasets.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/datasets', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgDatasetController_putDataset(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"DraftDataset"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgDatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.putDataset.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/organizations/:org/datasets/:name', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgDatasetController_delete(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + name: {"in":"path","name":"name","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgDatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.delete.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/datasets/:name', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgDatasetController_getDataset(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + name: {"in":"path","name":"name","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgDatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDataset.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/directory', + + async function DirectoryController_list(request: any, response: any, next: any) { + const args = { + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(DirectoryController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.list.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/directory/:id', + + async function DirectoryController_get(request: any, response: any, next: any) { + const args = { + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(DirectoryController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/gateways/:gatewayId/datasets', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function DatasetController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"DraftDataset"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(DatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/datasets/:name', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function DatasetController_getDataset(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + name: {"in":"path","name":"name","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(DatasetController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDataset.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/routes/availability', + + async function EndpointsController_check(request: any, response: any, next: any) { + const args = { + serviceName: {"in":"query","name":"serviceName","required":true,"dataType":"string"}, + gatewayId: {"in":"query","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(EndpointsController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.check.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/report', + authenticateMiddleware([{"jwt":[]}]), + + async function NamespaceController_report(request: any, response: any, next: any) { + const args = { + req: {"in":"request","name":"req","required":true,"dataType":"object"}, + ids: {"default":"[]","in":"query","name":"ids","dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.report.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways', + authenticateMiddleware([{"jwt":[]}]), + + async function NamespaceController_list(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.list.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function NamespaceController_profile(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.profile.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/ds/api/v3/gateways', + authenticateMiddleware([{"jwt":[]}]), + + async function NamespaceController_create(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + vars: {"in":"body","name":"vars","required":true,"ref":"Gateway"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.create.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/gateways/:gatewayId', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function NamespaceController_delete(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + force: {"default":false,"in":"query","name":"force","dataType":"boolean"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.delete.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/activity', + authenticateMiddleware([{"jwt":["Namespace.View"]}]), + + async function NamespaceController_namespaceActivity(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + first: {"default":20,"in":"query","name":"first","dataType":"double"}, + skip: {"default":0,"in":"query","name":"skip","dataType":"double"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.namespaceActivity.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/links', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function NamespaceController_get(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/directory/:id', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function GatewayDirectoryController_getDataset(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayDirectoryController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDataset.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/directory', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function GatewayDirectoryController_getDatasets(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayDirectoryController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getDatasets.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/gateways/:gatewayId/services', + authenticateMiddleware([{"jwt":["Gateway.Config"]}]), + upload.single('configFile'), + + async function GatewayController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + dryRun: {"in":"formData","name":"dryRun","required":true,"dataType":"string"}, + configFile: {"in":"formData","name":"configFile","required":true,"dataType":"file"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/services', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function GatewayController_getServices(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getServices.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/identifiers/:type', + + async function IdentifiersController_getNewID(request: any, response: any, next: any) { + const args = { + type: {"in":"path","name":"type","required":true,"dataType":"union","subSchemas":[{"dataType":"enum","enums":["environment"]},{"dataType":"enum","enums":["product"]},{"dataType":"enum","enums":["application"]},{"dataType":"enum","enums":["gateway"]}]}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(IdentifiersController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getNewID.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/gateways/:gatewayId/issuers', + authenticateMiddleware([{"jwt":["CredentialIssuer.Admin"]}]), + + async function IssuerController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"CredentialIssuer"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(IssuerController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/issuers', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function IssuerController_get(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(IssuerController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/gateways/:gatewayId/issuers/:name', + authenticateMiddleware([{"jwt":["CredentialIssuer.Admin"]}]), + + async function IssuerController_delete(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + name: {"in":"path","name":"name","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(IssuerController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.delete.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/access_requests', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrgAccessRequestsController_getRequests(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgAccessRequestsController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getRequests.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/organizations/:org/access_requests/:id', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrgAccessRequestsController_deleteRequest(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + id: {"in":"path","name":"id","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgAccessRequestsController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.deleteRequest.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/access_requests', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrgAccessRequestsController_put(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"OrgAccessRequestCreateInput"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgAccessRequestsController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations', + + async function OrganizationController_listOrganizations(request: any, response: any, next: any) { + const args = { + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.listOrganizations.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org', + authenticateMiddleware([{"jwt":["GroupAccess.Manage"]}]), + + async function OrganizationController_post(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"Organization"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.post.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org', + + async function OrganizationController_listOrganizationUnits(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.listOrganizationUnits.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/roles', + authenticateMiddleware([{"jwt":["GroupAccess.Manage"]}]), + + async function OrganizationController_getPolicies(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getPolicies.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/access', + authenticateMiddleware([{"jwt":["GroupAccess.Manage"]}]), + + async function OrganizationController_get(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/access', + authenticateMiddleware([{"jwt":["GroupAccess.Manage"]}]), + + async function OrganizationController_put(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"GroupMembership"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/gateways', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_listNamespaces(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.listNamespaces.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/ds/api/v3/organizations/:org/gateways', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_createGateway(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + vars: {"in":"body","name":"vars","required":true,"ref":"GatewayAdd"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.createGateway.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/:orgUnit/gateways/:gatewayId', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_assignNamespace(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + orgUnit: {"in":"path","name":"orgUnit","required":true,"dataType":"string"}, + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + enable: {"default":true,"in":"query","name":"enable","dataType":"boolean"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.assignNamespace.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/organizations/:org/:orgUnit/gateways/:gatewayId', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_unassignNamespace(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + orgUnit: {"in":"path","name":"orgUnit","required":true,"dataType":"string"}, + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.unassignNamespace.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/activity', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrganizationController_namespaceActivity(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + first: {"default":20,"in":"query","name":"first","dataType":"double"}, + skip: {"default":0,"in":"query","name":"skip","dataType":"double"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrganizationController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.namespaceActivity.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/api_specs', + authenticateMiddleware([{"jwt":["Namespace.Assign"]}]), + + async function OrgAPISpecController_put(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"OrgAPISpecCreateInput"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgAPISpecController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/api_specs', + + async function OrgAPISpecController_get(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgAPISpecController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/catalog', + + async function OrgProductController_getProductCatalog(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getProductCatalog.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/organizations/:org/products', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgProductController_getProducts(request: any, response: any, next: any) { + const args = { + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getProducts.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/organizations/:org/gateways/:gatewayId/products', + authenticateMiddleware([{"jwt":["Dataset.Manage"]}]), + + async function OrgProductController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + org: {"in":"path","name":"org","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"Product"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/roles', + + async function OrgRoleController_getRoles(request: any, response: any, next: any) { + const args = { + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(OrgRoleController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getRoles.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/gateways/:gatewayId/products', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function ProductController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"Product"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(ProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/gateways/:gatewayId/products', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function ProductController_get(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(ProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.get.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/gateways/:gatewayId/products/:appId', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function ProductController_delete(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + appId: {"in":"path","name":"appId","required":true,"dataType":"string"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(ProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.delete.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.delete('/ds/api/v3/gateways/:gatewayId/environments/:appId', + authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), + + async function ProductController_deleteEnvironment(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + appId: {"in":"path","name":"appId","required":true,"dataType":"string"}, + force: {"default":false,"in":"query","name":"force","dataType":"boolean"}, + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(ProductController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.deleteEnvironment.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v3/sdx/:gatewayId/config-from-pattern', + authenticateMiddleware([{"jwt":[]}]), + + async function SDXController_put(request: any, response: any, next: any) { + const args = { + gatewayId: {"in":"path","name":"gatewayId","required":true,"dataType":"string"}, + body: {"in":"body","name":"body","required":true,"ref":"GatewayPatternConfig"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(SDXController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/ds/api/v3/sdx', + authenticateMiddleware([{"jwt":[]}]), + + async function SDXController_getCatalog(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(SDXController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getCatalog.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function authenticateMiddleware(security: TsoaRoute.Security[] = []) { + return async function runAuthenticationMiddleware(request: any, _response: any, next: any) { + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // keep track of failed auth attempts so we can hand back the most + // recent one. This behavior was previously existing so preserving it + // here + const failedAttempts: any[] = []; + const pushAndRethrow = (error: any) => { + failedAttempts.push(error); + throw error; + }; + + const secMethodOrPromises: Promise[] = []; + for (const secMethod of security) { + if (Object.keys(secMethod).length > 1) { + const secMethodAndPromises: Promise[] = []; + + for (const name in secMethod) { + secMethodAndPromises.push( + expressAuthentication(request, name, secMethod[name]) + .catch(pushAndRethrow) + ); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + secMethodOrPromises.push(Promise.all(secMethodAndPromises) + .then(users => { return users[0]; })); + } else { + for (const name in secMethod) { + secMethodOrPromises.push( + expressAuthentication(request, name, secMethod[name]) + .catch(pushAndRethrow) + ); + } + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + try { + request['user'] = await promiseAny(secMethodOrPromises); + next(); + } + catch(err) { + // Show most recent error as response + const error = failedAttempts.pop(); + error.status = error.status || 401; + next(error); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function isController(object: any): object is Controller { + return 'getHeaders' in object && 'getStatus' in object && 'setStatus' in object; + } + + function promiseHandler(controllerObj: any, promise: any, response: any, successStatus: any, next: any) { + return Promise.resolve(promise) + .then((data: any) => { + let statusCode = successStatus; + let headers; + if (isController(controllerObj)) { + headers = controllerObj.getHeaders(); + statusCode = controllerObj.getStatus() || statusCode; + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + returnHandler(response, statusCode, data, headers) + }) + .catch((error: any) => next(error)); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function returnHandler(response: any, statusCode?: number, data?: any, headers: any = {}) { + if (response.headersSent) { + return; + } + Object.keys(headers).forEach((name: string) => { + response.set(name, headers[name]); + }); + if (data && typeof data.pipe === 'function' && data.readable && typeof data._read === 'function') { + data.pipe(response); + } else if (data !== null && data !== undefined) { + response.status(statusCode || 200).json(data); + } else { + response.status(statusCode || 204).end(); + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function responder(response: any): TsoaResponse { + return function(status, data, headers) { + returnHandler(response, status, data, headers); + }; + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function getValidatedArgs(args: any, request: any, response: any): any[] { + const fieldErrors: FieldErrors = {}; + const values = Object.keys(args).map((key) => { + const name = args[key].name; + switch (args[key].in) { + case 'request': + return request; + case 'query': + return validationService.ValidateParam(args[key], request.query[name], name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'path': + return validationService.ValidateParam(args[key], request.params[name], name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'header': + return validationService.ValidateParam(args[key], request.header(name), name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'body': + return validationService.ValidateParam(args[key], request.body, name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'body-prop': + return validationService.ValidateParam(args[key], request.body[name], name, fieldErrors, 'body.', {"noImplicitAdditionalProperties":"throw-on-extras"}); + case 'formData': + if (args[key].dataType === 'file') { + return validationService.ValidateParam(args[key], request.file, name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + } else if (args[key].dataType === 'array' && args[key].array.dataType === 'file') { + return validationService.ValidateParam(args[key], request.files, name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + } else { + return validationService.ValidateParam(args[key], request.body[name], name, fieldErrors, undefined, {"noImplicitAdditionalProperties":"throw-on-extras"}); + } + case 'res': + return responder(response); + } + }); + + if (Object.keys(fieldErrors).length > 0) { + throw new ValidateError(fieldErrors, ''); + } + return values; + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +} + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/src/controllers/v3/types-extra.ts b/src/controllers/v3/types-extra.ts index 438ddde0d..a94f43eb6 100644 --- a/src/controllers/v3/types-extra.ts +++ b/src/controllers/v3/types-extra.ts @@ -19,3 +19,100 @@ export interface PublishResult { results?: string; error?: string; } + +export interface GatewayAdd { + gatewayId?: string; // Primary Key + displayName?: string; + org?: string; + domains?: string; + dataPlane?: string; +} + +export interface OrgAPISpecCreateInput { + productEnvAppId: string; + specUrl: string; +} + +export interface OrgAccessRequestCreateInput { + org?: string; + orgMemberId: string; + userId?: string; + consumerProductEnvAppId: string; + providerProductEnvAppId: string; + businessProcess: string; + accessPointDN: string; + optionalClientScopes: string[]; +} + +export interface OrgAccessRequest { + id: string; + name: string; + isApproved: boolean; + isIssued: boolean; + isComplete: boolean; + requestor: { + name: string; + username: string; + }; + application: { + name: string; + appId: string; + namespace: string; + }; + productEnvironment: { + name: string; + appId: string; + flow: string; + product: { + namespace: string; + openapiSpecs: string[]; + name: string; + }; + }; + serviceAccess: { + id: string; + consumer: { + username: string; + tags: string[]; + }; + }; + createdAt: Scalars['DateTime']; +} + +export interface ProductCatalogOperation { + operationId: string; + method: string; + path: string; + summary: string; + scopes: string[]; +} + +export interface ProductCatalog { + appId: string; + name: string; + spec: { + title: string; + version: string; + description: string; + operations: ProductCatalogOperation[]; + } + credentialIssuer?: { + issuerUrl: string; + clientId: string; + } + product: { + name: string; + type: string; + organization: { + name: string; + } + } + namespace: { + name: string; + orgUnit: string; + permDataPlane: string; + permDomains: string[]; + enabled: boolean; + updatedAt: number; + } +} \ No newline at end of file diff --git a/src/controllers/v3/types.ts b/src/controllers/v3/types.ts index 7157354c0..c7aa565bb 100644 --- a/src/controllers/v3/types.ts +++ b/src/controllers/v3/types.ts @@ -263,6 +263,7 @@ export interface Application { appId?: string; // Primary Key name?: string; description?: string; + gatewayId?: string; owner?: UserRefID; organization?: OrganizationRefID; organizationUnit?: OrganizationUnitRefID; @@ -274,6 +275,7 @@ export interface Application { * @example { * "name": "my-new-product", * "appId": "000000000000", + * "type": "service", * "environments": [ * { * "name": "dev", @@ -288,10 +290,13 @@ export interface Application { export interface Product { appId?: string; // Primary Key name?: string; + type?: "app" | "service"; description?: string; gatewayId?: string; + openapiSpecs?: string[]; dataset?: DraftDatasetRefID; environments?: Environment[]; + organization?: OrganizationRefID; } @@ -337,7 +342,7 @@ export interface CredentialIssuer { flow?: "client-credentials"; mode?: "auto"; authPlugin?: string; - clientAuthenticator?: "client-secret" | "client-jwt" | "client-jwt-jwks-url"; + clientAuthenticator?: "client-secret" | "client-jwt" | "client-jwt-jwks-url" | "client-certificate"; instruction?: string; environmentDetails?: IssuerEnvironmentConfig[]; resourceType?: string; diff --git a/src/lists/Application.js b/src/lists/Application.js index dd00f66a5..654e647f0 100644 --- a/src/lists/Application.js +++ b/src/lists/Application.js @@ -25,6 +25,11 @@ module.exports = { type: Text, isRequired: true, }, + namespace: { + type: Text, + isRequired: false, + access: { update: false }, + }, description: { type: Text, isRequired: true, diff --git a/src/lists/Blob.js b/src/lists/Blob.js index 5adf60281..adb1ed9b6 100644 --- a/src/lists/Blob.js +++ b/src/lists/Blob.js @@ -18,6 +18,10 @@ module.exports = { { value: 'json', label: 'JSON' }, ], }, + namespace: { + type: Text, + isRequired: false, + }, blob: { type: Text, isRequired: true, diff --git a/src/lists/CredentialIssuer.js b/src/lists/CredentialIssuer.js index 8f62cce4e..9cddcc2c2 100644 --- a/src/lists/CredentialIssuer.js +++ b/src/lists/CredentialIssuer.js @@ -94,8 +94,13 @@ module.exports = { { value: 'client-secret', label: 'Client ID and Secret' }, { value: 'client-jwt', label: 'Signed JWT' }, { value: 'client-jwt-jwks-url', label: 'Signed JWT with JWKS URL' }, + { value: 'client-certificate', label: 'Client Certificate' }, ], }, + sdxIdentifier: { + type: Text, + isRequired: false, + }, clientMappers: { type: Text, isRequired: false, diff --git a/src/lists/Environment.js b/src/lists/Environment.js index 7f2f4052a..4ac1c7fca 100644 --- a/src/lists/Environment.js +++ b/src/lists/Environment.js @@ -87,6 +87,7 @@ module.exports = { many: false, access: { update: false }, }, + spec: { type: Relationship, ref: 'Blob', many: false, required: false }, }, access: EnforcementPoint, hooks: { diff --git a/src/lists/Product.js b/src/lists/Product.js index 6719819c5..a97e196a5 100644 --- a/src/lists/Product.js +++ b/src/lists/Product.js @@ -1,4 +1,4 @@ -const { Text, Relationship } = require('@keystonejs/fields'); +const { Text, Select, Relationship } = require('@keystonejs/fields'); const { Markdown } = require('@keystonejs/fields-markdown'); const { newProductID, isProductID } = require('../services/identifiers'); const { @@ -19,12 +19,26 @@ module.exports = { appId: { type: Text, isRequired: true, - isUnique: false, + isUnique: true, + access: { + create: true, + update: false, + }, }, name: { type: Text, isRequired: true, }, + type: { + type: Select, + emptyOption: false, + dataType: 'string', + defaultValue: 'service', + options: [ + { value: 'service', label: 'Service' }, + { value: 'app', label: 'Application' }, + ] + }, namespace: { type: Text, isRequired: true, @@ -35,6 +49,11 @@ module.exports = { isMultiline: true, isRequired: false, }, + // JSON structure: {label: '', version: '', blob: 'blod-reference'} + openapiSpecs: { + type: Text, + isRequired: false, + }, dataset: { type: Relationship, ref: 'Dataset' }, organization: { type: Relationship, ref: 'Organization', many: false }, organizationUnit: { type: Relationship, ref: 'OrganizationUnit' }, diff --git a/src/lists/extensions/Namespace.ts b/src/lists/extensions/Namespace.ts index 48041f554..c5d64aea1 100644 --- a/src/lists/extensions/Namespace.ts +++ b/src/lists/extensions/Namespace.ts @@ -460,7 +460,7 @@ module.exports = { }, { schema: - 'createNamespace(name: String, displayName: String): Namespace', + 'createNamespace(name: String, displayName: String, org: String, domains: String, dataPlane: String): Namespace', resolver: async ( item: any, args: any, @@ -554,7 +554,27 @@ module.exports = { envCtx.issuerEnvConfig.clientSecret ); - await kcGroupService.createIfMissing('ns', newNS); + const group = await kcGroupService.createIfMissing('ns', newNS); + + const groupDetail = await kcGroupService.getGroupById(group.id); + + groupDetail.attributes = groupDetail.attributes || {}; + let update = false; + if (args.org) { + update = true; + groupDetail.attributes['org'] = [ args.org ]; + } + if (args.domains) { + update = true; + groupDetail.attributes['perm-domains'] = args.domains.split(','); + } + if (args.dataPlane) { + update = true; + groupDetail.attributes['perm-data-plane'] = [ args.dataPlane ]; + } + if (update) { + await kcGroupService.updateGroup(groupDetail); + } await recordActivity( context.sudo(), diff --git a/src/lists/extensions/OrgAccessRequest.ts b/src/lists/extensions/OrgAccessRequest.ts new file mode 100644 index 000000000..eea996168 --- /dev/null +++ b/src/lists/extensions/OrgAccessRequest.ts @@ -0,0 +1,76 @@ +const { EnforcementPoint } = require('../../authz/enforcement'); +import { Logger } from '../../logger'; +import { OrgAccessRequestCreate } from '../..//services/workflow/org-access-request'; + +const logger = Logger('lists.orgaccessreq'); + +const typeOrgAccessRequestCreateInput = ` + input OrgAccessRequestCreateInput { + org: String!, + orgMemberId: String!, + userId: String!, + consumerProductEnvAppId: String!, + providerProductEnvAppId: String!, + businessProcess: String!, + accessPointDN: String!, + optionalClientScopes: [String!] + } +`; + +const typeOrgAccessRequest = ` + type OrgAccessRequest { + application: Application, + providerProdEnv: Environment, + accessRequest: AccessRequest, + } +`; + +module.exports = { + extensions: [ + (keystone: any) => { + keystone.extendGraphQLSchema({ + types: [ + { type: typeOrgAccessRequestCreateInput }, + { type: typeOrgAccessRequest }, + ], + queries: [], + mutations: [ + { + schema: + 'orgCreateAccessRequest(data: OrgAccessRequestCreateInput): OrgAccessRequest', + resolver: async ( + item: any, + args: any, + context: any, + info: any, + { query, access }: any + ) => { + const noauthContext = context.createContext({ skipAccessControl: true }); + const result = await OrgAccessRequestCreate( + noauthContext, + args.data.org, + args.data.orgMemberId, + args.data.userId, + args.data.consumerProductEnvAppId, + args.data.providerProductEnvAppId, + args.data.businessProcess, + args.data.accessPointDN, + args.data.optionalClientScopes || [] + ); + logger.debug('OrgCreateAccessRequest: %j', result); + return { + application: { + appId: result.accessRequest.application.appId, + }, + accessRequest: { + id: result.accessRequest.id, + }, + }; + }, + access: EnforcementPoint, + }, + ], + }); + }, + ], +}; diff --git a/src/nextapp/components/authorization-profile-controls/authentication.tsx b/src/nextapp/components/authorization-profile-controls/authentication.tsx index 760d9423d..1544085e2 100644 --- a/src/nextapp/components/authorization-profile-controls/authentication.tsx +++ b/src/nextapp/components/authorization-profile-controls/authentication.tsx @@ -108,6 +108,9 @@ const AuthorizationProfileAuthentication: React.FC Signed JWT with JWKS URL + + Client Certificate (mTLS) + diff --git a/src/nextapp/components/authorization-profile-form/authentication-form.tsx b/src/nextapp/components/authorization-profile-form/authentication-form.tsx index 96f893c30..6bc5902a4 100644 --- a/src/nextapp/components/authorization-profile-form/authentication-form.tsx +++ b/src/nextapp/components/authorization-profile-form/authentication-form.tsx @@ -82,6 +82,14 @@ const AuthenticationForm: React.FC = ({ Client Credential Flow, using signed JWT with JWKS URL or Public Key + + Client Credential Flow, using x509 Certificate + + diff --git a/src/package-lock.json b/src/package-lock.json index e452672db..ac260394a 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -13,7 +13,7 @@ "@chakra-ui/react": "^1.6.0", "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "@keycloak/keycloak-admin-client": "^17.0.0-dev.26", + "@keycloak/keycloak-admin-client": "^17.0.1", "@keystone-next/admin-ui": "^7.0.0", "@keystonejs/access-control": "^7.1.1", "@keystonejs/adapter-mongoose": "^11.2.2", @@ -60,6 +60,7 @@ "keycloak-connect": "^17.0.1", "lodash": "^4.17.21", "multer": "^1.4.2", + "node-fetch": "^2.7.0", "nodemailer": "^6.6.0", "npmlog": "^6.0.1", "numeral": "^2.0.6", @@ -153,7 +154,7 @@ "typescript": "4.2.4" }, "engines": { - "node": ">=10.0.0" + "node": ">=22.0.0 <23.0.0" } }, "node_modules/@ampproject/remapping": { @@ -5471,6 +5472,15 @@ "node-fetch": "2.6.1" } }, + "node_modules/@graphql-tools/links/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@graphql-tools/load": { "version": "6.2.8", "resolved": "https://registry.npmjs.org/@graphql-tools/load/-/load-6.2.8.tgz", @@ -5841,6 +5851,15 @@ "node-fetch": "2.6.1" } }, + "node_modules/@graphql-tools/url-loader/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@graphql-tools/url-loader/node_modules/tslib": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", @@ -7423,6 +7442,7 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-17.0.1.tgz", "integrity": "sha512-lgw6P7pGcJQbJExCE6+FpznDjadI571rCoSj2CmE8KAHu0BCsj2eqWP9HI3wb6zpkJwVQ86uLHTcsGvS8Ijjtw==", + "license": "Apache-2.0", "dependencies": { "axios": "^0.25.0", "camelize-ts": "^1.0.8", @@ -9512,6 +9532,15 @@ } } }, + "node_modules/@keystonejs/app-admin-ui/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@keystonejs/app-admin-ui/node_modules/pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", @@ -10239,6 +10268,15 @@ } } }, + "node_modules/@keystonejs/fields-auto-increment/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@keystonejs/fields-auto-increment/node_modules/pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", @@ -11060,6 +11098,15 @@ } } }, + "node_modules/@keystonejs/fields-mongoid/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@keystonejs/fields-mongoid/node_modules/pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", @@ -11804,6 +11851,15 @@ } } }, + "node_modules/@keystonejs/fields/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@keystonejs/fields/node_modules/pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", @@ -12844,6 +12900,15 @@ "node": ">=8" } }, + "node_modules/@prisma/sdk/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/@prisma/sdk/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -20557,6 +20622,15 @@ "uuid": "8.3.0" } }, + "node_modules/checkpoint-client/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/checkpoint-client/node_modules/uuid": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", @@ -22052,6 +22126,15 @@ "node-fetch": "2.6.1" } }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "license": "MIT", + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -22078,30 +22161,6 @@ "web-streams-polyfill": "^3.2.0" } }, - "node_modules/cross-undici-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/cross-undici-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/cross-undici-fetch/node_modules/undici": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/undici/-/undici-5.0.0.tgz", @@ -22110,20 +22169,6 @@ "node": ">=12.18" } }, - "node_modules/cross-undici-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/cross-undici-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0= sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/cryptiles": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-4.1.2.tgz", @@ -37716,11 +37761,45 @@ } }, "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, "node_modules/node-fingerprint": { @@ -53337,6 +53416,11 @@ "requires": { "node-fetch": "2.6.1" } + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" } } }, @@ -53666,6 +53750,11 @@ "node-fetch": "2.6.1" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "tslib": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", @@ -56513,6 +56602,11 @@ "tildify": "2.0.0" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", @@ -57232,6 +57326,11 @@ "tildify": "2.0.0" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", @@ -57651,6 +57750,11 @@ "tildify": "2.0.0" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", @@ -58311,6 +58415,11 @@ "tildify": "2.0.0" } }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "pg-connection-string": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", @@ -59114,6 +59223,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -65185,6 +65299,11 @@ "uuid": "8.3.0" }, "dependencies": { + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + }, "uuid": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", @@ -66395,6 +66514,13 @@ "integrity": "sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ==", "requires": { "node-fetch": "2.6.1" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + } } }, "cross-spawn": { @@ -66420,37 +66546,10 @@ "web-streams-polyfill": "^3.2.0" }, "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "undici": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/undici/-/undici-5.0.0.tgz", "integrity": "sha512-VhUpiZ3No1DOPPQVQnsDZyfcbTTcHdcgWej1PdFnSvOeJmOVDgiOHkunJmBLfmjt4CqgPQddPVjSWW0dsTs5Yg==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0= sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } } } }, @@ -78441,9 +78540,33 @@ } }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } }, "node-fingerprint": { "version": "0.0.2", diff --git a/src/package.json b/src/package.json index d238add76..00f80e53c 100644 --- a/src/package.json +++ b/src/package.json @@ -63,7 +63,7 @@ "@chakra-ui/react": "^1.6.0", "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", - "@keycloak/keycloak-admin-client": "^17.0.0-dev.26", + "@keycloak/keycloak-admin-client": "^17.0.1", "@keystone-next/admin-ui": "^7.0.0", "@keystonejs/access-control": "^7.1.1", "@keystonejs/adapter-mongoose": "^11.2.2", diff --git a/src/server.ts b/src/server.ts index f1815a867..63bbd19d8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -176,6 +176,7 @@ for (const _list of [ 'CredentialIssuerExt', 'Namespace', 'NamespaceActivity', + 'OrgAccessRequest', 'OrganizationPolicy', 'ServiceAccess', 'ServiceAccount', diff --git a/src/services/gwaapi/gwa-service.ts b/src/services/gwaapi/gwa-service.ts index 86078bdf5..cecd95169 100644 --- a/src/services/gwaapi/gwa-service.ts +++ b/src/services/gwaapi/gwa-service.ts @@ -1,6 +1,7 @@ import { checkStatus } from '../checkStatus'; import fetch from 'node-fetch'; import { logger } from '../../logger'; +import { de } from 'date-fns/locale'; export class GWAService { private gwaUrl: string; @@ -33,4 +34,29 @@ export class GWAService { }, }).then(checkStatus); } + + public async getGatewayConfigUsingPattern( + ns: string, + deleteFlag: boolean, + payload: any + ) { + const url = `${this.gwaUrl}/v2/namespaces/${ns}/gateway/pattern-output`; + logger.debug('[getGatewayConfigUsingPattern] ns=%s', ns); + + const deleteQualifier = deleteFlag ? payload.ns_qualifier : ''; + + return await fetch(url, { + method: 'put', + body: JSON.stringify({ + delete: deleteFlag, + deleteQualifier, + document: payload, + }), + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(checkStatus) + .then((res) => res.json()); + } } diff --git a/src/services/identifiers.ts b/src/services/identifiers.ts index 06e8f7d17..706a753a1 100644 --- a/src/services/identifiers.ts +++ b/src/services/identifiers.ts @@ -24,4 +24,8 @@ export function newNamespaceID(): string { return 'gw-' + uuidv4().replace(/-/g, '').toLowerCase().substring(0, 5); } +export function newJWKID(): string { + return uuidv4().replace(/-/g, '').toLowerCase().substring(0, 6); +} + export const newGatewayID = newNamespaceID; diff --git a/src/services/keycloak/client-registration-service.ts b/src/services/keycloak/client-registration-service.ts index 7169d2e49..d3a331b28 100644 --- a/src/services/keycloak/client-registration-service.ts +++ b/src/services/keycloak/client-registration-service.ts @@ -7,6 +7,7 @@ import { headers } from './keycloak-api'; import { strict as assert } from 'assert'; import { clientTemplateClientSecret } from './templates/client-template-client-secret'; +import { clientTemplateClientCertificate } from './templates/client-template-client-certificate'; import { clientTemplateClientJwt } from './templates/client-template-client-jwt'; import { clientTemplateSharedIdP } from './templates/client-template-shared-idp'; import { clientTemplateSharedIdPAuthz } from './templates/client-template-shared-idp-authz'; @@ -32,12 +33,14 @@ export interface ClientRegistration { clientId: string; clientSecret?: string; enabled?: boolean; + attributes?: { [key: string]: string }; } export enum ClientAuthenticator { ClientJWT = 'client-jwt', ClientJWTwithJWKS = 'client-jwt-jwks-url', ClientSecret = 'client-secret', + ClientCertificate = 'client-certificate', SharedIdP = 'shared-idp', SharedIdPWithAuthz = 'shared-idp-authz', } @@ -69,8 +72,10 @@ export class KeycloakClientRegistrationService { public async clientRegistration( authenticator: ClientAuthenticator, clientId: string, + name: string, clientSecret: string, certificate: string, + subjectDn: string, jwksUrl: string, clientMappers: ClientMapper[], enabled: boolean = false, @@ -81,6 +86,7 @@ export class KeycloakClientRegistrationService { case ClientAuthenticator.ClientSecret: body = Object.assign(JSON.parse(clientTemplateClientSecret), { enabled, + name, clientId, secret: clientSecret, }); @@ -88,15 +94,29 @@ export class KeycloakClientRegistrationService { case ClientAuthenticator.ClientJWT: body = Object.assign(JSON.parse(clientTemplateClientJwt), { enabled, + name, clientId, attributes: { 'jwt.credential.public.key': certificate, }, }); break; + case ClientAuthenticator.ClientCertificate: + body = Object.assign(JSON.parse(clientTemplateClientCertificate), { + enabled, + name, + clientId, + consentRequired: true, + attributes: { + "x509.allow.regex.pattern.comparison": "false", + "x509.subjectdn": subjectDn, + } + }); + break; case ClientAuthenticator.SharedIdP: body = Object.assign(JSON.parse(clientTemplateSharedIdP), { enabled, + name, clientId, baseUrl, attributes: {}, @@ -105,6 +125,7 @@ export class KeycloakClientRegistrationService { case ClientAuthenticator.SharedIdPWithAuthz: body = Object.assign(JSON.parse(clientTemplateSharedIdPAuthz), { enabled, + name, clientId, baseUrl, attributes: {}, diff --git a/src/services/keycloak/group-service.ts b/src/services/keycloak/group-service.ts index 45a72b7ca..19ff9aed1 100644 --- a/src/services/keycloak/group-service.ts +++ b/src/services/keycloak/group-service.ts @@ -77,11 +77,11 @@ export class KeycloakGroupService { public async createIfMissing( parentGroupName: string, groupName: string - ): Promise { + ): Promise<{ created: boolean; id: string }> { const groups = (await this.kcAdminClient.groups.find()).filter( (group: GroupRepresentation) => group.name == parentGroupName ); - await this.createIfMissingForParentGroup(groups[0], groupName); + return await this.createIfMissingForParentGroup(groups[0], groupName); } public async createRootGroup(groupName: string) { @@ -131,7 +131,7 @@ export class KeycloakGroupService { max: 500, briefRepresentation, }); - logger.debug('[search] %j', result); + logger.debug('[search] %j', result.length); return result; } diff --git a/src/services/keycloak/namespace-details.ts b/src/services/keycloak/namespace-details.ts index 63adf88b6..98eb46211 100644 --- a/src/services/keycloak/namespace-details.ts +++ b/src/services/keycloak/namespace-details.ts @@ -17,6 +17,7 @@ import { } from '../../lists/extensions/Common'; import { GWAService } from '../gwaapi'; import { strict as assert } from 'assert'; +import GroupRepresentation from '@keycloak/keycloak-admin-client/lib/defs/groupRepresentation'; const logger = Logger('kc.nsdetails'); @@ -79,6 +80,18 @@ export async function backfillGroupAttributes( assert.strictEqual(Boolean(nsPermissions), true, 'Invalid namespace'); + return backfillGroupRepAttributes(nsPermissions, detail, defaultSettings); + +} + +export async function backfillGroupRepAttributes( + nsPermissions: GroupRepresentation, + detail: any, + defaultSettings: any, +): Promise { + + assert.strictEqual(Boolean(nsPermissions), true, 'Invalid namespace'); + transformSingleValueAttributes(nsPermissions.attributes, [ 'description', 'perm-data-plane', @@ -90,12 +103,6 @@ export async function backfillGroupAttributes( 'org-updated-at', ]); - logger.debug( - '[backfillGroupAttributes] %s attributes %j', - ns, - nsPermissions.attributes - ); - const merged = { ...detail, ...defaultSettings, @@ -135,16 +142,20 @@ export async function transformOrgAndOrgUnit( merged: any, getOrgAdmins: boolean ): Promise { - const orgInfo = await getOrganizationUnit(context, merged.orgUnit); + const orgInfo = merged.orgUnit ? await getOrganizationUnit(context, merged.orgUnit) : undefined; if (orgInfo) { merged['org'] = { name: orgInfo.name, title: orgInfo.title }; - merged['orgUnit'] = { - name: orgInfo.orgUnits[0].name, - title: orgInfo.orgUnits[0].title, - }; + if (orgInfo.orgUnits) { + merged['orgUnit'] = { + name: orgInfo.orgUnits[0].name, + title: orgInfo.orgUnits[0].title, + }; + } } else { merged['org'] = { name: merged.org, title: merged.org }; - merged['orgUnit'] = { name: merged.orgUnit, title: merged.orgUnit }; + if (merged.orgUnit) { + merged['orgUnit'] = { name: merged.orgUnit, title: merged.orgUnit }; + } } // lookup org admins from diff --git a/src/services/keycloak/templates/client-template-client-certificate.ts b/src/services/keycloak/templates/client-template-client-certificate.ts new file mode 100644 index 000000000..8e4d28620 --- /dev/null +++ b/src/services/keycloak/templates/client-template-client-certificate.ts @@ -0,0 +1,65 @@ + +export const clientTemplateClientCertificate = JSON.stringify({ + access: { view: true, configure: true, manage: true }, + alwaysDisplayInConsole: false, + authenticationFlowBindingOverrides: {}, + attributes: { + "acr.loa.map": "{}", + "access.token.header.type.rfc9068": false, + "backchannel.logout.revoke.offline.tokens": false, + "backchannel.logout.session.required": true, + "client.introspection.response.allow.jwt.claim.enabled": false, + "client.use.lightweight.access.token.enabled": false, + "client_credentials.use_refresh_token": false, + "display.on.consent.screen": false, + "dpop.bound.access.tokens": false, + "exclude.session.state.from.auth.response": false, + "oauth2.device.authorization.grant.enabled": false, + "oidc.ciba.grant.enabled": false, + "realm_client": false, + "request.object.encryption.alg": "any", + "request.object.encryption.enc": "any", + "request.object.required": "not required", + "request.object.signature.alg": "any", + "require.pushed.authorization.requests": false, + "saml.client.signature": false, + "saml.encrypt": false, + "saml.assertion.signature": false, + "saml.authnstatement": false, + "saml.force.post.binding": false, + "saml.multivalued.roles": false, + "saml.onetimeuse.condition": false, + "saml.server.signature": false, + "saml.server.signature.keyinfo.ext": false, + "saml_force_name_id_format": false, + "standard.token.exchange.enabled": true, + "tls.client.certificate.bound.access.tokens": true, + "token.response.type.bearer.lower-case": false, + "use.refresh.tokens": true, + "x509.allow.regex.pattern.comparison": false, + "x509.subjectdn": "" + }, + bearerOnly: false, + clientAuthenticatorType: 'client-x509', + clientId: '', + consentRequired: false, + defaultClientScopes: [] as string[], + description: '', + directAccessGrantsEnabled: false, + enabled: false, + frontchannelLogout: false, + fullScopeAllowed: false, + implicitFlowEnabled: false, + name: '', + nodeReRegistrationTimeout: -1, + notBefore: 0, + optionalClientScopes: [] as string[], + protocol: 'openid-connect', + protocolMappers: [] as any[], + publicClient: false, + redirectUris: ['http://*', 'https://*'], + serviceAccountsEnabled: false, + standardFlowEnabled: true, + surrogateAuthRequired: false, + webOrigins: ['*'], +}); diff --git a/src/services/keystone/access-request.ts b/src/services/keystone/access-request.ts index 289bbd2c0..2d717c76d 100644 --- a/src/services/keystone/access-request.ts +++ b/src/services/keystone/access-request.ts @@ -1,18 +1,149 @@ import { gql } from 'graphql-request'; import { Logger } from '../../logger'; -import { AccessRequest, AccessRequestUpdateInput } from './types'; +import { + AccessRequest, + AccessRequestCreateInput, + AccessRequestUpdateInput, + AccessRequestWhereInput, +} from './types'; const assert = require('assert').strict; const logger = Logger('keystone.access-req'); +export async function addAccessRequest( + context: any, + data: any +): Promise { + const query = gql` + mutation AddAccessRequest( + $name: String! + $controls: String + $requestor: ID! + $applicationId: ID! + $productEnvironmentId: ID! + $additionalDetails: String + $acceptLegal: Boolean! + ) { + acceptLegal( + productEnvironmentId: $productEnvironmentId + acceptLegal: $acceptLegal + ) { + legalsAgreed + } + + createAccessRequest( + data: { + name: $name + controls: $controls + additionalDetails: $additionalDetails + requestor: { connect: { id: $requestor } } + application: { connect: { id: $applicationId } } + productEnvironment: { connect: { id: $productEnvironmentId } } + } + ) { + id + } + } + `; + + logger.debug('Mutation [addAccessRequest] data %j', data); + const result = await context.executeGraphQL({ + query, + variables: { ...data }, + }); + + logger.debug('Mutation [addAccessRequest] result %j', result); + + assert.strictEqual( + 'errors' in result, + false, + 'Error adding access request' + ); + + return result.data.createAccessRequest; +} + +export async function collectCredentials(context: any, id: string): Promise { + logger.debug('Collecting credentials for access request %s', id); + const query = gql` + mutation genCredential($id: ID!) { + updateAccessRequest(id: $id, data: { credential: "NEW" }) { + credential + } + }` + const result = await context.executeGraphQL({ + query, + variables: { id }, + }); + logger.debug('Mutation [collectCredentials] result %j', result); + assert.strictEqual( + 'errors' in result, + false, + 'Error collecting credentials' + ); + return result.data.updateAccessRequest; +} + +export async function getAccessRequest(context: any, id: string): Promise { + const query = gql` + query GetAccessRequestById($id: ID!) { + AccessRequest(where: { id: $id }) { + id + name + isApproved + isIssued + isComplete + controls + productEnvironment { + id + name + appId + product { + namespace + openapiSpecs + name + } + } + requestor { + username + } + application { + id + appId + namespace + name + } + serviceAccess { + id + consumer { + id + username + tags + } + } + } + } + `; + + const result = await context.executeGraphQL({ + query, + variables: { id }, + }); + logger.debug('Query [getAccessRequest] result %j', result); + return result.data.AccessRequest; +} + export async function getAccessRequestsByNamespace( context: any, - ns: string + nsList: string[] ): Promise { const query = gql` - query GetNamespaceAccessRequests($ns: String!) { + query GetNamespaceAccessRequests($nsList: [String]!) { allAccessRequests( - where: { productEnvironment: { product: { namespace: $ns } } } + where: { OR: [ + { productEnvironment: { product: { namespace_in: $nsList } } }, + { application: { namespace_in: $nsList } } + ] } ) { id name @@ -26,6 +157,7 @@ export async function getAccessRequestsByNamespace( application { name appId + namespace } requestor { username @@ -35,6 +167,8 @@ export async function getAccessRequestsByNamespace( appId flow product { + namespace + openapiSpecs name } } @@ -42,6 +176,7 @@ export async function getAccessRequestsByNamespace( id consumer { username + tags } } createdAt @@ -49,7 +184,7 @@ export async function getAccessRequestsByNamespace( } `; - const result = await context.executeGraphQL({ query, variables: { ns } }); + const result = await context.executeGraphQL({ query, variables: { nsList } }); logger.debug('Query [getAccessRequestsByNamespace] result %j', result); return result.data.allAccessRequests; } diff --git a/src/services/keystone/activity.ts b/src/services/keystone/activity.ts index 89b01cf54..cd50db967 100644 --- a/src/services/keystone/activity.ts +++ b/src/services/keystone/activity.ts @@ -133,7 +133,7 @@ export async function recordActivity( ? productNamespace : context.authedItem.namespace; const name = `${action} ${type}[${refId}]`; - logger.debug('[recordActivity] userid=%s name=%s', userId, name); + logger.debug('[recordActivity] ns=%s userid=%s name=%s', namespace, userId, name); const variables: { [key: string]: any } = { name, diff --git a/src/services/keystone/application.ts b/src/services/keystone/application.ts index ac9f7142c..1dd778e38 100644 --- a/src/services/keystone/application.ts +++ b/src/services/keystone/application.ts @@ -41,3 +41,44 @@ export async function lookupMyApplicationsById( logger.debug('[lookupMyApplicationsById] result %j', result); return result.data.myApplications[0]; } + +export async function lookupApplicationsByNamespaces( + context: any, + namespaces: string[] +): Promise { + const result = await context.executeGraphQL({ + query: `query GetApplicationByNamespaces($namespaces: [String!]) { + allApplications(where: {namespace_in: $namespaces}) { + id + appId + name + } + }`, + variables: { namespaces }, + }); + logger.debug('[lookupApplicationByNamespaces] result %j', result); + return result.data.allApplications; +} + + +export async function createApplication( + context: any, + data: { appId?: string, name: string, ownerId: string, description?: string, namespace?: string } +): Promise { + logger.debug('[createApplication] %j', data); + const result = await context.executeGraphQL({ + query: `mutation CreateApplication($appId: String, $name: String!, $description: String, $ownerId: ID!, $namespace: String) { + createApplication(data: {appId: $appId, name: $name, owner: {connect: {id: $ownerId}}, description: $description, namespace: $namespace}) { + id + appId + name + owner { + name + } + } + }`, + variables: data, + }); + logger.debug('[createApplication] result %j', result); + return result.data.createApplication; +} \ No newline at end of file diff --git a/src/services/keystone/product-environment.ts b/src/services/keystone/product-environment.ts index e710d0744..7fd95afd2 100644 --- a/src/services/keystone/product-environment.ts +++ b/src/services/keystone/product-environment.ts @@ -85,6 +85,7 @@ export async function lookupProductEnvironmentServicesBySlug( name flow product { + name namespace } credentialIssuer { diff --git a/src/services/keystone/types.ts b/src/services/keystone/types.ts index 5d5de8201..435ec3e75 100644 --- a/src/services/keystone/types.ts +++ b/src/services/keystone/types.ts @@ -758,6 +758,7 @@ export type Application = { id: Scalars['ID']; appId?: Maybe; name?: Maybe; + namespace?: Maybe; description?: Maybe; certificate?: Maybe; organization?: Maybe; diff --git a/src/services/org-groups/namespace.ts b/src/services/org-groups/namespace.ts index ef321f51f..d6519b2f0 100644 --- a/src/services/org-groups/namespace.ts +++ b/src/services/org-groups/namespace.ts @@ -156,11 +156,18 @@ export class NamespaceService { const matches = namespaceGroups .filter( (group) => - 'org' in group.attributes && group.attributes['org'][0] === org + 'org' in group.attributes && group.attributes['org'][0] === org && + !('decommissioned' in group.attributes) ) .map((group) => ({ name: group.name, - orgUnit: group.attributes['org-unit'][0], + orgUnit: 'org-unit' in group.attributes ? group.attributes['org-unit'][0] : null, + permDataPlane: + 'perm-data-plane' in group.attributes + ? group.attributes['perm-data-plane'].pop() : '', + permDomains: + 'perm-domains' in group.attributes + ? group.attributes['perm-domains'] : [], enabled: 'org-enabled' in group.attributes ? group.attributes['org-enabled'][0] === 'true' @@ -177,10 +184,16 @@ export class NamespaceService { async getNamespaceOrganizationDetails(ns: string): Promise { const nsGroup = await this.groupService.findByName('ns', ns, false); - if ('org' in nsGroup.attributes && 'org-unit' in nsGroup.attributes) { + if ('org' in nsGroup.attributes) { return { name: nsGroup.attributes['org'].pop(), - orgUnit: nsGroup.attributes['org-unit'].pop(), + orgUnit: 'org-unit' in nsGroup.attributes ? nsGroup.attributes['org-unit'].pop() : '', + permDataPlane: + 'perm-data-plane' in nsGroup.attributes + ? nsGroup.attributes['perm-data-plane'] : '', + permDomains: + 'perm-domains' in nsGroup.attributes + ? nsGroup.attributes['perm-domains'] : [], enabled: 'org-enabled' in nsGroup.attributes ? nsGroup.attributes['org-enabled'][0] === 'true' diff --git a/src/services/org-groups/types.ts b/src/services/org-groups/types.ts index 6eba1af0f..3f71423e0 100644 --- a/src/services/org-groups/types.ts +++ b/src/services/org-groups/types.ts @@ -33,5 +33,7 @@ export interface OrgNamespace { name: string; orgUnit: string; enabled: boolean; + permDataPlane: string; + permDomains: string[]; updatedAt: number; } diff --git a/src/services/report/data/consumer-requests.ts b/src/services/report/data/consumer-requests.ts index d0332e442..868d3d7c7 100644 --- a/src/services/report/data/consumer-requests.ts +++ b/src/services/report/data/consumer-requests.ts @@ -29,7 +29,7 @@ export async function getConsumerRequests( ): Promise { const dataPromises = namespaces.map( async (ns): Promise => { - const requests = await getAccessRequestsByNamespace(ksCtx, ns.name); + const requests = await getAccessRequestsByNamespace(ksCtx, [ ns.name ]); // services let data: ReportOfConsumerRequest[] = []; diff --git a/src/services/sdx/edge-servers.ts b/src/services/sdx/edge-servers.ts new file mode 100644 index 000000000..863e20fbe --- /dev/null +++ b/src/services/sdx/edge-servers.ts @@ -0,0 +1,14 @@ +export async function LookupEdgeServer(host: string) { + const servers = await fetch( + 'https://sdx-beta-api-gov-bc-ca-lab.dev.api.gov.bc.ca/api/rd/access-points', + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + const serverData = await servers.json(); + return serverData.filter((server: any) => server.host === host).pop(); +} diff --git a/src/services/sdx/gateway-patterns.ts b/src/services/sdx/gateway-patterns.ts new file mode 100644 index 000000000..e9f09d357 --- /dev/null +++ b/src/services/sdx/gateway-patterns.ts @@ -0,0 +1,229 @@ +/* + +- this service supports the controller for gateway pattern based configuration +- it uses the "catalog" to prepare the templated parameters before sending to +- the gwa-api service for generating the config +*/ + +import { GWAService } from '../gwaapi'; +import { logger } from '../../logger'; +import { CatalogEntry, GetCatalog } from './sdx-catalog'; +import { newEnvironmentID, newJWKID } from '../identifiers'; + +export interface GatewayPatternConfig { + pattern: string; + delete?: boolean; + parameters: Record; +} + +export async function GetConfigUsingPattern( + ctx: any, + inputs: GatewayPatternConfig +): Promise { + const catalog = await GetCatalog(ctx); + if (inputs.pattern.startsWith('sdx-keys-')) { + expectRequiredParams(inputs.parameters, ['locator', 'public_key_pem']); + const entry = catalog.find( + (e) => e.locator === inputs.parameters['locator'] + ); + if (!entry) { + throw new Error( + `GetConfigUsingPattern: unable to find catalog entry for locator ${inputs.parameters['locator']}` + ); + } + + return await evalKeysPattern( + inputs.pattern, + inputs.delete === true, + entry, + inputs.parameters['public_key_pem'] + ); + } else if (inputs.pattern.startsWith('sdx-p2p-consumer-')) { + expectRequiredParams(inputs.parameters, ['consumer', 'provider', 'req_id']); + const provider = catalog.find( + (e) => e.locator === inputs.parameters['provider'] + ); + const reqId = inputs.parameters['req_id']; + const consumer = catalog.find( + (e) => e.locator === inputs.parameters['consumer'] + ); + if (!consumer) { + throw new Error( + `GetConfigUsingPattern: unable to find catalog entry for locator ${inputs.parameters['locator']}` + ); + } + + return await evalConsumerPattern( + inputs.pattern, + inputs.delete === true, + reqId, + consumer, + provider + ); + } else if (inputs.pattern.startsWith('sdx-p2p-provider-')) { + expectRequiredParams(inputs.parameters, [ + 'consumer', + 'provider', + 'req_id', + 'upstream_uri', + ]); + const provider = catalog.find( + (e) => e.locator === inputs.parameters['provider'] + ); + if (!provider) { + throw new Error( + `GetConfigUsingPattern: unable to find catalog entry for locator ${inputs.parameters['provider']}` + ); + } + + const consumer = catalog.find( + (e) => e.locator === inputs.parameters['consumer'] + ); + const reqId = inputs.parameters['req_id']; + + const upstreamUri = inputs.parameters['upstream_uri']; + return await evalProviderPattern( + inputs.pattern, + inputs.delete === true, + reqId, + upstreamUri, + provider, + consumer + ); + } else { + throw new Error( + `GetConfigUsingPattern: unsupported pattern ${inputs.pattern}` + ); + } +} + +async function evalKeysPattern( + pattern: string, + deleteFlag: boolean, + entry: CatalogEntry, + publicKeyPem: string +) { + const gwa = new GWAService(process.env.GWA_API_URL); + + const kid = `urn:ca:bc:sdx:service:${entry.locator.toLowerCase()}:${newJWKID()}`; + const keyName = `SDX.${entry.locator}:0`; + const result = await gwa.getGatewayConfigUsingPattern( + entry.gateway.name, + deleteFlag, + { + pattern: pattern, + kid, + key_name: keyName, + ns_qualifier: `KEYS-${entry.product.name}`, + public_key_pem: publicKeyPem, + } + ); + return result; +} + +async function evalConsumerPattern( + pattern: string, + deleteFlag: boolean, + reqId: string, + consumer: CatalogEntry, + provider: CatalogEntry +) { + const gwa = new GWAService(process.env.GWA_API_URL); + + const result = await gwa.getGatewayConfigUsingPattern( + consumer.gateway.name, + deleteFlag, + { + pattern, + consumer_uri: consumer.locator, + gateway: consumer.gateway.name, + ns_qualifier: `AP-C-REQ-${reqId}`, + route_host: consumer.edgeServer.internal_endpoint, + provider_endpoint: `${provider.edgeServer.host}`, + route_path: `/${provider.locator}`, + service_name: `AP-C-REQ-${reqId}-${provider.product.name}`, + upstream_uri: `https://${provider.edgeServer.endpoint}`, + edge_kid: `urn:ca:bc:sdx:edge:${consumer.edgeServer.id}:0`, + trust_jwks_endpoint: 'http://localhost:8000/jwks', // TODO: update to route for real endpoint + } + ); + return result; +} + +async function evalProviderPattern( + pattern: string, + deleteFlag: boolean, + reqId: string, + upstreamUri: string, + provider: CatalogEntry, + consumer: CatalogEntry +) { + const gwa = new GWAService(process.env.GWA_API_URL); + + const result = await gwa.getGatewayConfigUsingPattern( + provider.gateway.name, + deleteFlag, + { + pattern, + consumer_uri: consumer.locator, + gateway: provider.gateway.name, + mtls_allow_list: `"${provider.edgeServer.dn}"`, + ns_qualifier: `AP-P-REQ-${reqId}`, + route_host: provider.edgeServer.host, + route_path: `/${provider.locator}`, + service_name: `AP-P-REQ-${reqId}-${provider.product.name}`, + upstream_uri: upstreamUri, + edge_kid: `urn:ca:bc:sdx:edge:${provider.edgeServer.id}:0`, + trust_jwks_endpoint: 'http://localhost:8000/jwks', // TODO: update to route for real endpoint + } + ); + return result; +} + +function expectRequiredParams( + provided: Record, + required: string[] +) { + for (const param of required) { + if (!provided[param]) { + throw new Error(`missing required parameter: ${param}`); + } + } +} + +/* +CONSUMER: + +consumer_client_id: ap-gw-31a33-default-dev +consumer_uri: DEV.MIN.CITZ.SINGLE-DIGITAL-GW +gateway: gw-31a33 +mtls_allow_list: '"CN=sdxgov.edge.sdx"' +ns_qualifier: AP-P-REQ-229 +openid_audience: ap-gw-31a33-default-dev +openid_issuer: https://sdx-authz-apps-gov-bc-ca-lab.apps.gov.bc.ca/auth/realms/sdx +openid_scope: '' +pattern: sdx-p2p-provider-r1 +route_host: ministryofpuppiesandkittens.xyz +route_path: /DEV/MIN/PUKI/TOYS +service_name: AP-P-REQ-229-TOYS +upstream_uri: https://httpbun.com +*/ + +/* +PROVIDER: + +consumer_client_id: ap-gw-31a33-default-dev +consumer_uri: DEV.MIN.CITZ.SINGLE-DIGITAL-GW +edge_kid: urn:ca:bc:sdx:edge:sdxgov:0 +gateway: gw-8aa16 +mtls_allow_list: '' +ns_qualifier: AP-C-REQ-229 +openid_audience: ap-gw-31a33-default-dev +openid_issuer: https://sdx-authz-apps-gov-bc-ca-lab.apps.gov.bc.ca/auth/realms/sdx +openid_scope: '' +pattern: sdx-p2p-consumer-r1 +route_host: sdx.gov.bc.ca +route_path: /DEV/MIN/PUKI/TOYS +service_name: AP-C-REQ-229-TOYS +upstream_uri: https://ministryofpuppiesandkittens.xyz +*/ diff --git a/src/services/sdx/member-id.ts b/src/services/sdx/member-id.ts new file mode 100644 index 000000000..c1b46c57d --- /dev/null +++ b/src/services/sdx/member-id.ts @@ -0,0 +1,14 @@ +export async function LookupMemberOrganization(id: string) { + const member = await fetch( + 'https://sdx-beta-api-gov-bc-ca-lab.dev.api.gov.bc.ca/api/rd/members', + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + const memberData = await member.json(); + return memberData.filter((member: any) => member.id === id).pop(); +} diff --git a/src/services/sdx/sdx-catalog.ts b/src/services/sdx/sdx-catalog.ts new file mode 100644 index 000000000..6e27468a4 --- /dev/null +++ b/src/services/sdx/sdx-catalog.ts @@ -0,0 +1,213 @@ +import YAML from 'yaml'; +import { gql } from 'graphql-request'; +import { Environment } from '../keystone/types'; +import { NamespaceService } from '../org-groups'; +import { OrgNamespace } from '../org-groups/types'; +import { getGwaProductEnvironment } from '../workflow'; +import { dynamicallySetEnvironmentDetails } from '../keystone'; +import { LookupMemberOrganization } from './member-id'; +import { LookupEdgeServer } from './edge-servers'; +import { id } from 'date-fns/locale'; + +export interface CatalogEntry { + id: string; + locator: string; + product: { + name: string; + type: string; + namespace: string; + }; + organization: { + name: string; + orgUnit?: string; + trustJwksEndpoint?: string; + }; + gateway: { + name: string; + permissions: { + dataPlane: string[]; + domains: string[]; + }; + }; + edgeServer: { + id: string; + host: string; + dn: string; + endpoint: string; + internal_endpoint: string; + }; + hasSpec: boolean; +} + +export async function GetCatalog(ctx: any): Promise { + const result = await ctx.executeGraphQL({ + context: ctx, + query: list, + }); + const envs = result.data.allEnvironments.filter( + (e: Environment) => e.product.organization != null + ); + + const output = envs.map((env: any) => { + //const spec = env.spec?.blob ? parseSpec(env.spec?.blob) : undefined; + const spec: any = undefined; + + if (env.credentialIssuer != null) { + const envDetails = JSON.parse( + dynamicallySetEnvironmentDetails(env.credentialIssuer) + ); + const credEnv = envDetails.find((e: any) => e.environment === env.name); + + env.credentialIssuer = { + issuerUrl: credEnv?.issuerUrl, + clientId: credEnv?.clientId, + }; + } + + const catalogEntry = { + id: env.appId, + environment: env.name, + product: { + name: env.product.name, + }, + organization: { + name: env.product.organization.name, + orgUnit: env.product.organization.orgUnit || undefined, + }, + gateway: { + name: env.product.namespace, + }, + hasSpec: env.spec?.id ? true : false, + }; + return catalogEntry; + }); + + const prodEnv = await getGwaProductEnvironment(ctx, false); + const envConfig = prodEnv.issuerEnvConfig; + + const svc = new NamespaceService(envConfig.issuerUrl); + await svc.login(envConfig.clientId, envConfig.clientSecret); + + const promises = output + .filter((env: any) => env.gateway.name) + .map(async (env: any) => { + const member = await LookupMemberOrganization(env.organization.name); + env.locator = [ + `${env.environment.toUpperCase()}`, + `${member.member_class}`, + `${member.member_id}`, + `${env.product.name}`, + ] + .join('.') + .toUpperCase(); + env.organization.trustJwksEndpoint = member.trust_jwks_endpoint; + + const nsAttributes = await getNamespaceAttributes(svc, env.gateway.name); + env.gateway.permissions = { + dataPlane: nsAttributes.permDataPlane, + domains: nsAttributes.permDomains, + }; + + const edgeServer = await LookupEdgeServer( + env.gateway.permissions.domains[0] + ); + env.edgeServer = { + id: edgeServer.id, + host: edgeServer.host, + dn: edgeServer.dn, + endpoint: edgeServer.endpoint, + internal_endpoint: edgeServer.internal_endpoint, + }; + }); + await Promise.all(promises); + return output; +} + +async function parseSpec(specBlob: string) { + const spec = YAML.parse(specBlob); + + const operations = + spec?.paths && + Object.keys(spec.paths).map((path) => { + return Object.keys(spec.paths[path]).map((method) => { + const op = spec.paths[path][method]; + return { + operationId: op.operationId, + method: method.toUpperCase(), + path, + summary: op.summary || '', + scopes: + op.security && op.security[0] && op.security[0]['bearer_auth'] + ? op.security[0]['bearer_auth'] + : [], + }; + }); + }); + + const flattenedOperations = []; + if (operations) { + for (const opList of operations) { + for (const op of opList) { + flattenedOperations.push(op); + } + } + } +} + +async function getNamespaceAttributes( + svc: NamespaceService, + ns: string +): Promise { + return await svc.getNamespaceOrganizationDetails(ns); +} + +const list = gql` + query OrgProductCatalog { + allEnvironments { + appId + name + spec { + id + blob + } + credentialIssuer { + name + clientId + inheritFrom { + environmentDetails + } + } + product { + name + type + namespace + organization { + name + } + } + } + } +`; + +/* + return { + appId: env.appId, + name: env.name, + spec: { + title: spec.info?.title || '', + version: spec.info?.version || '', + summary: spec.info?.summary || '', + description: spec.info?.description || '', + operations: flattenedOperations, + }, + credentialIssuer: env.credentialIssuer, + product: { + name: env.product.name, + type: env.product.type, + namespace: env.product.namespace, + organization: { + name: env.product.organization.name, + }, + }, + }; +*/ diff --git a/src/services/workflow/api-specs.ts b/src/services/workflow/api-specs.ts new file mode 100644 index 000000000..d409ce965 --- /dev/null +++ b/src/services/workflow/api-specs.ts @@ -0,0 +1,127 @@ +import { FieldErrors, ValidateError } from 'tsoa'; +import { Logger } from '../../logger'; +import YAML from 'js-yaml'; +import { strict as assert } from 'assert'; +import { id } from 'date-fns/locale'; +import { gql } from 'graphql-request'; + +const logger = Logger('wf.UpdAPISpec'); + +async function UpdateAPISpec( + ctx: any, + specUrl: string, + productEnvAppId: string +) { + // Fetch the specUrl and parse YAML + const response = await fetch(specUrl); + if (!response.ok) { + throw new ValidateError({}, `Failed to fetch spec from URL: ${specUrl}`); + } + const yamlText = await response.text(); + let parsedYaml: any; + try { + parsedYaml = YAML.load(yamlText); + } catch (err) { + logger.error('YAML parsing error: %j', err); + throw new ValidateError({}, 'Invalid YAML format'); + } + + const blobRef = `${productEnvAppId}-APISPEC-${parsedYaml.info.version}`; + + const blobExists = await ctx.executeGraphQL({ + query: `query ($blobRef: String!) { + allEnvironments (where: { appId: "${productEnvAppId}"}) { + id + } + + allBlobs (where: { ref: $blobRef}) { + id + } + }`, + variables: { appId: productEnvAppId, blobRef }, + }); + + assert(!blobExists.errors, 'Unable to delete existing Blob'); + + const variables = { + id: blobExists.data.allEnvironments[0].id, + blobRef, + blobType: 'yaml', + blob: YAML.dump(parsedYaml), + }; + + if (blobExists.data.allBlobs.length == 1) { + const result = await ctx.executeGraphQL({ + query: `mutation ($id: String!) { + deleteBlob (id: $id) { + id + } + }`, + variables: { id: blobExists.data.allBlobs[0].id }, + }); + assert(!result.errors, 'Unable to delete existing Blob'); + } + + const result = await ctx.executeGraphQL({ + query: `mutation ($id: String, $oldBlobId: String, $blob: String, $blobType: String, $blobRef: String) { + updateEnvironment (id: $id, data: { + spec: { + create: { + ref: $blobRef, + type: $blobType, + blob: $blob + } + } + }) { + id + spec { + id + } + } + }`, + variables, + }); + + if (result.errors) { + const errors: FieldErrors = {}; + result.errors.forEach((err: any, ind: number) => { + errors[`d${ind}`] = { message: err.message }; + }); + logger.error('%j', result); + throw new ValidateError(errors, 'Unable to update API Specification'); + } + return { + id: result.data.updateEnvironment.id, + spec: { + id: result.data.updateEnvironment.spec.id, + }, + }; +} + +async function GetAPISpecsByOrg(ctx: any, org: string) { + const query = gql` + query getProductEnvironments($org: String) { + allEnvironments(where: { product: { organization: { name: $org } } }) { + id + appId + spec { + id + blob + } + } + } + `; + + const specs = await ctx.executeGraphQL({ + query, + variables: { org }, + }); + if (specs.errors) { + logger.error('Error fetching Specs %j', specs.errors); + throw new Error('Error fetching Specs'); + } + return specs.data.allEnvironments + .filter((env: any) => env.spec) + .map((env: any) => ({ prodEnvId: env.appId, spec: env.spec })); +} +export { UpdateAPISpec, GetAPISpecsByOrg }; diff --git a/src/services/workflow/apply.ts b/src/services/workflow/apply.ts index 752ef9720..b8684db02 100644 --- a/src/services/workflow/apply.ts +++ b/src/services/workflow/apply.ts @@ -346,7 +346,7 @@ async function setupAuthorizationAndEnable( : issuerEnvConfig.initialAccessToken; const controls: RequestControls = { - ...{ defaultClientScopes: [] }, + ...{ defaultClientScopes: [], optionalClientScopes: [] }, ...setup.controls, }; @@ -362,11 +362,14 @@ async function setupAuthorizationAndEnable( issuerEnvConfig.clientId, issuerEnvConfig.clientSecret ); - const clientScopes = controls.defaultClientScopes; + + const optionalClientScopes = controls.optionalClientScopes; + + const defaultClientScopes = controls.defaultClientScopes; if (controls.roles) { - clientScopes.push('roles'); + defaultClientScopes.push('roles'); } - await kcClientService.syncAndApply(clientId, clientScopes, []); + await kcClientService.syncAndApply(clientId, defaultClientScopes, optionalClientScopes); if (controls.roles) { const clientRolesService = new KeycloakClientRolesService( diff --git a/src/services/workflow/client-credentials.ts b/src/services/workflow/client-credentials.ts index 8830a3192..8e29696d2 100644 --- a/src/services/workflow/client-credentials.ts +++ b/src/services/workflow/client-credentials.ts @@ -19,6 +19,9 @@ import { } from './types'; import { ClientAuthenticator } from '../keycloak/client-registration-service'; import { genClientId } from './client-shared-idp'; +import { Logger } from '../../logger'; + +const logger = Logger('wf.ClientCreds'); /** * Steps: @@ -77,22 +80,38 @@ export async function registerClient( // Find the Client ID for the ProductEnvironment - that will be used to associated the clientRoles - // lookup Application and use the ID to make sure a corresponding Consumer exists (1 -- 1) - const client = await new KeycloakClientRegistrationService( + const regService = new KeycloakClientRegistrationService( issuerEnvConfig.issuerUrl, openid.registration_endpoint, token - ).clientRegistration( + ); + + // lookup Application and use the ID to make sure a corresponding Consumer exists (1 -- 1) + const client = await regService.clientRegistration( issuer.clientAuthenticator, newClientId, + controls.clientName || "", // if no client name provided, use the clientId uuidv4(), controls.clientCertificate, + controls.subjectDn, controls.jwksUrl, clientMappers, false ); assert.strictEqual(client.clientId, newClientId); + if (issuer.clientAuthenticator === "client-certificate") { + logger.warn("Workaround to set standard.token.exchange.enabled for client-certificate - not setting on creation"); + regService.updateClientRegistration(newClientId, { + clientId: newClientId, + attributes: { + "standard.token.exchange.enabled": 'true', + "tls.client.certificate.bound.access.tokens": 'true', + //"dpop.bound.access.tokens": 'true', + } + }) + } + return { openid, client, diff --git a/src/services/workflow/client-shared-idp.ts b/src/services/workflow/client-shared-idp.ts index 984ee1b1e..c06a1cca5 100644 --- a/src/services/workflow/client-shared-idp.ts +++ b/src/services/workflow/client-shared-idp.ts @@ -119,8 +119,10 @@ async function addClientToSharedIdP( ).clientRegistration( clientAuthenticator, clientId, + '', uuidv4(), controls.clientCertificate, + controls.subjectDn, controls.jwksUrl, clientMappers, true, diff --git a/src/services/workflow/generate-credential.ts b/src/services/workflow/generate-credential.ts index 3fb2466bd..18cca8b13 100644 --- a/src/services/workflow/generate-credential.ts +++ b/src/services/workflow/generate-credential.ts @@ -204,6 +204,7 @@ export const generateCredential = async ( tokenEndpoint: newClient.openid.token_endpoint, clientPublicKey: clientSigning.publicKey, clientPrivateKey: clientSigning.privateKey, + subjectDn: controls.subjectDn ? controls.subjectDn : null, } as NewCredential; } return null; diff --git a/src/services/workflow/get-namespaces.ts b/src/services/workflow/get-namespaces.ts index 4132462b9..b4e9fcb02 100644 --- a/src/services/workflow/get-namespaces.ts +++ b/src/services/workflow/get-namespaces.ts @@ -31,6 +31,8 @@ import { import getSubjectToken from '../../auth/auth-token'; import { Logger } from '../../logger'; +import { NamespaceService } from '../org-groups'; +import { OrgNamespace } from '../org-groups/types'; const logger = Logger('wf.getns'); @@ -46,6 +48,17 @@ export async function getGwaProductEnvironment( return getEnvironmentContext(context, prodEnvId, {}, withSubject); } +export async function getOrgNamespaces( + org: string, + envCtx: EnvironmentContext +): Promise { + const envConfig = envCtx.issuerEnvConfig; + + const svc = new NamespaceService(envConfig.issuerUrl); + await svc.login(envConfig.clientId, envConfig.clientSecret); + return svc.listAssignedNamespacesByOrg(org); +} + export async function getMyNamespaces( envCtx: EnvironmentContext ): Promise { diff --git a/src/services/workflow/org-access-request.ts b/src/services/workflow/org-access-request.ts new file mode 100644 index 000000000..fa5a384a7 --- /dev/null +++ b/src/services/workflow/org-access-request.ts @@ -0,0 +1,206 @@ +import { strict as assert } from 'assert'; +import { Logger } from '../../logger'; +import { + lookupCredentialIssuerById, + lookupEnvironmentAndIssuerById, + lookupProduct, + lookupProductEnvironmentServicesBySlug, +} from '../keystone'; +import { + addAccessRequest, + collectCredentials, + getAccessRequest, + getAccessRequestsByNamespace, +} from '../keystone/access-request'; +import { + createApplication, + lookupApplicationsByNamespaces, +} from '../keystone/application'; +import { AccessRequest, Application, Environment } from '../keystone/types'; +import { saveConsumerLabels } from './consumer-management'; +import { getGwaProductEnvironment, getOrgNamespaces } from './get-namespaces'; +import { NewCredential } from './types'; + +const logger = Logger('wf.OrgAccessReq'); + +export const OrgAccessRequestCreate = async ( + context: any, + org: string, + orgMemberID: string, + userId: string, + consumerProdEnvAppId: string, + providerProdEnvAppId: string, + businessProcess: string, + accessPointDN: string, + optionalClientScopes: string[] +): Promise<{ + application: Application; + providerProdEnv: Environment; + accessRequest: AccessRequest; + credential: NewCredential; +}> => { + try { + // get list of namespaces for this org + const prodEnv = await getGwaProductEnvironment(context, false); + const nsList = await getOrgNamespaces(org, prodEnv); + + // get the consumer product environment details + const consumerProdEnv = await lookupProductEnvironmentServicesBySlug( + context, + consumerProdEnvAppId + ); + + assert( + nsList.filter((ns) => ns.name === consumerProdEnv.product.namespace) + .length === 1, + `Consumer Product Environment ${consumerProdEnvAppId} not found` + ); + + // create the application if it does not exist + const app = { + appId: `sdx${consumerProdEnv.appId}`, + name: `${consumerProdEnv.product.name} ${consumerProdEnv.name}`, + description: `SDX Resource Locator: ${formatResourceLocator( + orgMemberID, + consumerProdEnv + )} (Gateway ID ${consumerProdEnv.product.namespace})`, + owner: { id: userId }, + namespace: consumerProdEnv.product.namespace, + } as Application; + + const appId = await UpsertApplication(context, app); + logger.debug('App ID: %s', appId); + + // get the provider product environment details + const providerProdEnv = await lookupProductEnvironmentServicesBySlug( + context, + providerProdEnvAppId + ); + + // get the provider credential issuer details + const providerCredIssuer = await lookupCredentialIssuerById( + context, + providerProdEnv.credentialIssuer.id + ); + providerProdEnv.credentialIssuer = providerCredIssuer; + + // const clientName = `${formatResourceLocator( + // orgMemberID, + // consumerProdEnv + // )} TO ${formatResourceLocator(orgMemberID, providerProdEnv)}`; + const clientName = `${consumerProdEnv.product.name} from ${orgMemberID}`; + + // prepare the access request + const controls = { + clientName, + subjectDn: accessPointDN, + //defaultClientScopes: [], + optionalClientScopes, + }; + + const accessRequestData = { + acceptLegal: false, + additionalDetails: 'here is some additional details', + controls: JSON.stringify(controls), + name: clientName, + applicationId: appId, + productEnvironmentId: providerProdEnv.id, + requestor: userId, + } as any; + + // create the access request + const accessRequestCreated = await addAccessRequest( + context, + accessRequestData + ); + + // collect the credentials + const creds = await collectCredentials(context, accessRequestCreated.id); + const credDetails = JSON.parse(creds.credential); + + // get the latest details of the access request + const accessRequest = await getAccessRequest( + context, + accessRequestCreated.id + ); + + // add some standard labels to the consumer + const labels = [ + { + labelGroup: 'sdx-res-locator', + values: [formatResourceLocator(orgMemberID, consumerProdEnv)], + }, + { labelGroup: 'sdx-member', values: [orgMemberID] }, + ]; + + if (businessProcess) { + labels.push({ labelGroup: 'purpose', values: [businessProcess] }); + } + + await saveConsumerLabels( + context, + providerProdEnv.product.namespace, + accessRequest.serviceAccess.consumer.id, + labels + ); + + return { + application: app, + providerProdEnv, + accessRequest, + credential: credDetails, + }; + } catch (error) { + logger.error('OrgAccessRequestCreate error: %s', error?.message || error); + throw error; + } +}; + +const UpsertApplication = async ( + context: any, + application: Application +): Promise => { + const ns = application.namespace; + const apps = await lookupApplicationsByNamespaces(context, [ns]); + if (apps.filter((a) => a.appId === application.appId).length > 0) { + logger.debug(`Application ${application.appId} already exists`); + return apps.find((a) => a.appId === application.appId).id; + } else { + const app = await createApplication(context, { + appId: application.appId, + name: application.name, + description: application.description, + ownerId: application.owner?.id, + namespace: application.namespace, + }); + return app.id; + } +}; + +const checkAccessRequestExists = async ( + context: any, + namespace: string, + applicationId: string, + productEnvironmentId: string +): Promise => { + const accessRequests = await getAccessRequestsByNamespace(context, [ + namespace, + ]); + return ( + accessRequests.filter( + (ar) => + ar.application.id === applicationId && + ar.productEnvironment.id === productEnvironmentId + ).length > 0 + ); +}; + +const formatResourceLocator = ( + orgMemberID: string, + serviceProdEnv: Environment +): string => { + const env = serviceProdEnv.name.toUpperCase(); + const serviceId = serviceProdEnv.product.name; + + return `/${env}/${orgMemberID}/${serviceId}`; +}; diff --git a/src/services/workflow/types.ts b/src/services/workflow/types.ts index a00f452a9..0998daed9 100644 --- a/src/services/workflow/types.ts +++ b/src/services/workflow/types.ts @@ -20,6 +20,7 @@ export interface NewCredential { apiKey?: string; clientPublicKey?: string; clientPrivateKey?: string; + subjectDn?: string; // Subject DN for the client certificate, } export interface CredentialReference { @@ -44,13 +45,15 @@ export interface SubjectIdentity { email?: string; } export interface RequestControls { + clientName?: string; defaultClientScopes?: string[]; - defaultOptionalScopes?: string[]; + optionalClientScopes?: string[]; roles?: string[]; aclGroups?: string[]; plugins?: ConsumerPlugin[]; clientCertificate?: string; clientGenCertificate?: boolean; + subjectDn?: string; // Subject DN for the client certificate jwksUrl?: string; subject?: SubjectIdentity; } @@ -214,3 +217,14 @@ export interface ActivitySummary { activityAt: Scalars['DateTime']; blob?: any; } + +export interface OrgAccessRequestCreateInput { + org: string; + orgMemberId: string; + userId: string; + consumerProductEnvAppId: string; + providerProductEnvAppId: string; + businessProcess: string; + accessPointDN: string; + optionalClientScopes: string[]; +} \ No newline at end of file diff --git a/src/services/workflow/validate-access-request.ts b/src/services/workflow/validate-access-request.ts index ab92f5291..06a849d61 100644 --- a/src/services/workflow/validate-access-request.ts +++ b/src/services/workflow/validate-access-request.ts @@ -90,12 +90,15 @@ export const Validate = async ( ); // assert that either the Product is Active or it belongs to the authorized Subject gateway - assert.strictEqual( - prodEnv.active === true || - prodEnv.product.namespace === context.authedItem.namespace, - true, - 'Product not elligible for requesting access' - ); + // SDX : Temporarily remove this check + // as it will require a bit of thought on where the "namespace" comes from + // this is more of an entitlement check + // assert.strictEqual( + // prodEnv.active === true || + // prodEnv.product.namespace === context.authedItem.namespace, + // true, + // 'Product not elligible for requesting access' + // ); // assert that the Consumer does not already exist const application = await lookupMyApplicationsById( diff --git a/src/test/integrated/keystonejs/accessRequest.ts b/src/test/integrated/keystonejs/accessRequest.ts index 0ecade6ae..06a4a0789 100644 --- a/src/test/integrated/keystonejs/accessRequest.ts +++ b/src/test/integrated/keystonejs/accessRequest.ts @@ -4,25 +4,74 @@ To run: npm run ts-build npm run ts-watch node dist/test/integrated/keystonejs/accessRequest.js + + +NEEDS: + +kubectl port-forward -n 1d4461-prod service/patroni-spilo 15432:5432 & + +export ADAPTER=knex +export KNEX_DATABASE=keystonejs +export KNEX_HOST=localhost +export KNEX_PORT=15432 +export KNEX_USER=keystonejsuser +export KNEX_PASSWORD= + + +export KONG_URL="https://kong-admin-api-1d4461-prod.apps.silver.devops.gov.bc.ca" + +kubectl port-forward -n 1d4461-prod service/bcgov-aps-portal-feeder-generic-api 6767:80 & + +export FEEDER_URL=http://localhost:6767 + + +// userId is needed for Legal +// namespace has to match requesting product if not published + */ import InitKeystone from './init'; import { o } from '../util'; -import { getOpenAccessRequestsByConsumer } from '../../../services/keystone/access-request'; +import { + addAccessRequest, + collectCredentials, + getAccessRequest, + getAccessRequestsByNamespace, +} from '../../../services/keystone/access-request'; +import { + createApplication, + lookupApplicationsByNamespaces, +} from '../../../services/keystone/application'; +import { + revokeAllConsumerAccess, + saveConsumerLabels, +} from '../../../services/workflow'; +import { + getGwaProductEnvironment, + getOrgNamespaces, +} from '../../../services/workflow/get-namespaces'; +import { deleteRecord, getRecords, replaceKey, syncRecordsThrowErrors } from '../../../batch/feed-worker'; +import { OrgAccessRequestCreate } from '../../../services/workflow/org-access-request'; +import { OrgAccessRequestCreateInput } from '../../../services/workflow/types'; +import { lookupServiceAccessesByNamespace } from '../../../services/keystone'; (async () => { const keystone = await InitKeystone(); - const ns = 'gw-0dcd7'; - const skipAccessControl = false; +// const ns = 'gw-84b1f'; + const ns = 'gw-31a33'; + const skipAccessControl = true; + + const userId = '12'; const identity = { id: null, username: 'sample_username', + name: 'SampleF UserL', namespace: ns, roles: JSON.stringify(['api-owner']), scopes: [], - userId: null, + userId, } as any; const ctx = keystone.createContext({ @@ -30,14 +79,208 @@ import { getOpenAccessRequestsByConsumer } from '../../../services/keystone/acce authentication: { item: identity }, }); - // o(await getOrganizations(ctx)); + if (true) { + const result = await lookupServiceAccessesByNamespace(ctx, ns); + o(result); + + + + // const res = await deleteRecord(ctx, 'GatewayConsumer', 'b5f06ded-0c3d-4cb7-802a-ed3c03d5cbf8'); + // o(res); + // const revoke = await revokeAllConsumerAccess(ctx, ns, request.serviceAccess.id); + // o(revoke); + } + + if (true) { + const result = await ctx.executeGraphQL({ + query: ` + mutation OrgCreateAccessRequest ($data: OrgAccessRequestCreateInput) { + orgCreateAccessRequest (data: $data) { + application { + appId + } + accessRequest { + id + name + status + productEnvironment { + id + name + product { + id + name + namespace + } + } + application { + id + name + namespace + } + serviceAccess { + id + name + consumer { + id + name + namespace + } + } + } + } + } + `, + variables: { + data: { + org: 'ministry-of-puppies-and-kittens', + orgMemberId: 'MIN/PUKI', + userId, + consumerProductEnvAppId: 'E7FEB796', + providerProductEnvAppId: '1400BE49', + businessProcess: 'Vet Services', + accessPointDN: 'CN=sdx.gov.bc.ca', + optionalClientScopes: ['user/Test2'], + } as OrgAccessRequestCreateInput, + }, + }); + o(result); + + /* +{ + "org": "ministry-of-puppies-and-kittens", + "orgMemberId": "MIN/PUKI", + "userId": "12", + "consumerProductEnvAppId": "E7FEB796", + "providerProductEnvAppId": "1400BE49", + "businessProcess": "Vet Services", + "accessPointDN": "CN=sdx.gov.bc.ca", + "optionalClientScopes": [ + "user/Test2" + ] +} + */ + + } + + if (false) { + // 424C7EB5 SDX-WORKING-API (dev) + // 38D0FED9 SDX-SAMPLE-API (prod) + // + const result = await OrgAccessRequestCreate( + ctx, + 'ministry-of-citizens-services', + 'MIN/CITZ', + userId, + '424C7EB5', + '7A031F2A', + 'SDX Onboarding', + 'CN=abcd', + ['user/Test2'] + ); + o(result); + } + + if (false) { + // o(await getOrganizations(ctx)); + const app = await createApplication(ctx, { + name: 'App y ' + new Date().toISOString(), + description: 'App Desc', + ownerId: userId, + namespace: ns, + }); + + const controls = { + clientName: app.name, + subjectDn: 'CN=my-site', + //defaultClientScopes: [], + optionalClientScopes: ['user/Test1'], + }; + + const accessRequestData = { + acceptLegal: false, + additionalDetails: 'here is some additional details', + controls: JSON.stringify(controls), + name: 'Sampler API FOR Cope, Aidan CITZ:EX', + productEnvironmentId: '13', + requestor: userId, + } as any; + + accessRequestData.applicationId = app.id; + + const result = await addAccessRequest(ctx, accessRequestData); + o(result); + + const creds = await collectCredentials(ctx, result.id); + const credDetails = JSON.parse(creds.credential); + o(credDetails); + + const request = await getAccessRequest(ctx, result.id); + o(request); + + const labels = [ + { labelGroup: 'sdx-member', values: ['/MIN/CITZ'] }, + { labelGroup: 'sdx-res-locator', values: ['/LAB/MIN/CITZ/MYSVC-API'] }, + { labelGroup: 'application', values: [app.name] }, + ]; + + await saveConsumerLabels( + ctx, + ns, + request.serviceAccess.consumer.id, + labels + ); + + // const revoke = await revokeAllConsumerAccess(ctx, ns, request.serviceAccess.id); + // o(revoke); + + // const revoke = await deleteServiceAccess(ctx, request.serviceAccess.id); + // o(revoke); + } + + if (false) { + const org = 'ministry-of-citizens-services'; + const prodEnv = await getGwaProductEnvironment(ctx, false); + + const nsList = await getOrgNamespaces(org, prodEnv); + o(nsList); + + const apps = await lookupApplicationsByNamespaces(ctx, [ns]); + o(apps); + + const result = await getAccessRequestsByNamespace( + ctx, + nsList.map((n) => n.name) + ); + const recs = result.map((o) => replaceKey(o, 'gatewayId', 'namespace')); + o(recs); + + // const batchClause = { + // query: '$org: String', + // clause: '{ organization: { name: $org } }', + // variables: { org }, + // }; + + // const records = await getRecords( + // ctx, + // 'Product', + // undefined, + // ['environments'], + // batchClause + // ); + // o(records); + } + + if (false) { + const result = await getAccessRequestsByNamespace(ctx, [ns]); + o(result); + } - const serviceAccess = await getOpenAccessRequestsByConsumer( - ctx, - ns, - '653860ee26683257394cfe3c' - ); - o(serviceAccess); + // const serviceAccess = await getOpenAccessRequestsByConsumer( + // ctx, + // ns, + // '653860ee26683257394cfe3c' + // ); + // o(serviceAccess); await keystone.disconnect(); })(); diff --git a/src/test/integrated/keystonejs/batch.ts b/src/test/integrated/keystonejs/batch.ts index ccd982df4..0bc3518e8 100644 --- a/src/test/integrated/keystonejs/batch.ts +++ b/src/test/integrated/keystonejs/batch.ts @@ -13,6 +13,7 @@ import { transformAllRefID, removeEmpty, removeKeys, + syncRecordsThrowErrors, } from '../../../batch/feed-worker'; import { o } from '../util'; import { BatchService } from '../../../services/keystone/batch-service'; @@ -22,7 +23,7 @@ import { BatchService } from '../../../services/keystone/batch-service'; console.log('K = ' + keystone); const ns = 'platform'; - const skipAccessControl = false; + const skipAccessControl = true; const identity = { id: null, @@ -97,10 +98,35 @@ import { BatchService } from '../../../services/keystone/batch-service'; const res = await bapi.lookup( 'allOrganizations', - 'orgUnits.name', - 'heritage', - [] + 'name', + 'ministry-of-citizens-services', + ['extForeignKey'] ); - o(res); + const id = res.extForeignKey; + const out = await syncRecordsThrowErrors( + ctx, + 'Organization', + id, + { + description: 'Updated desc 2', + extForeignKey: id, + orgUnits: [ + { + name: 'new-unit', + title: 'New Unity', + extForeignKey: '00001-new-unit', + // extSource: 'custom', + // extRecordHash: '1234', + // description: 'Newly created unit', + // tags: ['tag1', 'tag2'], + } + ] + } + , + true + ) + + + o(out); await keystone.disconnect(); })(); diff --git a/src/test/integrated/keystonejs/init.ts b/src/test/integrated/keystonejs/init.ts index e69f7e11e..2555c401c 100644 --- a/src/test/integrated/keystonejs/init.ts +++ b/src/test/integrated/keystonejs/init.ts @@ -106,6 +106,7 @@ export default async function InitKeystone( 'CredentialIssuerExt', 'Namespace', 'NamespaceActivity', + 'OrgAccessRequest', 'OrganizationPolicy', 'ServiceAccess', 'ServiceAccount', diff --git a/src/test/integrated/keystonejs/product-apispec.ts b/src/test/integrated/keystonejs/product-apispec.ts new file mode 100644 index 000000000..a0d919589 --- /dev/null +++ b/src/test/integrated/keystonejs/product-apispec.ts @@ -0,0 +1,236 @@ +/* +Wire up directly with Keycloak and use the Services +To run: +npm run ts-build +npm run ts-watch +node dist/test/integrated/keystonejs/product-apispec.js +*/ + +import InitKeystone from './init'; +import { + getRecords, + deleteRecord, + parseJsonString, + transformAllRefID, + removeEmpty, + removeKeys, + syncRecords, + parseBlobString, +} from '../../../batch/feed-worker'; +import { o } from '../util'; +import { dynamicallySetEnvironmentDetails, lookupServiceAccessesByEnvironment } from '../../../services/keystone'; +import { + getActivity, + recordActivity, + recordActivityWithBlob, +} from '../../../services/keystone/activity'; +import { id } from 'date-fns/locale'; +import { + UpdateAPISpec, + GetAPISpecsByOrg, +} from '../../../services/workflow/api-specs'; +import { Environment } from '../../../services/keystone/types'; +import { gql } from 'graphql-request'; +import YAML from 'yaml'; +import { OrgNamespace } from '../../../services/org-groups/types'; +import { getGwaProductEnvironment } from '../../../services/workflow'; +import { NamespaceService } from '../../../services/org-groups'; + +async function getNamespaceAttributes( + ctx: any, + ns: string +): Promise { + const prodEnv = await getGwaProductEnvironment(ctx, false); + const envConfig = prodEnv.issuerEnvConfig; + + const svc = new NamespaceService(envConfig.issuerUrl); + await svc.login(envConfig.clientId, envConfig.clientSecret); + return await svc.getNamespaceOrganizationDetails(ns); +} + +(async () => { + const keystone = await InitKeystone(); + console.log('K = ' + keystone); + + const ns = 'gw-31a33'; + const skipAccessControl = true; + + const userId = '12'; + + const identity = { + id: null, + username: 'sample_username', + name: 'SampleF UserL', + namespace: ns, + roles: JSON.stringify(['api-owner']), + scopes: [], + userId, + } as any; + + const ctx = keystone.createContext({ + skipAccessControl, + authentication: { item: identity }, + }); + + if (true) { + const list = gql` + query OrgProductCatalog { + allEnvironments { + appId + name + spec { + blob + } + credentialIssuer { + name + clientId + inheritFrom { + environmentDetails + } + } + product { + name + namespace + organization { + name + } + } + } + } + `; + + const result = await keystone.executeGraphQL({ + context: ctx, + query: list, + }); + o(result); + const envs = result.data.allEnvironments.filter( + (e: Environment) => e.product.organization != null + ); + + const output = envs.map((env: any) => { + if (env.credentialIssuer != null) { + const envDetails = JSON.parse(dynamicallySetEnvironmentDetails(env.credentialIssuer)); + const credEnv = envDetails.find((e: any) => e.environment === env.name); + o(env) + env.credentialIssuer = { + issuerUrl: credEnv?.issuerUrl, + clientId: credEnv?.clientId, + } + o(env) + } + }); + + // o(env.credentialIssuer?.environmentDetails) + // return + // const envs = JSON.parse(env.credentialIssuer?.environmentDetails); + + // const issuerEnv = envs?.find((e: any) => e.environment === env.name); + // o(issuerEnv); + // return { + // appId: env.appId, + // name: env.name, + // credentialIssuer: { + // issuer: issuerEnv.issuerUrl, + // clientId: issuerEnv.clientId, + // }, + // product: { + // name: env.product.name, + // namespace: env.product.namespace, + // organization: { + // name: env.product.organization.name, + // }, + // }, + // }; + // }); + // o(output); + } + + if (false) { + const result = await GetAPISpecsByOrg( + ctx, + 'ministry-of-puppies-and-kittens' + ); + o(result); + } + if (false) { + const spec = 'https://bcgov.github.io/sdx-openapi/%3CService%3E.v1.yaml'; + const result = await UpdateAPISpec(ctx, spec, 'E7FEB796'); + o(result); + } + + if (false) { + // upgrade + + const variables = { + id: '20', + namespace: ns, + blobRef: 'B1678A2ADDD0-APISPEC-v1', + blobType: 'yaml', + blob: `gateway: gw-31a33 +patterns: + - name: pattern-1 + description: Pattern 1 + apis: + `, + }; + + const blobExists = await ctx.executeGraphQL({ + query: `query ($blobRef: String!) { + allBlobs (where: { ref: $blobRef}) { + id + } + }`, + variables, + }); + o(blobExists); + if (blobExists.data.allBlobs.length == 1) { + const result = await ctx.executeGraphQL({ + query: `mutation ($id: String!) { + deleteBlob (id: $id) { + id + } + }`, + variables: { id: blobExists.data.allBlobs[0].id }, + }); + o(result); + } + + const result = await ctx.executeGraphQL({ + query: `mutation ($id: String, $oldBlobId: String, $blob: String, $blobType: String, $blobRef: String) { + updateEnvironment (id: $id, data: { + spec: { + create: { + ref: $blobRef, + type: $blobType, + blob: $blob + } + } + }) { + id + spec { + id + } + } + }`, + variables, + }); + o(result); + + const getSpec = await ctx.executeGraphQL({ + query: `query ($blobRef: String!) { + allEnvironments (where: { spec: { ref: $blobRef}}) { + id + spec { + blob + } + } + }`, + variables, + }); + o(getSpec); + console.log(getSpec.data.allEnvironments[0].spec.blob); + } + + await keystone.disconnect(); +})(); diff --git a/src/test/integrated/sdx/getcatalog.ts b/src/test/integrated/sdx/getcatalog.ts new file mode 100644 index 000000000..34d999015 --- /dev/null +++ b/src/test/integrated/sdx/getcatalog.ts @@ -0,0 +1,73 @@ +/* +Wire up directly with Keycloak and use the Services +To run: +npm run ts-build +npm run ts-watch +node dist/test/integrated/sdx/getcatalog.js +*/ + +import { o } from '../util'; +import InitKeystone from '../keystonejs/init'; +import { GetCatalog } from '../../../services/sdx/sdx-catalog'; +import { GetConfigUsingPattern } from '../../../services/sdx/gateway-patterns'; + +(async () => { + const keystone = await InitKeystone(); + console.log('K = ' + keystone); + + const ns = 'platform'; + const skipAccessControl = false; + + const identity = { + id: null, + username: 'sample_username', + namespace: ns, + roles: JSON.stringify(['api-owner']), + scopes: [], + userId: null, + } as any; + + const ctx = keystone.createContext({ + skipAccessControl, + authentication: { item: identity }, + }); + + // o(await getOrganizations(ctx)); + + o(await GetCatalog(ctx)); + + // o( + // await GetConfigUsingPattern(ctx, { + // pattern: 'sdx-keys-r1', + // locator: 'DEV.MIN.CITZ.SINGLE-DIGITAL-GW', + // parameters: { + // public_key_pem: 'sample-public-key-pem', + // }, + // }) + // ); + + o( + await GetConfigUsingPattern(ctx, { + pattern: 'sdx-p2p-consumer-pub-r1', + parameters: { + req_id: '12345', + consumer: 'DEV.MIN.CITZ.SINGLE-DIGITAL-GW', + provider: 'DEV.MIN.PUKI.TOYS', + }, + }) + ); + + o( + await GetConfigUsingPattern(ctx, { + pattern: 'sdx-p2p-provider-pub-r1', + parameters: { + req_id: '12345', + consumer: 'DEV.MIN.CITZ.SINGLE-DIGITAL-GW', + provider: 'DEV.MIN.PUKI.TOYS', + upstream_uri: 'https://httpbun.com', + }, + }) + ); + + await keystone.disconnect(); +})(); diff --git a/src/test/integrated/workflow/namespace-activity.ts b/src/test/integrated/workflow/namespace-activity.ts index be3b24e65..7927ebe4b 100644 --- a/src/test/integrated/workflow/namespace-activity.ts +++ b/src/test/integrated/workflow/namespace-activity.ts @@ -9,7 +9,9 @@ node dist/test/integrated/workflow/namespace-activity.js import InitKeystone from '../keystonejs/init'; import { o } from '../util'; import { getFilteredNamespaceActivity } from '../../../services/workflow'; -import { ActivityQueryFilter } from '@/services/workflow/types'; +import { ActivityQueryFilter } from '../../../services/workflow/types'; +import { getAllNamespaces } from '../../../services/keycloak/namespace-details'; +import { getGwaProductEnvironment, getOrgNamespaces } from '../../../services/workflow/get-namespaces'; (async () => { const keystone = await InitKeystone(); @@ -39,6 +41,11 @@ import { ActivityQueryFilter } from '@/services/workflow/types'; const a = await getFilteredNamespaceActivity(ctx, ns, 20, 0, filter); o(a); + const prodEnv = await getGwaProductEnvironment(ctx, false); + + const nsList = await getOrgNamespaces("ministry-of-citizens-services", prodEnv); + o(nsList); + // 2022-09-13T16:47:09.367Z await keystone.disconnect(); })(); diff --git a/src/test/services/keycloak/client-registration.test.ts b/src/test/services/keycloak/client-registration.test.ts index 9922af236..b5485c555 100644 --- a/src/test/services/keycloak/client-registration.test.ts +++ b/src/test/services/keycloak/client-registration.test.ts @@ -14,8 +14,10 @@ describe('Keycloak Service', function () { const result = await regsvc.clientRegistration( ClientAuthenticator.ClientSecret, 'cid', + 'nm', 'csc', 'cert', + 'subdn', 'jwks', [], true @@ -32,8 +34,10 @@ describe('Keycloak Service', function () { const result = await regsvc.clientRegistration( ClientAuthenticator.ClientJWTwithJWKS, 'cid', + 'nm', 'csc', 'cert', + 'subdn', 'jwks', [], true