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

[#IOPAE-175] Update RegenerateServiceKey with Manage Flow #265

Merged
merged 2 commits into from
Feb 15, 2023
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
196 changes: 195 additions & 1 deletion RegenerateServiceKey/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import * as reporters from "@pagopa/ts-commons/lib/reporters";
import { SubscriptionKeyTypeEnum } from "@pagopa/io-functions-admin-sdk/SubscriptionKeyType";
import { SubscriptionKeyTypePayload } from "@pagopa/io-functions-admin-sdk/SubscriptionKeyTypePayload";
import { RegenerateServiceKeyHandler } from "../handler";
import { SubscriptionWithoutKeys } from "@pagopa/io-functions-admin-sdk/SubscriptionWithoutKeys";
import { Subscription } from "@pagopa/io-functions-admin-sdk/Subscription";

const mockContext = {
// eslint-disable no-console
Expand All @@ -42,6 +44,10 @@ const anOrganizationFiscalCode = "01234567890" as OrganizationFiscalCode;
const anEmail = "test@example.com" as EmailString;

const aServiceId = "s123" as NonEmptyString;
const aManageSubscriptionId = "MANAGE-123" as NonEmptyString;
const aUserId = "u123" as NonEmptyString;
const aDifferentUserId = "u456" as NonEmptyString;

const someSubscriptionKeys = {
primary_key: "primary_key",
secondary_key: "secondary_key"
Expand Down Expand Up @@ -86,7 +92,27 @@ const aUserAuthenticationDeveloper: IAzureApiAuthorization = {
groups: new Set([UserGroup.ApiServiceRead, UserGroup.ApiServiceWrite]),
kind: "IAzureApiAuthorization",
subscriptionId: aServiceId,
userId: "u123" as NonEmptyString
userId: aUserId
};
const aUserAuthenticationDeveloperWithManageKey: IAzureApiAuthorization = {
...aUserAuthenticationDeveloper,
subscriptionId: aManageSubscriptionId
};

const aDifferentUserAuthenticationDeveloperWithManageKey: IAzureApiAuthorization = {
...aUserAuthenticationDeveloperWithManageKey,
userId: aDifferentUserId
};

const aRetrievedServiceSubscription: SubscriptionWithoutKeys = {
id: aServiceId,
owner_id: aUserId,
scope: "aScope"
};

const aRetrievedServiceSubscriptionWithoutOwnerId: SubscriptionWithoutKeys = {
id: aServiceId,
scope: "aScope"
};

describe("RegenerateServiceKeyHandler", () => {
Expand Down Expand Up @@ -148,6 +174,11 @@ describe("RegenerateServiceKeyHandler", () => {

it("should respond with an Unauthorized error if service is no owned by current user", async () => {
const apiClientMock = {
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
),
RegenerateSubscriptionKeys: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: regeneratedPrimarySubscriptionKeys })
Expand Down Expand Up @@ -296,4 +327,167 @@ describe("RegenerateServiceKeyHandler", () => {

expect(result.kind).toBe("IResponseErrorForbiddenNotAuthorized");
});

// MANAGE Flow Tests
it("should respond with a regenerated subscription primary key, using a MANAGE API Key", async () => {
const apiClientMock = {
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
),
RegenerateSubscriptionKeys: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: regeneratedPrimarySubscriptionKeys })
)
)
};

const regenerateServiceKeyHandler = RegenerateServiceKeyHandler(
apiClientMock as any
);
const result = await regenerateServiceKeyHandler(
mockContext,
aUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
aSubscriptionKeyTypePayload
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.RegenerateSubscriptionKeys).toHaveBeenCalledTimes(1);
expect(result.kind).toBe("IResponseSuccessJson");
if (result.kind === "IResponseSuccessJson") {
expect(result.value).toEqual(regeneratedPrimarySubscriptionKeys);
}
});

it("should respond with a regenerated subscription secondary key, using a MANAGE API Key", async () => {
const apiClientMock = {
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
),
RegenerateSubscriptionKeys: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: regeneratedSecondarySubscriptionKeys })
)
)
};

const regenerateServiceKeyHandler = RegenerateServiceKeyHandler(
apiClientMock as any
);
const result = await regenerateServiceKeyHandler(
mockContext,
aUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
{ key_type: SubscriptionKeyTypeEnum.SECONDARY_KEY }
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.RegenerateSubscriptionKeys).toHaveBeenCalledTimes(1);
expect(result.kind).toBe("IResponseSuccessJson");
if (result.kind === "IResponseSuccessJson") {
expect(result.value).toEqual(regeneratedSecondarySubscriptionKeys);
}
});

it("should respond with an Unauthorized error if MANAGE API Key has a different ownerId", async () => {
const apiClientMock = {
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
),
RegenerateSubscriptionKeys: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: regeneratedPrimarySubscriptionKeys })
)
)
};

