Skip to content

Commit

Permalink
feat(core): add installOpenapiValidationMiddleware
Browse files Browse the repository at this point in the history
This method allows us to re-use the API server's internal
mechanisms to configure the OpenAPI spec validation in
test cases where we cannot depend on the API server itself
due to circular dependencies.
So this method is designed to be used both by the API server
and the test cases at the same time.

Closes: hyperledger-cacti#847
Signed-off-by: Elena Izaguirre <e.izaguirre.equiza@accenture.com>
  • Loading branch information
elenaizaguirre committed Sep 7, 2021
1 parent e7b60a0 commit 186cd1b
Show file tree
Hide file tree
Showing 29 changed files with 6,334 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Optional } from "typescript-optional";
import { Express } from "express";
import { v4 as uuidv4 } from "uuid";

import OAS from "../../json/openapi.json";

import {
Logger,
Checks,
Expand Down Expand Up @@ -101,6 +103,10 @@ export class CarbonAccountingPlugin
this.instanceId = options.instanceId;
}

public getOpenApiSpec(): unknown {
return OAS;
}

async registerWebServices(app: Express): Promise<IWebServiceEndpoint[]> {
const webServices = await this.getOrCreateWebServices();
webServices.forEach((ws) => ws.registerExpress(app));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Server } from "http";
import type { Server as SecureServer } from "https";
import { Optional } from "typescript-optional";
import { Express } from "express";
import OAS from "../../json/openapi.json";
import {
Logger,
Checks,
Expand Down Expand Up @@ -78,6 +79,10 @@ export class SupplyChainCactusPlugin
this.instanceId = options.instanceId;
}

public getOpenApiSpec(): unknown {
return OAS;
}

