Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/fix summaries after ignored test #1887

Merged
merged 5 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
Ark2307 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading