Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cactus-plugin-ledger-connector-cdl-socketio): support subscription key auth #2785

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions packages/cactus-plugin-ledger-connector-cdl-socketio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ This plugin provides `Cacti` a way to interact with Fujitsu CDL networks. Using

#### Configuring CDL API Gateway Access

- Set the base URL of GW service in `cdlApiGateway.url`. Do not include `api/v1`, just the base URL. (example: `"http://localhost:3000"`).
- Set the base URL of GW service in `cdlApiGateway.url` (example: `"http://localhost:3000"`).
- If the service certificate is signed with a known CA (node uses Mozilla DB), then you can skip the next steps.
- If the service is signed with unknown CA, you can specify the gateway certificate to trust manually:
- Set `cdlApiGateway.caPath` to path of API Gateway certificate (in PEM format). (example: `"/etc/cactus/connector-cdl-socketio/CA/cdl-api-gateway-ca.pem"`)
Expand Down Expand Up @@ -67,14 +67,16 @@ npm run start

## Manual Tests

- There are no automatic tests for this plugin.
- `cdl-connector-manual.test` contains a Jest test script that will check every implemented operation on a running CDL instance.
- There are no automatic tests for this plugin because there's no private instance of CDL available at a time.
- `cdl-connector-manual.test` 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.**
- Before running the script you must update the following variables in it:
- `ACCESS_TOKEN` - JWT token for authorization in CDL gateway.
- `TOKEN_AGENT_ID` - Token agent ID.
- `authInfo` - either `accessToken` or `subscriptionKey` based configuration.
- `VALIDATOR_KEY_PATH` - Path to validator public certificate.
- 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:
- `npm run build`
- `npx jest dist/lib/test/typescript/integration/cdl-connector-manual.test.js`

## Contributing

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FunctionArgsType } from "./type-defs";
import { read as configRead } from "../common/core/config";
import { cdlRequest } from "./cdl-request";
import sanitizeHtml from "sanitize-html";
Expand All @@ -8,23 +9,6 @@ import { getLogger } from "log4js";
const logger = getLogger("ServerPlugin[" + process.pid + "]");
logger.level = configRead("logLevel", "info");

type SupportedFunctions =
| "registerHistoryData"
| "getLineage"
| "searchByHeader"
| "searchByGlobalData"
| "status";

type FunctionArgsType = {
method: {
type: SupportedFunctions;
accessToken?: string;
trustAgentId?: string;
};
args: any;
reqID?: string;
};

