From 6efd8de9ff5bb8d6b894147e5c6e49aed2fa8ae4 Mon Sep 17 00:00:00 2001 From: Michal Bajer Date: Fri, 22 Dec 2023 13:23:11 +0000 Subject: [PATCH] feat(cactus-plugin-ledger-connector-cdl): add new connector plugin - Add new plugin for connecting with CDL service. - It supports as `cactus-plugin-ledger-connector-cdl-socketio` which is now depracated and will be removed shortly. - There are still no automatic tests for this package, only manual script that user can run against CDL instance to see if everything is working. - Fix minor formatting issue in cbdc example (issue from lint) Signed-off-by: Michal Bajer --- .cspell.json | 1 + ... => cdl-connector-socketio-manual.test.ts} | 0 .../Dockerfile | 5 + .../README.md | 147 ++++ .../openapitools.json | 7 + .../package.json | 82 ++ .../src/main/json/openapi.json | 505 ++++++++++++ .../src/main/typescript/cdl-gateway.ts | 127 +++ .../typescript-axios/.openapi-generator/FILES | 5 + .../.openapi-generator/VERSION | 1 + .../generated/openapi/typescript-axios/api.ts | 733 ++++++++++++++++++ .../openapi/typescript-axios/base.ts | 72 ++ .../openapi/typescript-axios/common.ts | 150 ++++ .../openapi/typescript-axios/configuration.ts | 101 +++ .../openapi/typescript-axios/index.ts | 18 + .../src/main/typescript/index.ts | 1 + .../src/main/typescript/index.web.ts | 1 + .../plugin-factory-ledger-connector.ts | 20 + .../typescript/plugin-ledger-connector-cdl.ts | 338 ++++++++ .../src/main/typescript/public-api.ts | 19 + .../src/main/typescript/type-defs.ts | 86 ++ .../web-services/get-lineage-v1-endpoint.ts | 99 +++ .../register-history-data-v1-endpoint.ts | 101 +++ ...earch-lineage-by-globaldata-v1-endpoint.ts | 101 +++ .../search-lineage-by-header-v1-endpoint.ts | 101 +++ .../manual/cdl-connector-manual.test.ts | 492 ++++++++++++ .../test/typescript/unit/api-surface.test.ts | 6 + .../tsconfig.json | 23 + tsconfig.json | 3 + yarn.lock | 26 + 30 files changed, 3371 insertions(+) rename packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/{cdl-connector-manual.test.ts => cdl-connector-socketio-manual.test.ts} (100%) create mode 100644 packages/cactus-plugin-ledger-connector-cdl/Dockerfile create mode 100644 packages/cactus-plugin-ledger-connector-cdl/README.md create mode 100644 packages/cactus-plugin-ledger-connector-cdl/openapitools.json create mode 100644 packages/cactus-plugin-ledger-connector-cdl/package.json create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/json/openapi.json create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/cdl-gateway.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/api.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/base.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/common.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/configuration.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/index.ts create mode 100755 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/index.ts create mode 100755 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/index.web.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/plugin-factory-ledger-connector.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/plugin-ledger-connector-cdl.ts create mode 100755 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/public-api.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/type-defs.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/get-lineage-v1-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/register-history-data-v1-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/search-lineage-by-globaldata-v1-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/search-lineage-by-header-v1-endpoint.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/test/typescript/manual/cdl-connector-manual.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/src/test/typescript/unit/api-surface.test.ts create mode 100644 packages/cactus-plugin-ledger-connector-cdl/tsconfig.json diff --git a/.cspell.json b/.cspell.json index 20ab59d29f..807cb9b4ce 100644 --- a/.cspell.json +++ b/.cspell.json @@ -12,6 +12,7 @@ "anoncreds", "ANYFORTX", "APIV", + "Apim", "approveformyorg", "Askar", "askar", diff --git a/packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/cdl-connector-manual.test.ts b/packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/cdl-connector-socketio-manual.test.ts similarity index 100% rename from packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/cdl-connector-manual.test.ts rename to packages/cactus-plugin-ledger-connector-cdl-socketio/src/test/typescript/integration/cdl-connector-socketio-manual.test.ts diff --git a/packages/cactus-plugin-ledger-connector-cdl/Dockerfile b/packages/cactus-plugin-ledger-connector-cdl/Dockerfile new file mode 100644 index 0000000000..9c333759da --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/Dockerfile @@ -0,0 +1,5 @@ +FROM ghcr.io/hyperledger/cactus-cmd-api-server:v1.0.0 + +ARG NPM_PKG_VERSION=latest + +RUN npm i @hyperledger/cactus-plugin-ledger-connector-cdl@${NPM_PKG_VERSION} --production diff --git a/packages/cactus-plugin-ledger-connector-cdl/README.md b/packages/cactus-plugin-ledger-connector-cdl/README.md new file mode 100644 index 0000000000..71ba1559c7 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/README.md @@ -0,0 +1,147 @@ +# `@hyperledger/cactus-plugin-ledger-connector-cdl` + +This plugin provides `Cacti` a way to interact with Fujitsu CDL networks. Using this you can: + +- Register new data trail. +- Get events. +- Search for events using header / global data fields as a query. + +## Summary + +- [Getting Started](#getting-started) +- [Usage](#usage) +- [ApiClient](#apiclient) +- [Runing the tests](#running-the-tests) +- [Contributing](#contributing) +- [License](#license) +- [Acknowledgments](#acknowledgments) + +## Getting Started + +Clone the git repository on your local machine. Follow these instructions that will get you a copy of the project up and running on +your local machine for development and testing purposes. + +### Prerequisites + +In the root of the project to install the dependencies execute the command: + +```sh +npm run configure +``` + +## Usage + +To use this plugin, import public-api, create new **PluginLedgerConnectorCDL** and initialize it. + +```typescript +const connector = new PluginLedgerConnectorCDL({ + instanceId: uuidV4(), + logLevel, + cdlApiGateway: { + url: cdlUrl, + }, + cdlApiSubscriptionGateway: { + url: cdlSubscriptionUrl, + }, +}); + +// Register endpoints +await connector.getOrCreateWebServices(); +await connector.registerWebServices(expressApp); +``` + +### Configuration + +#### Connector Setup + +- `logLevel` - connector log level +- `cdlApiGateway` - configuration of regular CDL endpoint (use it if you want to use access token to authenticate) +- `cdlApiSubscriptionGateway` - configuration of CDL endpoint for applications (use it if you want to use subscriptionId to authenticate). + +#### Gateway Setup + +- `url`: Gateway URL +- `userAgent`: Value of User-Agent header sent to CDL (to identify this client). +- `skipCertCheck`: Set to true to ignore self-signed and other rejected certificates. +- `caPath`: CA of CDL API gateway server in PEM format to use. +- `serverName`: Overwrite server name from cdlApiGateway.url to match one specified in CA. + +### Connector Methods + +- Connector can be used directly through it's public methods. + +#### Methods + +```typescript +async registerHistoryData(args: RegisterHistoryDataRequestV1): Promise +async getLineage(args: GetLineageRequestV1): Promise +async searchLineageByHeader(args: SearchLineageRequestV1): Promise +async searchLineageByGlobalData(args: SearchLineageRequestV1): Promise +``` + +## ApiClient + +All connector API endpoints are defined in [open-api specification](./src/main/json/openapi.json). +See [DefaultApi](./src/main/typescript/generated/openapi/typescript-axios/api.ts) for up-to-date listing of supported endpoints. + +### REST Functions + +- `registerHistoryDataV1` +- `getLineageV1` +- `searchLineageByHeaderV1` +- `searchLineageByGlobalDataV1` + +## Running the tests + +To check that all has been installed correctly and that the plugin has no errors run jest test suites. + +- Run this command at the project's root: + +```sh +npx jest cactus-plugin-ledger-connector-cdl +``` + +### Manual Tests + +- There are no automatic tests for this plugin because there's no private instance of CDL available at a time. +- `./src/test/typescript/manual/cdl-connector-manual.test.ts` contains a Jest test script that will check every implemented operation on a running CDL service. +- **You need access to a running instance of CDL in order to run this script.** + - You can check https://en-portal.research.global.fujitsu.com/ for free test access to a service. + - Please note that rate limiting set on a service may cause some tests to fail. +- Before running the script you must update the following variables in it: + - `authInfo` - either `accessToken` or `subscriptionKey` based configuration. + - `cdlUrl / cdlSubscriptionUrl` - URL to CDL service (only base path) +- Script can be used as a quick reference for using this connector plugin. +- Since script is not part of project jest suite, to run in execute the following commands from a package dir: + - `npx tsc` + - `npx jest dist/lib/test/typescript/manual/cdl-connector-manual.test.js` + +### Building/running the container image locally + +In the Cactus project root say: + +```sh +DOCKER_BUILDKIT=1 docker build -f ./packages/cactus-plugin-ledger-connector-cdl/Dockerfile . -t CDL_connector +``` + +Build with a specific version of the npm package: + +```sh +DOCKER_BUILDKIT=1 docker build --build-arg NPM_PKG_VERSION=0.4.1 -f ./packages/cactus-plugin-ledger-connector-cdl/Dockerfile . -t CDL_connector +``` + +## Contributing + +We welcome contributions to Hyperledger Cactus in many forms, and there’s always plenty to do! + +Please review [CONTIRBUTING.md](../../CONTRIBUTING.md) to get started. + +## License + +This distribution is published under the Apache License Version 2.0 found in the [LICENSE](../../LICENSE) file. + +## Acknowledgments + +``` + +``` diff --git a/packages/cactus-plugin-ledger-connector-cdl/openapitools.json b/packages/cactus-plugin-ledger-connector-cdl/openapitools.json new file mode 100644 index 0000000000..03392961f6 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.3.0" + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/package.json b/packages/cactus-plugin-ledger-connector-cdl/package.json new file mode 100644 index 0000000000..98c83d1e54 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/package.json @@ -0,0 +1,82 @@ +{ + "name": "@hyperledger/cactus-plugin-ledger-connector-cdl", + "version": "2.0.0-alpha.2", + "description": "Allows Cacti nodes to connect to Fujitsu CDL.", + "keywords": [ + "Hyperledger", + "Cacti", + "Cactus", + "Integration", + "Blockchain", + "Distributed Ledger Technology" + ], + "homepage": "https://github.com/hyperledger/cacti#readme", + "bugs": { + "url": "https://github.com/hyperledger/cacti/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hyperledger/cacti.git" + }, + "license": "Apache-2.0", + "author": { + "name": "Hyperledger Cacti Contributors", + "email": "cacti@lists.hyperledger.org", + "url": "https://www.hyperledger.org/use/cacti" + }, + "contributors": [ + { + "name": "Please add yourself to the list of contributors", + "email": "your.name@example.com", + "url": "https://example.com" + }, + { + "name": "Michal Bajer", + "email": "michal.bajer@fujitsu.com", + "url": "https://www.fujitsu.com/global/" + } + ], + "main": "dist/lib/main/typescript/index.js", + "module": "dist/lib/main/typescript/index.js", + "browser": "dist/cactus-plugin-ledger-connector-cdl.web.umd.js", + "types": "dist/lib/main/typescript/index.d.ts", + "files": [ + "dist/*" + ], + "scripts": { + "codegen": "run-p 'codegen:*'", + "codegen:openapi": "npm run generate-sdk", + "generate-sdk": "run-p 'generate-sdk:*'", + "generate-sdk:typescript-axios": "openapi-generator-cli generate -i ./src/main/json/openapi.json -g typescript-axios -o ./src/main/typescript/generated/openapi/typescript-axios/ --reserved-words-mappings protected=protected --ignore-file-override ../../openapi-generator-ignore", + "webpack": "npm-run-all webpack:dev", + "webpack:dev": "npm-run-all webpack:dev:node webpack:dev:web", + "webpack:dev:node": "webpack --env=dev --target=node --config ../../webpack.config.js", + "webpack:dev:web": "webpack --env=dev --target=web --config ../../webpack.config.js" + }, + "dependencies": { + "@hyperledger/cactus-common": "2.0.0-alpha.2", + "@hyperledger/cactus-core": "2.0.0-alpha.2", + "@hyperledger/cactus-core-api": "2.0.0-alpha.2", + "axios": "1.6.0", + "sanitize-html": "2.7.0" + }, + "devDependencies": { + "@types/express": "4.17.19", + "@types/node": "18.11.9", + "@types/sanitize-html": "2.6.2", + "body-parser": "1.20.2", + "express": "4.18.2", + "jest-extended": "4.0.1", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=18", + "npm": ">=8" + }, + "publishConfig": { + "access": "public" + }, + "browserMinified": "dist/cactus-plugin-ledger-connector-cdl.web.umd.min.js", + "mainMinified": "dist/cactus-plugin-ledger-connector-cdl.node.umd.min.js", + "watch": {} +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/json/openapi.json b/packages/cactus-plugin-ledger-connector-cdl/src/main/json/openapi.json new file mode 100644 index 0000000000..ef34d85e70 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/json/openapi.json @@ -0,0 +1,505 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Hyperledger Cacti Plugin - Connector CDL", + "description": "Can perform basic tasks on Fujitsu CDL service.", + "version": "v2.0.0-alpha.2", + "license": { + "name": "Apache-2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "components": { + "schemas": { + "GetLineageOptionDirectionV1": { + "type": "string", + "enum": ["backward", "forward", "both"], + "x-enum-varnames": ["Backward", "Forward", "Both"] + }, + "SearchLineageTypeV1": { + "type": "string", + "enum": ["exactmatch", "partialmatch", "regexpmatch"], + "x-enum-varnames": ["ExactMatch", "PartialMatch", "RegexMatch"] + }, + "GatewayConfigurationV1": { + "type": "object", + "required": ["url"], + "properties": { + "url": { + "type": "string", + "description": "Gateway URL", + "nullable": false + }, + "userAgent": { + "type": "string", + "description": "Value of User-Agent header sent to CDL (to identify this client)", + "nullable": false + }, + "skipCertCheck": { + "type": "boolean", + "description": "Set to true to ignore self-signed and other rejected certificates", + "default": "false", + "nullable": false + }, + "caPath": { + "type": "string", + "description": "CA of CDL API gateway server in PEM format to use", + "nullable": false + }, + "serverName": { + "type": "string", + "description": "Overwrite server name from cdlApiGateway.url to match one specified in CA", + "nullable": false + } + } + }, + "AuthInfoAccessTokenV1": { + "type": "object", + "required": ["accessToken", "trustAgentId"], + "properties": { + "accessToken": { + "type": "string", + "nullable": false + }, + "trustAgentId": { + "type": "string", + "nullable": false + } + } + }, + "AuthInfoSubscriptionKeyV1": { + "type": "object", + "required": [ + "subscriptionKey", + "trustAgentId", + "trustAgentRole", + "trustUserId", + "trustUserRole" + ], + "properties": { + "subscriptionKey": { + "type": "string", + "nullable": false + }, + "trustAgentId": { + "type": "string", + "nullable": false + }, + "trustAgentRole": { + "type": "string", + "nullable": false + }, + "trustUserId": { + "type": "string", + "nullable": false + }, + "trustUserRole": { + "type": "string", + "nullable": false + } + } + }, + "AuthInfoV1": { + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/AuthInfoAccessTokenV1" + }, + { + "$ref": "#/components/schemas/AuthInfoSubscriptionKeyV1" + } + ] + }, + "EventLineageV1": { + "type": "object", + "description": "CDL event linage information (used to identify related events)", + "required": [ + "cdl:EventId", + "cdl:LineageId", + "cdl:DataModelMode", + "cdl:DataModelVersion", + "cdl:DataRegistrationTimeStamp", + "cdl:NextEventIdList", + "cdl:PreviousEventIdList" + ], + "properties": { + "cdl:EventId": { + "nullable": false, + "type": " string" + }, + "cdl:LineageId": { + "nullable": false, + "type": " string" + }, + "cdl:DataModelMode": { + "nullable": false, + "type": " string" + }, + "cdl:DataModelVersion": { + "nullable": false, + "type": " string" + }, + "cdl:DataRegistrationTimeStamp": { + "nullable": false, + "type": " string" + }, + "cdl:NextEventIdList": { + "type": "array", + "items": { + "nullable": false, + "type": " string" + } + }, + "cdl:PreviousEventIdList": { + "type": "array", + "items": { + "nullable": false, + "type": " string" + } + } + } + }, + "TrailEventDetailsV1": { + "type": "object", + "description": "Details of newly created CDL event.", + "required": ["cdl:Lineage", "cdl:Tags", "cdl:Verification"], + "properties": { + "cdl:Event": { + "nullable": false + }, + "cdl:Lineage": { + "$ref": "#/components/schemas/EventLineageV1", + "nullable": false + }, + "cdl:Tags": { + "nullable": false + }, + "cdl:Verification": { + "nullable": false + } + } + }, + "CDLCommonResponseV1": { + "type": "object", + "required": ["result"], + "properties": { + "detail": { + "nullable": false + }, + "result": { + "type": " string", + "nullable": false + } + } + }, + "RegisterHistoryDataRequestV1": { + "type": "object", + "required": ["authInfo"], + "properties": { + "authInfo": { + "$ref": "#/components/schemas/AuthInfoV1", + "nullable": false + }, + "eventId": { + "type": "string", + "nullable": false + }, + "lineageId": { + "type": "string", + "nullable": false + }, + "tags": { + "nullable": false + }, + "properties": { + "nullable": false + } + } + }, + "RegisterHistoryDataV1Response": { + "type": "object", + "required": ["detail", "result"], + "properties": { + "detail": { + "$ref": "#/components/schemas/TrailEventDetailsV1", + "nullable": false + }, + "result": { + "type": " string", + "nullable": false + } + } + }, + "GetLineageRequestV1": { + "type": "object", + "required": ["authInfo", "eventId"], + "properties": { + "authInfo": { + "$ref": "#/components/schemas/AuthInfoV1", + "nullable": false + }, + "eventId": { + "type": "string", + "nullable": false + }, + "direction": { + "$ref": "#/components/schemas/GetLineageOptionDirectionV1", + "default": "backward", + "nullable": false + }, + "depth": { + "type": "string", + "default": "-1", + "nullable": false + } + } + }, + "GetLineageResponseV1": { + "type": "object", + "required": ["detail", "result"], + "properties": { + "detail": { + "type": "array", + "items": { + "nullable": false, + "$ref": "#/components/schemas/TrailEventDetailsV1" + } + }, + "result": { + "type": " string", + "nullable": false + } + } + }, + "SearchLineageRequestV1": { + "type": "object", + "required": ["authInfo", "searchType", "fields"], + "properties": { + "authInfo": { + "$ref": "#/components/schemas/AuthInfoV1", + "nullable": false + }, + "searchType": { + "$ref": "#/components/schemas/SearchLineageTypeV1", + "nullable": false + }, + "fields": { + "nullable": false + } + } + }, + "SearchLineageResponseV1": { + "type": "object", + "required": ["detail", "result"], + "properties": { + "detail": { + "type": "array", + "items": { + "nullable": false, + "$ref": "#/components/schemas/TrailEventDetailsV1" + } + }, + "result": { + "type": " string", + "nullable": false + } + } + }, + "ErrorExceptionResponseV1": { + "type": "object", + "description": "Error response from the connector.", + "required": ["message", "error"], + "properties": { + "message": { + "type": "string", + "description": "Short error description message.", + "nullable": false + }, + "error": { + "type": "string", + "description": "Detailed error information.", + "nullable": false + } + } + } + } + }, + "paths": { + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/register-history-data": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/register-history-data" + } + }, + "operationId": "registerHistoryDataV1", + "summary": "Register new data trail on CDL", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterHistoryDataRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RegisterHistoryDataV1Response" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorExceptionResponseV1" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/get-lineage": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/get-lineage" + } + }, + "operationId": "getLineageV1", + "summary": "Get lineage trail from CDL.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetLineageRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetLineageResponseV1" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorExceptionResponseV1" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-header": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-header" + } + }, + "operationId": "searchLineageByHeaderV1", + "summary": "Search lineage using header fields.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchLineageRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchLineageResponseV1" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorExceptionResponseV1" + } + } + } + } + } + } + }, + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-globaldata": { + "post": { + "x-hyperledger-cacti": { + "http": { + "verbLowerCase": "post", + "path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-globaldata" + } + }, + "operationId": "searchLineageByGlobalDataV1", + "summary": "Search lineage using global data fields.", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchLineageRequestV1" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchLineageResponseV1" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorExceptionResponseV1" + } + } + } + } + } + } + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/cdl-gateway.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/cdl-gateway.ts new file mode 100644 index 0000000000..8007464f3a --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/cdl-gateway.ts @@ -0,0 +1,127 @@ +import axios from "axios"; +import https from "node:https"; +import { readFileSync } from "node:fs"; +import { + Checks, + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { getAuthorizationHeaders } from "./type-defs"; +import { + AuthInfoV1, + CDLCommonResponseV1, + GatewayConfigurationV1, +} from "./public-api"; + +const DEFAULT_USER_AGENT = "CactiCDLConnector"; + +/** + * Helper class for sending requests to CDL Node. + */ +export class CDLGateway { + private readonly log: Logger; + private readonly baseURL: string; + private readonly userAgent: string; + private readonly httpsAgent: https.Agent; + + public get className(): string { + return "CDLGateway"; + } + + /** + * @param gatewayConfig gateway configuration (URL, certificates, etc...) + * @param logLevel log level for gateway + */ + constructor( + gatewayConfig: GatewayConfigurationV1, + logLevel: LogLevelDesc = "INFO", + ) { + Checks.truthy(gatewayConfig, `${this.className} arg gatewayConfig`); + Checks.truthy(gatewayConfig.url, `${this.className} arg gatewayConfig.url`); + const { skipCertCheck, caPath, serverName } = gatewayConfig; + this.baseURL = gatewayConfig.url; + this.userAgent = gatewayConfig.userAgent ?? DEFAULT_USER_AGENT; + + this.log = LoggerProvider.getOrCreate({ + level: logLevel, + label: this.className, + }); + + const agentOptions: https.AgentOptions = {}; + + if (skipCertCheck && typeof skipCertCheck === "boolean") { + this.log.info( + `Allowing self signed CDL API GW certificates (skipCertCheck=${skipCertCheck})`, + ); + agentOptions.rejectUnauthorized = false; + } + + if (caPath && typeof caPath === "string") { + this.log.info(`Using CDL API GW CA ${caPath}`); + const gatewayCAString = readFileSync(caPath, "ascii"); + this.log.debug("CDL Gateway certificate read:", gatewayCAString); + agentOptions.ca = gatewayCAString; + } + + if (serverName && typeof serverName === "string") { + this.log.info(`Overwrite CDL API GW server name with '${serverName}'`); + agentOptions.servername = serverName; + } + + this.httpsAgent = new https.Agent(agentOptions); + } + + /** + * Send request to CDL node. + * HTTP method is determined based on presence of arguments (i.e. dataPayload triggers POST request). + * Headers are set according to authInfo argument. + * + * @param url endpoint path to call (without base URL) + * @param authInfo authentication info + * @param queryParams query parameters (passed in URL) + * @param dataPayload post body + * @returns response from CDL + */ + public async request( + url: string, + authInfo: AuthInfoV1, + queryParams?: Record, + dataPayload?: Record, + ): Promise { + const { httpsAgent, baseURL, userAgent } = this; + + let httpMethod = "get"; + if (dataPayload) { + httpMethod = "post"; + } + const authHeaders = getAuthorizationHeaders(authInfo); + + this.log.debug(`cdl request ${httpMethod} ${url} executed`); + + try { + const requestResponse = await axios({ + httpsAgent, + method: httpMethod, + baseURL, + url, + responseType: "json", + headers: { + "User-Agent": userAgent, + "Content-Type": "application/json;charset=UTF-8", + ...authHeaders, + }, + params: queryParams, + data: dataPayload, + }); + + return requestResponse.data; + } catch (error) { + if ("toJSON" in error) { + this.log.error("CDL API request failed:", error.toJSON()); + } + + throw error; + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES new file mode 100644 index 0000000000..53250c0269 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/FILES @@ -0,0 +1,5 @@ +api.ts +base.ts +common.ts +configuration.ts +index.ts diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION new file mode 100644 index 0000000000..e7e42a4b58 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.3.0 \ No newline at end of file diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/api.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/api.ts new file mode 100644 index 0000000000..50ad0ca5ac --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/api.ts @@ -0,0 +1,733 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector CDL + * Can perform basic tasks on Fujitsu CDL service. + * + * The version of the OpenAPI document: v2.0.0-alpha.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common'; +import type { RequestArgs } from './base'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base'; + +/** + * + * @export + * @interface AuthInfoAccessTokenV1 + */ +export interface AuthInfoAccessTokenV1 { + /** + * + * @type {string} + * @memberof AuthInfoAccessTokenV1 + */ + 'accessToken': string; + /** + * + * @type {string} + * @memberof AuthInfoAccessTokenV1 + */ + 'trustAgentId': string; +} +/** + * + * @export + * @interface AuthInfoSubscriptionKeyV1 + */ +export interface AuthInfoSubscriptionKeyV1 { + /** + * + * @type {string} + * @memberof AuthInfoSubscriptionKeyV1 + */ + 'subscriptionKey': string; + /** + * + * @type {string} + * @memberof AuthInfoSubscriptionKeyV1 + */ + 'trustAgentId': string; + /** + * + * @type {string} + * @memberof AuthInfoSubscriptionKeyV1 + */ + 'trustAgentRole': string; + /** + * + * @type {string} + * @memberof AuthInfoSubscriptionKeyV1 + */ + 'trustUserId': string; + /** + * + * @type {string} + * @memberof AuthInfoSubscriptionKeyV1 + */ + 'trustUserRole': string; +} +/** + * @type AuthInfoV1 + * @export + */ +export type AuthInfoV1 = AuthInfoAccessTokenV1 | AuthInfoSubscriptionKeyV1; + +/** + * + * @export + * @interface CDLCommonResponseV1 + */ +export interface CDLCommonResponseV1 { + /** + * + * @type {any} + * @memberof CDLCommonResponseV1 + */ + 'detail'?: any; + /** + * + * @type { String} + * @memberof CDLCommonResponseV1 + */ + 'result': String; +} +/** + * Error response from the connector. + * @export + * @interface ErrorExceptionResponseV1 + */ +export interface ErrorExceptionResponseV1 { + /** + * Short error description message. + * @type {string} + * @memberof ErrorExceptionResponseV1 + */ + 'message': string; + /** + * Detailed error information. + * @type {string} + * @memberof ErrorExceptionResponseV1 + */ + 'error': string; +} +/** + * CDL event linage information (used to identify related events) + * @export + * @interface EventLineageV1 + */ +export interface EventLineageV1 { + /** + * + * @type { String} + * @memberof EventLineageV1 + */ + 'cdl:EventId': String; + /** + * + * @type { String} + * @memberof EventLineageV1 + */ + 'cdl:LineageId': String; + /** + * + * @type { String} + * @memberof EventLineageV1 + */ + 'cdl:DataModelMode': String; + /** + * + * @type { String} + * @memberof EventLineageV1 + */ + 'cdl:DataModelVersion': String; + /** + * + * @type { String} + * @memberof EventLineageV1 + */ + 'cdl:DataRegistrationTimeStamp': String; + /** + * + * @type {Array< String>} + * @memberof EventLineageV1 + */ + 'cdl:NextEventIdList': Array< String>; + /** + * + * @type {Array< String>} + * @memberof EventLineageV1 + */ + 'cdl:PreviousEventIdList': Array< String>; +} +/** + * + * @export + * @interface GatewayConfigurationV1 + */ +export interface GatewayConfigurationV1 { + /** + * Gateway URL + * @type {string} + * @memberof GatewayConfigurationV1 + */ + 'url': string; + /** + * Value of User-Agent header sent to CDL (to identify this client) + * @type {string} + * @memberof GatewayConfigurationV1 + */ + 'userAgent'?: string; + /** + * Set to true to ignore self-signed and other rejected certificates + * @type {boolean} + * @memberof GatewayConfigurationV1 + */ + 'skipCertCheck'?: boolean; + /** + * CA of CDL API gateway server in PEM format to use + * @type {string} + * @memberof GatewayConfigurationV1 + */ + 'caPath'?: string; + /** + * Overwrite server name from cdlApiGateway.url to match one specified in CA + * @type {string} + * @memberof GatewayConfigurationV1 + */ + 'serverName'?: string; +} +/** + * + * @export + * @enum {string} + */ + +export const GetLineageOptionDirectionV1 = { + Backward: 'backward', + Forward: 'forward', + Both: 'both' +} as const; + +export type GetLineageOptionDirectionV1 = typeof GetLineageOptionDirectionV1[keyof typeof GetLineageOptionDirectionV1]; + + +/** + * + * @export + * @interface GetLineageRequestV1 + */ +export interface GetLineageRequestV1 { + /** + * + * @type {AuthInfoV1} + * @memberof GetLineageRequestV1 + */ + 'authInfo': AuthInfoV1; + /** + * + * @type {string} + * @memberof GetLineageRequestV1 + */ + 'eventId': string; + /** + * + * @type {GetLineageOptionDirectionV1} + * @memberof GetLineageRequestV1 + */ + 'direction'?: GetLineageOptionDirectionV1; + /** + * + * @type {string} + * @memberof GetLineageRequestV1 + */ + 'depth'?: string; +} + + +/** + * + * @export + * @interface GetLineageResponseV1 + */ +export interface GetLineageResponseV1 { + /** + * + * @type {Array} + * @memberof GetLineageResponseV1 + */ + 'detail': Array; + /** + * + * @type { String} + * @memberof GetLineageResponseV1 + */ + 'result': String; +} +/** + * + * @export + * @interface RegisterHistoryDataRequestV1 + */ +export interface RegisterHistoryDataRequestV1 { + /** + * + * @type {AuthInfoV1} + * @memberof RegisterHistoryDataRequestV1 + */ + 'authInfo': AuthInfoV1; + /** + * + * @type {string} + * @memberof RegisterHistoryDataRequestV1 + */ + 'eventId'?: string; + /** + * + * @type {string} + * @memberof RegisterHistoryDataRequestV1 + */ + 'lineageId'?: string; + /** + * + * @type {any} + * @memberof RegisterHistoryDataRequestV1 + */ + 'tags'?: any; + /** + * + * @type {any} + * @memberof RegisterHistoryDataRequestV1 + */ + 'properties'?: any; +} +/** + * + * @export + * @interface RegisterHistoryDataV1Response + */ +export interface RegisterHistoryDataV1Response { + /** + * + * @type {TrailEventDetailsV1} + * @memberof RegisterHistoryDataV1Response + */ + 'detail': TrailEventDetailsV1; + /** + * + * @type { String} + * @memberof RegisterHistoryDataV1Response + */ + 'result': String; +} +/** + * + * @export + * @interface SearchLineageRequestV1 + */ +export interface SearchLineageRequestV1 { + /** + * + * @type {AuthInfoV1} + * @memberof SearchLineageRequestV1 + */ + 'authInfo': AuthInfoV1; + /** + * + * @type {SearchLineageTypeV1} + * @memberof SearchLineageRequestV1 + */ + 'searchType': SearchLineageTypeV1; + /** + * + * @type {any} + * @memberof SearchLineageRequestV1 + */ + 'fields': any; +} + + +/** + * + * @export + * @interface SearchLineageResponseV1 + */ +export interface SearchLineageResponseV1 { + /** + * + * @type {Array} + * @memberof SearchLineageResponseV1 + */ + 'detail': Array; + /** + * + * @type { String} + * @memberof SearchLineageResponseV1 + */ + 'result': String; +} +/** + * + * @export + * @enum {string} + */ + +export const SearchLineageTypeV1 = { + ExactMatch: 'exactmatch', + PartialMatch: 'partialmatch', + RegexMatch: 'regexpmatch' +} as const; + +export type SearchLineageTypeV1 = typeof SearchLineageTypeV1[keyof typeof SearchLineageTypeV1]; + + +/** + * Details of newly created CDL event. + * @export + * @interface TrailEventDetailsV1 + */ +export interface TrailEventDetailsV1 { + /** + * + * @type {any} + * @memberof TrailEventDetailsV1 + */ + 'cdl:Event'?: any; + /** + * + * @type {EventLineageV1} + * @memberof TrailEventDetailsV1 + */ + 'cdl:Lineage': EventLineageV1; + /** + * + * @type {any} + * @memberof TrailEventDetailsV1 + */ + 'cdl:Tags': any; + /** + * + * @type {any} + * @memberof TrailEventDetailsV1 + */ + 'cdl:Verification': any; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @summary Get lineage trail from CDL. + * @param {GetLineageRequestV1} [getLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getLineageV1: async (getLineageRequestV1?: GetLineageRequestV1, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/get-lineage`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(getLineageRequestV1, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Register new data trail on CDL + * @param {RegisterHistoryDataRequestV1} [registerHistoryDataRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + registerHistoryDataV1: async (registerHistoryDataRequestV1?: RegisterHistoryDataRequestV1, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/register-history-data`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(registerHistoryDataRequestV1, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Search lineage using global data fields. + * @param {SearchLineageRequestV1} [searchLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + searchLineageByGlobalDataV1: async (searchLineageRequestV1?: SearchLineageRequestV1, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-globaldata`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(searchLineageRequestV1, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @summary Search lineage using header fields. + * @param {SearchLineageRequestV1} [searchLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + searchLineageByHeaderV1: async (searchLineageRequestV1?: SearchLineageRequestV1, options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-header`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(searchLineageRequestV1, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * + * @summary Get lineage trail from CDL. + * @param {GetLineageRequestV1} [getLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getLineageV1(getLineageRequestV1?: GetLineageRequestV1, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getLineageV1(getLineageRequestV1, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Register new data trail on CDL + * @param {RegisterHistoryDataRequestV1} [registerHistoryDataRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async registerHistoryDataV1(registerHistoryDataRequestV1?: RegisterHistoryDataRequestV1, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.registerHistoryDataV1(registerHistoryDataRequestV1, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Search lineage using global data fields. + * @param {SearchLineageRequestV1} [searchLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async searchLineageByGlobalDataV1(searchLineageRequestV1?: SearchLineageRequestV1, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.searchLineageByGlobalDataV1(searchLineageRequestV1, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @summary Search lineage using header fields. + * @param {SearchLineageRequestV1} [searchLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async searchLineageByHeaderV1(searchLineageRequestV1?: SearchLineageRequestV1, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.searchLineageByHeaderV1(searchLineageRequestV1, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = DefaultApiFp(configuration) + return { + /** + * + * @summary Get lineage trail from CDL. + * @param {GetLineageRequestV1} [getLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getLineageV1(getLineageRequestV1?: GetLineageRequestV1, options?: any): AxiosPromise { + return localVarFp.getLineageV1(getLineageRequestV1, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Register new data trail on CDL + * @param {RegisterHistoryDataRequestV1} [registerHistoryDataRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + registerHistoryDataV1(registerHistoryDataRequestV1?: RegisterHistoryDataRequestV1, options?: any): AxiosPromise { + return localVarFp.registerHistoryDataV1(registerHistoryDataRequestV1, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Search lineage using global data fields. + * @param {SearchLineageRequestV1} [searchLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + searchLineageByGlobalDataV1(searchLineageRequestV1?: SearchLineageRequestV1, options?: any): AxiosPromise { + return localVarFp.searchLineageByGlobalDataV1(searchLineageRequestV1, options).then((request) => request(axios, basePath)); + }, + /** + * + * @summary Search lineage using header fields. + * @param {SearchLineageRequestV1} [searchLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + searchLineageByHeaderV1(searchLineageRequestV1?: SearchLineageRequestV1, options?: any): AxiosPromise { + return localVarFp.searchLineageByHeaderV1(searchLineageRequestV1, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * + * @summary Get lineage trail from CDL. + * @param {GetLineageRequestV1} [getLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public getLineageV1(getLineageRequestV1?: GetLineageRequestV1, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).getLineageV1(getLineageRequestV1, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Register new data trail on CDL + * @param {RegisterHistoryDataRequestV1} [registerHistoryDataRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public registerHistoryDataV1(registerHistoryDataRequestV1?: RegisterHistoryDataRequestV1, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).registerHistoryDataV1(registerHistoryDataRequestV1, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Search lineage using global data fields. + * @param {SearchLineageRequestV1} [searchLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public searchLineageByGlobalDataV1(searchLineageRequestV1?: SearchLineageRequestV1, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).searchLineageByGlobalDataV1(searchLineageRequestV1, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @summary Search lineage using header fields. + * @param {SearchLineageRequestV1} [searchLineageRequestV1] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public searchLineageByHeaderV1(searchLineageRequestV1?: SearchLineageRequestV1, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).searchLineageByHeaderV1(searchLineageRequestV1, options).then((request) => request(this.axios, this.basePath)); + } +} + + diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/base.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/base.ts new file mode 100644 index 0000000000..de9b439263 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/base.ts @@ -0,0 +1,72 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector CDL + * Can perform basic tasks on Fujitsu CDL service. + * + * The version of the OpenAPI document: v2.0.0-alpha.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +// Some imports not used depending on template conditions +// @ts-ignore +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; + +export const BASE_PATH = "http://localhost".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: AxiosRequestConfig; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath || this.basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + constructor(public field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/common.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/common.ts new file mode 100644 index 0000000000..1953277174 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/common.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector CDL + * Can perform basic tasks on Fujitsu CDL service. + * + * The version of the OpenAPI document: v2.0.0-alpha.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from "./configuration"; +import type { RequestArgs } from "./base"; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { RequiredError } from "./base"; + +/** + * + * @export + */ +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { + if (parameter == null) return; + if (typeof parameter === "object") { + if (Array.isArray(parameter)) { + (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); + } + else { + Object.keys(parameter).forEach(currentKey => + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + ); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/configuration.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/configuration.ts new file mode 100644 index 0000000000..8f1e3dadf4 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/configuration.ts @@ -0,0 +1,101 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector CDL + * Can perform basic tasks on Fujitsu CDL service. + * + * The version of the OpenAPI document: v2.0.0-alpha.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + basePath?: string; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/index.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/index.ts new file mode 100644 index 0000000000..64a3baa4af --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/generated/openapi/typescript-axios/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Hyperledger Cacti Plugin - Connector CDL + * Can perform basic tasks on Fujitsu CDL service. + * + * The version of the OpenAPI document: v2.0.0-alpha.2 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; + diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/index.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/index.ts new file mode 100755 index 0000000000..87cb558397 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/index.ts @@ -0,0 +1 @@ +export * from "./public-api"; diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/index.web.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/index.web.ts new file mode 100755 index 0000000000..cb0ff5c3b5 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/index.web.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/plugin-factory-ledger-connector.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/plugin-factory-ledger-connector.ts new file mode 100644 index 0000000000..8f4432ee89 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/plugin-factory-ledger-connector.ts @@ -0,0 +1,20 @@ +import { + IPluginFactoryOptions, + PluginFactory, +} from "@hyperledger/cactus-core-api"; +import { + IPluginLedgerConnectorCDLOptions, + PluginLedgerConnectorCDL, +} from "./plugin-ledger-connector-cdl"; + +export class PluginFactoryLedgerConnector extends PluginFactory< + PluginLedgerConnectorCDL, + IPluginLedgerConnectorCDLOptions, + IPluginFactoryOptions +> { + async create( + pluginOptions: IPluginLedgerConnectorCDLOptions, + ): Promise { + return new PluginLedgerConnectorCDL(pluginOptions); + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/plugin-ledger-connector-cdl.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/plugin-ledger-connector-cdl.ts new file mode 100644 index 0000000000..1438f67972 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/plugin-ledger-connector-cdl.ts @@ -0,0 +1,338 @@ +import type { Express } from "express"; +import sanitizeHtml from "sanitize-html"; + +import { + Checks, + Logger, + LoggerProvider, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { consensusHasTransactionFinality } from "@hyperledger/cactus-core"; +import { + ConsensusAlgorithmFamily, + IWebServiceEndpoint, + IPluginWebService, + ICactusPlugin, + ICactusPluginOptions, +} from "@hyperledger/cactus-core-api"; + +import { RegisterHistoryDataEndpoint } from "./web-services/register-history-data-v1-endpoint"; +import { GetLineageDataEndpoint } from "./web-services/get-lineage-v1-endpoint"; +import { SearchLineageByHeaderEndpoint } from "./web-services/search-lineage-by-header-v1-endpoint"; +import { SearchLineageByGlobalDataEndpoint } from "./web-services/search-lineage-by-globaldata-v1-endpoint"; + +import OAS from "../json/openapi.json"; +import { + RegisterHistoryDataRequestV1, + GatewayConfigurationV1, + AuthInfoV1, + GetLineageRequestV1, + GetLineageOptionDirectionV1, + GetLineageResponseV1, + SearchLineageRequestV1, + SearchLineageResponseV1, + RegisterHistoryDataV1Response, +} from "./generated/openapi/typescript-axios"; +import { + HTTP_HEADER_SUBSCRIPTION_KEY, + getAuthorizationHeaders, +} from "./type-defs"; +import { CDLGateway } from "./cdl-gateway"; + +export interface IPluginLedgerConnectorCDLOptions extends ICactusPluginOptions { + logLevel?: LogLevelDesc; + cdlApiGateway?: GatewayConfigurationV1; + cdlApiSubscriptionGateway?: GatewayConfigurationV1; +} + +/** + * Connector plugin for interacting with Fujitsu CDL service. + */ +export class PluginLedgerConnectorCDL + implements ICactusPlugin, IPluginWebService +{ + private readonly instanceId: string; + private readonly log: Logger; + private endpoints: IWebServiceEndpoint[] | undefined; + private cdlApiGateway: CDLGateway | undefined; + private cdlApiSubscriptionGateway: CDLGateway | undefined; + + public get className(): string { + return "PluginLedgerConnectorCDL"; + } + + constructor(public readonly options: IPluginLedgerConnectorCDLOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.instanceId, `${fnTag} options.instanceId`); + Checks.truthy( + options.cdlApiGateway || options.cdlApiSubscriptionGateway, + `${fnTag} options.cdlApiGateway or options.cdlApiSubscriptionGateway must be defined`, + ); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + + this.instanceId = options.instanceId; + + if (options.cdlApiGateway) { + this.log.info("cdlApiGateway created"); + this.cdlApiGateway = new CDLGateway(options.cdlApiGateway, level); + } + + if (options.cdlApiSubscriptionGateway) { + this.log.info("cdlApiSubscriptionGateway created"); + this.cdlApiSubscriptionGateway = new CDLGateway( + options.cdlApiSubscriptionGateway, + level, + ); + } + } + + public getOpenApiSpec(): unknown { + return OAS; + } + + public getInstanceId(): string { + return this.instanceId; + } + + public async shutdown(): Promise { + this.log.info(`Shutting down ${this.className}...`); + } + + public async onPluginInit(): Promise { + return; + } + + async registerWebServices(app: Express): Promise { + const webServices = await this.getOrCreateWebServices(); + await Promise.all(webServices.map((ws) => ws.registerExpress(app))); + return webServices; + } + + public async getOrCreateWebServices(): Promise { + if (Array.isArray(this.endpoints)) { + return this.endpoints; + } + + const endpoints: IWebServiceEndpoint[] = []; + + { + const endpoint = new RegisterHistoryDataEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new GetLineageDataEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new SearchLineageByHeaderEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + { + const endpoint = new SearchLineageByGlobalDataEndpoint({ + connector: this, + logLevel: this.options.logLevel, + }); + endpoints.push(endpoint); + } + + this.endpoints = endpoints; + return endpoints; + } + + public getPackageName(): string { + return `@hyperledger/cactus-plugin-ledger-connector-cdl`; + } + + public async getConsensusAlgorithmFamily(): Promise { + return ConsensusAlgorithmFamily.Authority; + } + + public async hasTransactionFinality(): Promise { + const currentConsensusAlgorithmFamily = + await this.getConsensusAlgorithmFamily(); + + return consensusHasTransactionFinality(currentConsensusAlgorithmFamily); + } + + /** + * Throws if any property in an object starts with `cdl:` (not allowed by the API) + * @param properties object with string fields. + */ + private checkPropertyNames(properties?: Record) { + const invalidProps = Object.keys(properties ?? {}).filter((k) => + k.startsWith("cdl:"), + ); + if (invalidProps.length > 0) { + throw new Error( + `Properties can't start with 'cdl:'. Invalid properties provided: ${invalidProps}`, + ); + } + } + + /** + * Get correct gateway based on authInfo. + * Can return either gateway for regular users (i.e. access token one) or for apps (subscriptionId one). + * Will throw an error if valid gateway could not be found / wrong configuration provided. + * + * @param authInfo authentication info + * @returns `CDLGateway` + */ + private getGatewayByAuthInfo(authInfo: AuthInfoV1): CDLGateway { + const headers = getAuthorizationHeaders(authInfo); + + if (HTTP_HEADER_SUBSCRIPTION_KEY in headers) { + if (this.cdlApiSubscriptionGateway) { + this.log.debug("Using subscription key gateway for this request"); + return this.cdlApiSubscriptionGateway; + } else { + throw new Error( + `cdlApiSubscriptionGateway not configured but found ${HTTP_HEADER_SUBSCRIPTION_KEY} in request header!`, + ); + } + } + + if (this.cdlApiGateway) { + this.log.debug("Using access token gateway for this request"); + return this.cdlApiGateway; + } else { + throw new Error( + `cdlApiGateway not configured, provide ${HTTP_HEADER_SUBSCRIPTION_KEY} to use subscription gateway!`, + ); + } + } + + /** + * Common logic for sending trail search requests + */ + private async searchRequest( + searchEndpoint: string, + args: SearchLineageRequestV1, + ): Promise { + Checks.truthy(searchEndpoint, "searchRequest() searchEndpoint"); + Checks.truthy(args.searchType, "searchRequest() args.searchType"); + Checks.truthy(args.fields, "searchRequest() args.fields"); + + const gateway = this.getGatewayByAuthInfo(args.authInfo); + const responseData = await gateway.request( + searchEndpoint, + args.authInfo, + {}, + { + searchType: args.searchType, + body: args.fields, + }, + ); + + if (responseData.result !== "OK") { + throw new Error(JSON.stringify(responseData)); + } + + this.log.debug(`searchRequest ${searchEndpoint} results:`, responseData); + + return responseData as SearchLineageResponseV1; + } + + /** + * Send request to `trail_registration` CDL endpoint. + */ + async registerHistoryData( + args: RegisterHistoryDataRequestV1, + ): Promise { + this.log.debug("registerHistoryData() args:", JSON.stringify(args)); + + // Check args + this.checkPropertyNames(args.tags); + this.checkPropertyNames(args.properties); + + const gateway = this.getGatewayByAuthInfo(args.authInfo); + const responseData = await gateway.request( + `trail_registration`, + args.authInfo, + {}, + { + "cdl:EventId": args.eventId ?? "", + "cdl:LineageId": args.lineageId ?? "", + "cdl:Tags": args.tags, + ...args.properties, + }, + ); + + if (responseData.result !== "OK") { + throw new Error(JSON.stringify(responseData)); + } + + this.log.debug("registerHistoryData results:", responseData); + return responseData as RegisterHistoryDataV1Response; + } + + /** + * Get data from `trail_acquisition` CDL endpoint. + */ + async getLineage(args: GetLineageRequestV1): Promise { + this.log.debug("getLineage() args:", JSON.stringify(args)); + Checks.truthy(args.eventId, "getLineage() args.eventId"); + + const direction = ( + args.direction ?? GetLineageOptionDirectionV1.Backward + ).toUpperCase(); + let depth = parseInt(args.depth ?? "-1", 10); + if (isNaN(depth)) { + this.log.warn( + "Could not parse depth from the argument, using default (-1). Wrong input:", + args.depth, + ); + depth = -1; + } + + const gateway = this.getGatewayByAuthInfo(args.authInfo); + const responseData = await gateway.request( + `trail_acquisition/${sanitizeHtml(args.eventId)}`, + args.authInfo, + { + direction, + depth, + }, + ); + + if (responseData.result !== "OK") { + throw new Error(JSON.stringify(responseData)); + } + + this.log.debug("getLineage results:", responseData); + + return responseData as GetLineageResponseV1; + } + + /** + * Search data using `trail_search_headers` CDL endpoint. + */ + async searchLineageByHeader( + args: SearchLineageRequestV1, + ): Promise { + this.log.debug("searchByHeader() args:", JSON.stringify(args)); + return this.searchRequest("trail_search_headers", args); + } + + /** + * Search data using `trail_search_globaldata` CDL endpoint. + */ + async searchLineageByGlobalData( + args: SearchLineageRequestV1, + ): Promise { + this.log.debug("searchByGlobalData() args:", JSON.stringify(args)); + return this.searchRequest("trail_search_globaldata", args); + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/public-api.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/public-api.ts new file mode 100755 index 0000000000..1678204b1e --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/public-api.ts @@ -0,0 +1,19 @@ +export * from "./generated/openapi/typescript-axios"; + +export { + PluginLedgerConnectorCDL, + IPluginLedgerConnectorCDLOptions, +} from "./plugin-ledger-connector-cdl"; + +export { PluginFactoryLedgerConnector } from "./plugin-factory-ledger-connector"; + +import { IPluginFactoryOptions } from "@hyperledger/cactus-core-api"; +import { PluginFactoryLedgerConnector } from "./plugin-factory-ledger-connector"; + +export * from "./generated/openapi/typescript-axios/api"; + +export async function createPluginFactory( + pluginFactoryOptions: IPluginFactoryOptions, +): Promise { + return new PluginFactoryLedgerConnector(pluginFactoryOptions); +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/type-defs.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/type-defs.ts new file mode 100644 index 0000000000..68af391529 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/type-defs.ts @@ -0,0 +1,86 @@ +import { + AuthInfoAccessTokenV1, + AuthInfoSubscriptionKeyV1, + AuthInfoV1, +} from "./public-api"; + +// CDL specific header names +export const HTTP_HEADER_SUBSCRIPTION_KEY = "Ocp-Apim-Subscription-Key"; +export const HTTP_HEADER_TRUST_USER_ID = "Trust-User-Id"; +export const HTTP_HEADER_TRUST_USER_ROLE = "Trust-User-Role"; +export const HTTP_HEADER_TRUST_AGENT_ID = "Trust-Agent-Id"; +export const HTTP_HEADER_TRUST_AGENT_ROLE = "Trust-Agent-Role"; + +export type HTTPAuthHeadersType = Record; + +/** + * Get HTTP request headers based on `AuthInfoV1` provided by the user. + * Will throw if mixed / invalid authInfo was provided. + * + * @param authInfo authentication data + * @returns object with headers and their values + */ +export function getAuthorizationHeaders( + authInfo: AuthInfoV1, +): HTTPAuthHeadersType { + if ( + isAuthInfoAccessTokenV1(authInfo) && + isAuthInfoSubscriptionKeyV1(authInfo) + ) { + throw new Error( + "Mixed authInfo configuration detected - use either accessToken or subscriptionKey!", + ); + } + + if (isAuthInfoAccessTokenV1(authInfo)) { + return { + Authorization: `Bearer ${authInfo.accessToken}`, + [HTTP_HEADER_TRUST_AGENT_ID]: authInfo.trustAgentId, + }; + } else if (isAuthInfoSubscriptionKeyV1(authInfo)) { + return { + [HTTP_HEADER_SUBSCRIPTION_KEY]: authInfo.subscriptionKey, + [HTTP_HEADER_TRUST_USER_ID]: authInfo.trustUserId, + [HTTP_HEADER_TRUST_USER_ROLE]: authInfo.trustUserRole, + [HTTP_HEADER_TRUST_AGENT_ID]: authInfo.trustAgentId, + [HTTP_HEADER_TRUST_AGENT_ROLE]: authInfo.trustAgentRole, + }; + } else { + throw new Error( + "Missing authInfo information or information not complete!", + ); + } +} + +/** + * True if provided argument is of `AuthInfoAccessTokenV1` type. + * Use for type narrowing. + */ +export function isAuthInfoAccessTokenV1( + authInfo: AuthInfoV1, +): authInfo is AuthInfoAccessTokenV1 { + const typedAuthInfo = authInfo as AuthInfoAccessTokenV1; + return ( + typedAuthInfo && + typeof typedAuthInfo.accessToken !== "undefined" && + typeof typedAuthInfo.trustAgentId !== "undefined" + ); +} + +/** + * True if provided argument is of `AuthInfoSubscriptionKeyV1` type. + * Use for type narrowing. + */ +export function isAuthInfoSubscriptionKeyV1( + authInfo: AuthInfoV1, +): authInfo is AuthInfoSubscriptionKeyV1 { + const typedAuthInfo = authInfo as AuthInfoSubscriptionKeyV1; + return ( + typedAuthInfo && + typeof typedAuthInfo.subscriptionKey !== "undefined" && + typeof typedAuthInfo.trustAgentId !== "undefined" && + typeof typedAuthInfo.trustAgentRole !== "undefined" && + typeof typedAuthInfo.trustUserId !== "undefined" && + typeof typedAuthInfo.trustUserRole !== "undefined" + ); +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/get-lineage-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/get-lineage-v1-endpoint.ts new file mode 100644 index 0000000000..d511a572fb --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/get-lineage-v1-endpoint.ts @@ -0,0 +1,99 @@ +import type { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, + safeStringifyException, +} from "@hyperledger/cactus-common"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; +import { PluginLedgerConnectorCDL } from "../plugin-ledger-connector-cdl"; +import OAS from "../../json/openapi.json"; + +export interface IGetLineageDataOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorCDL; +} + +export class GetLineageDataEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "GetLineageDataEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return GetLineageDataEndpoint.CLASS_NAME; + } + + constructor(public readonly options: IGetLineageDataOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/get-lineage"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/get-lineage" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + res.status(200).json(await this.options.connector.getLineage(req.body)); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + + res.status(500).json({ + message: "Internal Server Error", + error: safeStringifyException(ex), + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/register-history-data-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/register-history-data-v1-endpoint.ts new file mode 100644 index 0000000000..81155dc4e2 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/register-history-data-v1-endpoint.ts @@ -0,0 +1,101 @@ +import type { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, + safeStringifyException, +} from "@hyperledger/cactus-common"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; +import { PluginLedgerConnectorCDL } from "../plugin-ledger-connector-cdl"; +import OAS from "../../json/openapi.json"; + +export interface IRegisterHistoryDataOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorCDL; +} + +export class RegisterHistoryDataEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "RegisterHistoryDataEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return RegisterHistoryDataEndpoint.CLASS_NAME; + } + + constructor(public readonly options: IRegisterHistoryDataOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/register-history-data"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/register-history-data" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + res + .status(200) + .json(await this.options.connector.registerHistoryData(req.body)); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + + res.status(500).json({ + message: "Internal Server Error", + error: safeStringifyException(ex), + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/search-lineage-by-globaldata-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/search-lineage-by-globaldata-v1-endpoint.ts new file mode 100644 index 0000000000..1e6d7e7e8c --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/search-lineage-by-globaldata-v1-endpoint.ts @@ -0,0 +1,101 @@ +import type { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, + safeStringifyException, +} from "@hyperledger/cactus-common"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; +import { PluginLedgerConnectorCDL } from "../plugin-ledger-connector-cdl"; +import OAS from "../../json/openapi.json"; + +export interface ISearchLineageByGlobalDataOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorCDL; +} + +export class SearchLineageByGlobalDataEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "SearchLineageByGlobalDataEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return SearchLineageByGlobalDataEndpoint.CLASS_NAME; + } + + constructor(public readonly options: ISearchLineageByGlobalDataOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-globaldata"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-globaldata" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + res + .status(200) + .json(await this.options.connector.searchLineageByGlobalData(req.body)); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + + res.status(500).json({ + message: "Internal Server Error", + error: safeStringifyException(ex), + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/search-lineage-by-header-v1-endpoint.ts b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/search-lineage-by-header-v1-endpoint.ts new file mode 100644 index 0000000000..31f8de6948 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/main/typescript/web-services/search-lineage-by-header-v1-endpoint.ts @@ -0,0 +1,101 @@ +import type { Express, Request, Response } from "express"; + +import { + IWebServiceEndpoint, + IExpressRequestHandler, + IEndpointAuthzOptions, +} from "@hyperledger/cactus-core-api"; + +import { + Logger, + Checks, + LogLevelDesc, + LoggerProvider, + IAsyncProvider, + safeStringifyException, +} from "@hyperledger/cactus-common"; + +import { registerWebServiceEndpoint } from "@hyperledger/cactus-core"; +import { PluginLedgerConnectorCDL } from "../plugin-ledger-connector-cdl"; +import OAS from "../../json/openapi.json"; + +export interface ISearchLineageByHeaderOptions { + logLevel?: LogLevelDesc; + connector: PluginLedgerConnectorCDL; +} + +export class SearchLineageByHeaderEndpoint implements IWebServiceEndpoint { + public static readonly CLASS_NAME = "SearchLineageByHeaderEndpoint"; + + private readonly log: Logger; + + public get className(): string { + return SearchLineageByHeaderEndpoint.CLASS_NAME; + } + + constructor(public readonly options: ISearchLineageByHeaderOptions) { + const fnTag = `${this.className}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + Checks.truthy(options.connector, `${fnTag} arg options.connector`); + + const level = this.options.logLevel || "INFO"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + } + + public get oasPath(): (typeof OAS.paths)["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-header"] { + return OAS.paths[ + "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-cdl/search-lineage-by-header" + ]; + } + + public getPath(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.path; + } + + public getVerbLowerCase(): string { + return this.oasPath.post["x-hyperledger-cacti"].http.verbLowerCase; + } + + public getOperationId(): string { + return this.oasPath.post.operationId; + } + + getAuthorizationOptionsProvider(): IAsyncProvider { + // TODO: make this an injectable dependency in the constructor + return { + get: async () => ({ + isProtected: true, + requiredRoles: [], + }), + }; + } + + public async registerExpress( + expressApp: Express, + ): Promise { + await registerWebServiceEndpoint(expressApp, this); + return this; + } + + public getExpressRequestHandler(): IExpressRequestHandler { + return this.handleRequest.bind(this); + } + + public async handleRequest(req: Request, res: Response): Promise { + const reqTag = `${this.getVerbLowerCase()} - ${this.getPath()}`; + this.log.debug(reqTag); + try { + res + .status(200) + .json(await this.options.connector.searchLineageByHeader(req.body)); + } catch (ex) { + this.log.error(`Crash while serving ${reqTag}`, ex); + + res.status(500).json({ + message: "Internal Server Error", + error: safeStringifyException(ex), + }); + } + } +} diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/test/typescript/manual/cdl-connector-manual.test.ts b/packages/cactus-plugin-ledger-connector-cdl/src/test/typescript/manual/cdl-connector-manual.test.ts new file mode 100644 index 0000000000..ce9d13ed7e --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/test/typescript/manual/cdl-connector-manual.test.ts @@ -0,0 +1,492 @@ +/** + * Manual tests for CDL connector. + * Must be executed with access to CDL service. + * Check out CDL connector readme for instructions on how to run these tests. + * To run: + * npx jest dist/lib/test/typescript/manual/cdl-connector-manual.test.js + */ + +////////////////////////////////// +// Constants +////////////////////////////////// + +// Setup: Obtain either accessToken or subscription key and fill matching authInfo structure below. +const cdlUrl = "https://en-apigateway.research.global.fujitsu.com/dataetrust/"; +const cdlSubscriptionUrl = + "https://en-apigateway.research.global.fujitsu.com/dataetrust/"; +const skipCertCheck = true; +// const authInfo = { +// accessToken: "_____accessToken_____" +// trustAgentId: "_____trustAgentId_____", +// }; +const authInfo = { + subscriptionKey: "_____subscriptionKey_____", + trustAgentId: "_____trustAgentId_____", + trustAgentRole: "_____trustAgentRole_____", + trustUserId: "_____trustUserId_____", + trustUserRole: "_____trustUserRole_____", +}; + +const rateLimitTimeout = 1000 * 2; // 2 seconds +const testLogLevel: LogLevelDesc = "info"; +const sutLogLevel: LogLevelDesc = "info"; +const setupTimeout = 1000 * 60; // 1 minute timeout for setup + +// ApiClient settings +const syncReqTimeout = 1000 * 10; // 10 seconds + +import http from "node:http"; +import { AddressInfo } from "node:net"; +import "jest-extended"; +import express from "express"; +import bodyParser from "body-parser"; +import { v4 as uuidV4 } from "uuid"; +import { Configuration } from "@hyperledger/cactus-core-api"; + +import { + LogLevelDesc, + LoggerProvider, + Logger, + IListenOptions, + Servers, +} from "@hyperledger/cactus-common"; + +import { + AuthInfoV1, + DefaultApi as CDLApi, + GetLineageOptionDirectionV1, + GetLineageRequestV1, + PluginLedgerConnectorCDL, + RegisterHistoryDataRequestV1, + SearchLineageRequestV1, + SearchLineageTypeV1, + TrailEventDetailsV1, +} from "../../../main/typescript/index"; + +// Logger setup +const log: Logger = LoggerProvider.getOrCreate({ + label: "cdl-connector-manual.test", + level: testLogLevel, +}); + +describe("CDL Connector manual tests", () => { + let apiClient: CDLApi; + let connector: PluginLedgerConnectorCDL; + const expressApp = express(); + expressApp.use(bodyParser.json({ limit: "250mb" })); + const server = http.createServer(expressApp); + + ////////////////////////////////// + // Helper Methods + ////////////////////////////////// + + async function registerHistoryDataOnCDL( + request: RegisterHistoryDataRequestV1, + ) { + const response = await apiClient.registerHistoryDataV1(request); + + const status = response.status; + log.info( + "registerHistoryDataOnCDL done. Status", + status, + response.statusText, + ); + expect(status).toEqual(200); + + expect(response.data).toBeTruthy(); + expect(response.data.result).toEqual("OK"); + expect(response.data.detail).toBeTruthy(); + const event = response.data.detail; + log.debug("registerHistoryDataOnCDL event", event); + expect(event).toBeTruthy(); + expect(event["cdl:Lineage"]).toBeTruthy(); + expect(event["cdl:Lineage"]["cdl:EventId"]).toBeTruthy(); + expect(event["cdl:Lineage"]["cdl:LineageId"]).toBeTruthy(); + expect(event["cdl:Tags"]).toBeTruthy(); + expect(event["cdl:Verification"]).toBeTruthy(); + return event; + } + + async function getLineageFromCDL( + request: GetLineageRequestV1, + ): Promise { + const response = await apiClient.getLineageV1(request); + + const status = response.status; + log.info("getLineageFromCDL done. Status", status, response.statusText); + expect(status).toEqual(200); + + expect(response.data).toBeTruthy(); + expect(response.data.result).toEqual("OK"); + expect(response.data.detail).toBeTruthy(); + const eventList = response.data.detail; + log.debug("getLineageFromCDL eventList", eventList); + expect(eventList).toBeTruthy(); + expect(eventList.length).toBeGreaterThan(0); + return eventList; + } + + async function searchByHeaderOnCDL( + request: SearchLineageRequestV1, + ): Promise { + const response = await apiClient.searchLineageByHeaderV1(request); + + const status = response.status; + log.info("searchByHeaderOnCDL done. Status", status, response.statusText); + expect(status).toEqual(200); + + expect(response.data).toBeTruthy(); + expect(response.data.result).toEqual("OK"); + expect(response.data.detail).toBeTruthy(); + const eventList = response.data.detail; + log.debug("searchByHeaderOnCDL eventList", eventList); + expect(eventList).toBeTruthy(); + return eventList; + } + + async function searchByGlobalDataOnCDL( + request: SearchLineageRequestV1, + ): Promise { + const response = await apiClient.searchLineageByGlobalDataV1(request); + + const status = response.status; + log.info( + "searchByGlobalDataOnCDL done. Status", + status, + response.statusText, + ); + expect(status).toEqual(200); + + expect(response.data).toBeTruthy(); + expect(response.data.result).toEqual("OK"); + expect(response.data.detail).toBeTruthy(); + const eventList = response.data.detail; + log.debug("searchByGlobalDataOnCDL eventList", eventList); + expect(eventList).toBeTruthy(); + return eventList; + } + + ////////////////////////////////// + // Environment Setup + ////////////////////////////////// + + beforeAll(async () => { + const listenOptions: IListenOptions = { + hostname: "127.0.0.1", + port: 0, + server, + }; + const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo; + const { address, port } = addressInfo; + const apiHost = `http://${address}:${port}`; + apiClient = new CDLApi(new Configuration({ basePath: apiHost })); + + connector = new PluginLedgerConnectorCDL({ + instanceId: uuidV4(), + logLevel: sutLogLevel, + cdlApiGateway: { + url: cdlUrl, + userAgent: "cactiCdlConnectorManualTest", + skipCertCheck, + }, + cdlApiSubscriptionGateway: { + url: cdlSubscriptionUrl, + userAgent: "cactiCdlConnectorManualTest", + skipCertCheck, + }, + }); + await connector.getOrCreateWebServices(); + await connector.registerWebServices(expressApp); + }, setupTimeout); + + afterAll(async () => { + log.info("Close connector server..."); + await Servers.shutdown(server); + + if (connector) { + log.info("Close the connector..."); + await connector.shutdown(); + } + }, setupTimeout); + + afterEach(async () => { + // Limit number of requests send to prevent errors + await new Promise((resolve) => setTimeout(resolve, rateLimitTimeout)); + }); + + ////////////////////////////////// + // Tests + ////////////////////////////////// + + test( + "Request fails when authInfo is missing", + async () => { + try { + await registerHistoryDataOnCDL({ + authInfo: {} as AuthInfoV1, + tags: { + test: "abc", + }, + properties: { + prop1: "abc", + prop2: "cba", + }, + }); + expect(true).toBeFalse(); // Should fail! + } catch (error) { + log.info("Failed as expected!"); + } + }, + syncReqTimeout * 2, + ); + + test( + "Request fails when mixed authInfo is used", + async () => { + try { + await registerHistoryDataOnCDL({ + authInfo: { + accessToken: "foo-accessToken", + subscriptionKey: "foo-subscriptionKey", + trustAgentId: "foo-trustAgentId", + trustAgentRole: "foo-trustAgentRole", + trustUserId: "foo-trustUserId", + trustUserRole: "foo-trustUserRole", + }, + tags: { + test: "abc", + }, + properties: { + prop1: "abc", + prop2: "cba", + }, + }); + expect(true).toBeFalse(); // Should fail! + } catch (error) { + log.info("Failed as expected!"); + } + }, + syncReqTimeout * 2, + ); + + test("Register single history data", async () => { + const newEvent = await registerHistoryDataOnCDL({ + authInfo, + tags: { + test: "abc", + }, + properties: { + prop1: "abc", + prop2: "cba", + }, + }); + + // Check custom properties and tags + expect(newEvent["cdl:Event"]["prop1"]).toEqual("abc"); + expect(newEvent["cdl:Event"]["prop2"]).toEqual("cba"); + expect(newEvent["cdl:Tags"]["test"]).toEqual("abc"); + }); + + test("Register history data in single lineage", async () => { + const firstEvent = await registerHistoryDataOnCDL({ + authInfo, + }); + const firstEventId = firstEvent["cdl:Lineage"]["cdl:EventId"].toString(); + + const secondEvent = await registerHistoryDataOnCDL({ + authInfo, + lineageId: firstEventId, + }); + + // Check if two events belong to same lineage + expect(secondEvent["cdl:Lineage"]["cdl:LineageId"]).toEqual(firstEventId); + }); + + /** + * Tests for getLineage endpoint + */ + describe("Get lineage tests", () => { + const eventsInLineage: string[] = []; + + beforeAll(async () => { + const firstEvent = await registerHistoryDataOnCDL({ + authInfo, + }); + const firstEventId = firstEvent["cdl:Lineage"]["cdl:EventId"].toString(); + log.info("First eventId (lineageId):", firstEventId); + eventsInLineage.push(firstEventId); + + for (let i = 0; i < 2; i++) { + const event = await registerHistoryDataOnCDL({ + authInfo, + lineageId: firstEventId, + }); + eventsInLineage.push(event["cdl:Lineage"]["cdl:EventId"].toString()); + } + + log.info("Events in test lineage:", eventsInLineage); + }); + + // both middle + test("Get lineage forward all (default) on the first event (lineageId)", async () => { + const eventList = await getLineageFromCDL({ + authInfo, + eventId: eventsInLineage[0], + direction: GetLineageOptionDirectionV1.Forward, + depth: "-1", + }); + + // Forward from first should return all events in lineage + expect(eventList.length).toEqual(eventsInLineage.length); + }); + + test("Get lineage forward all (default) on the last event", async () => { + const eventList = await getLineageFromCDL({ + authInfo, + eventId: eventsInLineage[eventsInLineage.length - 1], + direction: GetLineageOptionDirectionV1.Forward, + depth: "-1", + }); + + // Forward from last should return only one event + expect(eventList.length).toEqual(1); + }); + + test("Get lineage backward all on the last event", async () => { + const eventList = await getLineageFromCDL({ + authInfo, + eventId: eventsInLineage[eventsInLineage.length - 1], + direction: GetLineageOptionDirectionV1.Backward, + depth: "-1", + }); + + // Backward from last should return all events in lineage + expect(eventList.length).toEqual(eventsInLineage.length); + }); + + test("Get lineage both all on the middle event", async () => { + const eventList = await getLineageFromCDL({ + authInfo, + eventId: eventsInLineage[1], + direction: GetLineageOptionDirectionV1.Both, + depth: "-1", + }); + + // Both on middle event should return all events in lineage + expect(eventList.length).toEqual(eventsInLineage.length); + }); + }); + + /** + * Tests for searchByHeader and searchByGlobalData endpoints + */ + describe("Search data tests", () => { + const privateTagValue = uuidV4(); + const customEventPropValue = uuidV4(); + let searchedEvent: TrailEventDetailsV1; + let searchedEventTimestamp: string; + + beforeAll(async () => { + searchedEvent = await registerHistoryDataOnCDL({ + authInfo, + tags: { + privateTag: privateTagValue, + }, + properties: { + customEventProp: customEventPropValue, + }, + }); + log.info("Event to search for:", searchedEvent); + + searchedEventTimestamp = + searchedEvent["cdl:Lineage"][ + "cdl:DataRegistrationTimeStamp" + ].toString(); + }); + + test("Search header data using exact match", async () => { + log.info( + "Search for events with exact timestamp:", + searchedEventTimestamp, + ); + const events = await searchByHeaderOnCDL({ + authInfo, + searchType: SearchLineageTypeV1.ExactMatch, + fields: { + "cdl:DataRegistrationTimeStamp": searchedEventTimestamp, + }, + }); + + expect(events).toBeTruthy(); + expect(events.length).toBeGreaterThan(0); + for (const e of events) { + expect(e["cdl:Lineage"]["cdl:DataRegistrationTimeStamp"]).toEqual( + searchedEventTimestamp, + ); + } + }); + + test("Search header data using partial match", async () => { + const datePart = searchedEventTimestamp.split("T")[0]; + log.info("Search for events with partialmatch (date):", datePart); + + const events = await searchByHeaderOnCDL({ + authInfo, + searchType: SearchLineageTypeV1.PartialMatch, + fields: { + "cdl:DataRegistrationTimeStamp": datePart, + }, + }); + + expect(events).toBeTruthy(); + expect(events.length).toBeGreaterThan(0); + for (const e of events) { + expect(e["cdl:Lineage"]["cdl:DataRegistrationTimeStamp"]).toMatch( + datePart, + ); + } + }); + + test("Search header data using regex match", async () => { + const datePart = searchedEventTimestamp.split("T")[0]; + const dateRegex = `^${datePart}.*$`; + log.info("Search for events with regexpmatch:", dateRegex); + + const events = await searchByHeaderOnCDL({ + authInfo, + searchType: SearchLineageTypeV1.RegexMatch, + fields: { + "cdl:DataRegistrationTimeStamp": dateRegex, + }, + }); + + expect(events).toBeTruthy(); + expect(events.length).toBeGreaterThan(0); + for (const e of events) { + expect(e["cdl:Lineage"]["cdl:DataRegistrationTimeStamp"]).toMatch( + datePart, + ); + } + }); + + test("Search global data using exact match", async () => { + log.info( + "Search for events with exact customEventProp:", + customEventPropValue, + ); + const events = await searchByGlobalDataOnCDL({ + authInfo, + searchType: SearchLineageTypeV1.ExactMatch, + fields: { + customEventProp: customEventPropValue, + }, + }); + + expect(events).toBeTruthy(); + expect(events.length).toEqual(1); + for (const e of events) { + expect(e["cdl:Event"]["customEventProp"]).toEqual(customEventPropValue); + } + }); + }); +}); diff --git a/packages/cactus-plugin-ledger-connector-cdl/src/test/typescript/unit/api-surface.test.ts b/packages/cactus-plugin-ledger-connector-cdl/src/test/typescript/unit/api-surface.test.ts new file mode 100644 index 0000000000..34aba3a0ae --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/src/test/typescript/unit/api-surface.test.ts @@ -0,0 +1,6 @@ +import * as apiSurface from "../../../main/typescript/public-api"; +import "jest-extended"; + +test("Library can be loaded", async () => { + expect(apiSurface).toBeTruthy(); +}); diff --git a/packages/cactus-plugin-ledger-connector-cdl/tsconfig.json b/packages/cactus-plugin-ledger-connector-cdl/tsconfig.json new file mode 100644 index 0000000000..3e32cda3c2 --- /dev/null +++ b/packages/cactus-plugin-ledger-connector-cdl/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./dist/lib", + "declarationDir": "./dist/lib", + "resolveJsonModule": true, + "rootDir": "./src", + "tsBuildInfoFile": "../../.build-cache/cactus-plugin-ledger-connector-cdl.tsbuildinfo" + }, + "include": ["./src", "src/**/*.json"], + "references": [ + { + "path": "../cactus-common/tsconfig.json" + }, + { + "path": "../cactus-core/tsconfig.json" + }, + { + "path": "../cactus-core-api/tsconfig.json" + } + ] +} diff --git a/tsconfig.json b/tsconfig.json index 52401c758d..3eead32f3c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -67,6 +67,9 @@ { "path": "./packages/cactus-plugin-ledger-connector-go-ethereum-socketio/tsconfig.json" }, + { + "path": "./packages/cactus-plugin-ledger-connector-cdl/tsconfig.json" + }, { "path": "./packages/cactus-plugin-ledger-connector-cdl-socketio/tsconfig.json" }, diff --git a/yarn.lock b/yarn.lock index 135237a76e..e5e30aee8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7835,6 +7835,25 @@ __metadata: languageName: unknown linkType: soft +"@hyperledger/cactus-plugin-ledger-connector-cdl@workspace:packages/cactus-plugin-ledger-connector-cdl": + version: 0.0.0-use.local + resolution: "@hyperledger/cactus-plugin-ledger-connector-cdl@workspace:packages/cactus-plugin-ledger-connector-cdl" + dependencies: + "@hyperledger/cactus-common": 2.0.0-alpha.2 + "@hyperledger/cactus-core": 2.0.0-alpha.2 + "@hyperledger/cactus-core-api": 2.0.0-alpha.2 + "@types/express": 4.17.19 + "@types/node": 18.11.9 + "@types/sanitize-html": 2.6.2 + axios: 1.6.0 + body-parser: 1.20.2 + express: 4.18.2 + jest-extended: 4.0.1 + sanitize-html: 2.7.0 + uuid: 8.3.2 + languageName: unknown + linkType: soft + "@hyperledger/cactus-plugin-ledger-connector-corda@2.0.0-alpha.2, @hyperledger/cactus-plugin-ledger-connector-corda@workspace:packages/cactus-plugin-ledger-connector-corda": version: 0.0.0-use.local resolution: "@hyperledger/cactus-plugin-ledger-connector-corda@workspace:packages/cactus-plugin-ledger-connector-corda" @@ -13846,6 +13865,13 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:18.11.9": + version: 18.11.9 + resolution: "@types/node@npm:18.11.9" + checksum: cc0aae109e9b7adefc32eecb838d6fad931663bb06484b5e9cbbbf74865c721b03d16fd8d74ad90e31dbe093d956a7c2c306ba5429ba0c00f3f7505103d7a496 + languageName: node + linkType: hard + "@types/node@npm:20.4.7": version: 20.4.7 resolution: "@types/node@npm:20.4.7"