Skip to content

Commit

Permalink
[INNO] - New endpoint for deleting subscriptions (#2602)
Browse files Browse the repository at this point in the history
* - Renaming the tsx to ts

* - Adding new Rest endpoint for deleting subscriptions

* - Fixing snapshot

* - Updating the request to get the installationId from params instead of body
- Added a frontend API service for hitting this new endpoint

* - Review changes

* - Correction in API

* - Fixing snapshot

* - Review changes

* - Review changes

* - Updated snapshot

* - Review changes
  • Loading branch information
krazziekay authored Dec 7, 2023
1 parent 6540f42 commit 247f143
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 96 deletions.
6 changes: 4 additions & 2 deletions spa/src/api/subscriptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { axiosRest } from "../axiosInstance";
import { axiosRest, axiosRestWithGitHubToken } from "../axiosInstance";

export default {
getSubscriptions: () => axiosRest.get("/rest/subscriptions")
getSubscriptions: () => axiosRest.get("/rest/subscriptions"),
deleteSubscription: (subscriptionId: number) =>
axiosRestWithGitHubToken.delete("/rest/app/cloud/subscription/:subscriptionId", { params: { subscriptionId } })
};
2 changes: 2 additions & 0 deletions src/rest/rest-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ subRouter.use("/installation", GitHubAppsRoute);

subRouter.use("/jira/cloudid", JiraCloudIDRouter);

subRouter.use("/subscriptions/:subscriptionId", SubscriptionsRouter);

subRouter.use(GitHubTokenHandler);

subRouter.use("/org", GitHubOrgsRouter);
Expand Down
97 changes: 97 additions & 0 deletions src/rest/routes/subscriptions/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Installation } from "models/installation";
import { Subscription } from "models/subscription";
import { encodeSymmetric } from "atlassian-jwt";
import { getFrontendApp } from "~/src/app";
import supertest from "supertest";
import { envVars } from "config/env";