/*
* ServerPlugin
* Class definition for server plugins
Expand Down Expand Up @@ -87,7 +71,7 @@ export class ServerPlugin {

const responseData = await cdlRequest(
`trail_registration`,
getAccessTokenOrThrow(args),
args.method.authInfo,
{},
{
"cdl:EventId": typedArgs.eventId ?? "",
Expand Down Expand Up @@ -133,7 +117,7 @@ export class ServerPlugin {

const responseData = await cdlRequest(
`trail_acquisition/${sanitizeHtml(typedArgs.eventId)}`,
getAccessTokenOrThrow(args),
args.method.authInfo,
{
direction,
depth,
Expand Down Expand Up @@ -197,7 +181,7 @@ async function searchRequest(

const responseData = await cdlRequest(
searchType,
getAccessTokenOrThrow(args),
args.method.authInfo,
{},
{
searchType: typedArgs.searchType,
Expand All @@ -211,18 +195,3 @@ async function searchRequest(

return responseData;
}

function getAccessTokenOrThrow(args: FunctionArgsType): [string, string] {
const accessToken = args?.method?.accessToken;
const trustAgentId = args?.method?.trustAgentId;

if (!accessToken) {
throw new Error("Missing CDL accessToken");
}

if (!trustAgentId) {
throw new Error("Missing CDL trustAgentId");
}

return [accessToken, trustAgentId];
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import {
AuthInfoArgsType,
isAuthInfoAccessTokenArgsType,
isAuthInfoSubscriptionKeyArgsType,
} from "./type-defs";
import { read as configRead } from "../common/core/config";
import axios from "axios";
import https from "https";
Expand Down Expand Up @@ -43,7 +48,7 @@ const COMMON_HTTPS_AGENT = getHttpsAgent();

export async function cdlRequest(
url: string,
accessToken: [string, string],
authInfo: AuthInfoArgsType,
queryParams?: any,
dataPayload?: any,
) {
Expand All @@ -59,13 +64,12 @@ export async function cdlRequest(
httpsAgent: COMMON_HTTPS_AGENT,
method: httpMethod,
baseURL: configRead<string>("cdlApiGateway.url"),
url: `api/v1/${url}`,
url,
responseType: "json",
headers: {
"User-Agent": configRead<string>("userAgent", "CactiCDLConnector"),
Authorization: `Bearer ${accessToken[0]}`,
"Trust-Agent-Id": accessToken[1],
"Content-Type": "application/json;charset=UTF-8",
...getAuthorizationHeaders(authInfo),
},
params: queryParams,
data: dataPayload,
Expand All @@ -80,3 +84,35 @@ export async function cdlRequest(
throw error;
}
}

function getAuthorizationHeaders(
authInfo: AuthInfoArgsType,
): Record<string, string | number | boolean> {
if (
isAuthInfoAccessTokenArgsType(authInfo) &&
isAuthInfoSubscriptionKeyArgsType(authInfo)
) {
throw new Error(
"Mixed authInfo configuration detected - use either accessToken or subscriptionKey!",
);
}

if (isAuthInfoAccessTokenArgsType(authInfo)) {
return {
Authorization: `Bearer ${authInfo.accessToken}`,
"Trust-Agent-Id": authInfo.trustAgentId,
};
} else if (isAuthInfoSubscriptionKeyArgsType(authInfo)) {
return {
"Ocp-Apim-Subscription-Key": authInfo.subscriptionKey,
"Trust-User-Id": authInfo.trustUserId,
"Trust-User-Role": authInfo.trustUserRole,
"Trust-Agent-Id": authInfo.trustAgentId,
"Trust-Agent-Role": authInfo.trustAgentRole,
};
} else {
throw new Error(
"Missing authInfo information or information not complete!",
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { type } from "os";

export type SupportedFunctions =
| "registerHistoryData"
| "getLineage"
| "searchByHeader"
| "searchByGlobalData"
| "status";

export type AuthInfoAccessTokenArgsType = {
accessToken: string;
trustAgentId: string;
};

export type AuthInfoSubscriptionKeyArgsType = {
subscriptionKey: string;
trustAgentId: string;
trustAgentRole: string;
trustUserId: string;
trustUserRole: string;
};

export type AuthInfoArgsType =
| AuthInfoAccessTokenArgsType
| AuthInfoSubscriptionKeyArgsType;

export function isAuthInfoAccessTokenArgsType(
authInfo: AuthInfoArgsType,
): authInfo is AuthInfoAccessTokenArgsType {
const typedAuthInfo = authInfo as AuthInfoAccessTokenArgsType;
return (
typedAuthInfo &&
typeof typedAuthInfo.accessToken !== "undefined" &&
typeof typedAuthInfo.trustAgentId !== "undefined"
);
}

export function isAuthInfoSubscriptionKeyArgsType(
authInfo: AuthInfoArgsType,
): authInfo is AuthInfoSubscriptionKeyArgsType {
const typedAuthInfo = authInfo as AuthInfoSubscriptionKeyArgsType;
return (
typedAuthInfo &&
typeof typedAuthInfo.subscriptionKey !== "undefined" &&
typeof typedAuthInfo.trustAgentId !== "undefined" &&
typeof typedAuthInfo.trustAgentRole !== "undefined" &&
typeof typedAuthInfo.trustUserId !== "undefined" &&
typeof typedAuthInfo.trustUserRole !== "undefined"
);
}

export type FunctionArgsType = {
method: {
type: SupportedFunctions;
authInfo: AuthInfoArgsType;
};
args: any;
reqID?: string;
};
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
/**
* Manual tests for CDL connector.
* Must be exectued on Azure environment with access to CDL service.
* Must be exectued with access to CDL service.
* Check out CDL connector readme for instructions on how to run these tests.
*/

