Skip to content

Commit

Permalink
Arc 2405 check jira admin (#2336)
Browse files Browse the repository at this point in the history
* ARC-2405 add jira admin check
  • Loading branch information
gxueatlassian authored Aug 11, 2023
1 parent bf3b547 commit 3d03a9f
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 7 deletions.
105 changes: 105 additions & 0 deletions src/rest/middleware/jira-admin/jira-admin-check.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { encodeSymmetric } from "atlassian-jwt";
import { Application } from "express";
import supertest from "supertest";
import { Installation } from "models/installation";
import { booleanFlag, BooleanFlags } from "config/feature-flags";
import { when } from "jest-when";
import { JiraClient } from "models/jira-client";
import { getFrontendApp } from "~/src/app";

jest.mock("config/feature-flags");
jest.mock("models/jira-client");

const testSharedSecret = "test-secret";

describe("Jira Admin Check", () => {

let app: Application;
let installation: Installation;

const USER_ACC_ID = "12345";

beforeEach(async () => {

when(booleanFlag).calledWith(BooleanFlags.JIRA_ADMIN_CHECK, jiraHost).mockResolvedValue(true);

app = getFrontendApp();

installation = await Installation.install({
clientKey: "jira-client-key",
host: jiraHost,
sharedSecret: testSharedSecret
});

});

const mockPermission = (permissions: string[]) => {
when(JiraClient.getNewClient).calledWith(expect.anything(), expect.anything())
.mockImplementation((reqInst: Installation) => {
if (reqInst.id === installation.id) {
return {
checkAdminPermissions: jest.fn((userAccountId) => {
if (userAccountId === USER_ACC_ID) {
return { data: { globalPermissions: permissions } };
} else {
return { data: { globalPermissions: ["ADMINISTER", "OTHER_ROLE"] } };
}
})
} as any;
} else {
throw new Error("Wrong installation " + reqInst);
}
});

};

it("should fail if is not admin", async () => {

mockPermission([ "OTHER_ROLE" ]);

const res = await sendRequestWithToken();

expect(res.status).toEqual(401);
expect(JSON.parse(res.text)).toEqual(expect.objectContaining({
errorCode: "INSUFFICIENT_PERMISSION",
message: expect.stringContaining("Forbidden")
}));

});

it("should pass request if is admin", async () => {

mockPermission([ "ADMINISTER", "OTHER_ROLE" ]);

const res = await sendRequestWithToken();

expect(res.status).toEqual(200);
expect(JSON.parse(res.text)).toEqual({
redirectUrl: expect.stringContaining("oauth/authorize"),
state: expect.anything()
});

});

const sendRequestWithToken = async () => {
return await supertest(app)
.get(`/rest/app/cloud/oauth/redirectUrl`)
.set("Authorization", getToken())
.send();
};

const getToken = ({
secret = testSharedSecret,
iss = "jira-client-key",
sub = USER_ACC_ID,
exp = Date.now() / 1000 + 10000,
qsh = "context-qsh" } = {}): any => {
return encodeSymmetric({
qsh,
iss,
sub,
exp
}, secret);
};

});
32 changes: 32 additions & 0 deletions src/rest/middleware/jira-admin/jira-admin-check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { NextFunction, Request, Response } from "express";
import { JiraClient } from "models/jira-client";
import { booleanFlag, BooleanFlags } from "config/feature-flags";
import { InsufficientPermissionError, InvalidTokenError } from "config/errors";
import { errorWrapper } from "../../helper";

const ADMIN_PERMISSION = "ADMINISTER";
export const JiraAdminEnforceMiddleware = errorWrapper("jiraAdminEnforceMiddleware", async (req: Request, res: Response, next: NextFunction): Promise<void | Response> => {

const { accountId, installation, jiraHost } = res.locals;

if (!(await booleanFlag(BooleanFlags.JIRA_ADMIN_CHECK, jiraHost))) {
return next();
}

if (!accountId) {
throw new InvalidTokenError("Missing userAccountId");
}

const jiraClient = await JiraClient.getNewClient(installation, req.log);

const permissions = await jiraClient.checkAdminPermissions(accountId);

const isAdmin = permissions.data.globalPermissions.includes(ADMIN_PERMISSION);
if (!isAdmin) {
throw new InsufficientPermissionError("Forbidden - User does not have Jira administer permissions.");
}

req.log.debug({ isAdmin }, "Admin permissions checked");
next();

});
2 changes: 2 additions & 0 deletions src/rest/rest-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GitHubTokenHandler } from "./middleware/jwt/github-token";
import { GitHubAppsRoute } from "./routes/github-apps";
import { JiraCloudIDRouter } from "./routes/jira";
import { RestErrorHandler } from "./middleware/error";
import { JiraAdminEnforceMiddleware } from "./middleware/jira-admin/jira-admin-check";
import { AnalyticsProxyHandler } from "./routes/analytics-proxy";

