Skip to content

Commit

Permalink
BHBC-1478: Update API to check project level permissions. (#630)
Browse files Browse the repository at this point in the history
  • Loading branch information
NickPhura authored Nov 9, 2021
1 parent de8216f commit 938a9e7
Show file tree
Hide file tree
Showing 85 changed files with 3,537 additions and 1,947 deletions.
1,540 changes: 615 additions & 925 deletions api/package-lock.json

Large diffs are not rendered by default.

54 changes: 25 additions & 29 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,68 +29,64 @@
},
"dependencies": {
"adm-zip": "~0.5.5",
"ajv": "~8.6.2",
"ajv": "~8.6.3",
"aws-sdk": "~2.742.0",
"axios": "~0.21.4",
"axios": "~0.23.0",
"db-migrate": "~0.11.11",
"db-migrate-pg": "~1.2.2",
"xlsx": "~0.17.0",
"xlsx": "~0.17.3",
"clamdjs": "~1.0.2",
"express": "~4.17.1",
"express-openapi": "~7.0.1",
"express-openapi": "~9.3.0",
"jsonpath": "~1.1.1",
"jsonwebtoken": "~8.5.1",
"jwks-rsa": "~1.9.0",
"jwks-rsa": "~2.0.5",
"knex": "~0.21.4",
"lodash": "~4.17.21",
"mime": "~2.5.2",
"moment": "~2.29.1",
"multer": "^1.4.2",
"pg": "~8.5.1",
"qs": "~6.9.4",
"multer": "~1.4.3",
"pg": "~8.7.1",
"qs": "~6.10.1",
"sql-template-strings": "~2.2.2",
"swagger-object-validator": "~1.2.2",
"typescript": "~3.9.4",
"uuid": "~8.3.2",
"validator": "~13.1.1",
"winston": "~3.3.3"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "~1.0.1",
"@types/adm-zip": "~0.4.34",
"@types/chai": "~4.2.12",
"@types/express": "~4.17.0",
"@types/geojson": "~7946.0.3",
"@types/gulp": "~4.0.6",
"@types/chai": "~4.2.22",
"@types/express": "~4.17.13",
"@types/geojson": "~7946.0.8",
"@types/gulp": "~4.0.9",
"@types/jsonpath": "~0.2.0",
"@types/jsonwebtoken": "~8.5.0",
"@types/lodash": "~4.14.173",
"@types/jsonwebtoken": "~8.5.5",
"@types/lodash": "~4.14.176",
"@types/mime": "~2.0.3",
"@types/mocha": "~8.0.1",
"@types/multer": "^1.4.5",
"@types/pg": "~7.14.4",
"@types/sinon": "^10.0.0",
"@types/sinon-chai": "^3.2.5",
"@types/uuid": "~8.3.0",
"@types/mocha": "~9.0.0",
"@types/multer": "~1.4.7",
"@types/pg": "~8.6.1",
"@types/sinon": "~10.0.4",
"@types/sinon-chai": "~3.2.5",
"@types/uuid": "~8.3.1",
"@types/yamljs": "~0.2.31",
"@typescript-eslint/eslint-plugin": "~3.7.1",
"@typescript-eslint/parser": "~3.7.1",
"chai": "~4.2.0",
"chai": "~4.3.4",
"del": "~6.0.0",
"eslint": "~7.5.0",
"eslint-config-prettier": "~6.11.0",
"eslint-plugin-prettier": "~3.1.4",
"gulp": "~4.0.2",
"gulp-typescript": "~5.0.1",
"mocha": "~8.2.1",
"nock": "~13.0.3",
"nodemon": "~2.0.7",
"mocha": "~8.4.0",
"nodemon": "~2.0.14",
"npm-run-all": "~4.1.5",
"nyc": "~15.1.0",
"prettier": "~2.2.1",
"sinon": "^10.0.0",
"sinon-chai": "^3.6.0",
"supertest": "~6.0.1",
"sinon": "~11.1.2",
"sinon-chai": "~3.7.0",
"ts-mocha": "~8.0.0",
"ts-node": "~9.1.1"
}
Expand Down
18 changes: 9 additions & 9 deletions api/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import express from 'express';
import express, { NextFunction, Request, Response } from 'express';
import { initialize } from 'express-openapi';
import multer from 'multer';
import { OpenAPI } from 'openapi-types';
import { initDBPool, defaultPoolConfig } from './database/db';
import { OpenAPIV3 } from 'openapi-types';
import { defaultPoolConfig, initDBPool } from './database/db';
import { ensureCustomError } from './errors/CustomError';
import { rootAPIDoc } from './openapi/root-api-doc';
import { authenticate, authorize } from './security/auth-utils';
import { authenticateRequest } from './request-handlers/security/authentication';
import { getLogger } from './utils/logger';