//////////////////////////////////
// Constants
//////////////////////////////////

const ACCESS_TOKEN = "_____FILL_TOKEN_HERE_____"
const TOKEN_AGENT_ID = "_____FILL_AGENT_ID_HERE_____"
const VALIDATOR_KEY_PATH = "_____FILL_KEY_PATH_HERE_____"
// Setup: Start validator first and store it's crt under this path
const VALIDATOR_KEY_PATH =
"/etc/cactus/connector-cdl-socketio/CA/connector.crt";

// Setup: Obtain eitehr accessToken or subscription key and fill matching authInfo structure below.
// const authInfo = {
// accessToken: "_____accessToken_____"
// trustAgentId: "_____trustAgentId_____",
// };
const authInfo = {
subscriptionKey: "_____subscriptionKey_____",
trustAgentId: "_____trustAgentId_____",
trustAgentRole: "_____trustAgentRole_____",
trustUserId: "_____trustUserId_____",
trustUserRole: "_____trustUserRole_____",
};

const testLogLevel: LogLevelDesc = "info";
const sutLogLevel: LogLevelDesc = "info";
Expand Down Expand Up @@ -51,8 +64,7 @@ describe("CDL Connector manual tests", () => {
{},
{
type: "registerHistoryData",
accessToken: ACCESS_TOKEN,
trustAgentId: TOKEN_AGENT_ID,
authInfo,
},
args,
);
Expand All @@ -76,8 +88,7 @@ describe("CDL Connector manual tests", () => {
{},
{
type: "getLineage",
accessToken: ACCESS_TOKEN,
trustAgentId: TOKEN_AGENT_ID,
authInfo,
},
args,
);
Expand All @@ -97,8 +108,7 @@ describe("CDL Connector manual tests", () => {
{},
{
type: "searchByHeader",
accessToken: ACCESS_TOKEN,
trustAgentId: TOKEN_AGENT_ID,
authInfo,
},
args,
);
Expand All @@ -117,8 +127,7 @@ describe("CDL Connector manual tests", () => {
{},
{
type: "searchByGlobalData",
accessToken: ACCESS_TOKEN,
trustAgentId: TOKEN_AGENT_ID,
authInfo,
},
args,
);
Expand Down Expand Up @@ -212,6 +221,60 @@ describe("CDL Connector manual tests", () => {
expect(response.data.status).toEqual("OK.");
});

test(
"Request fails when authInfo is missing",
async () => {
const response = await apiClient.sendSyncRequest(
{},
{
type: "registerHistoryData",
},
{
eventId: "",
lineageId: "",
tags: {},
properties: {
prop1: "shouldFail",
prop2: "shouldFail",
},
},
);
expect(response.status).toEqual(504);
},
syncReqTimeout * 2,
);

test(
"Request fails when mixed authInfo is used",
async () => {
const response = await apiClient.sendSyncRequest(
{},
{
type: "registerHistoryData",
authInfo: {
accessToken: "foo-accessToken",
subscriptionKey: "foo-subscriptionKey",
trustAgentId: "foo-trustAgentId",
trustAgentRole: "foo-trustAgentRole",
trustUserId: "foo-trustUserId",
trustUserRole: "foo-trustUserRole",
},
},
{
eventId: "",
lineageId: "",
tags: {},
properties: {
prop1: "shouldFail",
prop2: "shouldFail",
},
},
);
expect(response.status).toEqual(504);
},
syncReqTimeout * 2,
);

test("Register single history data", async () => {
const newEvent = await registerHistoryDataOnCDL({
eventId: "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"./src/main/typescript/common/core/bin/*.ts",
"./src/main/typescript/common/core/config/*.ts",
"./src/main/typescript/connector/*.ts",
"./src/main/typescript/*.ts"
"./src/main/typescript/*.ts",
"./src/test/typescript/integration/*.ts"
],
"references": [
{
Expand Down
Loading