export const RestRouter = Router({ mergeParams: true });
Expand All @@ -26,6 +27,7 @@ subRouter.get("/github-requested", OrgsInstallRequestedHandler);
// TODO: what about Jira admin validation (a.k.a. authorization, we
// have done authentication only)?
subRouter.use(JwtHandler);
subRouter.use(JiraAdminEnforceMiddleware);

subRouter.post("/analytics-proxy", AnalyticsProxyHandler);

Expand Down
14 changes: 7 additions & 7 deletions test/snapshots/app.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ exports[`app getFrontendApp please review routes and update snapshot when adding
:GET ^/?(?=/|$)^/rest/?(?=/|$)^/app/(?:([^/]+?))/?(?=/|$)^/github-requested/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,OrgsInstallRequestedHandler
:POST ^/?(?=/|$)^/rest/?(?=/|$)^/app/(?:([^/]+?))/?(?=/|$)^/analytics-proxy/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,AnalyticsProxyHandler
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,jiraAdminEnforceMiddleware,AnalyticsProxyHandler
:GET ^/?(?=/|$)^/rest/?(?=/|$)^/app/(?:([^/]+?))/?(?=/|$)^/oauth/?(?=/|$)^/redirectUrl/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,OAuthRedirectUrl
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,jiraAdminEnforceMiddleware,OAuthRedirectUrl
:POST ^/?(?=/|$)^/rest/?(?=/|$)^/app/(?:([^/]+?))/?(?=/|$)^/oauth/?(?=/|$)^/exchangeToken/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,OAuthExchangeToken
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,jiraAdminEnforceMiddleware,OAuthExchangeToken
:GET ^/?(?=/|$)^/rest/?(?=/|$)^/app/(?:([^/]+?))/?(?=/|$)^/installation/?(?=/|$)^/new/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,GetGitHubAppsUrl
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,jiraAdminEnforceMiddleware,GetGitHubAppsUrl
:GET ^/?(?=/|$)^/rest/?(?=/|$)^/app/(?:([^/]+?))/?(?=/|$)^/jira/cloudid/?(?=/|$)^/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,JiraCloudIDGet
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,jiraAdminEnforceMiddleware,JiraCloudIDGet
:GET ^/?(?=/|$)^/rest/?(?=/|$)^/app/(?:([^/]+?))/?(?=/|$)^/org/?(?=/|$)^/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,GitHubTokenHandler,GitHubOrgsFetchOrgs
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,jiraAdminEnforceMiddleware,GitHubTokenHandler,GitHubOrgsFetchOrgs
:POST ^/?(?=/|$)^/rest/?(?=/|$)^/app/(?:([^/]+?))/?(?=/|$)^/org/?(?=/|$)^/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,GitHubTokenHandler,GitHubOrgsConnectJira
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,JwtHandler,jiraAdminEnforceMiddleware,GitHubTokenHandler,GitHubOrgsConnectJira
:GET ^/?(?=/|$)^/?(?=/|$)^/deepcheck/?$
query,expressInit,elapsedTimeMetrics,sentryRequestMiddleware,urlencodedParser,jsonParser,cookieParser,LogMiddleware,DeepcheckGet
:GET ^/?(?=/|$)^/?(?=/|$)^/healthcheck/?$
Expand Down

0 comments on commit 3d03a9f

Please sign in to comment.