Skip to content

Commit

Permalink
Merge pull request #1887 from akto-api-security/fix/fix_summaries_aft…
Browse files Browse the repository at this point in the history
…er_ignored_test

Fix/fix summaries after ignored test
  • Loading branch information
notshivansh authored Jan 2, 2025
2 parents 2d32682 + 440c629 commit 047658f
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.akto.log.LoggerMaker;
import com.akto.log.LoggerMaker.LogDb;
import com.akto.util.Constants;
import com.akto.util.enums.GlobalEnums;
import com.akto.util.enums.GlobalEnums.TestErrorSource;
import com.akto.utils.DeleteTestRunUtils;
import com.akto.utils.Utils;
Expand All @@ -36,10 +37,12 @@
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

import java.nio.file.DirectoryStream.Filter;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -509,23 +512,34 @@ public String fetchTestingRunResultSummary() {
}
}

private static Bson vulnerableFilter = Filters.and(
Filters.eq(TestingRunResult.VULNERABLE, true),
Filters.or(
Filters.exists(TestingRunResult.IS_IGNORED_RESULT, false),
Filters.eq(TestingRunResult.IS_IGNORED_RESULT, false)
)

);

private List<Bson> prepareTestRunResultsFilters(ObjectId testingRunResultSummaryId, QueryMode queryMode) {
List<Bson> filterList = new ArrayList<>();
filterList.add(Filters.eq(TestingRunResult.TEST_RUN_RESULT_SUMMARY_ID, testingRunResultSummaryId));

if(reportFilterList != null) {
Bson filtersForTestingRunResults = com.akto.action.testing.Utils.createFiltersForTestingReport(reportFilterList);
if (!filtersForTestingRunResults.equals(Filters.empty())) filterList.add(filtersForTestingRunResults);
if (!filtersForTestingRunResults.equals(Filters.empty())) {
filterList.add(filtersForTestingRunResults);
}
}

if(queryMode == null) {
if(fetchOnlyVulnerable) {
filterList.add(Filters.eq(TestingRunResult.VULNERABLE, true));
filterList.add(vulnerableFilter);
}
} else {
switch (queryMode) {
case VULNERABLE:
filterList.add(Filters.eq(TestingRunResult.VULNERABLE, true));
filterList.add(vulnerableFilter);
break;
case SKIPPED_EXEC_API_REQUEST_FAILED:
filterList.add(Filters.eq(TestingRunResult.VULNERABLE, false));
Expand Down Expand Up @@ -1058,6 +1072,80 @@ public String modifyTestingRunConfig(){
return SUCCESS.toUpperCase();
}

public String handleRefreshTableCount(){
if(this.testingRunResultSummaryHexId == null || this.testingRunResultSummaryHexId.isEmpty()){
addActionError("Invalid summary id");
return ERROR.toUpperCase();
}
int accountId = Context.accountId.get();
executorService.schedule( new Runnable() {
public void run() {
Context.accountId.set(accountId);
try {
ObjectId summaryObjectId = new ObjectId(testingRunResultSummaryHexId);
List<TestingRunResult> testingRunResults = TestingRunResultDao.instance.findAll(
Filters.and(
Filters.eq(TestingRunResult.TEST_RUN_RESULT_SUMMARY_ID, summaryObjectId),
vulnerableFilter
),
Projections.include(TestingRunResult.API_INFO_KEY, TestingRunResult.TEST_SUB_TYPE)
);

if(testingRunResults.isEmpty()){
return;
}

Set<TestingIssuesId> issuesIds = new HashSet<>();
Map<TestingIssuesId, ObjectId> mapIssueToResultId = new HashMap<>();
Set<ObjectId> ignoredResults = new HashSet<>();
for(TestingRunResult runResult: testingRunResults){
TestingIssuesId issuesId = new TestingIssuesId(runResult.getApiInfoKey(), TestErrorSource.AUTOMATED_TESTING , runResult.getTestSubType());
issuesIds.add(issuesId);
mapIssueToResultId.put(issuesId, runResult.getId());
ignoredResults.add(runResult.getId());
}

List<TestingRunIssues> issues = TestingRunIssuesDao.instance.findAll(
Filters.and(
Filters.in(Constants.ID, issuesIds),
Filters.eq(TestingRunIssues.TEST_RUN_ISSUES_STATUS, GlobalEnums.TestRunIssueStatus.OPEN)
), Projections.include(TestingRunIssues.KEY_SEVERITY)
);

Map<String, Integer> totalCountIssues = new HashMap<>();
totalCountIssues.put("HIGH", 0);
totalCountIssues.put("MEDIUM", 0);
totalCountIssues.put("LOW", 0);

for(TestingRunIssues runIssue: issues){
int initCount = totalCountIssues.getOrDefault(runIssue.getSeverity().name(), 0);
totalCountIssues.put(runIssue.getSeverity().name(), initCount + 1);
if(mapIssueToResultId.containsKey(runIssue.getId())){
ObjectId resId = mapIssueToResultId.get(runIssue.getId());
ignoredResults.remove(resId);
}
}

// update testing run result summary
TestingRunResultSummariesDao.instance.updateOne(
Filters.eq(Constants.ID, summaryObjectId),
Updates.set(TestingRunResultSummary.COUNT_ISSUES, totalCountIssues)
);

// update testing run results, by setting them isIgnored true
TestingRunResultDao.instance.updateMany(
Filters.in(Constants.ID, ignoredResults),
Updates.set(TestingRunResult.IS_IGNORED_RESULT, true)
);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 0 , TimeUnit.SECONDS);

return SUCCESS.toUpperCase();
}


public void setType(TestingEndpoints.Type type) {
this.type = type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.akto.util.enums.GlobalEnums.Severity;
import com.akto.util.enums.GlobalEnums.TestCategory;
import com.akto.util.enums.GlobalEnums.TestRunIssueStatus;
import com.akto.utils.jobs.CleanInventory;
import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.*;
Expand All @@ -50,6 +51,9 @@
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.akto.util.Constants.ID;
import static com.akto.util.Constants.ONE_DAY_TIMESTAMP;
Expand Down Expand Up @@ -77,6 +81,9 @@ public class IssuesAction extends UserAction {
long endTimeStamp;
private Map<Integer,Map<String,Integer>> severityInfo = new HashMap<>();

private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();


private Bson createFilters (boolean useFilterStatus) {
Bson filters = Filters.empty();
if (useFilterStatus && filterStatus != null && !filterStatus.isEmpty()) {
Expand Down Expand Up @@ -513,6 +520,8 @@ public String updateIssueStatus () {
return SUCCESS.toUpperCase();
}

private Map<String,String> testingRunResultHexIdsMap;

public String bulkUpdateIssueStatus () {
if (issueIdArray == null || statusToBeUpdated == null || ignoreReason == null) {
throw new IllegalStateException();
Expand All @@ -530,6 +539,42 @@ public String bulkUpdateIssueStatus () {
update = Updates.combine(update, Updates.unset(TestingRunIssues.IGNORE_REASON));
}
TestingRunIssuesDao.instance.updateMany(Filters.in(ID, issueIdArray), update);

int accountId = Context.accountId.get();
executorService.schedule( new Runnable() {
public void run() {
Context.accountId.set(accountId);
try {

final Map<String, Integer> countIssuesMap = new HashMap<>();
countIssuesMap.put(Severity.HIGH.toString(), 0);
countIssuesMap.put(Severity.MEDIUM.toString(), 0);
countIssuesMap.put(Severity.LOW.toString(), 0);

// update summaries accordingly with issues ignored

Map<ObjectId,String> mapSummaryToResultId = TestingRunResultDao.instance.mapSummaryIdToTestingResultHexId(testingRunResultHexIdsMap.keySet());
Map<ObjectId,Map<String,Integer>> summaryWiseCountMap = new HashMap<>();

for(ObjectId summaryId: mapSummaryToResultId.keySet()){
String resultHexId = mapSummaryToResultId.get(summaryId);
Map<String, Integer> countMap = summaryWiseCountMap.getOrDefault(summaryId, countIssuesMap);
String severity = testingRunResultHexIdsMap.get(resultHexId);
int initialCount = countMap.getOrDefault(severity, 0);
countMap.put(severity, initialCount + 1);
summaryWiseCountMap.put(summaryId, countMap);
}
if(!summaryWiseCountMap.isEmpty()){
TestingRunResultSummariesDao.instance.bulkUpdateTestingRunResultSummariesCount(summaryWiseCountMap);
}

} catch (Exception e) {
e.printStackTrace();
}
}
}, 0 , TimeUnit.SECONDS);


return SUCCESS.toUpperCase();
}

Expand Down Expand Up @@ -904,4 +949,8 @@ public Map<Integer, Map<String, Integer>> getSeverityInfo() {
public void setSeverityInfo(Map<Integer, Map<String, Integer>> severityInfo) {
this.severityInfo = severityInfo;
}

public void setTestingRunResultHexIdsMap(Map<String, String> testingRunResultHexIdsMap) {
this.testingRunResultHexIdsMap = testingRunResultHexIdsMap;
}
}
21 changes: 21 additions & 0 deletions apps/dashboard/src/main/resources/struts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3513,6 +3513,27 @@
</result>
</action>

<action name="api/handleRefreshTableCount" class="com.akto.action.testing.StartTestAction" method="handleRefreshTableCount">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">TEST_RESULTS</param>
<param name="accessType">READ</param>
</interceptor-ref>

<result name="FORBIDDEN" type="json">
<param name="statusCode">403</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
<result name="SUCCESS" type="json"/>
<result name="ERROR" type="json">
<param name="statusCode">422</param>
<param name="ignoreHierarchy">false</param>
<param name="includeProperties">^actionErrors.*</param>
</result>
</action>

<action name="api/fetchTestingRunResultSummary" class="com.akto.action.testing.StartTestAction" method="fetchTestingRunResultSummary">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ function IssuesPage() {
}

function ignoreAction(ignoreReason){
api.bulkUpdateIssueStatus(items, "IGNORED", ignoreReason ).then((res) => {
api.bulkUpdateIssueStatus(items, "IGNORED", ignoreReason, {} ).then((res) => {
setToast(true, false, `Issue${items.length==1 ? "" : "s"} ignored`)
resetResourcesSelected()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ export default {
data: {issuesIds, issueStatusQuery}
})
},
bulkUpdateIssueStatus (issueIdArray, statusToBeUpdated, ignoreReason) {
bulkUpdateIssueStatus (issueIdArray, statusToBeUpdated, ignoreReason, testingRunResultHexIdsMap) {
return request({
url: 'api/bulkUpdateIssueStatus',
method: 'post',
data: {issueIdArray, statusToBeUpdated, ignoreReason}
data: {issueIdArray, statusToBeUpdated, ignoreReason, testingRunResultHexIdsMap}
})
},
fetchTestingRunResult (issueId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,13 @@ const editableConfigsComp = (
}
}

const handleRefreshTableCount = async(summaryHexId) => {
await api.handleRefreshTableCount(summaryHexId).then((res) => {
func.setToast(true, false, "Re-calculating issues count")
setSecondaryPopover(false)
})
}

const EmptyData = () => {
return(
<div style={{margin: 'auto', marginTop: '20vh'}}>
Expand Down Expand Up @@ -720,6 +727,11 @@ const editableConfigsComp = (
content: 'Edit testing config settings',
icon: EditMajor,
onAction: () => { setShowEditableSettings(true); handleAddSettings(); }
},
{
content: 'Re-Calculate Issues Count',
icon: RefreshMajor,
onAction: () => {handleRefreshTableCount(currentSummary.hexId)}
}
]})
const moreActionsComp = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import "./style.css"
import ActivityTracker from '../../dashboard/components/ActivityTracker'
import observeFunc from "../../observe/transform.js"
import settingFunctions from '../../settings/module.js'
import DropdownSearch from '../../../components/shared/DropdownSearch.jsx'
import JiraTicketCreationModal from '../../../components/shared/JiraTicketCreationModal.jsx'

function TestRunResultFlyout(props) {
Expand Down Expand Up @@ -66,7 +65,12 @@ function TestRunResultFlyout(props) {
},[issueDetails?.id?.apiInfoKey])

function ignoreAction(ignoreReason){
issuesApi.bulkUpdateIssueStatus([issueDetails.id], "IGNORED", ignoreReason ).then((res) => {
const severity = (selectedTestRunResult && selectedTestRunResult.vulnerable) ? issueDetails.severity : "";
let obj = {}
if(issueDetails?.testRunIssueStatus !== "IGNORED"){
obj = {[selectedTestRunResult.id]: severity.toUpperCase()}
}
issuesApi.bulkUpdateIssueStatus([issueDetails.id], "IGNORED", ignoreReason, obj ).then((res) => {
func.setToast(true, false, `Issue ignored`)
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -504,5 +504,12 @@ export default {
method: 'post',
data: {...filters, issueIds, endTimeStamp}
})
},
handleRefreshTableCount(testingRunResultSummaryHexId) {
return request({
url: '/api/handleRefreshTableCount',
method: 'post',
data: {testingRunResultSummaryHexId}
})
}
}
7 changes: 0 additions & 7 deletions apps/dashboard/web/src/apps/dashboard/views/issues/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ export default {
data: {issueId, statusToBeUpdated, ignoreReason}
})
},
bulkUpdateIssueStatus (issueIdArray, statusToBeUpdated, ignoreReason) {
return request({
url: 'api/bulkUpdateIssueStatus',
method: 'post',
data: {issueIdArray, statusToBeUpdated, ignoreReason}
})
},
fetchTestingRunResult (issueId) {
return request({
url: 'api/fetchTestingRunResult',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class TestingRunResultDao extends AccountsContextDaoWithRbac<TestingRunResult> {

Expand Down Expand Up @@ -184,6 +188,27 @@ public List<TestingRunResult> fetchLatestTestingRunResult(Bson filters, int limi
return testingRunResults;
}

public Map<ObjectId,String> mapSummaryIdToTestingResultHexId(Set<String> testingRunResultHexIds){
Map<ObjectId,String> finalMap = new HashMap<>();
if(testingRunResultHexIds == null || testingRunResultHexIds.isEmpty()){
return finalMap;
}

List<ObjectId> objectIdList = testingRunResultHexIds.stream()
.map(ObjectId::new)
.collect(Collectors.toList());

// doing only for 1000 results at a time
objectIdList = objectIdList.subList(0, 1000);

List<TestingRunResult> runResults = instance.findAll(Filters.in(Constants.ID, objectIdList), Projections.include(TestingRunResult.TEST_RUN_RESULT_SUMMARY_ID));
for(TestingRunResult runResult: runResults){
finalMap.put(runResult.getTestRunResultSummaryId(), runResult.getHexId());
}

return finalMap;
}

public void createIndicesIfAbsent() {

String dbName = Context.accountId.get()+"";
Expand Down
Loading

0 comments on commit 047658f

Please sign in to comment.