const regenerateServiceKeyHandler = RegenerateServiceKeyHandler(
apiClientMock as any
);
const result = await regenerateServiceKeyHandler(
mockContext,
aDifferentUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
{ key_type: SubscriptionKeyTypeEnum.PRIMARY_KEY }
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.RegenerateSubscriptionKeys).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseErrorForbiddenNotAuthorized");
});

it("should respond with an Unauthorized error if getSubscription of a serviceId doesn't return an ownerId", async () => {
const apiClientMock = {
getSubscription: jest.fn(() =>
Promise.resolve(
right({
status: 200,
value: aRetrievedServiceSubscriptionWithoutOwnerId
})
)
),
RegenerateSubscriptionKeys: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: regeneratedPrimarySubscriptionKeys })
)
)
};

const regenerateServiceKeyHandler = RegenerateServiceKeyHandler(
apiClientMock as any
);
const result = await regenerateServiceKeyHandler(
mockContext,
aDifferentUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
{ key_type: SubscriptionKeyTypeEnum.PRIMARY_KEY }
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.RegenerateSubscriptionKeys).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseErrorForbiddenNotAuthorized");
});

it("should respond with an Error if getSubscription returns an error", async () => {
const apiClientMock = {
getSubscription: jest.fn(() =>
Promise.reject(new Error("Internal Server Error"))
),
RegenerateSubscriptionKeys: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: regeneratedPrimarySubscriptionKeys })
)
)
};

const regenerateServiceKeyHandler = RegenerateServiceKeyHandler(
apiClientMock as any
);
const result = await regenerateServiceKeyHandler(
mockContext,
aUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
{ key_type: SubscriptionKeyTypeEnum.PRIMARY_KEY }
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.RegenerateSubscriptionKeys).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseErrorInternal");
});
});
27 changes: 24 additions & 3 deletions RegenerateServiceKey/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
IResponseErrorNotFound,
IResponseErrorTooManyRequests,
IResponseSuccessJson,
ResponseErrorForbiddenNotAuthorized,
ResponseSuccessJson
} from "@pagopa/ts-commons/lib/responses";
import { NonEmptyString } from "@pagopa/ts-commons/lib/strings";
Expand All @@ -42,13 +43,21 @@ import { RequiredBodyPayloadMiddleware } from "@pagopa/io-functions-commons/dist
import { pipe } from "fp-ts/lib/function";
import * as TE from "fp-ts/lib/TaskEither";
import { TaskEither } from "fp-ts/lib/TaskEither";
import { SequenceMiddleware } from "@pagopa/ts-commons/lib/sequence_middleware";
import {
AzureUserAttributesManageMiddleware,
IAzureUserAttributesManage
} from "@pagopa/io-functions-commons/dist/src/utils/middlewares/azure_user_attributes_manage";
import { APIClient } from "../clients/admin";
import { SubscriptionKeys } from "../generated/definitions/SubscriptionKeys";
import { SubscriptionKeyTypePayload } from "../generated/definitions/SubscriptionKeyTypePayload";
import { withApiRequestWrapper } from "../utils/api";
import { getLogger, ILogger } from "../utils/logging";
import { ErrorResponses, IResponseErrorUnauthorized } from "../utils/responses";
import { serviceOwnerCheckTask } from "../utils/subscription";
import {
serviceOwnerCheckManageTask,
serviceOwnerCheckTask
} from "../utils/subscription";

type ResponseTypes =
| IResponseSuccessJson<SubscriptionKeys>
Expand All @@ -70,7 +79,7 @@ type IRegenerateServiceKeyHandler = (
context: Context,
auth: IAzureApiAuthorization,
clientIp: ClientIp,
attrs: IAzureUserAttributes,
attrs: IAzureUserAttributes | IAzureUserAttributesManage,
serviceId: NonEmptyString,
subscriptionKeyTypePayload: SubscriptionKeyTypePayload
) => Promise<ResponseTypes>;
Expand Down Expand Up @@ -105,6 +114,15 @@ export function RegenerateServiceKeyHandler(
return (_, apiAuth, ___, ____, serviceId, subscriptionKeyTypePayload) =>
pipe(
serviceOwnerCheckTask(serviceId, apiAuth.subscriptionId),
TE.orElse(__ =>
serviceOwnerCheckManageTask(
getLogger(_, logPrefix, "GetSubscription"),
apiClient,
serviceId,
apiAuth.subscriptionId,
apiAuth.userId
)
),
TE.chain(() =>
regenerateServiceKeyTask(
getLogger(_, logPrefix, "RegenerateServiceKey"),
Expand All @@ -130,7 +148,10 @@ export function RegenerateServiceKey(
ContextMiddleware(),
AzureApiAuthMiddleware(new Set([UserGroup.ApiServiceWrite])),
ClientIpMiddleware,
AzureUserAttributesMiddleware(serviceModel),
SequenceMiddleware(ResponseErrorForbiddenNotAuthorized)(
AzureUserAttributesMiddleware(serviceModel),
AzureUserAttributesManageMiddleware()
),
RequiredParamMiddleware("service_id", NonEmptyString),
RequiredBodyPayloadMiddleware(SubscriptionKeyTypePayload)
);
Expand Down