diff --git a/frontend/src/app/app.service.ts b/frontend/src/app/app.service.ts index e41334aa..27fb3a4a 100644 --- a/frontend/src/app/app.service.ts +++ b/frontend/src/app/app.service.ts @@ -169,6 +169,14 @@ export class AppService { }); } + /** + * Delete assessment by ID + * @returns success/error message + */ + deleteAssessment(assessmentId: number) { + return this.http.delete(`${this.api}/assessment/${assessmentId}`); + } + /** * Function is responsible for returning all vulnerabilites related to an assessment * @param assessmentId is the ID associated with the assessment diff --git a/frontend/src/app/assessments/assessments.component.html b/frontend/src/app/assessments/assessments.component.html index 1b8cafa4..eb2d4804 100644 --- a/frontend/src/app/assessments/assessments.component.html +++ b/frontend/src/app/assessments/assessments.component.html @@ -24,10 +24,14 @@ style="margin-right: 10px;" data-toggle="tooltip" data-placement="bottom" title="Edit Assessment"> - + @@ -40,4 +44,4 @@ style="margin-right: 5px;" data-toggle="tooltip" data-placement="bottom" title="Back to Organization"> Back to Organization - + \ No newline at end of file diff --git a/frontend/src/app/assessments/assessments.component.ts b/frontend/src/app/assessments/assessments.component.ts index 1afefdcb..fac2983f 100644 --- a/frontend/src/app/assessments/assessments.component.ts +++ b/frontend/src/app/assessments/assessments.component.ts @@ -1,13 +1,16 @@ import { Component, OnInit } from '@angular/core'; import { AppService } from '../app.service'; +import { AlertService } from '../alert/alert.service'; import { ActivatedRoute, Router } from '@angular/router'; import { faPencilAlt } from '@fortawesome/free-solid-svg-icons'; import { faHaykal } from '@fortawesome/free-solid-svg-icons'; +import { faTrash } from '@fortawesome/free-solid-svg-icons'; +import { Assessment } from '../assessment-form/Assessment'; @Component({ selector: 'app-assessments', templateUrl: './assessments.component.html', - styleUrls: ['./assessments.component.sass'] + styleUrls: ['./assessments.component.sass'], }) export class AssessmentsComponent implements OnInit { assessmentAry: any = []; @@ -15,11 +18,18 @@ export class AssessmentsComponent implements OnInit { orgId: number; faPencilAlt = faPencilAlt; faHaykal = faHaykal; - - constructor(public activatedRoute: ActivatedRoute, public router: Router) {} + faTrash = faTrash; + constructor( + public activatedRoute: ActivatedRoute, + public router: Router, + public appService: AppService, + public alertService: AlertService + ) {} ngOnInit() { - this.activatedRoute.data.subscribe(({ assessments }) => (this.assessmentAry = assessments)); + this.activatedRoute.data.subscribe( + ({ assessments }) => (this.assessmentAry = assessments) + ); this.activatedRoute.params.subscribe((params) => { this.assetId = params.assetId; this.orgId = params.orgId; @@ -31,7 +41,9 @@ export class AssessmentsComponent implements OnInit { * @param id of vulnerability to load */ navigateToVulnerability(id: number) { - this.router.navigate([`organization/${this.orgId}/asset/${this.assetId}/assessment/${id}/vulnerability`]); + this.router.navigate([ + `organization/${this.orgId}/asset/${this.assetId}/assessment/${id}/vulnerability`, + ]); } /** @@ -46,7 +58,9 @@ export class AssessmentsComponent implements OnInit { * Function responsible for directing the user to the main Assessment view */ navigateToAssessment() { - this.router.navigate([`organization/${this.orgId}/asset/${this.assetId}/assessment`]); + this.router.navigate([ + `organization/${this.orgId}/asset/${this.assetId}/assessment`, + ]); } /** @@ -55,6 +69,27 @@ export class AssessmentsComponent implements OnInit { * @param assessmentId is the ID associated to the assessment to load */ navigateToAssessmentById(assessmentId: number) { - this.router.navigate([`organization/${this.orgId}/asset/${this.assetId}/assessment/${assessmentId}`]); + this.router.navigate([ + `organization/${this.orgId}/asset/${this.assetId}/assessment/${assessmentId}`, + ]); + } + + /** + * Delete assessment by ID + * ID + * @param assessmentId is the ID associated to the assessment to load + */ + deleteAssessment(assessment: Assessment) { + const r = confirm(`Delete the assessment "${assessment.name}"`); + if (r === true) { + this.appService + .deleteAssessment(assessment.id) + .subscribe((success: string) => { + this.alertService.success(success); + this.appService + .getAssessments(this.orgId) + .then((res) => (this.assessmentAry = res)); + }); + } } } diff --git a/frontend/src/app/vulnerability/vulnerability.component.html b/frontend/src/app/vulnerability/vulnerability.component.html index f17f33e0..a272805a 100644 --- a/frontend/src/app/vulnerability/vulnerability.component.html +++ b/frontend/src/app/vulnerability/vulnerability.component.html @@ -20,15 +20,11 @@ {{ vuln?.jiraId }} {{ vuln?.status }} - - @@ -41,12 +37,8 @@ - - + \ No newline at end of file diff --git a/frontend/src/app/vulnerability/vulnerability.component.ts b/frontend/src/app/vulnerability/vulnerability.component.ts index 5eef052b..57b442b6 100644 --- a/frontend/src/app/vulnerability/vulnerability.component.ts +++ b/frontend/src/app/vulnerability/vulnerability.component.ts @@ -4,11 +4,12 @@ import { faPencilAlt } from '@fortawesome/free-solid-svg-icons'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; import { Vulnerability } from '../vuln-form/Vulnerability'; import { AppService } from '../app.service'; +import { AlertService } from '../alert/alert.service'; @Component({ selector: 'app-vulnerability', templateUrl: './vulnerability.component.html', - styleUrls: ['./vulnerability.component.sass'] + styleUrls: ['./vulnerability.component.sass'], }) export class VulnerabilityComponent implements OnInit { vulnAry: any = []; @@ -18,10 +19,17 @@ export class VulnerabilityComponent implements OnInit { faPencilAlt = faPencilAlt; faTrash = faTrash; - constructor(public activatedRoute: ActivatedRoute, public router: Router, public appService: AppService) {} + constructor( + public activatedRoute: ActivatedRoute, + public router: Router, + public appService: AppService, + public alertService: AlertService + ) {} ngOnInit() { - this.activatedRoute.data.subscribe(({ vulnerabilities }) => (this.vulnAry = vulnerabilities)); + this.activatedRoute.data.subscribe( + ({ vulnerabilities }) => (this.vulnAry = vulnerabilities) + ); this.activatedRoute.params.subscribe((params) => { this.assetId = params.assetId; this.assessmentId = params.assessmentId; @@ -35,7 +43,7 @@ export class VulnerabilityComponent implements OnInit { */ navigateToVulnerabilityForm() { this.router.navigate([ - `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/vuln-form` + `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/vuln-form`, ]); } @@ -45,7 +53,7 @@ export class VulnerabilityComponent implements OnInit { */ navigateToVulnerabilityFormById(vulnId: number) { this.router.navigate([ - `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/vuln-form/${vulnId}` + `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/vuln-form/${vulnId}`, ]); } @@ -60,7 +68,9 @@ export class VulnerabilityComponent implements OnInit { * Function responsible for navigating to report area, takes no params directly */ navigateToReport() { - this.router.navigate([`organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/report`]); + this.router.navigate([ + `organization/${this.orgId}/asset/${this.assetId}/assessment/${this.assessmentId}/report`, + ]); } /** @@ -71,9 +81,11 @@ export class VulnerabilityComponent implements OnInit { deleteVuln(vuln: Vulnerability) { const r = confirm(`Delete the vulnerability "${vuln.name}"`); if (r === true) { - this.appService.deleteVuln(vuln.id).subscribe((success) => { - // TODO: Success message - this.appService.getVulnerabilities(this.assessmentId).then((res) => (this.vulnAry = res)); + this.appService.deleteVuln(vuln.id).subscribe((success: string) => { + this.alertService.success(success); + this.appService + .getVulnerabilities(this.assessmentId) + .then((res) => (this.vulnAry = res)); }); } } diff --git a/src/app.ts b/src/app.ts index 5c2ccc0f..18259134 100644 --- a/src/app.ts +++ b/src/app.ts @@ -18,9 +18,9 @@ const orgController = require('./routes/organization.controller'); const userController = require('./routes/user.controller'); const fileUploadController = require('./routes/file-upload.controller'); import * as assetController from './routes/asset.controller'; -const assessmentController = require('./routes/assessment.controller'); +import * as assessmentController from './routes/assessment.controller'; const vulnController = require('./routes/vulnerability.controller'); -const jwtMiddleware = require('./middleware/jwt.middleware'); +import * as jwtMiddleware from './middleware/jwt.middleware'; const puppeteerUtility = require('./utilities/puppeteer.utility'); const helmet = require('helmet'); const cors = require('cors'); @@ -73,6 +73,7 @@ createConnection().then((_) => { app.get('/api/assessment/:id', jwtMiddleware.checkToken, assessmentController.getAssessmentsByAssetId); app.get('/api/assessment/:id/vulnerability', jwtMiddleware.checkToken, assessmentController.getAssessmentVulns); app.post('/api/assessment', jwtMiddleware.checkToken, assessmentController.createAssessment); + app.delete('/api/assessment/:assessmentId', jwtMiddleware.checkToken, assessmentController.deleteAssessmentById); app.get( '/api/asset/:assetId/assessment/:assessmentId', jwtMiddleware.checkToken, diff --git a/src/middleware/jwt.middleware.ts b/src/middleware/jwt.middleware.ts index 38f10207..86aa3b65 100644 --- a/src/middleware/jwt.middleware.ts +++ b/src/middleware/jwt.middleware.ts @@ -5,7 +5,7 @@ import jwt = require('jsonwebtoken'); * @param {Request} req * @param {Response} res */ -const checkToken = (req, res, next) => { +export const checkToken = (req, res, next) => { const token = req.headers.authorization; // Express headers are auto converted to lowercase if (token) { jwt.verify(token, process.env.JWT_KEY, (err, decoded) => { @@ -26,7 +26,7 @@ const checkToken = (req, res, next) => { * @param {Request} req * @param {Response} res */ -const checkRefreshToken = (req, res, next) => { +export const checkRefreshToken = (req, res, next) => { const token = req.body.refreshToken; if (token) { jwt.verify(token, process.env.JWT_REFRESH_KEY, (err, decoded) => { @@ -41,8 +41,3 @@ const checkRefreshToken = (req, res, next) => { return res.status(401).json('Refresh token not supplied'); } }; - -module.exports = { - checkToken, - checkRefreshToken -}; diff --git a/src/routes/assessment.controller.spec.ts b/src/routes/assessment.controller.spec.ts new file mode 100644 index 00000000..8a0514d9 --- /dev/null +++ b/src/routes/assessment.controller.spec.ts @@ -0,0 +1,81 @@ +import { getConnection } from 'typeorm'; +import { Assessment } from '../entity/Assessment'; +import { Asset } from '../entity/Asset'; +import { Vulnerability } from '../entity/Vulnerability'; +import { Organization } from '../entity/Organization'; +import { createConnection } from 'typeorm'; +import { File } from '../entity/File'; +import { User } from '../entity/User'; +import { ProblemLocation } from '../entity/ProblemLocation'; +import { Resource } from '../entity/Resource'; +import MockExpressResponse = require('mock-express-response'); +import MockExpressRequest = require('mock-express-request'); +import * as assessmentController from './assessment.controller'; + +describe('Assessment Controller', () => { + beforeEach(async () => { + await createConnection({ + type: 'sqlite', + database: ':memory:', + dropSchema: true, + entities: [Asset, Organization, File, Vulnerability, Assessment, User, ProblemLocation, Resource], + synchronize: true, + logging: false, + name: 'default' + }); + }); + afterEach(() => { + const conn = getConnection('default'); + return conn.close(); + }); + test('Assessment Delete', async () => { + const response = new MockExpressResponse(); + const request = new MockExpressRequest({ + params: { + assessmentId: 'abc' + } + }); + await assessmentController.deleteAssessmentById(request, response); + expect(response.statusCode).toBe(400); + const response2 = new MockExpressResponse(); + const request2 = new MockExpressRequest({ + params: { + assessmentId: null + } + }); + await assessmentController.deleteAssessmentById(request2, response2); + expect(response2.statusCode).toBe(400); + const response3 = new MockExpressResponse(); + const request3 = new MockExpressRequest({ + params: { + assessmentId: 3 + } + }); + await assessmentController.deleteAssessmentById(request3, response3); + expect(response3.statusCode).toBe(404); + const assessment: Assessment = { + id: null, + name: 'testAssessment', + executiveSummary: '', + jiraId: '', + testUrl: '', + prodUrl: '', + scope: '', + tag: '', + startDate: new Date(), + endDate: new Date(), + asset: new Asset(), + testers: null, + vulnerabilities: null + }; + await getConnection().getRepository(Assessment).insert(assessment); + const response4 = new MockExpressResponse(); + const request4 = new MockExpressRequest({ + params: { + assessmentId: 1 + } + }); + await assessmentController.deleteAssessmentById(request4, response4); + expect(response4.statusCode).toBe(200); + }); +}); diff --git a/src/routes/assessment.controller.ts b/src/routes/assessment.controller.ts index badfc308..0787d4bb 100644 --- a/src/routes/assessment.controller.ts +++ b/src/routes/assessment.controller.ts @@ -15,7 +15,7 @@ const userController = require('../routes/user.controller'); * @param {Response} res * @returns Asset assessments */ -const getAssessmentsByAssetId = async (req: UserRequest, res: Response) => { +export const getAssessmentsByAssetId = async (req: UserRequest, res: Response) => { if (!req.params.id) { return res.status(400).json('Invalid Assessment request'); } @@ -38,7 +38,7 @@ const getAssessmentsByAssetId = async (req: UserRequest, res: Response) => { * @param {Response} res * @returns assessment vulnerabilities */ -const getAssessmentVulns = async (req: UserRequest, res: Response) => { +export const getAssessmentVulns = async (req: UserRequest, res: Response) => { if (!req.params.id) { return res.status(400).json('Invalid Vulnerability request'); } @@ -61,7 +61,7 @@ const getAssessmentVulns = async (req: UserRequest, res: Response) => { * @param {Response} res c * @returns success message */ -const createAssessment = async (req: UserRequest, res: Response) => { +export const createAssessment = async (req: UserRequest, res: Response) => { if (isNaN(req.body.asset)) { return res.status(400).json('Asset ID is invalid'); } @@ -99,7 +99,7 @@ const createAssessment = async (req: UserRequest, res: Response) => { * @param {Response} res * @returns assessment */ -const getAssessmentById = async (req: UserRequest, res: Response) => { +export const getAssessmentById = async (req: UserRequest, res: Response) => { if (!req.params.assessmentId) { return res.status(400).send('Invalid assessment request'); } @@ -126,7 +126,7 @@ const getAssessmentById = async (req: UserRequest, res: Response) => { * @param {Response} res * @returns success message */ -const updateAssessmentById = async (req: UserRequest, res: Response) => { +export const updateAssessmentById = async (req: UserRequest, res: Response) => { if (!req.params.assessmentId) { return res.status(400).send('Invalid assessment request'); } @@ -164,7 +164,7 @@ const updateAssessmentById = async (req: UserRequest, res: Response) => { * @param {Response} res * @returns report information */ -const queryReportDataByAssessment = async (req: UserRequest, res: Response) => { +export const queryReportDataByAssessment = async (req: UserRequest, res: Response) => { if (!req.params.assessmentId) { return res.status(400).send('Invalid report request'); } @@ -210,11 +210,24 @@ const queryReportDataByAssessment = async (req: UserRequest, res: Response) => { res.status(200).json(report); }; -module.exports = { - getAssessmentsByAssetId, - getAssessmentVulns, - createAssessment, - getAssessmentById, - updateAssessmentById, - queryReportDataByAssessment +/** + * @description Delete assessment by ID + * @param {UserRequest} req vulnID is required + * @param {Response} res contains JSON object with the success/fail + * @returns success/error message + */ +export const deleteAssessmentById = async (req: UserRequest, res: Response) => { + if (!req.params.assessmentId) { + return res.status(400).send('Invalid assessment ID'); + } + if (isNaN(+req.params.assessmentId)) { + return res.status(400).send('Invalid assessment ID'); + } + const assessment = await getConnection().getRepository(Assessment).findOne(req.params.assessmentId); + if (!assessment) { + return res.status(404).send('Assessment does not exist.'); + } else { + await getConnection().getRepository(Assessment).delete(assessment); + res.status(200).json(`Assessment #${assessment.id}: "${assessment.name}" successfully deleted`); + } }; diff --git a/src/routes/vulnerability.controller.ts b/src/routes/vulnerability.controller.ts index 720dcf04..3b1fe7af 100644 --- a/src/routes/vulnerability.controller.ts +++ b/src/routes/vulnerability.controller.ts @@ -1,12 +1,12 @@ import { UserRequest } from '../interfaces/user-request.interface'; import { Response } from 'express'; -import { getConnection } from 'typeorm' +import { getConnection } from 'typeorm'; import { Assessment } from '../entity/Assessment'; import { validate } from 'class-validator'; import { Vulnerability } from '../entity/Vulnerability'; import { File } from '../entity/File'; -import { ProblemLocation } from '../entity/ProblemLocation' -import { Resource } from '../entity/Resource' +import { ProblemLocation } from '../entity/ProblemLocation'; +import { Resource } from '../entity/Resource'; const fileUploadController = require('../routes/file-upload.controller'); /** @@ -16,21 +16,23 @@ const fileUploadController = require('../routes/file-upload.controller'); * @returns vulnerability data */ const getVulnById = async (req: UserRequest, res: Response) => { - if (!req.params.vulnId) { - return res.status(400).send('Invalid Vulnerability UserRequest'); - } - if (isNaN(+req.params.vulnId)) { - return res.status(400).send('Invalid Vulnerability ID'); - } - // TODO: Utilize createQueryBuilder to only return screenshot IDs and not the full object - const vuln = await getConnection().getRepository(Vulnerability).findOne(req.params.vulnId, { - relations: ['screenshots', 'problemLocations', 'resources'] + if (!req.params.vulnId) { + return res.status(400).send('Invalid Vulnerability UserRequest'); + } + if (isNaN(+req.params.vulnId)) { + return res.status(400).send('Invalid Vulnerability ID'); + } + // TODO: Utilize createQueryBuilder to only return screenshot IDs and not the full object + const vuln = await getConnection() + .getRepository(Vulnerability) + .findOne(req.params.vulnId, { + relations: ['screenshots', 'problemLocations', 'resources'] }); - if (!vuln) { - return res.status(404).send('Vulnerability does not exist.'); - } - res.status(200).json(vuln); -} + if (!vuln) { + return res.status(404).send('Vulnerability does not exist.'); + } + res.status(200).json(vuln); +}; /** * @description Delete vulnerability by ID * @param {UserRequest} req vulnID is required @@ -38,20 +40,20 @@ const getVulnById = async (req: UserRequest, res: Response) => { * @returns success/error message */ const deleteVulnById = async (req: UserRequest, res: Response) => { - if (!req.params.vulnId) { - return res.status(400).send('Invalid vulnerability UserRequest'); - } - if (isNaN(+req.params.vulnId)) { - return res.status(400).send('Invalid vulnerability ID'); - } - const vuln = await getConnection().getRepository(Vulnerability).findOne(req.params.vulnId); - if (!vuln) { - return res.status(404).send('Vulnerability does not exist.'); - } else { - await getConnection().getRepository(Vulnerability).delete(vuln); - res.status(200).json('Vulnerability successfully deleted'); - } -} + if (!req.params.vulnId) { + return res.status(400).send('Invalid vulnerability UserRequest'); + } + if (isNaN(+req.params.vulnId)) { + return res.status(400).send('Invalid vulnerability ID'); + } + const vuln = await getConnection().getRepository(Vulnerability).findOne(req.params.vulnId); + if (!vuln) { + return res.status(404).send('Vulnerability does not exist.'); + } else { + await getConnection().getRepository(Vulnerability).delete(vuln); + res.status(200).json(`Vulnerability #${vuln.id}: "${vuln.name}" successfully deleted`); + } +}; /** * @description Update vulnerability by ID * @param {UserRequest} req @@ -60,85 +62,88 @@ const deleteVulnById = async (req: UserRequest, res: Response) => { * // TODO: Break apart this function into smaller functions. Also make common as create is almost the same. */ const patchVulnById = async (req: UserRequest, res: Response) => { - req = await fileUploadController.uploadFileArray(req, res); - if (isNaN(+req.body.assessment) || !req.body.assessment) { - return res.status(400).json('Invalid Assessment ID'); + req = await fileUploadController.uploadFileArray(req, res); + if (isNaN(+req.body.assessment) || !req.body.assessment) { + return res.status(400).json('Invalid Assessment ID'); + } + const assessment = await getConnection().getRepository(Assessment).findOne(req.body.assessment); + if (!assessment) { + return res.status(404).json('Assessment does not exist'); + } + if (isNaN(+req.params.vulnId)) { + return res.status(400).json('Vulnerability ID is invalid'); + } + const vulnerability = await getConnection().getRepository(Vulnerability).findOne(req.params.vulnId); + if (!vulnerability) { + return res.status(404).json('Vulnerability does not exist'); + } + const callback = (resStatus: number, message: any) => { + res.status(resStatus).send(message); + }; + vulnerability.id = +req.params.vulnId; + vulnerability.impact = req.body.impact; + vulnerability.likelihood = req.body.likelihood; + vulnerability.risk = req.body.risk; + vulnerability.status = req.body.status; + vulnerability.description = req.body.description; + vulnerability.remediation = req.body.remediation; + vulnerability.jiraId = req.body.jiraId; + vulnerability.cvssScore = req.body.cvssScore; + vulnerability.cvssUrl = req.body.cvssUrl; + vulnerability.detailedInfo = req.body.detailedInfo; + vulnerability.assessment = assessment; + vulnerability.name = req.body.name; + vulnerability.systemic = req.body.systemic; + const errors = await validate(vulnerability); + if (errors.length > 0) { + return res.status(400).send('Vulnerability form validation failed'); + } else { + await getConnection().getRepository(Vulnerability).save(vulnerability); + // Remove deleted files + if (req.body.screenshotsToDelete) { + const existingScreenshots = await getConnection() + .getRepository(File) + .find({ where: { vulnerability: vulnerability.id } }); + const existingScreenshotIds = existingScreenshots.map((screenshot) => screenshot.id); + let screenshotsToDelete = JSON.parse(req.body.screenshotsToDelete); + // We only want to remove the files associated to the vulnerability + screenshotsToDelete = existingScreenshotIds.filter((value) => screenshotsToDelete.includes(value)); + for (const screenshotId of screenshotsToDelete) { + getConnection().getRepository(File).delete(screenshotId); + } } - const assessment = await getConnection().getRepository(Assessment).findOne(req.body.assessment); - if (!assessment) { - return res.status(404).json('Assessment does not exist'); + saveScreenshots(req.files, vulnerability, callback); + // Remove deleted problem locations + if (req.body.problemLocations.length) { + const clientProdLocs = JSON.parse(req.body.problemLocations); + const clientProdLocsIds = clientProdLocs.map((value) => value.id); + const existingProbLocs = await getConnection() + .getRepository(ProblemLocation) + .find({ where: { vulnerability: vulnerability.id } }); + const existingProbLocIds = existingProbLocs.map((probLoc) => probLoc.id); + const prodLocsToDelete = existingProbLocIds.filter((value) => !clientProdLocsIds.includes(value)); + for (const probLoc of prodLocsToDelete) { + getConnection().getRepository(ProblemLocation).delete(probLoc); + } + saveProblemLocations(clientProdLocs, vulnerability, callback); } - if (isNaN(+req.params.vulnId)) { - return res.status(400).json('Vulnerability ID is invalid'); + // Remove deleted resources + if (req.body.resources.length) { + const clientResources = JSON.parse(req.body.resources); + const clientResourceIds = clientResources.map((value) => value.id); + const existingResources = await getConnection() + .getRepository(Resource) + .find({ where: { vulnerability: vulnerability.id } }); + const existingResourceIds = existingResources.map((resource) => resource.id); + const resourcesToDelete = existingResourceIds.filter((value) => !clientResourceIds.includes(value)); + for (const resource of resourcesToDelete) { + getConnection().getRepository(Resource).delete(resource); + } + saveResources(clientResources, vulnerability, callback); } - const vulnerability = await getConnection().getRepository(Vulnerability).findOne(req.params.vulnId); - if (!vulnerability) { - return res.status(404).json('Vulnerability does not exist'); - } - const callback = (resStatus: number, message: any) => { - res.status(resStatus).send(message); - }; - vulnerability.id = +req.params.vulnId; - vulnerability.impact = req.body.impact; - vulnerability.likelihood = req.body.likelihood; - vulnerability.risk = req.body.risk; - vulnerability.status = req.body.status; - vulnerability.description = req.body.description; - vulnerability.remediation = req.body.remediation; - vulnerability.jiraId = req.body.jiraId; - vulnerability.cvssScore = req.body.cvssScore; - vulnerability.cvssUrl = req.body.cvssUrl; - vulnerability.detailedInfo = req.body.detailedInfo; - vulnerability.assessment = assessment; - vulnerability.name = req.body.name; - vulnerability.systemic = req.body.systemic; - const errors = await validate(vulnerability); - if (errors.length > 0) { - return res.status(400).send('Vulnerability form validation failed'); - } else { - await getConnection().getRepository(Vulnerability).save(vulnerability); - // Remove deleted files - if (req.body.screenshotsToDelete) { - const existingScreenshots = await getConnection().getRepository(File) - .find({ where: { vulnerability: vulnerability.id } }); - const existingScreenshotIds = existingScreenshots.map(screenshot => screenshot.id); - let screenshotsToDelete = JSON.parse(req.body.screenshotsToDelete); - // We only want to remove the files associated to the vulnerability - screenshotsToDelete = existingScreenshotIds.filter(value => screenshotsToDelete.includes(value)); - for (const screenshotId of screenshotsToDelete) { - getConnection().getRepository(File).delete(screenshotId); - } - } - saveScreenshots(req.files, vulnerability, callback); - // Remove deleted problem locations - if (req.body.problemLocations.length) { - const clientProdLocs = JSON.parse(req.body.problemLocations); - const clientProdLocsIds = clientProdLocs.map(value => value.id); - const existingProbLocs = await getConnection().getRepository(ProblemLocation) - .find({ where: { vulnerability: vulnerability.id } }); - const existingProbLocIds = existingProbLocs.map(probLoc => probLoc.id); - const prodLocsToDelete = existingProbLocIds.filter(value => !clientProdLocsIds.includes(value)); - for (const probLoc of prodLocsToDelete) { - getConnection().getRepository(ProblemLocation).delete(probLoc); - } - saveProblemLocations(clientProdLocs, vulnerability, callback); - } - // Remove deleted resources - if (req.body.resources.length) { - const clientResources = JSON.parse(req.body.resources); - const clientResourceIds = clientResources.map(value => value.id); - const existingResources = await getConnection().getRepository(Resource) - .find({ where: { vulnerability: vulnerability.id } }); - const existingResourceIds = existingResources.map(resource => resource.id); - const resourcesToDelete = existingResourceIds.filter(value => !clientResourceIds.includes(value)); - for (const resource of resourcesToDelete) { - getConnection().getRepository(Resource).delete(resource); - } - saveResources(clientResources, vulnerability, callback) - } - return res.status(200).json('Vulnerability patched successfully'); - } -} + return res.status(200).json('Vulnerability patched successfully'); + } +}; /** * @description Create vulnerability * @param {UserRequest} req @@ -146,44 +151,44 @@ const patchVulnById = async (req: UserRequest, res: Response) => { * @returns success/error message */ const createVuln = async (req: UserRequest, res: Response) => { - req = await fileUploadController.uploadFileArray(req, res); - if (isNaN(+req.body.assessment) || !req.body.assessment) { - return res.status(400).json('Invalid Assessment ID'); - } - const assessment = await getConnection().getRepository(Assessment).findOne(req.body.assessment); - if (!assessment) { - return res.status(404).json('Assessment does not exist'); - } - const callback = (resStatus: number, message: any) => { - res.status(resStatus).send(message); - }; - const vulnerability = new Vulnerability(); - vulnerability.impact = req.body.impact; - vulnerability.likelihood = req.body.likelihood; - vulnerability.risk = req.body.risk; - vulnerability.status = req.body.status; - vulnerability.description = req.body.description; - vulnerability.remediation = req.body.remediation; - vulnerability.jiraId = req.body.jiraId; - vulnerability.cvssScore = req.body.cvssScore; - vulnerability.cvssUrl = req.body.cvssUrl; - vulnerability.detailedInfo = req.body.detailedInfo; - vulnerability.assessment = assessment; - vulnerability.name = req.body.name; - vulnerability.systemic = req.body.systemic; - const errors = await validate(vulnerability); - if (errors.length > 0) { - return res.status(400).send('Vulnerability form validation failed'); - } else { - await getConnection().getRepository(Vulnerability).save(vulnerability); - saveScreenshots(req.files, vulnerability, callback) - const problemLocations = JSON.parse(req.body.problemLocations); - saveProblemLocations(problemLocations, vulnerability, callback) - const resources = JSON.parse(req.body.resources); - saveResources(resources, vulnerability, callback) - res.status(200).json('Vulnerability saved successfully'); - } -} + req = await fileUploadController.uploadFileArray(req, res); + if (isNaN(+req.body.assessment) || !req.body.assessment) { + return res.status(400).json('Invalid Assessment ID'); + } + const assessment = await getConnection().getRepository(Assessment).findOne(req.body.assessment); + if (!assessment) { + return res.status(404).json('Assessment does not exist'); + } + const callback = (resStatus: number, message: any) => { + res.status(resStatus).send(message); + }; + const vulnerability = new Vulnerability(); + vulnerability.impact = req.body.impact; + vulnerability.likelihood = req.body.likelihood; + vulnerability.risk = req.body.risk; + vulnerability.status = req.body.status; + vulnerability.description = req.body.description; + vulnerability.remediation = req.body.remediation; + vulnerability.jiraId = req.body.jiraId; + vulnerability.cvssScore = req.body.cvssScore; + vulnerability.cvssUrl = req.body.cvssUrl; + vulnerability.detailedInfo = req.body.detailedInfo; + vulnerability.assessment = assessment; + vulnerability.name = req.body.name; + vulnerability.systemic = req.body.systemic; + const errors = await validate(vulnerability); + if (errors.length > 0) { + return res.status(400).send('Vulnerability form validation failed'); + } else { + await getConnection().getRepository(Vulnerability).save(vulnerability); + saveScreenshots(req.files, vulnerability, callback); + const problemLocations = JSON.parse(req.body.problemLocations); + saveProblemLocations(problemLocations, vulnerability, callback); + const resources = JSON.parse(req.body.resources); + saveResources(resources, vulnerability, callback); + res.status(200).json('Vulnerability saved successfully'); + } +}; /** * @description Save screenshots to vulnerability * @param {UserRequest} req @@ -192,18 +197,18 @@ const createVuln = async (req: UserRequest, res: Response) => { * @returns saved file or error message */ const saveScreenshots = async (files: File[], vulnerability: Vulnerability, callback) => { - for (const screenshot of files) { - let file = new File(); - file = screenshot; - file.vulnerability = vulnerability; - const fileErrors = await validate(file); - if (fileErrors.length > 0) { - return callback(400, JSON.stringify('File validation failed')); - } else { - await getConnection().getRepository(File).save(file); - } + for (const screenshot of files) { + let file = new File(); + file = screenshot; + file.vulnerability = vulnerability; + const fileErrors = await validate(file); + if (fileErrors.length > 0) { + return callback(400, JSON.stringify('File validation failed')); + } else { + await getConnection().getRepository(File).save(file); } -} + } +}; /** * @description Save problem locations to vulnerability * @param {UserRequest} req @@ -212,22 +217,22 @@ const saveScreenshots = async (files: File[], vulnerability: Vulnerability, call * @returns saved problem locations or error message */ const saveProblemLocations = async (problemLocations: ProblemLocation[], vulnerability: Vulnerability, callback) => { - for (const probLoc of problemLocations) { - if (probLoc && probLoc.location && probLoc.target) { - let problemLocation = new ProblemLocation(); - problemLocation = probLoc; - problemLocation.vulnerability = vulnerability; - const plErrors = await validate(problemLocation); - if (plErrors.length > 0) { - return callback(400, JSON.stringify('Problem Location validation failed')); - } else { - await getConnection().getRepository(ProblemLocation).save(problemLocation); - } - } else { - return callback(400, JSON.stringify('Invalid Problem Location')); - } + for (const probLoc of problemLocations) { + if (probLoc && probLoc.location && probLoc.target) { + let problemLocation = new ProblemLocation(); + problemLocation = probLoc; + problemLocation.vulnerability = vulnerability; + const plErrors = await validate(problemLocation); + if (plErrors.length > 0) { + return callback(400, JSON.stringify('Problem Location validation failed')); + } else { + await getConnection().getRepository(ProblemLocation).save(problemLocation); + } + } else { + return callback(400, JSON.stringify('Invalid Problem Location')); } -} + } +}; /** * @description Save resources to vulnerability * @param {UserRequest} req @@ -236,25 +241,25 @@ const saveProblemLocations = async (problemLocations: ProblemLocation[], vulnera * @returns saved resources or error message */ const saveResources = async (resources: Resource[], vulnerability: Vulnerability, callback) => { - for (const resource of resources) { - if (resource.description && resource.url) { - let newResource = new Resource(); - newResource = resource; - newResource.vulnerability = vulnerability; - const nrErrors = await validate(newResource); - if (nrErrors.length > 0) { - return callback(400, JSON.stringify('Resource Location validation failed')) - } else { - await getConnection().getRepository(Resource).save(newResource); - } - } else { - return callback(400, JSON.stringify('Resource Location Invalid')) - } + for (const resource of resources) { + if (resource.description && resource.url) { + let newResource = new Resource(); + newResource = resource; + newResource.vulnerability = vulnerability; + const nrErrors = await validate(newResource); + if (nrErrors.length > 0) { + return callback(400, JSON.stringify('Resource Location validation failed')); + } else { + await getConnection().getRepository(Resource).save(newResource); + } + } else { + return callback(400, JSON.stringify('Resource Location Invalid')); } -} + } +}; module.exports = { - getVulnById, - deleteVulnById, - patchVulnById, - createVuln -} \ No newline at end of file + getVulnById, + deleteVulnById, + patchVulnById, + createVuln +};