describe("Subscription", () => {
const testSharedSecret = "test-secret";
const gitHubInstallationId = 15;
const getToken = ({
secret = testSharedSecret,
iss = "jira-client-key",
exp = Date.now() / 1000 + 10000,
qsh = "context-qsh" } = {}): string => encodeSymmetric({
qsh,
iss,
exp
}, secret);
let app, subscription;
beforeEach(async () => {
app = getFrontendApp();
await Installation.install({
clientKey: "jira-client-key",
host: jiraHost,
sharedSecret: testSharedSecret
});
subscription = await Subscription.create({
gitHubInstallationId,
jiraHost
});
});

it("Should return 400 for invalid delete subscription route", async () => {
const resp = await supertest(app)
.delete("/rest/subscriptions/" + gitHubInstallationId)
.set("authorization", `${getToken()}`);

expect(resp.status).toBe(404);
});

it("Should return 401 for valid delete subscription route when missing githubToken", async () => {
const resp = await supertest(app)
.delete("/rest/app/cloud/subscriptions/" + gitHubInstallationId);

expect(resp.status).toBe(401);
expect(await Subscription.count()).toEqual(1);
});

it("Should return 404 for valid delete subscription route when no valid subscriptionId is passed", async () => {
const resp = await supertest(app)
.delete("/rest/app/cloud/subscriptions/random-installation-id")
.set("authorization", `${getToken()}`);

expect(resp.status).toBe(404);
expect(await Subscription.count()).toEqual(1);
});

it("Should return 404 for valid delete subscription route when a different subscriptionId is passed", async () => {
const resp = await supertest(app)
.delete("/rest/app/cloud/subscriptions/12")
.set("authorization", `${getToken()}`);

expect(resp.status).toBe(404);
expect(await Subscription.count()).toEqual(1);
});

it("Should return 204 for valid delete subscription route when subscription is deleted", async () => {
jiraNock
.delete("/rest/devinfo/0.10/bulkByProperties")
.query({ installationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/builds/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/deployments/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.put(`/rest/atlassian-connect/latest/addons/${envVars.APP_KEY}/properties/is-configured`, {
isConfigured: false
})
.reply(200);

const resp = await supertest(app)
.delete("/rest/app/cloud/subscriptions/" + subscription.id)
.set("authorization", `${getToken()}`);

expect(resp.status).toBe(204);
expect(await Subscription.count()).toEqual(0);
});
});
40 changes: 40 additions & 0 deletions src/rest/routes/subscriptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Router, Request, Response } from "express";
import { errorWrapper } from "../../helper";
import { getAllSubscriptions } from "./service";
import { Installation } from "models/installation";
import { removeSubscription } from "utils/jira-utils";
import { GitHubServerApp } from "models/github-server-app";
import { InvalidArgumentError } from "config/errors";

export const SubscriptionsRouter = Router({ mergeParams: true });

SubscriptionsRouter.get("/", errorWrapper("SubscriptionsGet", async (req: Request, res: Response) => {
const { jiraHost, installation } = res.locals;
const { ghCloudSubscriptions, ghEnterpriseServers } = await getAllSubscriptions(jiraHost as string, (installation as Installation).id, req);

res.status(200).json({
ghCloudSubscriptions,
ghEnterpriseServers
});
}));

/**
* This delete endpoint only handles Github cloud subscriptions
*/
SubscriptionsRouter.delete("/", errorWrapper("SubscriptionDelete", async (req: Request, res: Response) => {
const subscriptionId: number = Number(req.params.subscriptionId);
const { installation } = res.locals as { installation: Installation; };

const cloudOrUUID = req.params.cloudOrUUID;
if (!cloudOrUUID) {
throw new InvalidArgumentError("Invalid route, couldn't determine if its cloud or enterprise!");
}

// TODO: Check and add test cases for GHE later
const gitHubAppId = cloudOrUUID === "cloud" ? undefined :
(await GitHubServerApp.getForUuidAndInstallationId(cloudOrUUID, installation.id))?.appId; //TODO: validate the uuid regex

await removeSubscription(installation, undefined, gitHubAppId, req.log, subscriptionId);

res.sendStatus(204);
}));
17 changes: 0 additions & 17 deletions src/rest/routes/subscriptions/index.tsx

This file was deleted.

20 changes: 10 additions & 10 deletions src/routes/jira/jira-delete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("DELETE /jira/configuration", () => {

beforeEach(async () => {
subscription = {
githubInstallationId: 15,
gitHubInstallationId: 15,
jiraHost,
destroy: jest.fn().mockResolvedValue(undefined)
};
Expand All @@ -41,17 +41,17 @@ describe("DELETE /jira/configuration", () => {
it("Delete Jira Configuration", async () => {
jiraNock
.delete("/rest/devinfo/0.10/bulkByProperties")
.query({ installationId: subscription.githubInstallationId })
.query({ installationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/builds/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.githubInstallationId })
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/deployments/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.githubInstallationId })
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
Expand All @@ -67,7 +67,7 @@ describe("DELETE /jira/configuration", () => {
jiraHost: subscription.jiraHost
},
params: {
installationId: subscription.githubInstallationId
installationId: subscription.gitHubInstallationId
}
};

Expand All @@ -81,17 +81,17 @@ describe("DELETE /jira/configuration", () => {
when(booleanFlag).calledWith(BooleanFlags.ENABLE_GITHUB_SECURITY_IN_JIRA, expect.anything()).mockResolvedValue(true);
jiraNock
.delete("/rest/devinfo/0.10/bulkByProperties")
.query({ installationId: subscription.githubInstallationId })
.query({ installationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/builds/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.githubInstallationId })
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
.delete("/rest/deployments/0.1/bulkByProperties")
.query({ gitHubInstallationId: subscription.githubInstallationId })
.query({ gitHubInstallationId: subscription.gitHubInstallationId })
.reply(200, "OK");

jiraNock
Expand All @@ -115,7 +115,7 @@ describe("DELETE /jira/configuration", () => {
jiraHost: subscription.jiraHost
},
params: {
installationId: subscription.githubInstallationId
installationId: subscription.gitHubInstallationId
}
};

Expand All @@ -132,7 +132,7 @@ describe("DELETE /jira/configuration", () => {
jiraHost: subscription.jiraHost
},
params: {
installationId: subscription.githubInstallationId
installationId: subscription.gitHubInstallationId
}
};

Expand Down
69 changes: 2 additions & 67 deletions src/routes/jira/jira-delete.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import Logger from "bunyan";
import { Errors } from "config/errors";
import { Request, Response } from "express";
import { AnalyticsEventTypes, AnalyticsTrackEventsEnum, AnalyticsTrackSource } from "interfaces/common";
import { Subscription } from "models/subscription";
import { sendAnalytics } from "utils/analytics-client";
import { getCloudOrServerFromGitHubAppId } from "utils/get-cloud-or-server";
import { BooleanFlags, booleanFlag } from "~/src/config/feature-flags";
import { getJiraClient } from "~/src/jira/client/jira-client";
import { Installation } from "~/src/models/installation";
import { JiraClient } from "~/src/models/jira-client";
import { isConnected } from "utils/is-connected";
import { saveConfiguredAppProperties } from "utils/app-properties-utils";
import { removeSubscription } from "utils/jira-utils";

/**
* Handle the when a user deletes an entry in the UI
Expand Down Expand Up @@ -39,61 +29,6 @@ export const JiraDelete = async (req: Request, res: Response): Promise<void> =>
req.log.info("No gitHubAppId passed. Disconnecting cloud subscription.");
}

const subscription = await Subscription.getSingleInstallation(
jiraHost,
gitHubInstallationId,
gitHubAppId
);

if (!subscription) {
req.log.warn("Cannot find subscription");
res.status(404).send("Cannot find Subscription");
return;
}

const jiraClient = await getJiraClient(jiraHost, gitHubInstallationId, gitHubAppId, req.log);
if (jiraClient === undefined) {
throw new Error("jiraClient is undefined");
}

await jiraClient.devinfo.installation.delete(gitHubInstallationId);
if (await booleanFlag(BooleanFlags.ENABLE_GITHUB_SECURITY_IN_JIRA, jiraHost)) {
await deleteSecurityWorkspaceLinkAndVulns(installation, subscription, req.log);
req.log.info({ subscriptionId: subscription.id }, "Deleted security workspace and vulnerabilities");
}
await subscription.destroy();

if (!(await isConnected(jiraHost))) {
await saveConfiguredAppProperties(jiraHost, req.log, false);
}

await sendAnalytics(jiraHost, AnalyticsEventTypes.TrackEvent, {
action: AnalyticsTrackEventsEnum.DisconnectToOrgTrackEventName,
actionSubject: AnalyticsTrackEventsEnum.DisconnectToOrgTrackEventName,
source: !gitHubAppId ? AnalyticsTrackSource.Cloud : AnalyticsTrackSource.GitHubEnterprise
}, {
gitHubProduct: getCloudOrServerFromGitHubAppId(gitHubAppId)
});

await removeSubscription(installation, gitHubInstallationId, gitHubAppId, req.log, undefined);
res.sendStatus(204);
};

const deleteSecurityWorkspaceLinkAndVulns = async (
installation: Installation,
subscription: Subscription,
logger: Logger
) => {

try {
logger.info("Fetching info about GitHub installation");

const jiraClient = await JiraClient.getNewClient(installation, logger);
await Promise.allSettled([
jiraClient.deleteWorkspace(subscription.id),
jiraClient.deleteVulnerabilities(subscription.id)
]);
} catch (err: unknown) {
logger.warn({ err }, "Failed to delete security workspace or vulnerabilities from Jira");
}

};
Loading

0 comments on commit 247f143

Please sign in to comment.