var mongoose = require('mongoose'); var Schema = mongoose.Schema; var customField = { _id: false, customField: { type: Schema.Types.ObjectId, ref: 'CustomField' }, text: Schema.Types.Mixed, }; var VulnerabilityDetails = { _id: false, locale: String, // language: String, title: { type: String, unique: true, sparse: true }, vulnType: String, description: String, observation: String, remediation: String, cwes: [String], references: [String], customFields: [customField], }; var VulnerabilitySchema = new Schema( { cvssv3: String, priority: { type: Number, enum: [1, 2, 3, 4] }, remediationComplexity: { type: Number, enum: [1, 2, 3] }, details: [VulnerabilityDetails], status: { type: Number, enum: [0, 1, 2], default: 1 }, // 0: validated, 1: created, 2: updated, category: String, creator: { type: Schema.Types.ObjectId, ref: 'User' }, }, { timestamps: true }, ); /* *** Statics *** */ // Get all vulnerabilities VulnerabilitySchema.statics.getAll = () => { return new Promise((resolve, reject) => { var query = Vulnerability.find(); query.populate('creator', '-_id username'); query .exec() .then(rows => { resolve(rows); }) .catch(err => { reject(err); }); }); }; // Get all vulnerabilities for download VulnerabilitySchema.statics.export = () => { return new Promise((resolve, reject) => { var query = Vulnerability.find(); query.select( 'details cvssv3 priority remediationComplexity cwes references category -_id', ); query .exec() .then(rows => { resolve(rows); }) .catch(err => { reject(err); }); }); }; // Create vulnerability VulnerabilitySchema.statics.create = vulnerabilities => { return new Promise((resolve, reject) => { Vulnerability.insertMany(vulnerabilities, { ordered: false }) .then(rows => { resolve({ created: rows.length, duplicates: 0 }); }) .catch(err => { if (err.code === 11000) { if (err.result.nInserted === 0) reject({ fn: 'BadParameters', message: 'Vulnerability title already exists', }); else { var errorMessages = []; err.writeErrors.forEach(e => errorMessages.push(e.errmsg || 'no errmsg'), ); resolve({ created: err.result.nInserted, duplicates: errorMessages, }); } } else reject(err); }); }); }; // Update vulnerability VulnerabilitySchema.statics.update = (vulnerabilityId, vulnerability) => { return new Promise((resolve, reject) => { var VulnerabilityUpdate = mongoose.model('VulnerabilityUpdate'); var query = Vulnerability.findByIdAndUpdate(vulnerabilityId, vulnerability); query .exec() .then(row => { if (!row) reject({ fn: 'NotFound', message: 'Vulnerability not found' }); else { var query = VulnerabilityUpdate.deleteMany({ vulnerability: vulnerabilityId, }); return query.exec(); } }) .then(row => { resolve('Vulnerability updated successfully'); }) .catch(err => { if (err.code === 11000) reject({ fn: 'BadParameters', message: 'Vulnerability title already exists', }); else reject(err); }); }); }; // Delete all vulnerabilities VulnerabilitySchema.statics.deleteAll = () => { return new Promise((resolve, reject) => { var query = Vulnerability.deleteMany(); query .exec() .then(() => { resolve('All vulnerabilities deleted successfully'); }) .catch(err => { reject(err); }); }); }; // Delete vulnerability VulnerabilitySchema.statics.delete = vulnerabilityId => { return new Promise((resolve, reject) => { var query = Vulnerability.findByIdAndDelete(vulnerabilityId); query .exec() .then(rows => { if (rows) resolve(rows); else reject({ fn: 'NotFound', message: 'Vulnerability not found' }); }) .catch(err => { reject(err); }); }); }; // Get vulnerabilities by language VulnerabilitySchema.statics.getAllByLanguage = locale => { return new Promise((resolve, reject) => { var query = Vulnerability.find({ 'details.locale': locale }); query.select('details cvssv3 priority remediationComplexity category'); query .exec() .then(rows => { if (rows) { var result = []; rows.forEach(row => { row.details.forEach(detail => { if (detail.locale === locale && detail.title) { var temp = {}; temp.cvssv3 = row.cvssv3; temp.priority = row.priority; temp.remediationComplexity = row.remediationComplexity; temp.category = row.category; temp.detail = detail; temp._id = row._id; result.push(temp); } }); }); resolve(result); } else reject({ fn: 'NotFound', message: 'Locale with existing title not found', }); }) .catch(err => { reject(err); }); }); }; VulnerabilitySchema.statics.Merge = (vulnIdPrime, vulnIdMerge, locale) => { return new Promise((resolve, reject) => { var mergeDetail = null; var mergeVuln = null; var primeVuln = null; var query = Vulnerability.findById(vulnIdMerge); query .exec() .then(row => { if (!row) reject({ fn: 'NotFound', message: 'Vulnerability not found' }); else { mergeVuln = row; mergeDetail = row.details.find(d => d.locale === locale); var query = Vulnerability.findById(vulnIdPrime); return query.exec(); } }) .then(row => { if (!row) reject({ fn: 'NotFound', message: 'Vulnerability not found' }); else { if (row.details.findIndex(d => d.locale === locale && d.title) !== -1) reject({ fn: 'BadParameters', message: 'Language already exists in this vulnerability', }); else { primeVuln = row; var removeIndex = mergeVuln.details .map(d => d.title) .indexOf(mergeDetail.title); mergeVuln.details.splice(removeIndex, 1); if (mergeVuln.details.length === 0) return Vulnerability.findByIdAndDelete(mergeVuln._id); else return mergeVuln.save(); } } }) .then(() => { var detail = {}; detail.locale = mergeDetail.locale; detail.title = mergeDetail.title; if (mergeDetail.vulnType) detail.vulnType = mergeDetail.vulnType; if (mergeDetail.description) detail.description = mergeDetail.description; if (mergeDetail.observation) detail.observation = mergeDetail.observation; if (mergeDetail.remediation) detail.remediation = mergeDetail.remediation; if (mergeDetail.customFields) detail.customFields = mergeDetail.customFields; primeVuln.details.push(detail); return primeVuln.save(); }) .then(() => { resolve('Vulnerability merge successfull'); }) .catch(err => { reject(err); }); }); }; /* *** Methods *** */ var Vulnerability = mongoose.model('Vulnerability', VulnerabilitySchema); module.exports = Vulnerability;