fix: map findings one at a time via JSON POST, only mark successfully mapped queue items as complete
This commit is contained in:
@@ -887,36 +887,39 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
|
||||
|
||||
const mapUrl = `/client/${encodeURIComponent(clientId)}/workflowBatch/falsePositive/${encodeURIComponent(mapUuid)}/map`;
|
||||
|
||||
// Use multipart form (same format as the create endpoint)
|
||||
const formFields = [{ name: 'subjectFilterRequest', value: buildSubjectFilterRequest(findingIds) }];
|
||||
|
||||
let mapResult;
|
||||
try {
|
||||
mapResult = await ivantiFormPost(mapUrl, formFields, [], apiKey, skipTls);
|
||||
} catch (networkErr) {
|
||||
logAudit(db, {
|
||||
userId: req.user.id, username: req.user.username,
|
||||
action: 'ivanti_fp_findings_add_failed', entityType: 'ivanti_workflow',
|
||||
details: { error: networkErr.message, submissionId, findingIds },
|
||||
ipAddress: req.ip
|
||||
});
|
||||
return res.status(502).json({ success: false, error: 'Failed to connect to Ivanti API.', details: networkErr.message });
|
||||
}
|
||||
|
||||
if (mapResult.status !== 200 && mapResult.status !== 201 && mapResult.status !== 202) {
|
||||
const errorMap = {
|
||||
401: 'Ivanti API key is invalid or missing. Contact your administrator.',
|
||||
419: 'API key lacks permissions for this operation.',
|
||||
429: 'Ivanti API rate limit reached. Please try again in a few minutes.'
|
||||
// Map each finding individually via JSON POST (Ivanti map endpoint only accepts one finding per call)
|
||||
const mappedIds = [];
|
||||
const failedIds = [];
|
||||
for (const fid of findingIds) {
|
||||
const mapBody = {
|
||||
subject: 'hostFinding',
|
||||
filterRequest: {
|
||||
filters: [{
|
||||
field: 'id',
|
||||
exclusive: false,
|
||||
operator: 'EXACT',
|
||||
value: String(fid)
|
||||
}]
|
||||
}
|
||||
};
|
||||
const errorMsg = mapResult.status >= 500
|
||||
? 'Ivanti API is temporarily unavailable. Please try again later.'
|
||||
: (errorMap[mapResult.status] || `Operation failed: ${mapResult.status}`);
|
||||
return res.status(mapResult.status === 429 ? 429 : 502).json({ success: false, error: errorMsg });
|
||||
try {
|
||||
const result = await ivantiPost(mapUrl, mapBody, apiKey, skipTls);
|
||||
if (result.status === 200 || result.status === 201 || result.status === 202) {
|
||||
mappedIds.push(fid);
|
||||
} else {
|
||||
failedIds.push({ id: fid, status: result.status });
|
||||
}
|
||||
} catch (err) {
|
||||
failedIds.push({ id: fid, error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Merge finding IDs
|
||||
const mergedJson = mergeFindings(submission.finding_ids_json, findingIds);
|
||||
if (mappedIds.length === 0) {
|
||||
return res.status(502).json({ success: false, error: 'Failed to map any findings to the workflow.' });
|
||||
}
|
||||
|
||||
// 5. Merge only successfully mapped finding IDs
|
||||
const mergedJson = mergeFindings(submission.finding_ids_json, mappedIds);
|
||||
const now = new Date().toISOString();
|
||||
|
||||
try {
|
||||
@@ -931,25 +934,34 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
|
||||
console.error('Failed to update finding_ids_json:', dbErr);
|
||||
}
|
||||
|
||||
// 6. Mark queue items complete
|
||||
// 6. Mark only successfully mapped queue items as complete
|
||||
let queueItemsUpdated = 0;
|
||||
try {
|
||||
const queuePlaceholders = queueItemIds.map(() => '?').join(',');
|
||||
queueItemsUpdated = await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`UPDATE ivanti_todo_queue SET status='complete', updated_at=CURRENT_TIMESTAMP WHERE id IN (${queuePlaceholders}) AND user_id=?`,
|
||||
[...queueItemIds, req.user.id],
|
||||
function (err) { if (err) reject(err); else resolve(this.changes); }
|
||||
);
|
||||
});
|
||||
} catch (queueErr) {
|
||||
console.error('Failed to update queue items:', queueErr);
|
||||
// Build a set of successfully mapped finding IDs to match against queue items
|
||||
const mappedSet = new Set(mappedIds.map(String));
|
||||
const successQueueIds = queueItemIds.filter((qid, idx) => {
|
||||
const queueItem = queueRows.find(r => r.id === qid);
|
||||
return queueItem && mappedSet.has(String(findingIds[idx]));
|
||||
});
|
||||
if (successQueueIds.length > 0) {
|
||||
try {
|
||||
const queuePlaceholders = successQueueIds.map(() => '?').join(',');
|
||||
queueItemsUpdated = await new Promise((resolve, reject) => {
|
||||
db.run(
|
||||
`UPDATE ivanti_todo_queue SET status='complete', updated_at=CURRENT_TIMESTAMP WHERE id IN (${queuePlaceholders}) AND user_id=?`,
|
||||
[...successQueueIds, req.user.id],
|
||||
function (err) { if (err) reject(err); else resolve(this.changes); }
|
||||
);
|
||||
});
|
||||
} catch (queueErr) {
|
||||
console.error('Failed to update queue items:', queueErr);
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Insert history row
|
||||
const historyEntry = buildSubmissionHistoryEntry('findings_added', {
|
||||
addedFindingIds: findingIds,
|
||||
queueItemIds: queueItemIds
|
||||
addedFindingIds: mappedIds,
|
||||
failedFindingIds: failedIds.map(f => f.id || f),
|
||||
queueItemIds: successQueueIds
|
||||
}, req.user.id, req.user.username);
|
||||
|
||||
try {
|
||||
@@ -974,7 +986,7 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
|
||||
ipAddress: req.ip
|
||||
});
|
||||
|
||||
res.json({ success: true, addedFindings: findingIds, queueItemsUpdated });
|
||||
res.json({ success: true, addedFindings: mappedIds, failedFindings: failedIds, queueItemsUpdated });
|
||||
})().catch((unexpectedErr) => {
|
||||
console.error('Unexpected error in POST /submissions/:id/findings:', unexpectedErr);
|
||||
res.status(500).json({ success: false, error: 'Internal server error.' });
|
||||
|
||||
Reference in New Issue
Block a user