Skip to content

Commit

Permalink
Merge pull request #1847 from akto-api-security/feature/export_select…
Browse files Browse the repository at this point in the history
…ed_issues

Feature/export selected issues
  • Loading branch information
Ark2307 authored Dec 23, 2024
2 parents ffa7358 + 7b7139c commit a9d8098
Show file tree
Hide file tree
Showing 14 changed files with 326 additions and 592 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,17 @@
import com.akto.dto.testing.*;
import com.akto.dto.testing.TestingRun.State;
import com.akto.dto.testing.TestingRun.TestingRunType;
import com.akto.dto.testing.TestResult.Confidence;
import com.akto.dto.testing.TestResult.TestError;
import com.akto.dto.testing.TestingRun.State;
import com.akto.dto.testing.TestingRun.TestingRunType;
import com.akto.dto.testing.WorkflowTestResult.NodeResult;
import com.akto.dto.testing.info.CurrentTestsStatus;
import com.akto.dto.testing.info.CurrentTestsStatus.StatusForIndividualTest;
import com.akto.dto.testing.sources.TestSourceConfig;
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;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
import com.mongodb.BasicDBObject;
import com.mongodb.client.model.*;
import com.mongodb.client.result.InsertOneResult;
import com.opensymphony.xwork2.Action;
Expand Down Expand Up @@ -656,14 +649,17 @@ public String fetchTestingRunResults() {
return SUCCESS.toUpperCase();
}

private Map<String, List<String>> reportFilterList;