async registerWebServices(app: Express): Promise<IWebServiceEndpoint[]> {
const webServices = await this.getOrCreateWebServices();
await Promise.all(webServices.map((ws) => ws.registerExpress(app)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import type {
SetObjectResponseV1,
} from "@hyperledger/cactus-core-api";

import OAS from "../json/openapi.json";

import { GetObjectEndpointV1 } from "./web-services/get-object-endpoint-v1";
import { SetObjectEndpointV1 } from "./web-services/set-object-endpoint-v1";
import { HasObjectEndpointV1 } from "./web-services/has-object-endpoint-v1";
Expand Down Expand Up @@ -76,6 +78,10 @@ export class PluginObjectStoreIpfs implements IPluginObjectStore {
this.log.info(`Created ${this.className}. InstanceID=${opts.instanceId}`);
}

public getOpenApiSpec(): unknown {
return OAS;
}

public async onPluginInit(): Promise<unknown> {
return; // no-op
}
Expand Down
2 changes: 1 addition & 1 deletion packages/cactus-cmd-api-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"express": "4.17.1",
"express-http-proxy": "1.6.2",
"express-jwt": "6.0.0",
"express-openapi-validator": "3.10.0",
"express-openapi-validator": "4.12.12",
"fs-extra": "10.0.0",
"jose": "1.28.1",
"lmify": "0.3.0",
Expand Down
20 changes: 8 additions & 12 deletions packages/cactus-cmd-api-server/src/main/typescript/api-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Server as GrpcServer } from "@grpc/grpc-js";
import { ServerCredentials as GrpcServerCredentials } from "@grpc/grpc-js";
import type { Application, Request, Response, RequestHandler } from "express";
import express from "express";
import { OpenApiValidator } from "express-openapi-validator";
import { OpenAPIV3 } from "express-openapi-validator/dist/framework/types";
import compression from "compression";
import bodyParser from "body-parser";
import cors from "cors";
Expand All @@ -35,12 +35,13 @@ import {
} from "@hyperledger/cactus-core-api";

import { PluginRegistry } from "@hyperledger/cactus-core";
import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core";

import { Logger, LoggerProvider, Servers } from "@hyperledger/cactus-common";

import { ICactusApiServerOptions } from "./config/config-service";
import OAS from "../json/openapi.json";
import { OpenAPIV3 } from "express-openapi-validator/dist/framework/types";
// import { OpenAPIV3 } from "express-openapi-validator/dist/framework/types";

import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter";
import { AuthorizerFactory } from "./authzn/authorizer-factory";
Expand Down Expand Up @@ -606,8 +607,8 @@ export class ApiServer {
this.log.info(`Authorization request handler configured OK.`);
}

const openApiValidator = this.createOpenApiValidator();
await openApiValidator.install(app);
// const openApiValidator = this.createOpenApiValidator();
// await openApiValidator.install(app);

this.getOrCreateWebServices(app); // The API server's own endpoints

Expand All @@ -619,6 +620,9 @@ export class ApiServer {
.map(async (plugin: ICactusPlugin) => {
const p = plugin as IPluginWebService;
await p.getOrCreateWebServices();
const apiSpec = p.getOpenApiSpec() as OpenAPIV3.Document;
if (apiSpec)
await installOpenapiValidationMiddleware({ app, apiSpec, logLevel });
const webSvcs = await p.registerWebServices(app, wsApi);
return webSvcs;
});
Expand Down Expand Up @@ -683,14 +687,6 @@ export class ApiServer {
}
}

createOpenApiValidator(): OpenApiValidator {
return new OpenApiValidator({
apiSpec: OAS as OpenAPIV3.Document,
validateRequests: true,
validateResponses: false,
});
}

createCorsMiddleware(allowedDomains: string[]): RequestHandler {
const allDomainsOk = allowedDomains.includes("*");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export class PluginLedgerConnectorStub
this.log.debug(`Instantiated ${this.className} OK`);
}

public getOpenApiSpec(): unknown {
return null;
}

public getInstanceId(): string {
return this.instanceId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface IPluginWebService extends ICactusPlugin {

getHttpServer(): Optional<Server | SecureServer>;
shutdown(): Promise<void>;
getOpenApiSpec(): unknown;
}

export function isIPluginWebService(x: unknown): x is IPluginWebService {
Expand All @@ -26,6 +27,7 @@ export function isIPluginWebService(x: unknown): x is IPluginWebService {
typeof (x as IPluginWebService).getHttpServer === "function" &&
typeof (x as IPluginWebService).getPackageName === "function" &&
typeof (x as IPluginWebService).getInstanceId === "function" &&
typeof (x as IPluginWebService).shutdown === "function"
typeof (x as IPluginWebService).shutdown === "function" &&
typeof (x as IPluginWebService).getOpenApiSpec === "function"
);
}
1 change: 1 addition & 0 deletions packages/cactus-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@hyperledger/cactus-core-api": "0.9.0",
"express": "4.17.1",
"express-jwt-authz": "2.4.1",
"express-openapi-validator": "4.12.12",
"typescript-optional": "2.0.1"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions packages/cactus-core/src/main/typescript/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ export {
} from "./web-services/authorization-options-provider";

export { consensusHasTransactionFinality } from "./consensus-has-transaction-finality";

export { IInstallOpenapiValidationMiddlewareRequest } from "./web-services/install-open-api-validator-middleware";
export { installOpenapiValidationMiddleware } from "./web-services/install-open-api-validator-middleware";
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Application, NextFunction, Request, Response } from "express";
import * as OpenApiValidator from "express-openapi-validator";
import { OpenAPIV3 } from "express-openapi-validator/dist/framework/types";

import {
Checks,
LoggerProvider,
LogLevelDesc,
} from "@hyperledger/cactus-common";

export interface IInstallOpenapiValidationMiddlewareRequest {
readonly logLevel: LogLevelDesc;
readonly app: Application;
readonly apiSpec: unknown;
}

/**
* Installs the middleware that validates openapi specifications
* @param app
* @param pluginOAS
*/
export async function installOpenapiValidationMiddleware(
req: IInstallOpenapiValidationMiddlewareRequest,
): Promise<void> {
const fnTag = "installOpenapiValidationMiddleware";
Checks.truthy(req, `${fnTag} req`);
Checks.truthy(req.apiSpec, `${fnTag} req.apiSpec`);
Checks.truthy(req.app, `${fnTag} req.app`);
const { app, apiSpec, logLevel } = req;
const log = LoggerProvider.getOrCreate({
label: fnTag,
level: logLevel || "INFO",
});
log.debug(`Installing validation for OpenAPI specs: `, apiSpec);

app.use(
OpenApiValidator.middleware({
apiSpec: apiSpec as OpenAPIV3.Document,
validateApiSpec: false,
}),
);
app.use(
(
err: {
status?: number;
errors: [
{
path: string;
message: string;
errorCode: string;
},
];
},
req: Request,
res: Response,
next: NextFunction,
) => {
if (err) {
res.status(err.status || 500);
res.send(err.errors);
} else {
next();
}
},
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { JWS, JWK } from "jose";
import jsonStableStringify from "json-stable-stringify";
import { v4 as uuidv4 } from "uuid";

import OAS from "../json/openapi.json";

import {
ConsortiumDatabase,
IPluginWebService,
Expand Down Expand Up @@ -98,6 +100,10 @@ export class PluginConsortiumManual
this.prometheusExporter.setNodeCount(this.getNodeCount());
}

public getOpenApiSpec(): unknown {
return OAS;
}

public getInstanceId(): string {
return this.instanceId;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Server as SecureServer } from "https";
import { Express } from "express";
import { Optional } from "typescript-optional";

import OAS from "../json/openapi.json";

import {
IPluginWebService,
ICactusPlugin,
Expand Down Expand Up @@ -56,6 +58,10 @@ export class PluginHtlcEthBesuErc20
this.pluginRegistry = opts.pluginRegistry;
}

public getOpenApiSpec(): unknown {
return OAS;
}

public get className(): string {
return PluginHtlcEthBesuErc20.CLASS_NAME;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Server as SecureServer } from "https";
import { Express } from "express";
import { Optional } from "typescript-optional";

import OAS from "../json/openapi.json";

import {
IPluginWebService,
ICactusPlugin,
Expand Down Expand Up @@ -61,6 +63,10 @@ export class PluginHtlcEthBesu implements ICactusPlugin, IPluginWebService {
return PluginHtlcEthBesu.CLASS_NAME;
}

public getOpenApiSpec(): unknown {
return OAS;
}

/**
* Feature is deprecated, we won't need this method in the future.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
import type { Express } from "express";
import { Optional } from "typescript-optional";

import OAS from "../json/openapi.json";

import {
Logger,
Checks,
Expand Down Expand Up @@ -141,6 +143,10 @@ export class PluginKeychainAwsSm
this.log.info(`Created ${this.className}. KeychainID=${opts.keychainId}`);
}

public getOpenApiSpec(): unknown {
return OAS;
}

public getAwsClient(): SecretsManager {
return this.awsClient;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { Server as SecureServer } from "https";
import type { Express } from "express";
import { Optional } from "typescript-optional";

import OAS from "../json/openapi.json";

import {
Logger,
Checks,
Expand Down Expand Up @@ -124,6 +126,10 @@ export class PluginKeychainAzureKv
this.log.info(`Created ${this.className}. KeychainID=${opts.keychainId}`);
}

public getOpenApiSpec(): unknown {
return OAS;
}

async registerWebServices(app: Express): Promise<IWebServiceEndpoint[]> {
const webServices = await this.getOrCreateWebServices();
await Promise.all(webServices.map((ws) => ws.registerExpress(app)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type { Server as SecureServer } from "https";
import type { Express } from "express";
import { Optional } from "typescript-optional";

import OAS from "../json/openapi.json";

import {
Logger,
Checks,
Expand Down Expand Up @@ -59,6 +61,10 @@ export class PluginKeychainGoogleSm
this.log.info(`Created ${this.className}. KeychainID=${opts.keychainId}`);
}

public getOpenApiSpec(): unknown {
return OAS;
}

async registerWebServices(app: Express): Promise<IWebServiceEndpoint[]> {
const webServices = await this.getOrCreateWebServices();
await Promise.all(webServices.map((ws) => ws.registerExpress(app)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
IWebServiceEndpoint,
} from "@hyperledger/cactus-core-api";

import OAS from "../json/openapi.json";

import { PrometheusExporter } from "./prometheus-exporter/prometheus-exporter";
import { Express } from "express";

Expand Down Expand Up @@ -67,6 +69,10 @@ export class PluginKeychainMemory {
);
}

public getOpenApiSpec(): unknown {
return OAS;
}

public getPrometheusExporter(): PrometheusExporter {
return this.prometheusExporter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Server as SecureServer } from "https";
import { Express } from "express";
import { Optional } from "typescript-optional";

import OAS from "../json/openapi.json";

import {
Logger,
Checks,
Expand Down Expand Up @@ -71,6 +73,10 @@ export class PluginKeychainVaultRemoteAdapter
this.log.info(`Created ${this.className}. KeychainID=${opts.keychainId}`);
}

public getOpenApiSpec(): unknown {
return OAS;
}

/**
* Dummy implementation that wires no web services on the host API server
* because there is no need. All the functionality is implemented somewhere
Expand Down
Loading

0 comments on commit 186cd1b

Please sign in to comment.