Skip to content

Commit

Permalink
fix: add validation on data.method when using transport.request (#801)
Browse files Browse the repository at this point in the history
* fix: add validation on data.method when using tranport.request

Signed-off-by: SuZhoue-Joe <suzhou@amazon.com>

* feat: add validation on endpoint

Signed-off-by: SuZhoue-Joe <suzhou@amazon.com>

* feat: add unit test

Signed-off-by: SuZhoue-Joe <suzhou@amazon.com>

* feat: add more protect

Signed-off-by: SuZhoue-Joe <suzhou@amazon.com>

---------

Signed-off-by: SuZhoue-Joe <suzhou@amazon.com>
  • Loading branch information
SuZhou-Joe committed Jul 5, 2023
1 parent c821a2a commit 13fb97f
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 1 deletion.
97 changes: 97 additions & 0 deletions server/services/CommonService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {
ILegacyCustomClusterClient,
OpenSearchDashboardsRequest,
OpenSearchDashboardsResponseFactory,
RequestHandlerContext,
} from "opensearch-dashboards/server";
import CommonService from "./CommonService";

const contextMock = {
core: {},
} as RequestHandlerContext;
const responseMock = ({
custom: jest.fn((args) => args),
} as unknown) as OpenSearchDashboardsResponseFactory;

const mockedClient = {
callAsCurrentUser: jest.fn(),
callAsInternalUser: jest.fn(),
close: jest.fn(),
asScoped: jest.fn(() => ({
callAsCurrentUser: jest.fn((...args) => args),
callAsInternalUser: jest.fn(),
})),
} as any;

describe("CommonService spec", () => {
it("http method should valid when calling transport.request", async () => {
const commonService = new CommonService(mockedClient);
const result = await commonService.apiCaller(
contextMock,
{
body: {
endpoint: "transport.request",
data: {
method: "invalid method",
},
},
} as OpenSearchDashboardsRequest,
responseMock
);
expect(result).toEqual({
statusCode: 200,
body: {
ok: false,
error: `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE']. Received 'invalid method'.`,
},
});
});

it("should return error when no endpoint is provided", async () => {
const commonService = new CommonService(mockedClient);
const result = await commonService.apiCaller(
contextMock,
{
body: {
endpoint: "",
},
} as OpenSearchDashboardsRequest,
responseMock
);
expect(result).toEqual({
statusCode: 200,
body: {
ok: false,
error: `Expected non-empty string on endpoint`,
},
});
});

it("should patch path when data.path does not start with /", async () => {
const commonService = new CommonService(mockedClient);
const result = await commonService.apiCaller(
contextMock,
{
body: {
endpoint: "transport.request",
data: {
path: "",
},
},
} as OpenSearchDashboardsRequest,
responseMock
);
expect(result).toEqual({
statusCode: 200,
body: {
ok: true,
response: [
"transport.request",
{
path: "/",
},
],
},
});
});
});
34 changes: 33 additions & 1 deletion server/services/CommonService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {
} from "../../../../src/core/server";
import { IAPICaller } from "../../models/interfaces";

const VALID_METHODS = ["HEAD", "GET", "POST", "PUT", "DELETE"];

export interface ICommonCaller {
<T>(arg: any): T;
}

export default class IndexService {
export default class CommonService {
osDriver: ILegacyCustomClusterClient;

constructor(osDriver: ILegacyCustomClusterClient) {
Expand All @@ -36,12 +38,42 @@ export default class IndexService {
try {
const { callAsCurrentUser: callWithRequest } = this.osDriver.asScoped(request);
const finalData = data;

/**
* The endpoint must not be an empty string, reference from proxy caller
*/
if (!endpoint) {
return response.custom({
statusCode: 200,
body: {
ok: false,
error: `Expected non-empty string on endpoint`,
},
});
}

/**
* Update path parameter to follow RFC/generic HTTP convention
*/
if (endpoint === "transport.request" && typeof finalData?.path === "string" && !/^\//.test(finalData?.path || "")) {
finalData.path = `/${finalData.path || ""}`;
}

/**
* Check valid method here
*/
if (endpoint === "transport.request" && data?.method) {
if (VALID_METHODS.indexOf(data.method.toUpperCase?.()) === -1) {
return response.custom({
statusCode: 200,
body: {
ok: false,
error: `Method must be one of, case insensitive ['HEAD', 'GET', 'POST', 'PUT', 'DELETE']. Received '${data.method}'.`,
},
});
}
}

const payload = useQuery ? JSON.parse(finalData || "{}") : finalData;
const commonCallerResponse = await callWithRequest(endpoint, payload || {});
return response.custom({
Expand Down

0 comments on commit 13fb97f

Please sign in to comment.