const defaultLog = getLogger('app');
Expand All @@ -24,7 +24,7 @@ const MAX_UPLOAD_FILE_SIZE = Number(process.env.MAX_UPLOAD_FILE_SIZE) || 5242880
const app: express.Express = express();

// Enable CORS
app.use(function (req: any, res: any, next: any) {
app.use(function (req: Request, res: Response, next: NextFunction) {
defaultLog.info(`${req.method} ${req.url}`);

res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Authorization, responseType');
Expand All @@ -37,7 +37,7 @@ app.use(function (req: any, res: any, next: any) {

// Initialize express-openapi framework
initialize({
apiDoc: rootAPIDoc as OpenAPI.Document, // base open api spec
apiDoc: rootAPIDoc as OpenAPIV3.Document, // base open api spec
app: app, // express app to initialize
paths: './src/paths', // base folder for endpoint routes
pathsIgnore: new RegExp('.(spec|test)$'), // ignore test files in paths
Expand All @@ -53,9 +53,9 @@ initialize({
'application/x-www-form-urlencoded': express.urlencoded({ limit: MAX_REQ_BODY_SIZE, extended: true })
},
securityHandlers: {
// applies authentication logic
Bearer: async function (req: any, scopes: string[]) {
return (await authenticate(req)) && authorize(req, scopes);
// authenticates the request bearer token, for endpoints that specify `Bearer` security
Bearer: async function (req: any) {
return authenticateRequest(req);
}
},
errorTransformer: function (openapiError: object, ajvError: object): object {
Expand Down
14 changes: 13 additions & 1 deletion api/src/constants/roles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* System level roles
* System level roles.
*
* @export
* @enum {number}
Expand All @@ -9,6 +9,18 @@ export enum SYSTEM_ROLE {
PROJECT_ADMIN = 'Project Administrator'
}

/**
* Project level roles.
*
* @export
* @enum {number}
*/
export enum PROJECT_ROLE {
PROJECT_LEAD = 'Project Lead',
PROJECT_TEAM_MEMBER = 'Project Team Member',
PROJECT_REVIEWER = 'Project Reviewer'
}

/**
* Used when adding/updating an object in S3 storage to mark the object as requiring authentication.
*
Expand Down
16 changes: 16 additions & 0 deletions api/src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,19 @@ export class UserObject {
this.role_names = (obj?.role_names?.length && obj.role_names) || [];
}
}

export class ProjectUserObject {
project_id: number;
system_user_id: number;
project_role_ids: number[];
project_role_names: string[];

constructor(obj?: any) {
defaultLog.debug({ label: 'ProjectUserObject', message: 'params', obj });

this.project_id = obj?.project_id || null;
this.system_user_id = obj?.system_user_id || null;
this.project_role_ids = (obj?.project_role_ids?.length && obj.project_role_ids) || [];
this.project_role_names = (obj?.project_role_names?.length && obj.project_role_names) || [];
}
}
21 changes: 17 additions & 4 deletions api/src/paths/access-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,40 @@

import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { SYSTEM_ROLE } from '../constants/roles';
import { PROJECT_ROLE } from '../constants/roles';
import { getDBConnection } from '../database/db';
import { HTTP400, HTTP500 } from '../errors/CustomError';
import { UserObject } from '../models/user';
import { getUserByUserIdentifierSQL } from '../queries/users/user-queries';
import { authorizeRequestHandler } from '../request-handlers/security/authorization';
import { getLogger } from '../utils/logger';
import { logRequest } from '../utils/path-utils';
import { updateAdministrativeActivity } from './administrative-activity';
import { addSystemUser } from './user';
import { addSystemRoles } from './user/{userId}/system-roles';

const defaultLog = getLogger('paths/access-request');

export const PUT: Operation = [logRequest('paths/access-request', 'POST'), updateAccessRequest()];
export const PUT: Operation = [
authorizeRequestHandler((req) => {
return {
and: [
{
validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD],
projectId: Number(req.params.projectId),
discriminator: 'ProjectRole'
}
]
};
}),
updateAccessRequest()
];

PUT.apiDoc = {
description: "Update a user's system access request and add any specified system roles to the user.",
tags: ['user'],
security: [
{
Bearer: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_ADMIN]
Bearer: []
}
],
requestBody: {
Expand Down
20 changes: 16 additions & 4 deletions api/src/paths/administrative-activities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@ import { SYSTEM_ROLE } from '../constants/roles';
import { getDBConnection } from '../database/db';
import { HTTP400 } from '../errors/CustomError';
import { getAdministrativeActivitiesSQL } from '../queries/administrative-activity/administrative-activity-queries';
import { authorizeRequestHandler } from '../request-handlers/security/authorization';
import { getLogger } from '../utils/logger';
import { logRequest } from '../utils/path-utils';

const defaultLog = getLogger('paths/administrative-activity');
const defaultLog = getLogger('paths/administrative-activities');

export const GET: Operation = [logRequest('paths/administrative-activity', 'GET'), getAdministrativeActivities()];
export const GET: Operation = [
authorizeRequestHandler(() => {
return {
and: [
{
validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN],
discriminator: 'SystemRole'
}
]
};
}),
getAdministrativeActivities()
];

export enum ADMINISTRATIVE_ACTIVITY_STATUS_TYPE {
PENDING = 'Pending',
Expand All @@ -25,7 +37,7 @@ GET.apiDoc = {
tags: ['admin'],
security: [
{
Bearer: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_ADMIN]
Bearer: []
}
],
parameters: [
Expand Down
20 changes: 15 additions & 5 deletions api/src/paths/administrative-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import {
postAdministrativeActivitySQL,
putAdministrativeActivitySQL
} from '../queries/administrative-activity/administrative-activity-queries';
import { authorizeRequestHandler } from '../request-handlers/security/authorization';
import { getUserIdentifier } from '../utils/keycloak-utils';
import { getLogger } from '../utils/logger';
import { logRequest } from '../utils/path-utils';

const defaultLog = getLogger('paths/administrative-activity-request');

export const POST: Operation = [logRequest('paths/administrative-activity', 'POST'), createAdministrativeActivity()];
export const GET: Operation = [logRequest('paths/administrative-activity', 'GET'), getPendingAccessRequestsCount()];
export const POST: Operation = [createAdministrativeActivity()];

export const GET: Operation = [getPendingAccessRequestsCount()];

POST.apiDoc = {
description: 'Create a new Administrative Activity.',
Expand Down Expand Up @@ -215,7 +216,16 @@ export function getPendingAccessRequestsCount(): RequestHandler {
}

export const PUT: Operation = [
logRequest('paths/administrative-activity', 'PUT'),
authorizeRequestHandler(() => {
return {
and: [
{
validSystemRoles: [SYSTEM_ROLE.SYSTEM_ADMIN],
discriminator: 'SystemRole'
}
]
};
}),
getUpdateAdministrativeActivityHandler()
];

Expand All @@ -224,7 +234,7 @@ PUT.apiDoc = {
tags: ['admin'],
security: [
{
Bearer: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_ADMIN]
Bearer: []
}
],
requestBody: {
Expand Down
3 changes: 1 addition & 2 deletions api/src/paths/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { getAPIUserDBConnection } from '../database/db';
import { HTTP500 } from '../errors/CustomError';
import { getAllCodeSets } from '../utils/code-utils';
import { getLogger } from '../utils/logger';
import { logRequest } from '../utils/path-utils';

const defaultLog = getLogger('paths/code');

export const GET: Operation = [logRequest('paths/code', 'GET'), getAllCodes()];
export const GET: Operation = [getAllCodes()];

GET.apiDoc = {
description: 'Get all Codes.',
Expand Down
40 changes: 34 additions & 6 deletions api/src/paths/draft.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { SYSTEM_ROLE } from '../constants/roles';
import { PROJECT_ROLE } from '../constants/roles';
import { getDBConnection } from '../database/db';
import { HTTP400 } from '../errors/CustomError';
import { draftResponseObject } from '../openapi/schemas/draft';
import { postDraftSQL, putDraftSQL } from '../queries/draft-queries';
import { authorizeRequestHandler } from '../request-handlers/security/authorization';
import { getLogger } from '../utils/logger';
import { logRequest } from '../utils/path-utils';

const defaultLog = getLogger('paths/draft');

export const PUT: Operation = [logRequest('paths/draft', 'PUT'), updateDraft()];
export const POST: Operation = [logRequest('paths/draft', 'POST'), createDraft()];
export const PUT: Operation = [
authorizeRequestHandler((req) => {
return {
and: [
{
validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD],
projectId: Number(req.params.projectId),
discriminator: 'ProjectRole'
}
]
};
}),
updateDraft()
];

export const POST: Operation = [
authorizeRequestHandler((req) => {
return {
and: [
{
validProjectRoles: [PROJECT_ROLE.PROJECT_LEAD],
projectId: Number(req.params.projectId),
discriminator: 'ProjectRole'
}
]
};
}),

createDraft()
];

const postPutResponses = {
200: {
Expand Down Expand Up @@ -46,7 +74,7 @@ POST.apiDoc = {
tags: ['draft'],
security: [
{
Bearer: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_ADMIN]
Bearer: []
}
],
requestBody: {
Expand Down Expand Up @@ -82,7 +110,7 @@ PUT.apiDoc = {
tags: ['draft'],
security: [
{
Bearer: [SYSTEM_ROLE.SYSTEM_ADMIN, SYSTEM_ROLE.PROJECT_ADMIN]
Bearer: []
}
],
requestBody: {
Expand Down
Loading

0 comments on commit 938a9e7

Please sign in to comment.