public String fetchVulnerableTestRunResults() {
ObjectId testingRunResultSummaryId;
try {
testingRunResultSummaryId = new ObjectId(testingRunResultSummaryHexId);

Bson filterForReport = com.akto.action.testing.Utils.createFiltersForTestingReport(reportFilterList);
Bson filters = Filters.and(
Filters.eq(TestingRunResult.TEST_RUN_RESULT_SUMMARY_ID, testingRunResultSummaryId),
Filters.eq(TestingRunResult.VULNERABLE, true)
Filters.eq(TestingRunResult.VULNERABLE, true),
filterForReport
);

List<TestingRunResult> testingRunResultList = TestingRunResultDao.instance.findAll(filters, skip, 50, null);
Expand Down Expand Up @@ -1357,4 +1353,8 @@ public void setTestingRunConfigId(int testingRunConfigId) {
public Map<String, Integer> getTestCountMap() {
return testCountMap;
}

public void setReportFilterList(Map<String, List<String>> reportFilterList) {
this.reportFilterList = reportFilterList;
}
}
50 changes: 50 additions & 0 deletions apps/dashboard/src/main/java/com/akto/action/testing/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.akto.action.testing;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.bson.conversions.Bson;

import com.akto.dto.ApiInfo.ApiInfoKey;
import com.akto.dto.testing.GenericTestResult;
import com.akto.dto.testing.TestingRunResult;
import com.akto.dto.type.SingleTypeInfo;
import com.mongodb.client.model.Filters;

public class Utils {

public static Bson createFiltersForTestingReport(Map<String, List<String>> filterMap){
List<Bson> filterList = new ArrayList<>();
for(Map.Entry<String, List<String>> entry: filterMap.entrySet()) {
String key = entry.getKey();
List<String> value = entry.getValue();

if (value.size() == 0) continue;
List<Integer> collectionIds = new ArrayList<>();
if(key.equals(SingleTypeInfo._API_COLLECTION_ID)){
for(String str: value){
collectionIds.add(Integer.parseInt(str));
}
}

switch (key) {
case SingleTypeInfo._METHOD:
filterList.add(Filters.in(TestingRunResult.API_INFO_KEY + "." + ApiInfoKey.METHOD, value));
break;
case SingleTypeInfo._API_COLLECTION_ID:
filterList.add(Filters.in(TestingRunResult.API_INFO_KEY + "." + ApiInfoKey.API_COLLECTION_ID, collectionIds));
break;
case "categoryFilter":
filterList.add(Filters.in(TestingRunResult.TEST_SUPER_TYPE, value));
break;
case "severityStatus":
filterList.add(Filters.in(TestingRunResult.TEST_RESULTS + ".0." + GenericTestResult._CONFIDENCE, value));
default:
break;
}
}
return Filters.and(filterList);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.akto.dao.test_editor.YamlTemplateDao;
import com.akto.dao.testing.TestingRunResultDao;
import com.akto.dao.testing.TestingRunResultSummariesDao;
import com.akto.dao.testing.sources.TestReportsDao;
import com.akto.dao.testing.sources.TestSourceConfigsDao;
import com.akto.dao.testing_run_findings.TestingRunIssuesDao;
import com.akto.dto.ApiInfo;
Expand All @@ -23,6 +24,7 @@
import com.akto.dto.test_run_findings.TestingIssuesId;
import com.akto.dto.test_run_findings.TestingRunIssues;
import com.akto.dto.testing.*;
import com.akto.dto.testing.sources.TestReports;
import com.akto.dto.testing.sources.TestSourceConfig;
import com.akto.log.LoggerMaker;
import com.akto.log.LoggerMaker.LogDb;
Expand All @@ -37,6 +39,8 @@
import com.mongodb.BasicDBObject;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.*;
import com.mongodb.client.result.InsertOneResult;

import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
Expand All @@ -62,7 +66,6 @@ public class IssuesAction extends UserAction {
private String ignoreReason;
private int skip;
private int limit;
private long totalIssuesCount;
private List<TestRunIssueStatus> filterStatus;
private List<Integer> filterCollectionsId;
private List<Severity> filterSeverity;
Expand Down Expand Up @@ -296,8 +299,12 @@ private void addGroupAndProjectStages(List<Bson> pipeline, String dateUnit) {
public String fetchVulnerableTestingRunResultsFromIssues() {
Bson filters = createFilters(true);
try {
List<TestingRunIssues> issues = TestingRunIssuesDao.instance.findAll(filters, skip, 50, null);
this.totalIssuesCount = issues.size();
List<TestingRunIssues> issues = new ArrayList<>();
if(issuesIds != null && !issuesIds.isEmpty()){
issues = TestingRunIssuesDao.instance.findAll(Filters.in(Constants.ID, issuesIds));
}else{
issues = TestingRunIssuesDao.instance.findAll(filters, skip, 50, null);
}
List<Bson> andFilters = new ArrayList<>();
for (TestingRunIssues issue : issues) {
andFilters.add(Filters.and(
Expand Down Expand Up @@ -562,6 +569,37 @@ public String fetchTestingRunResultsSummary() {
return SUCCESS.toUpperCase();
}

private Map<String, List<String>> reportFilterList;
private String generatedReportId;
private List<TestingIssuesId> issuesIdsForReport;
private BasicDBObject response;

public String generateTestReport () {
try {
TestReports testReport = new TestReports(reportFilterList, Context.now(), "", this.issuesIdsForReport);
InsertOneResult insertTResult = TestReportsDao.instance.insertOne(testReport);
this.generatedReportId = insertTResult.getInsertedId().toString();
return SUCCESS.toUpperCase();
} catch (Exception e) {
e.printStackTrace();
addActionError("Error in generating pdf report");
return ERROR.toUpperCase();
}
}

public String getReportFilters () {
if(this.generatedReportId == null){
addActionError("Report id cannot be null");
return ERROR.toUpperCase();
}
response = new BasicDBObject();
ObjectId reportId = new ObjectId(this.generatedReportId);
TestReports reportDoc = TestReportsDao.instance.findOne(Filters.eq(Constants.ID, reportId));
response.put(TestReports.FILTERS_FOR_REPORT, reportDoc.getFiltersForReport());
response.put(TestReports.ISSUE_IDS_FOR_REPORT, reportDoc.getIssuesIdsForReport());
return SUCCESS.toUpperCase();
}

public List<TestingRunIssues> getIssues() {
return issues;
}
Expand Down Expand Up @@ -610,14 +648,6 @@ public void setLimit(int limit) {
this.limit = limit;
}

public long getTotalIssuesCount() {
return totalIssuesCount;
}

public void setTotalIssuesCount(long totalIssuesCount) {
this.totalIssuesCount = totalIssuesCount;
}

public List<TestRunIssueStatus> getFilterStatus() {
return filterStatus;
}
Expand Down Expand Up @@ -792,4 +822,23 @@ public void setTestingRunSummaryId(String testingRunSummaryId) {
public TestingRunResultSummary getTestingRunResultSummary() {
return testingRunResultSummary;
}

public void setReportFilterList(Map<String, List<String>> reportFilterList) {
this.reportFilterList = reportFilterList;
}

public String getGeneratedReportId() {
return generatedReportId;
}

public void setGeneratedReportId(String generatedReportId) {
this.generatedReportId = generatedReportId;
}
public void setIssuesIdsForReport(List<TestingIssuesId> issuesIdsForReport) {
this.issuesIdsForReport = issuesIdsForReport;
}

public BasicDBObject getResponse() {
return response;
}
}
46 changes: 46 additions & 0 deletions apps/dashboard/src/main/resources/struts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3959,6 +3959,52 @@
</result>
</action>

<action name="api/generateReportPDF" class="com.akto.action.testing_issues.IssuesAction" method="generateTestReport">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">ISSUES</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">
<param name="root">generatedReportId</param>
</result>
<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/getReportFilters" class="com.akto.action.testing_issues.IssuesAction" method="getReportFilters">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
<interceptor-ref name="roleAccessInterceptor">
<param name="featureLabel">ISSUES</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">
<param name="root">response</param>
</result>
<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/fetchVulnerableTestingRunResultsFromIssues" class="com.akto.action.testing_issues.IssuesAction" method="fetchVulnerableTestingRunResultsFromIssues">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Store from "../../../store";
import func from "@/util/func";
import { MarkFulfilledMinor, ReportMinor, ExternalMinor } from '@shopify/polaris-icons';
import PersistStore from "../../../../main/PersistStore";
import { Button, HorizontalGrid, HorizontalStack, IndexFiltersMode, Modal, Text, VerticalStack } from "@shopify/polaris";
import { Button, HorizontalGrid, HorizontalStack, IndexFiltersMode } from "@shopify/polaris";
import EmptyScreensLayout from "../../../components/banners/EmptyScreensLayout";
import { ISSUES_PAGE_DOCS_URL } from "../../../../main/onboardingData";
import {SelectCollectionComponent} from "../../testing/TestRunsPage/TestrunsBannerComponent"
Expand All @@ -27,9 +27,9 @@ import SpinnerCentered from "../../../components/progress/SpinnerCentered.jsx";
import TableStore from "../../../components/tables/TableStore.js";
import CriticalFindingsGraph from "./CriticalFindingsGraph.jsx";
import CriticalUnsecuredAPIsOverTimeGraph from "./CriticalUnsecuredAPIsOverTimeGraph.jsx";
import DropdownSearch from "../../../components/shared/DropdownSearch.jsx";
import settingFunctions from "../../settings/module.js";
import JiraTicketCreationModal from "../../../components/shared/JiraTicketCreationModal.jsx";
import testingApi from "../../testing/api.js"

const sortOptions = [
{ label: 'Severity', value: 'severity asc', directionLabel: 'Highest', sortKey: 'severity', columnIndex: 2 },
Expand Down Expand Up @@ -194,16 +194,13 @@ function IssuesPage() {
} else {
setHeaders(prevHeaders => prevHeaders.filter(header => header.value !== "issueStatus"))
}
resetResourcesSelected();
}, [selectedTab])

useEffect(() => {
setKey(!key)
}, [startTimestamp, endTimestamp])

useEffect(() => {
resetResourcesSelected()
}, [selectedTab])

const [searchParams, setSearchParams] = useSearchParams();
const resultId = searchParams.get("result")

Expand Down Expand Up @@ -273,6 +270,10 @@ function IssuesPage() {
content: 'No time to fix',
onAction: () => { ignoreAction("No time to fix") }
},
{
content: 'Export selected Issues',
onAction: () => { openVulnerabilityReport(items) }
},
{
content: 'Create jira ticket',
onAction: () => { createJiraTicketBulk() }
Expand Down Expand Up @@ -329,9 +330,13 @@ function IssuesPage() {
}
}

const openVulnerabilityReport = () => {
let summaryId = btoa(JSON.stringify(issuesFilters))
window.open('/dashboard/issues/summary/' + summaryId, '_blank');
const openVulnerabilityReport = async(items = []) => {
await testingApi.generatePDFReport(issuesFilters, items).then((res) => {
const responseId = res.split("=")[1];
window.open('/dashboard/issues/summary/' + responseId.split("}")[0], '_blank');
})

resetResourcesSelected();
}

const infoItems = [
Expand Down Expand Up @@ -373,10 +378,10 @@ function IssuesPage() {

let obj = {
'filterStatus': filterStatus,
'filterCollectionsId': filterCollectionsId,
'filterCollectionsId': [filterCollectionsId.toString()],
'filterSeverity': filterSeverity,
filterSubCategory: filterSubCategory,
startEpoch: startTimestamp
startEpoch: [startTimestamp.toString()]
}
setIssuesFilters(obj)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ export default {
data: {skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, sortKey, sortOrder, startEpoch, endTimeStamp}
})
},
fetchVulnerableTestingRunResultsFromIssues(filters, skip) {
fetchVulnerableTestingRunResultsFromIssues(filters, issuesIds , skip) {
filters['skip'] = skip
return request({
url: 'api/fetchVulnerableTestingRunResultsFromIssues',
method: 'post',
data: filters
data: {...filters, issuesIds}
})
},
fetchIssuesFromResultIds(issuesIds, issueStatusQuery) {
Expand Down
Loading

0 comments on commit a9d8098

Please sign in to comment.