Spaces:
Runtime error
Runtime error
module.exports = function (app, io) { | |
var Response = require('../lib/httpResponse'); | |
var Audit = require('mongoose').model('Audit'); | |
var acl = require('../lib/auth').acl; | |
var reportGenerator = require('../lib/report-generator'); | |
var _ = require('lodash'); | |
var utils = require('../lib/utils'); | |
var Settings = require('mongoose').model('Settings'); | |
/* ### AUDITS LIST ### */ | |
// Get audits list of user (all for admin) with regex filter on findings | |
app.get('/api/audits', acl.hasPermission('audits:read'), function (req, res) { | |
var getUsersRoom = function (room) { | |
return utils.getSockets(io, room).map(s => s.username); | |
}; | |
var filters = {}; | |
if (req.query.findingTitle) | |
filters['findings.title'] = new RegExp( | |
utils.escapeRegex(req.query.findingTitle), | |
'i', | |
); | |
if (req.query.type && req.query.type === 'default') | |
filters.$or = [{ type: 'default' }, { type: { $exists: false } }]; | |
if (req.query.type && ['multi', 'retest'].includes(req.query.type)) | |
filters.type = req.query.type; | |
Audit.getAudits( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.decodedToken.id, | |
filters, | |
) | |
.then(msg => { | |
var result = []; | |
msg.forEach(audit => { | |
var a = {}; | |
a._id = audit._id; | |
a.name = audit.name; | |
a.language = audit.language; | |
a.auditType = audit.auditType; | |
a.creator = audit.creator; | |
a.collaborators = audit.collaborators; | |
a.company = audit.company; | |
a.createdAt = audit.createdAt; | |
a.ext = | |
!!audit.template && !!audit.template.ext | |
? audit.template.ext | |
: 'Template error'; | |
a.reviewers = audit.reviewers; | |
a.approvals = audit.approvals; | |
a.state = audit.state; | |
a.type = audit.type; | |
a.parentId = audit.parentId; | |
if (acl.isAllowed(req.decodedToken.role, 'audits:users-connected')) { | |
a.connected = getUsersRoom(audit._id.toString()); | |
} | |
result.push(a); | |
}); | |
Response.Ok(res, result); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}); | |
// Create audit (default or multi) with name, auditType, language provided | |
// parentId can be set only if type is default | |
app.post( | |
'/api/audits', | |
acl.hasPermission('audits:create'), | |
function (req, res) { | |
if (!req.body.name || !req.body.language || !req.body.auditType) { | |
Response.BadParameters( | |
res, | |
'Missing some required parameters: name, language, auditType', | |
); | |
return; | |
} | |
if (!utils.validFilename(req.body.language)) { | |
Response.BadParameters(res, 'Invalid characters for language'); | |
return; | |
} | |
var audit = {}; | |
// Required params | |
audit.name = req.body.name; | |
audit.language = req.body.language; | |
audit.auditType = req.body.auditType; | |
audit.type = 'default'; | |
// Optional params | |
if (req.body.type && req.body.type === 'multi') | |
audit.type = req.body.type; | |
if (audit.type === 'default' && req.body.parentId) | |
audit.parentId = req.body.parentId; | |
Audit.create(audit, req.decodedToken.id) | |
.then(inserted => | |
Response.Created(res, { | |
message: 'Audit created successfully', | |
audit: inserted, | |
}), | |
) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Get audits children | |
app.get( | |
'/api/audits/:auditId/children', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
var getUsersRoom = function (room) { | |
return utils.getSockets(io, room).map(s => s.username); | |
}; | |
Audit.getAuditChildren( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(msg => { | |
var result = []; | |
msg.forEach(audit => { | |
var a = {}; | |
a._id = audit._id; | |
a.name = audit.name; | |
a.auditType = audit.auditType; | |
a.approvals = audit.approvals; | |
a.state = audit.state; | |
if ( | |
acl.isAllowed(req.decodedToken.role, 'audits:users-connected') | |
) { | |
a.connected = getUsersRoom(audit._id.toString()); | |
} | |
result.push(a); | |
}); | |
Response.Ok(res, result); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Get audit retest with auditId | |
app.get( | |
'/api/audits/:auditId/retest', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getRetest( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(msg => Response.Ok(res, msg)) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Create audit retest with auditId | |
app.post( | |
'/api/audits/:auditId/retest', | |
acl.hasPermission('audits:create'), | |
function (req, res) { | |
if (!req.body.auditType) { | |
Response.BadParameters( | |
res, | |
'Missing some required parameters: auditType', | |
); | |
return; | |
} | |
Audit.createRetest( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
req.body.auditType, | |
) | |
.then(inserted => | |
Response.Created(res, { | |
message: 'Audit Retest created successfully', | |
audit: inserted, | |
}), | |
) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Delete audit if creator or admin | |
app.delete( | |
'/api/audits/:auditId', | |
acl.hasPermission('audits:delete'), | |
function (req, res) { | |
Audit.delete( | |
acl.isAllowed(req.decodedToken.role, 'audits:delete-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(msg => Response.Ok(res, msg)) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
/* ### AUDITS EDIT ### */ | |
// Get Audit with ID | |
app.get( | |
'/api/audits/:auditId', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(msg => Response.Ok(res, msg)) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Get audit general information | |
app.get( | |
'/api/audits/:auditId/general', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getGeneral( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(msg => Response.Ok(res, msg)) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Update audit general information | |
app.put( | |
'/api/audits/:auditId/general', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
var update = {}; | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
if (req.body.reviewers) { | |
if (req.body.reviewers.some(element => !element._id)) { | |
Response.BadParameters(res, 'One or more reviewer is missing an _id'); | |
return; | |
} | |
// Is the new reviewer the creator of the audit? | |
if ( | |
req.body.reviewers.some(element => element._id === audit.creator._id) | |
) { | |
Response.BadParameters( | |
res, | |
'A user cannot simultaneously be a reviewer and a collaborator/creator', | |
); | |
return; | |
} | |
// Is the new reviewer one of the new collaborators that will override current collaborators? | |
if (req.body.collaborators) { | |
req.body.reviewers.forEach(reviewer => { | |
if ( | |
req.body.collaborators.some( | |
element => !element._id || element._id === reviewer._id, | |
) | |
) { | |
Response.BadParameters( | |
res, | |
'A user cannot simultaneously be a reviewer and a collaborator/creator', | |
); | |
return; | |
} | |
}); | |
} | |
// If no new collaborators are being set, is the new reviewer one of the current collaborators? | |
else if (audit.collaborators) { | |
req.body.reviewers.forEach(reviewer => { | |
if ( | |
audit.collaborators.some(element => element._id === reviewer._id) | |
) { | |
Response.BadParameters( | |
res, | |
'A user cannot simultaneously be a reviewer and a collaborator/creator', | |
); | |
return; | |
} | |
}); | |
} | |
} | |
if (req.body.collaborators) { | |
if (req.body.collaborators.some(element => !element._id)) { | |
Response.BadParameters( | |
res, | |
'One or more collaborator is missing an _id', | |
); | |
return; | |
} | |
// Are the new collaborators part of the current reviewers? | |
req.body.collaborators.forEach(collaborator => { | |
if ( | |
audit.reviewers.some(element => element._id === collaborator._id) | |
) { | |
Response.BadParameters( | |
res, | |
'A user cannot simultaneously be a reviewer and a collaborator/creator', | |
); | |
return; | |
} | |
}); | |
// If the new collaborator already gave a review, remove said review, accept collaborator | |
if (audit.approvals) { | |
var newApprovals = audit.approvals.filter( | |
approval => | |
!req.body.collaborators.some( | |
collaborator => approval.toString() === collaborator._id, | |
), | |
); | |
update.approvals = newApprovals; | |
} | |
} | |
// Optional parameters | |
if (req.body.name) update.name = req.body.name; | |
if (req.body.date) update.date = req.body.date; | |
if (req.body.date_start) update.date_start = req.body.date_start; | |
if (req.body.date_end) update.date_end = req.body.date_end; | |
if (req.body.client !== undefined) update.client = req.body.client; | |
if (req.body.company !== undefined) { | |
update.company = {}; | |
if (req.body.company && req.body.company._id) | |
update.company._id = req.body.company._id; | |
else if (req.body.company && req.body.company.name) | |
update.company.name = req.body.company.name; | |
else update.company = null; | |
} | |
if (req.body.collaborators) update.collaborators = req.body.collaborators; | |
if (req.body.reviewers) update.reviewers = req.body.reviewers; | |
if (req.body.language && utils.validFilename(req.body.language)) | |
update.language = req.body.language; | |
if (req.body.scope && typeof (req.body.scope === 'array')) { | |
update.scope = req.body.scope.map(item => { | |
return { name: item }; | |
}); | |
} | |
if (req.body.template) update.template = req.body.template; | |
if (req.body.customFields) update.customFields = req.body.customFields; | |
if ( | |
settings.reviews.enabled && | |
settings.reviews.private.removeApprovalsUponUpdate | |
) | |
update.approvals = []; | |
Audit.updateGeneral( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
update, | |
) | |
.then(msg => { | |
io.to(req.params.auditId).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Get audit network information | |
app.get( | |
'/api/audits/:auditId/network', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getNetwork( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(msg => Response.Ok(res, msg)) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Update audit network information | |
app.put( | |
'/api/audits/:auditId/network', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
var update = {}; | |
// Optional parameters | |
if (req.body.scope) update.scope = req.body.scope; | |
if ( | |
settings.reviews.enabled && | |
settings.reviews.private.removeApprovalsUponUpdate | |
) | |
update.approvals = []; | |
Audit.updateNetwork( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
update, | |
) | |
.then(msg => Response.Ok(res, msg)) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// POST to export an encrypted PDF. | |
app.post( | |
'/api/audits/:auditId/generate/pdf', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(async audit => { | |
if ( | |
['ppt', 'pptx', 'doc', 'docx', 'docm'].includes(audit.template.ext) | |
) { | |
let reportPdf; | |
if (req.body.password) { | |
reportPdf = await reportGenerator.generateEncryptedPdf( | |
audit, | |
req.body.password, | |
); | |
} else { | |
Response.BadParameters(res, 'No password included'); | |
} | |
if (reportPdf) { | |
res.setHeader( | |
'Content-Disposition', | |
`attachment; filename=${audit.name}.pdf`, | |
); | |
res.setHeader('Content-Type', 'application/pdf'); | |
res.send(reportPdf); | |
} else { | |
console.error('Error generating PDF'); | |
Response.Internal(res, 'Error generating PDF'); | |
} | |
} else { | |
Response.BadParameters( | |
res, | |
'Template not in a Microsoft Word/Powerpoint format', | |
); | |
} | |
}) | |
.catch(err => { | |
console.log(err); | |
if (err.code === 'ENOENT') | |
Response.BadParameters(res, 'Template File not found'); | |
else Response.Internal(res, err); | |
}); | |
}, | |
); | |
// Generate report as PDF | |
app.get( | |
'/api/audits/:auditId/generate/pdf', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(async audit => { | |
if ( | |
['ppt', 'pptx', 'doc', 'docx', 'docm'].find( | |
ext => ext === audit.template.ext, | |
) | |
) { | |
var reportPdf = await reportGenerator.generatePdf(audit); | |
Response.SendFile(res, `${audit.name}.pdf`, reportPdf); | |
} else { | |
Response.BadParameters( | |
res, | |
'Template not in a Microsoft Word/Powerpoint format', | |
); | |
} | |
}) | |
.catch(err => { | |
console.log(err); | |
if (err.code === 'ENOENT') | |
Response.BadParameters(res, 'Template File not found'); | |
else Response.Internal(res, err); | |
}); | |
}, | |
); | |
// Generate Report as csv | |
app.get( | |
'/api/audits/:auditId/generate/csv', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(async audit => { | |
var reportCsv = await reportGenerator.generateCsv(audit); | |
Response.SendFile(res, `${audit.name}.csv`, reportCsv); | |
}) | |
.catch(err => { | |
console.log(err); | |
Response.Internal(res, err); | |
}); | |
}, | |
); | |
// Generate Report as json | |
app.get( | |
'/api/audits/:auditId/generate/json', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(async audit => { | |
Response.SendFile(res, `${audit.name}.json`, audit); | |
}) | |
.catch(err => { | |
console.log(err); | |
Response.Internal(res, err); | |
}); | |
}, | |
); | |
// Add finding to audit | |
app.post( | |
'/api/audits/:auditId/findings', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
if (!req.body.title) { | |
Response.BadParameters(res, 'Missing some required parameters: title'); | |
return; | |
} | |
var finding = {}; | |
// Required parameters | |
finding.title = req.body.title; | |
// Optional parameters | |
if (req.body.vulnType) finding.vulnType = req.body.vulnType; | |
if (req.body.description) finding.description = req.body.description; | |
if (req.body.observation) finding.observation = req.body.observation; | |
if (req.body.remediation) finding.remediation = req.body.remediation; | |
if (req.body.remediationComplexity) | |
finding.remediationComplexity = req.body.remediationComplexity; | |
if (req.body.priority) finding.priority = req.body.priority; | |
if (req.body.references) finding.references = req.body.references; | |
if (req.body.cwes) finding.cwes = req.body.cwes; | |
if (req.body.cvssv3) finding.cvssv3 = req.body.cvssv3; | |
if (req.body.poc) finding.poc = req.body.poc; | |
if (req.body.scope) finding.scope = req.body.scope; | |
if (req.body.status !== undefined) finding.status = req.body.status; | |
if (req.body.category) finding.category = req.body.category; | |
if (req.body.customFields) finding.customFields = req.body.customFields; | |
if ( | |
settings.reviews.enabled && | |
settings.reviews.private.removeApprovalsUponUpdate | |
) { | |
Audit.updateGeneral( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
{ approvals: [] }, | |
); | |
} | |
Audit.createFinding( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
finding, | |
) | |
.then(msg => { | |
io.to(req.params.auditId).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Get finding of audit | |
app.get( | |
'/api/audits/:auditId/findings/:findingId', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getFinding( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
req.params.findingId, | |
) | |
.then(msg => Response.Ok(res, msg)) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Update finding of audit | |
app.put( | |
'/api/audits/:auditId/findings/:findingId', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
var finding = {}; | |
// Optional parameters | |
if (req.body.title) finding.title = req.body.title; | |
if (req.body.vulnType) finding.vulnType = req.body.vulnType; | |
if (!_.isNil(req.body.description)) | |
finding.description = req.body.description; | |
if (!_.isNil(req.body.observation)) | |
finding.observation = req.body.observation; | |
if (!_.isNil(req.body.remediation)) | |
finding.remediation = req.body.remediation; | |
if (req.body.remediationComplexity) | |
finding.remediationComplexity = req.body.remediationComplexity; | |
if (req.body.priority) finding.priority = req.body.priority; | |
if (req.body.references) finding.references = req.body.references; | |
if (req.body.cwes) finding.cwes = req.body.cwes; | |
if (req.body.cvssv3) finding.cvssv3 = req.body.cvssv3; | |
if (!_.isNil(req.body.poc)) finding.poc = req.body.poc; | |
if (!_.isNil(req.body.scope)) finding.scope = req.body.scope; | |
if (req.body.status !== undefined) finding.status = req.body.status; | |
if (req.body.category) finding.category = req.body.category; | |
if (req.body.customFields) finding.customFields = req.body.customFields; | |
if (req.body.retestDescription) | |
finding.retestDescription = req.body.retestDescription; | |
if (req.body.retestStatus) finding.retestStatus = req.body.retestStatus; | |
if ( | |
settings.reviews.enabled && | |
settings.reviews.private.removeApprovalsUponUpdate | |
) { | |
Audit.updateGeneral( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
{ approvals: [] }, | |
); | |
} | |
Audit.updateFinding( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
req.params.findingId, | |
finding, | |
) | |
.then(msg => { | |
io.to(req.params.auditId).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Delete finding of audit | |
app.delete( | |
'/api/audits/:auditId/findings/:findingId', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
Audit.deleteFinding( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
req.params.findingId, | |
) | |
.then(msg => { | |
io.to(req.params.auditId).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Get section of audit | |
app.get( | |
'/api/audits/:auditId/sections/:sectionId', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getSection( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
req.params.sectionId, | |
) | |
.then(msg => Response.Ok(res, msg)) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Update section of audit | |
app.put( | |
'/api/audits/:auditId/sections/:sectionId', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
if (typeof req.body.customFields === 'undefined') { | |
Response.BadParameters( | |
res, | |
'Missing some required parameters: customFields', | |
); | |
return; | |
} | |
var section = {}; | |
// Mandatory parameters | |
section.customFields = req.body.customFields; | |
// For retrocompatibility with old section.text usage | |
if (req.body.text) section.text = req.body.text; | |
if ( | |
settings.reviews.enabled && | |
settings.reviews.private.removeApprovalsUponUpdate | |
) { | |
Audit.updateGeneral( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
{ approvals: [] }, | |
); | |
} | |
Audit.updateSection( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
req.params.sectionId, | |
section, | |
) | |
.then(msg => { | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Generate Report for specific audit | |
app.get( | |
'/api/audits/:auditId/generate', | |
acl.hasPermission('audits:read'), | |
function (req, res) { | |
Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(async audit => { | |
var settings = await Settings.getAll(); | |
if ( | |
settings.reviews.enabled && | |
settings.reviews.public.mandatoryReview && | |
audit.state !== 'APPROVED' | |
) { | |
Response.Forbidden( | |
res, | |
'Audit was not approved therefore cannot be exported.', | |
); | |
return; | |
} | |
if (!audit.template) | |
throw { fn: 'BadParameters', message: 'Template not defined' }; | |
var reportDoc = await reportGenerator.generateDoc(audit); | |
Response.SendFile( | |
res, | |
`${audit.name.replace(/[\\\/:*?"<>|]/g, '')}.${audit.template.ext || 'docx'}`, | |
reportDoc, | |
); | |
}) | |
.catch(err => { | |
if (err.code === 'ENOENT') | |
Response.BadParameters(res, 'Template File not found'); | |
else Response.Internal(res, err); | |
}); | |
}, | |
); | |
// Update sort options of an audit | |
app.put( | |
'/api/audits/:auditId/sortfindings', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
var update = {}; | |
// Optional parameters | |
if (req.body.sortFindings) update.sortFindings = req.body.sortFindings; | |
if ( | |
settings.reviews.enabled && | |
settings.reviews.private.removeApprovalsUponUpdate | |
) | |
update.approvals = []; | |
Audit.updateSortFindings( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
update, | |
) | |
.then(msg => { | |
io.to(req.params.auditId).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Update finding position (oldIndex -> newIndex) | |
app.put( | |
'/api/audits/:auditId/movefinding', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
if ( | |
typeof req.body.oldIndex === 'undefined' || | |
typeof req.body.newIndex === 'undefined' | |
) { | |
Response.BadParameters( | |
res, | |
'Missing some required parameters: oldIndex, newIndex', | |
); | |
return; | |
} | |
var move = {}; | |
// Required parameters | |
move.oldIndex = req.body.oldIndex; | |
move.newIndex = req.body.newIndex; | |
if ( | |
settings.reviews.enabled && | |
settings.reviews.private.removeApprovalsUponUpdate | |
) { | |
Audit.updateGeneral( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
{ approvals: [] }, | |
); | |
} | |
Audit.moveFindingPosition( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
move, | |
) | |
.then(msg => { | |
io.to(req.params.auditId).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Give or remove a reviewer's approval to an audit | |
app.put( | |
'/api/audits/:auditId/toggleApproval', | |
acl.hasPermission('audits:review'), | |
async function (req, res) { | |
const settings = await Settings.getAll(); | |
if (!settings.reviews.enabled) { | |
Response.Forbidden(res, 'Audit reviews are not enabled.'); | |
return; | |
} | |
Audit.findById(req.params.auditId) | |
.then(audit => { | |
if (audit.state !== 'REVIEW' && audit.state !== 'APPROVED') { | |
Response.Forbidden( | |
res, | |
'The audit is not approvable in the current state.', | |
); | |
return; | |
} | |
var hasApprovedBefore = false; | |
var newApprovalsArray = []; | |
if (audit.approvals) { | |
audit.approvals.forEach(approval => { | |
if (approval._id.toString() === req.decodedToken.id) { | |
hasApprovedBefore = true; | |
} else { | |
newApprovalsArray.push(approval); | |
} | |
}); | |
} | |
if (!hasApprovedBefore) { | |
newApprovalsArray.push({ | |
_id: req.decodedToken.id, | |
role: req.decodedToken.role, | |
username: req.decodedToken.username, | |
firstname: req.decodedToken.firstname, | |
lastname: req.decodedToken.lastname, | |
}); | |
} | |
var update = { approvals: newApprovalsArray }; | |
Audit.updateApprovals( | |
acl.isAllowed(req.decodedToken.role, 'audits:review-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
update, | |
) | |
.then(() => { | |
io.to(req.params.auditId).emit('updateAudit'); | |
Response.Ok(res, 'Approval updated successfully.'); | |
}) | |
.catch(err => { | |
Response.Internal(res, err); | |
}); | |
}) | |
.catch(err => { | |
Response.Internal(res, err); | |
}); | |
}, | |
); | |
// Sets the audit state to EDIT or REVIEW | |
app.put( | |
'/api/audits/:auditId/updateReadyForReview', | |
acl.hasPermission('audits:update'), | |
async function (req, res) { | |
const settings = await Settings.getAll(); | |
if (!settings.reviews.enabled) { | |
Response.Forbidden(res, 'Audit reviews are not enabled.'); | |
return; | |
} | |
var update = {}; | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
if (audit.state !== 'EDIT' && audit.state !== 'REVIEW') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the proper state for this action.', | |
); | |
return; | |
} | |
if ( | |
req.body.state != undefined && | |
(req.body.state === 'EDIT' || req.body.state === 'REVIEW') | |
) | |
update.state = req.body.state; | |
if (update.state === 'EDIT') { | |
var newApprovalsArray = []; | |
if (audit.approvals) { | |
audit.approvals.forEach(approval => { | |
if (approval._id.toString() !== req.decodedToken.id) { | |
newApprovalsArray.push(approval); | |
} | |
}); | |
update.approvals = newApprovalsArray; | |
} | |
} | |
Audit.updateGeneral( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
update, | |
) | |
.then(msg => { | |
io.to(req.params.auditId).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Update parentId of Audit | |
app.put( | |
'/api/audits/:auditId/updateParent', | |
acl.hasPermission('audits:create'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.body.parentId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && audit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
if (!req.body.parentId) { | |
Response.BadParameters( | |
res, | |
'Missing some required parameters: parentId', | |
); | |
return; | |
} | |
Audit.updateParent( | |
acl.isAllowed(req.decodedToken.role, 'audits:update-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
req.body.parentId, | |
) | |
.then(msg => { | |
io.to(req.body.parentId).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
// Delete parentId of Audit | |
app.delete( | |
'/api/audits/:auditId/deleteParent', | |
acl.hasPermission('audits:delete'), | |
async function (req, res) { | |
var settings = await Settings.getAll(); | |
var audit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
); | |
var parentAudit = await Audit.getAudit( | |
acl.isAllowed(req.decodedToken.role, 'audits:read-all'), | |
audit.parentId, | |
req.decodedToken.id, | |
); | |
if (settings.reviews.enabled && parentAudit.state !== 'EDIT') { | |
Response.Forbidden( | |
res, | |
'The audit is not in the EDIT state and therefore cannot be edited.', | |
); | |
return; | |
} | |
Audit.deleteParent( | |
acl.isAllowed(req.decodedToken.role, 'audits:delete-all'), | |
req.params.auditId, | |
req.decodedToken.id, | |
) | |
.then(msg => { | |
if (msg.parentId) io.to(msg.parentId.toString()).emit('updateAudit'); | |
Response.Ok(res, msg); | |
}) | |
.catch(err => Response.Internal(res, err)); | |
}, | |
); | |
}; | |