Spaces:
Runtime error
Runtime error
var mongoose = require('mongoose'); //.set('debug', true); | |
const CVSS31 = require('../lib/cvsscalc31'); | |
var Schema = mongoose.Schema; | |
var Paragraph = { | |
text: String, | |
images: [{ image: String, caption: String }], | |
}; | |
var customField = { | |
_id: false, | |
customField: { type: Schema.Types.Mixed, ref: 'CustomField' }, | |
text: Schema.Types.Mixed, | |
}; | |
var Finding = { | |
id: Schema.Types.ObjectId, | |
identifier: Number, //incremental ID to be shown in the report | |
title: String, | |
vulnType: String, | |
description: String, | |
observation: String, | |
remediation: String, | |
remediationComplexity: { type: Number, enum: [1, 2, 3] }, | |
priority: { type: Number, enum: [1, 2, 3, 4] }, | |
references: [String], | |
cwes: [String], | |
cvssv3: String, | |
paragraphs: [Paragraph], | |
poc: String, | |
scope: String, | |
status: { type: Number, enum: [0, 1], default: 1 }, // 0: done, 1: redacting | |
category: String, | |
customFields: [customField], | |
retestStatus: { type: String, enum: ['ok', 'ko', 'unknown', 'partial'] }, | |
retestDescription: String, | |
}; | |
var Service = { | |
port: Number, | |
protocol: { type: String, enum: ['tcp', 'udp'] }, | |
name: String, | |
product: String, | |
version: String, | |
}; | |
var Host = { | |
hostname: String, | |
ip: String, | |
os: String, | |
services: [Service], | |
}; | |
var SortOption = { | |
_id: false, | |
category: String, | |
sortValue: String, | |
sortOrder: { type: String, enum: ['desc', 'asc'] }, | |
sortAuto: Boolean, | |
}; | |
var AuditSchema = new Schema( | |
{ | |
name: { type: String, required: true }, | |
auditType: String, | |
date: String, | |
date_start: String, | |
date_end: String, | |
summary: String, | |
company: { type: Schema.Types.ObjectId, ref: 'Company' }, | |
client: { type: Schema.Types.ObjectId, ref: 'Client' }, | |
collaborators: [{ type: Schema.Types.ObjectId, ref: 'User' }], | |
reviewers: [{ type: Schema.Types.ObjectId, ref: 'User' }], | |
language: { type: String, required: true }, | |
scope: [{ _id: false, name: String, hosts: [Host] }], | |
findings: [Finding], | |
template: { type: Schema.Types.ObjectId, ref: 'Template' }, | |
creator: { type: Schema.Types.ObjectId, ref: 'User' }, | |
sections: [ | |
{ | |
field: String, | |
name: String, | |
text: String, | |
customFields: [customField], | |
}, | |
], // keep text for retrocompatibility | |
customFields: [customField], | |
sortFindings: [SortOption], | |
state: { | |
type: String, | |
enum: ['EDIT', 'REVIEW', 'APPROVED'], | |
default: 'EDIT', | |
}, | |
approvals: [{ type: Schema.Types.ObjectId, ref: 'User' }], | |
type: { | |
type: String, | |
enum: ['default', 'multi', 'retest'], | |
default: 'default', | |
}, | |
parentId: { type: Schema.Types.ObjectId, ref: 'Audit' }, | |
}, | |
{ timestamps: true }, | |
); | |
/* | |
*** Statics *** | |
*/ | |
// Get all audits (admin) | |
AuditSchema.statics.getAudits = (isAdmin, userId, filters) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.find(filters); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query.populate('creator', 'username'); | |
query.populate('collaborators', 'username'); | |
query.populate('reviewers', 'username firstname lastname'); | |
query.populate('approvals', 'username firstname lastname'); | |
query.populate('company', 'name'); | |
query.populate('template', '-_id ext'); | |
query.select( | |
'id name auditType language creator collaborators company createdAt state type parentId template', | |
); | |
query | |
.exec() | |
.then(rows => { | |
resolve(rows); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Get Audit with ID to generate report | |
AuditSchema.statics.getAudit = (isAdmin, auditId, userId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query.populate('template'); | |
query.populate('creator', 'username firstname lastname email phone role'); | |
query.populate('company'); | |
query.populate('client'); | |
query.populate( | |
'collaborators', | |
'username firstname lastname email phone role', | |
); | |
query.populate('reviewers', 'username firstname lastname role'); | |
query.populate('approvals', 'username firstname lastname role'); | |
query.populate('customFields.customField', 'label fieldType text'); | |
query.populate({ | |
path: 'findings', | |
populate: { | |
path: 'customFields.customField', | |
select: 'label fieldType text', | |
}, | |
}); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
resolve(row); | |
}) | |
.catch(err => { | |
if (err.name === 'CastError') | |
reject({ fn: 'BadParameters', message: 'Bad Audit Id' }); | |
else reject(err); | |
}); | |
}); | |
}; | |
AuditSchema.statics.getAuditChildren = (isAdmin, auditId, userId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.find({ parentId: auditId }); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query | |
.exec() | |
.then(rows => { | |
if (!rows) | |
throw { | |
fn: 'NotFound', | |
message: 'Children not found or Insufficient Privileges', | |
}; | |
resolve(rows); | |
}) | |
.catch(err => { | |
if (err.name === 'CastError') | |
reject({ fn: 'BadParameters', message: 'Bad Audit Id' }); | |
else reject(err); | |
}); | |
}); | |
}; | |
// Get Audit Retest | |
AuditSchema.statics.getRetest = (isAdmin, auditId, userId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findOne({ parentId: auditId }); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { fn: 'NotFound', message: 'No retest found for this audit' }; | |
else { | |
resolve(row); | |
} | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Create Audit Retest | |
AuditSchema.statics.createRetest = (isAdmin, auditId, userId, auditType) => { | |
return new Promise((resolve, reject) => { | |
var audit = {}; | |
audit.creator = userId; | |
audit.type = 'retest'; | |
audit.parentId = auditId; | |
audit.auditType = auditType; | |
audit.findings = []; | |
audit.sections = []; | |
audit.customFields = []; | |
var auditTypeSections = []; | |
var customSections = []; | |
var customFields = []; | |
var AuditType = mongoose.model('AuditType'); | |
var query = Audit.findById(auditId); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query | |
.exec() | |
.then(async row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
else { | |
var retest = await Audit.findOne({ parentId: auditId }).exec(); | |
if (retest) | |
throw { | |
fn: 'BadParameters', | |
message: 'Retest already exists for this Audit', | |
}; | |
audit.name = row.name; | |
audit.company = row.company; | |
audit.client = row.client; | |
audit.collaborators = row.collaborators; | |
audit.reviewers = row.reviewers; | |
audit.language = row.language; | |
audit.scope = row.scope; | |
audit.findings = row.findings; | |
// row.findings.forEach(finding => { | |
// var tmpFinding = {} | |
// tmpFinding.title = finding.title | |
// tmpFinding.identifier = finding.identifier | |
// tmpFinding.cvssv3 = finding.cvssv3 | |
// tmpFinding.vulnType = finding.vulnType | |
// tmpFinding.category = finding.category | |
// audit.findings.push(tmpFinding) | |
// }) | |
return AuditType.getByName(auditType); | |
} | |
}) | |
.then(row => { | |
if (row) { | |
auditTypeSections = row.sections; | |
var auditTypeTemplate = row.templates.find( | |
e => e.locale === audit.language, | |
); | |
if (auditTypeTemplate) audit.template = auditTypeTemplate.template; | |
var Section = mongoose.model('CustomSection'); | |
var CustomField = mongoose.model('CustomField'); | |
var promises = []; | |
promises.push(Section.getAll()); | |
promises.push(CustomField.getAll()); | |
return Promise.all(promises); | |
} else throw { fn: 'NotFound', message: 'AuditType not found' }; | |
}) | |
.then(resolved => { | |
customSections = resolved[0]; | |
customFields = resolved[1]; | |
customSections.forEach(section => { | |
// Add sections with customFields (and default text) to audit | |
var tmpSection = {}; | |
if (auditTypeSections.includes(section.field)) { | |
tmpSection.field = section.field; | |
tmpSection.name = section.name; | |
tmpSection.customFields = []; | |
customFields.forEach(field => { | |
field = field.toObject(); | |
if ( | |
field.display === 'section' && | |
field.displaySub === tmpSection.name | |
) { | |
var fieldText = field.text.find( | |
e => e.locale === audit.language, | |
); | |
if (fieldText) fieldText = fieldText.value; | |
else fieldText = ''; | |
delete field.text; | |
tmpSection.customFields.push({ | |
customField: field, | |
text: fieldText, | |
}); | |
} | |
}); | |
audit.sections.push(tmpSection); | |
} | |
}); | |
customFields.forEach(field => { | |
// Add customFields (and default text) to audit | |
field = field.toObject(); | |
if (field.display === 'general') { | |
var fieldText = field.text.find(e => e.locale === audit.language); | |
if (fieldText) fieldText = fieldText.value; | |
else fieldText = ''; | |
delete field.text; | |
audit.customFields.push({ customField: field, text: fieldText }); | |
} | |
}); | |
return new Audit(audit).save(); | |
}) | |
.then(rows => { | |
resolve(rows); | |
}) | |
.catch(err => { | |
console.log(err); | |
if (err.name === 'ValidationError') | |
reject({ fn: 'BadParameters', message: 'Audit validation failed' }); | |
else reject(err); | |
}); | |
}); | |
}; | |
// Create audit | |
AuditSchema.statics.create = (audit, userId) => { | |
return new Promise((resolve, reject) => { | |
audit.creator = userId; | |
audit.sections = []; | |
audit.customFields = []; | |
var auditTypeSections = []; | |
var customSections = []; | |
var customFields = []; | |
var AuditType = mongoose.model('AuditType'); | |
AuditType.getByName(audit.auditType) | |
.then(row => { | |
if (row) { | |
auditTypeSections = row.sections; | |
var auditTypeTemplate = row.templates.find( | |
e => e.locale === audit.language, | |
); | |
if (auditTypeTemplate) audit.template = auditTypeTemplate.template; | |
var Section = mongoose.model('CustomSection'); | |
var CustomField = mongoose.model('CustomField'); | |
var promises = []; | |
promises.push(Section.getAll()); | |
promises.push(CustomField.getAll()); | |
return Promise.all(promises); | |
} else throw { fn: 'NotFound', message: 'AuditType not found' }; | |
}) | |
.then(resolved => { | |
customSections = resolved[0]; | |
customFields = resolved[1]; | |
customSections.forEach(section => { | |
// Add sections with customFields (and default text) to audit | |
var tmpSection = {}; | |
if (auditTypeSections.includes(section.field)) { | |
tmpSection.field = section.field; | |
tmpSection.name = section.name; | |
tmpSection.customFields = []; | |
customFields.forEach(field => { | |
field = field.toObject(); | |
if ( | |
field.display === 'section' && | |
field.displaySub === tmpSection.name | |
) { | |
var fieldText = field.text.find( | |
e => e.locale === audit.language, | |
); | |
if (fieldText) fieldText = fieldText.value; | |
else fieldText = ''; | |
delete field.text; | |
tmpSection.customFields.push({ | |
customField: field, | |
text: fieldText, | |
}); | |
} | |
}); | |
audit.sections.push(tmpSection); | |
} | |
}); | |
customFields.forEach(field => { | |
// Add customFields (and default text) to audit | |
field = field.toObject(); | |
if (field.display === 'general') { | |
var fieldText = field.text.find(e => e.locale === audit.language); | |
if (fieldText) fieldText = fieldText.value; | |
else fieldText = ''; | |
delete field.text; | |
audit.customFields.push({ customField: field, text: fieldText }); | |
} | |
}); | |
var VulnerabilityCategory = mongoose.model('VulnerabilityCategory'); | |
return VulnerabilityCategory.getAll(); | |
}) | |
.then(rows => { | |
// Add default sort options for each vulnerability category | |
audit.sortFindings = []; | |
rows.forEach(e => { | |
audit.sortFindings.push({ | |
category: e.name, | |
sortValue: e.sortValue, | |
sortOrder: e.sortOrder, | |
sortAuto: e.sortAuto, | |
}); | |
}); | |
return new Audit(audit).save(); | |
}) | |
.then(rows => { | |
resolve(rows); | |
}) | |
.catch(err => { | |
console.log(err); | |
if (err.name === 'ValidationError') | |
reject({ fn: 'BadParameters', message: 'Audit validation failed' }); | |
else reject(err); | |
}); | |
}); | |
}; | |
// Delete audit | |
AuditSchema.statics.delete = (isAdmin, auditId, userId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findOneAndDelete({ _id: auditId }); | |
if (!isAdmin) query.or([{ creator: userId }]); | |
return query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
resolve(row); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Get audit general information | |
AuditSchema.statics.getGeneral = (isAdmin, auditId, userId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query.populate({ | |
path: 'client', | |
select: 'email firstname lastname', | |
populate: { | |
path: 'company', | |
select: 'name', | |
}, | |
}); | |
query.populate('creator', 'username firstname lastname'); | |
query.populate('collaborators', 'username firstname lastname'); | |
query.populate('reviewers', 'username firstname lastname'); | |
query.populate('company'); | |
query.select( | |
'name auditType date date_start date_end client collaborators language scope.name template customFields', | |
); | |
query | |
.lean() | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
var formatScope = row.scope.map(item => { | |
return item.name; | |
}); | |
for (var i = 0; i < formatScope.length; i++) { | |
row.scope[i] = formatScope[i]; | |
} | |
resolve(row); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Update audit general information | |
AuditSchema.statics.updateGeneral = (isAdmin, auditId, userId, update) => { | |
return new Promise(async (resolve, reject) => { | |
if (update.company && update.company.name) { | |
var Company = mongoose.model('Company'); | |
try { | |
update.company = await Company.create({ name: update.company.name }); | |
} catch (error) { | |
console.log(error); | |
delete update.company; | |
} | |
} | |
var query = Audit.findByIdAndUpdate(auditId, update); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
resolve('Audit General updated successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Get audit Network information | |
AuditSchema.statics.getNetwork = (isAdmin, auditId, userId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query.select('scope'); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
resolve(row); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Update audit Network information | |
AuditSchema.statics.updateNetwork = (isAdmin, auditId, userId, scope) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findByIdAndUpdate(auditId, scope); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
resolve('Audit Network updated successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Create finding | |
AuditSchema.statics.createFinding = (isAdmin, auditId, userId, finding) => { | |
return new Promise((resolve, reject) => { | |
Audit.getLastFindingIdentifier(auditId).then(identifier => { | |
finding.identifier = ++identifier; | |
var query = Audit.findByIdAndUpdate(auditId, { | |
$push: { findings: finding }, | |
}); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
return query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
else { | |
var sortOption = row.sortFindings.find( | |
e => e.category === (finding.category || 'No Category'), | |
); | |
if ((sortOption && sortOption.sortAuto) || !sortOption) | |
// if sort is set to automatic or undefined then we sort (default sort will be applied to undefined sortOption) | |
return Audit.updateSortFindings(isAdmin, auditId, userId, null); | |
// if manual sorting then we do not sort | |
else resolve('Audit Finding created succesfully'); | |
} | |
}) | |
.then(() => { | |
resolve('Audit Finding created successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}); | |
}; | |
AuditSchema.statics.getLastFindingIdentifier = auditId => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.aggregate([ | |
{ $match: { _id: new mongoose.Types.ObjectId(auditId) } }, | |
]); | |
query.unwind('findings'); | |
query.sort({ 'findings.identifier': -1 }); | |
query | |
.exec() | |
.then(row => { | |
if (!row) throw { fn: 'NotFound', message: 'Audit not found' }; | |
else if (row.length === 0 || !row[0].findings.identifier) resolve(0); | |
else resolve(row[0].findings.identifier); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Get finding of audit | |
AuditSchema.statics.getFinding = (isAdmin, auditId, userId, findingId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query.select('findings'); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
var finding = row.findings.id(findingId); | |
if (finding === null) | |
throw { fn: 'NotFound', message: 'Finding not found' }; | |
else resolve(finding); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Update finding of audit | |
AuditSchema.statics.updateFinding = ( | |
isAdmin, | |
auditId, | |
userId, | |
findingId, | |
newFinding, | |
) => { | |
return new Promise((resolve, reject) => { | |
var sortAuto = true; | |
var query = Audit.findById(auditId); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
var finding = row.findings.id(findingId); | |
if (finding === null) | |
reject({ fn: 'NotFound', message: 'Finding not found' }); | |
else { | |
var sortOption = row.sortFindings.find( | |
e => e.category === (newFinding.category || 'No Category'), | |
); | |
if (sortOption && !sortOption.sortAuto) sortAuto = false; | |
Object.keys(newFinding).forEach(key => { | |
finding[key] = newFinding[key]; | |
}); | |
return row.save({ validateBeforeSave: false }); // Disable schema validation since scope changed from Array to String | |
} | |
}) | |
.then(() => { | |
if (sortAuto) | |
return Audit.updateSortFindings(isAdmin, auditId, userId, null); | |
else resolve('Audit Finding updated successfully'); | |
}) | |
.then(() => { | |
resolve('Audit Finding updated successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Delete finding of audit | |
AuditSchema.statics.deleteFinding = (isAdmin, auditId, userId, findingId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query.select('findings'); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
var finding = row.findings.id(findingId); | |
if (finding === null) | |
reject({ fn: 'NotFound', message: 'Finding not found' }); | |
else { | |
row.findings.pull(findingId); | |
return row.save(); | |
} | |
}) | |
.then(() => { | |
resolve('Audit Finding deleted successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Create section | |
AuditSchema.statics.createSection = (isAdmin, auditId, userId, section) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findOneAndUpdate( | |
{ _id: auditId, 'sections.field': { $ne: section.field } }, | |
{ $push: { sections: section } }, | |
); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: | |
'Audit not found or Section already exists or Insufficient Privileges', | |
}; | |
resolve('Audit Section created successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Get section of audit | |
AuditSchema.statics.getSection = (isAdmin, auditId, userId, sectionId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) | |
query.or([ | |
{ creator: userId }, | |
{ collaborators: userId }, | |
{ reviewers: userId }, | |
]); | |
query.select('sections'); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
var section = row.sections.id(sectionId); | |
if (section === null) | |
throw { fn: 'NotFound', message: 'Section id not found' }; | |
else resolve(section); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Update section of audit | |
AuditSchema.statics.updateSection = ( | |
isAdmin, | |
auditId, | |
userId, | |
sectionId, | |
newSection, | |
) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
var section = row.sections.id(sectionId); | |
if (section === null) | |
throw { fn: 'NotFound', message: 'Section not found' }; | |
else { | |
Object.keys(newSection).forEach(key => { | |
section[key] = newSection[key]; | |
}); | |
return row.save(); | |
} | |
}) | |
.then(() => { | |
resolve('Audit Section updated successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Delete section of audit | |
AuditSchema.statics.deleteSection = (isAdmin, auditId, userId, sectionId) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query.select('sections'); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
var section = row.sections.id(sectionId); | |
if (section === null) | |
throw { fn: 'NotFound', message: 'Section not found' }; | |
else { | |
row.sections.pull(sectionId); | |
return row.save(); | |
} | |
}) | |
.then(() => { | |
resolve('Audit Section deleted successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Update audit sort options for findings and run the sorting. If update param is null then just run sorting | |
(AuditSchema.statics.updateSortFindings = ( | |
isAdmin, | |
auditId, | |
userId, | |
update, | |
) => { | |
return new Promise((resolve, reject) => { | |
var audit = {}; | |
var query = Audit.findById(auditId); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
else { | |
audit = row; | |
if (update) | |
// if update is null then we only sort findings (no sort options saving) | |
audit.sortFindings = update.sortFindings; // saving sort options to audit | |
var VulnerabilityCategory = mongoose.model('VulnerabilityCategory'); | |
return VulnerabilityCategory.getAll(); | |
} | |
}) | |
.then(row => { | |
var _ = require('lodash'); | |
var findings = []; | |
var categoriesOrder = row.map(e => e.name); | |
categoriesOrder.push('undefined'); // Put uncategorized findings at the end | |
// Group findings by category | |
var findingList = _.chain(audit.findings) | |
.groupBy('category') | |
.toPairs() | |
.sort( | |
(a, b) => | |
categoriesOrder.indexOf(a[0]) - categoriesOrder.indexOf(b[0]), | |
) | |
.fromPairs() | |
.map((value, key) => { | |
if (key === 'undefined') key = 'No Category'; | |
var sortOption = audit.sortFindings.find( | |
option => option.category === key, | |
); // Get sort option saved in audit | |
if (!sortOption) | |
// no option for category in audit | |
sortOption = row.find(e => e.name === key); // Get sort option from default in vulnerability category | |
if (!sortOption) | |
// no default option or category don't exist | |
sortOption = { | |
sortValue: 'cvssScore', | |
sortOrder: 'desc', | |
sortAuto: true, | |
}; // set a default sort option | |
return { category: key, findings: value, sortOption: sortOption }; | |
}) | |
.value(); | |
findingList.forEach(group => { | |
var order = -1; // desc | |
if (group.sortOption.sortOrder === 'asc') order = 1; | |
var tmpFindings = group.findings.sort((a, b) => { | |
var cvssA = CVSS31.calculateCVSSFromVector(a.cvssv3); | |
var cvssB = CVSS31.calculateCVSSFromVector(b.cvssv3); | |
// Get built-in value (findings[sortValue]) | |
var left = a[group.sortOption.sortValue]; | |
// If sort value is a CVSS Score calculate it | |
if (cvssA.success && group.sortOption.sortValue === 'cvssScore') | |
left = cvssA.baseMetricScore; | |
else if ( | |
cvssA.success && | |
group.sortOption.sortValue === 'cvssTemporalScore' | |
) | |
left = cvssA.temporalMetricScore; | |
else if ( | |
cvssA.success && | |
group.sortOption.sortValue === 'cvssEnvironmentalScore' | |
) | |
left = cvssA.environmentalMetricScore; | |
// Not found then get customField sortValue | |
if (!left) { | |
left = a.customFields.find( | |
e => e.customField.label === group.sortOption.sortValue, | |
); | |
if (left) left = left.text; | |
} | |
// Not found then set default to 0 | |
if (!left) left = 0; | |
// Convert to string in case of int value | |
left = left.toString(); | |
// Same for right value to compare | |
var right = b[group.sortOption.sortValue]; | |
if (cvssB.success && group.sortOption.sortValue === 'cvssScore') | |
right = cvssB.baseMetricScore; | |
else if ( | |
cvssB.success && | |
group.sortOption.sortValue === 'cvssTemporalScore' | |
) | |
right = cvssB.temporalMetricScore; | |
else if ( | |
cvssB.success && | |
group.sortOption.sortValue === 'cvssEnvironmentalScore' | |
) | |
right = cvssB.environmentalMetricScore; | |
if (!right) { | |
right = b.customFields.find( | |
e => e.customField.label === group.sortOption.sortValue, | |
); | |
if (right) right = right.text; | |
} | |
if (!right) right = 0; | |
right = right.toString(); | |
return ( | |
left.localeCompare(right, undefined, { numeric: true }) * order | |
); | |
}); | |
findings = findings.concat(tmpFindings); | |
}); | |
audit.findings = findings; | |
return audit.save(); | |
}) | |
.then(() => { | |
resolve('Audit findings sorted successfully'); | |
}) | |
.catch(err => { | |
console.log(err); | |
reject(err); | |
}); | |
}); | |
}), | |
// Move finding from move.oldIndex to move.newIndex | |
(AuditSchema.statics.moveFindingPosition = ( | |
isAdmin, | |
auditId, | |
userId, | |
move, | |
) => { | |
return new Promise((resolve, reject) => { | |
var query = Audit.findById(auditId); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
var tmp = row.findings[move.oldIndex]; | |
row.findings.splice(move.oldIndex, 1); | |
row.findings.splice(move.newIndex, 0, tmp); | |
row.markModified('findings'); | |
return row.save(); | |
}) | |
.then(msg => { | |
resolve('Audit Finding moved successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}); | |
AuditSchema.statics.updateApprovals = (isAdmin, auditId, userId, update) => { | |
return new Promise(async (resolve, reject) => { | |
var Settings = mongoose.model('Settings'); | |
var settings = await Settings.getAll(); | |
if (update.approvals.length >= settings.reviews.public.minReviewers) { | |
update.state = 'APPROVED'; | |
} else { | |
update.state = 'REVIEW'; | |
} | |
var query = Audit.findByIdAndUpdate(auditId, update); | |
query.nor([{ creator: userId }, { collaborators: userId }]); | |
if (!isAdmin) query.or([{ reviewers: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
resolve('Audit approvals updated successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Update audit parent | |
AuditSchema.statics.updateParent = (isAdmin, auditId, userId, parentId) => { | |
return new Promise(async (resolve, reject) => { | |
var query = Audit.findByIdAndUpdate(auditId, { parentId: parentId }); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
resolve('Audit Parent updated successfully'); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
// Delete audit parent | |
AuditSchema.statics.deleteParent = (isAdmin, auditId, userId) => { | |
return new Promise(async (resolve, reject) => { | |
var query = Audit.findByIdAndUpdate(auditId, { parentId: null }); | |
if (!isAdmin) query.or([{ creator: userId }, { collaborators: userId }]); | |
query | |
.exec() | |
.then(row => { | |
if (!row) | |
throw { | |
fn: 'NotFound', | |
message: 'Audit not found or Insufficient Privileges', | |
}; | |
resolve(row); | |
}) | |
.catch(err => { | |
reject(err); | |
}); | |
}); | |
}; | |
/* | |
*** Methods *** | |
*/ | |
var Audit = mongoose.model('Audit', AuditSchema); | |
// Audit.syncIndexes() | |
module.exports = Audit; | |