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`;
|
const mapUrl = `/client/${encodeURIComponent(clientId)}/workflowBatch/falsePositive/${encodeURIComponent(mapUuid)}/map`;
|
||||||
|
|
||||||
// Use multipart form (same format as the create endpoint)
|
// Map each finding individually via JSON POST (Ivanti map endpoint only accepts one finding per call)
|
||||||
const formFields = [{ name: 'subjectFilterRequest', value: buildSubjectFilterRequest(findingIds) }];
|
const mappedIds = [];
|
||||||
|
const failedIds = [];
|
||||||
let mapResult;
|
for (const fid of findingIds) {
|
||||||
try {
|
const mapBody = {
|
||||||
mapResult = await ivantiFormPost(mapUrl, formFields, [], apiKey, skipTls);
|
subject: 'hostFinding',
|
||||||
} catch (networkErr) {
|
filterRequest: {
|
||||||
logAudit(db, {
|
filters: [{
|
||||||
userId: req.user.id, username: req.user.username,
|
field: 'id',
|
||||||
action: 'ivanti_fp_findings_add_failed', entityType: 'ivanti_workflow',
|
exclusive: false,
|
||||||
details: { error: networkErr.message, submissionId, findingIds },
|
operator: 'EXACT',
|
||||||
ipAddress: req.ip
|
value: String(fid)
|
||||||
});
|
}]
|
||||||
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.'
|
|
||||||
};
|
};
|
||||||
const errorMsg = mapResult.status >= 500
|
try {
|
||||||
? 'Ivanti API is temporarily unavailable. Please try again later.'
|
const result = await ivantiPost(mapUrl, mapBody, apiKey, skipTls);
|
||||||
: (errorMap[mapResult.status] || `Operation failed: ${mapResult.status}`);
|
if (result.status === 200 || result.status === 201 || result.status === 202) {
|
||||||
return res.status(mapResult.status === 429 ? 429 : 502).json({ success: false, error: errorMsg });
|
mappedIds.push(fid);
|
||||||
|
} else {
|
||||||
|
failedIds.push({ id: fid, status: result.status });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
failedIds.push({ id: fid, error: err.message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Merge finding IDs
|
if (mappedIds.length === 0) {
|
||||||
const mergedJson = mergeFindings(submission.finding_ids_json, findingIds);
|
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();
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -931,25 +934,34 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
|
|||||||
console.error('Failed to update finding_ids_json:', dbErr);
|
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;
|
let queueItemsUpdated = 0;
|
||||||
|
// 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 {
|
try {
|
||||||
const queuePlaceholders = queueItemIds.map(() => '?').join(',');
|
const queuePlaceholders = successQueueIds.map(() => '?').join(',');
|
||||||
queueItemsUpdated = await new Promise((resolve, reject) => {
|
queueItemsUpdated = await new Promise((resolve, reject) => {
|
||||||
db.run(
|
db.run(
|
||||||
`UPDATE ivanti_todo_queue SET status='complete', updated_at=CURRENT_TIMESTAMP WHERE id IN (${queuePlaceholders}) AND user_id=?`,
|
`UPDATE ivanti_todo_queue SET status='complete', updated_at=CURRENT_TIMESTAMP WHERE id IN (${queuePlaceholders}) AND user_id=?`,
|
||||||
[...queueItemIds, req.user.id],
|
[...successQueueIds, req.user.id],
|
||||||
function (err) { if (err) reject(err); else resolve(this.changes); }
|
function (err) { if (err) reject(err); else resolve(this.changes); }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} catch (queueErr) {
|
} catch (queueErr) {
|
||||||
console.error('Failed to update queue items:', queueErr);
|
console.error('Failed to update queue items:', queueErr);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 7. Insert history row
|
// 7. Insert history row
|
||||||
const historyEntry = buildSubmissionHistoryEntry('findings_added', {
|
const historyEntry = buildSubmissionHistoryEntry('findings_added', {
|
||||||
addedFindingIds: findingIds,
|
addedFindingIds: mappedIds,
|
||||||
queueItemIds: queueItemIds
|
failedFindingIds: failedIds.map(f => f.id || f),
|
||||||
|
queueItemIds: successQueueIds
|
||||||
}, req.user.id, req.user.username);
|
}, req.user.id, req.user.username);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -974,7 +986,7 @@ function createIvantiFpWorkflowRouter(db, requireAuth) {
|
|||||||
ipAddress: req.ip
|
ipAddress: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ success: true, addedFindings: findingIds, queueItemsUpdated });
|
res.json({ success: true, addedFindings: mappedIds, failedFindings: failedIds, queueItemsUpdated });
|
||||||
})().catch((unexpectedErr) => {
|
})().catch((unexpectedErr) => {
|
||||||
console.error('Unexpected error in POST /submissions/:id/findings:', unexpectedErr);
|
console.error('Unexpected error in POST /submissions/:id/findings:', unexpectedErr);
|
||||||
res.status(500).json({ success: false, error: 'Internal server error.' });
|
res.status(500).json({ success: false, error: 'Internal server error.' });
|
||||||
|
|||||||
Reference in New Issue
Block a user