Skip to content

Commit

Permalink
Merge pull request #1873 from akto-api-security/feature/replace_api_w…
Browse files Browse the repository at this point in the history
…ith_issue

replace API severity graph with issue severity graph
  • Loading branch information
notshivansh authored Dec 31, 2024
2 parents de8686f + 3d27baa commit ec52ec3
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.*;
import com.mongodb.client.result.InsertOneResult;
import com.opensymphony.xwork2.Action;

import org.bson.Document;
import org.bson.conversions.Bson;
Expand All @@ -55,7 +56,7 @@

public class IssuesAction extends UserAction {

private static final LoggerMaker loggerMaker = new LoggerMaker(IssuesAction.class);
private static final LoggerMaker loggerMaker = new LoggerMaker(IssuesAction.class, LogDb.DASHBOARD);
private static final Logger logger = LoggerFactory.getLogger(IssuesAction.class);
private List<TestingRunIssues> issues;
private TestingIssuesId issueId;
Expand All @@ -74,6 +75,8 @@ public class IssuesAction extends UserAction {
private List<TestingRunIssues> similarlyAffectedIssues;
private int startEpoch;
long endTimeStamp;
private Map<Integer,Map<String,Integer>> severityInfo = new HashMap<>();

private Bson createFilters (boolean useFilterStatus) {
Bson filters = Filters.empty();
if (useFilterStatus && filterStatus != null && !filterStatus.isEmpty()) {
Expand All @@ -89,8 +92,12 @@ private Bson createFilters (boolean useFilterStatus) {
filters = Filters.and(filters, Filters.in(ID + "."
+ TestingIssuesId.TEST_SUB_CATEGORY, filterSubCategory));
}
if (startEpoch != 0 && endTimeStamp != 0) {

if (startEpoch != 0) {
filters = Filters.and(filters, Filters.gte(TestingRunIssues.CREATION_TIME, startEpoch));
}

if(endTimeStamp != 0){
filters = Filters.and(filters, Filters.lt(TestingRunIssues.CREATION_TIME, endTimeStamp));
}

Expand Down Expand Up @@ -628,6 +635,18 @@ public String getReportFilters () {
return SUCCESS.toUpperCase();
}

public String fetchSeverityInfoForIssues() {
Bson filter = createFilters(true);

if (issuesIds != null && !issuesIds.isEmpty()) {
filter = Filters.and(filter, Filters.in(Constants.ID, issuesIds));
}

this.severityInfo = TestingRunIssuesDao.instance.getSeveritiesMapForCollections(filter, false);
return Action.SUCCESS.toUpperCase();
}


public List<TestingRunIssues> getIssues() {
return issues;
}
Expand Down Expand Up @@ -877,4 +896,12 @@ public void setIssuesIdsForReport(List<TestingIssuesId> issuesIdsForReport) {
public BasicDBObject getResponse() {
return response;
}

public Map<Integer, Map<String, Integer>> getSeverityInfo() {
return severityInfo;
}

public void setSeverityInfo(Map<Integer, Map<String, Integer>> severityInfo) {
this.severityInfo = severityInfo;
}
}
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 @@ -4026,6 +4026,27 @@
</result>
</action>

<action name="api/fetchSeverityInfoForIssues" class="com.akto.action.testing_issues.IssuesAction" method="fetchSeverityInfoForIssues">
<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"/>
<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/fetchIssuesFromResultIds" class="com.akto.action.testing_issues.IssuesAction" method="fetchIssuesFromResultIds">
<interceptor-ref name="json"/>
<interceptor-ref name="defaultStack" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ function HomeDashboard() {
observeApi.getUserEndpoints(),
api.findTotalIssues(startTimestamp, endTimestamp),
api.fetchApiStats(startTimestamp, endTimestamp),
api.fetchEndpointsCount(startTimestamp, endTimestamp)
api.fetchEndpointsCount(startTimestamp, endTimestamp),
testingApi.fetchSeverityInfoForIssues({ startEpoch: startTimestamp }, [], endTimestamp)
];

let results = await Promise.allSettled(apiPromises);
Expand All @@ -143,6 +144,7 @@ function HomeDashboard() {
let findTotalIssuesResp = results[1].status === 'fulfilled' ? results[1].value : {}
let apisStatsResp = results[2].status === 'fulfilled' ? results[2].value : {}
let fetchEndpointsCountResp = results[3].status === 'fulfilled' ? results[3].value : {}
let issueSeverityMap = results[4].status === 'fulfilled' ? results[4].value : {}

setShowBannerComponent(!userEndpoints)

Expand All @@ -153,7 +155,7 @@ function HomeDashboard() {
buildAuthTypesData(apisStatsResp.apiStatsEnd)
buildSetRiskScoreData(apisStatsResp.apiStatsEnd) //todo
getCollectionsWithCoverage()
buildSeverityMap(apisStatsResp.apiStatsEnd)
buildSeverityMap(issueSeverityMap.severityInfo)
buildIssuesSummary(findTotalIssuesResp)

const fetchHistoricalDataResp = { "finalHistoricalData": finalHistoricalData, "initialHistoricalData": initialHistoricalData }
Expand Down Expand Up @@ -447,8 +449,17 @@ function HomeDashboard() {
/>
) : null

function buildSeverityMap(apiStats) {
const countMap = apiStats ? apiStats.criticalMap : {};
function buildSeverityMap(severityInfo) {
const countMap = { HIGH: 0, MEDIUM: 0, LOW: 0 }

if (severityInfo && severityInfo != undefined && severityInfo != null && severityInfo instanceof Object) {
for (const apiCollectionId in severityInfo) {
let temp = severityInfo[apiCollectionId]
for (const key in temp) {
countMap[key] += temp[key]
}
}
}

const result = {
"High": {
Expand Down Expand Up @@ -508,11 +519,11 @@ function HomeDashboard() {
/>
</div>
}
title="Vulnerable APIs by Severity"
titleToolTip="Breakdown of vulnerable APIs categorized by severity level (High, Medium, Low). Click to see details for each category."
title="Issues by Severity"
titleToolTip="Breakdown of issues categorized by severity level (High, Medium, Low). Click to see details for each category."
linkText="Fix critical issues"
linkUrl="/dashboard/issues"
/> : <EmptyCard title="Vulnerable APIs by Severity" subTitleComponent={showTestingComponents ? <Text alignment='center' color='subdued'>No vulnerable APIs found</Text>: runTestEmptyCardComponent}/>
/> : <EmptyCard title="Issues by Severity" subTitleComponent={showTestingComponents ? <Text alignment='center' color='subdued'>No issues found for this time-frame</Text>: runTestEmptyCardComponent}/>

const criticalUnsecuredAPIsOverTime = <CriticalUnsecuredAPIsOverTimeGraph linkText={"Fix critical issues"} linkUrl={"/dashboard/issues"} />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,5 +487,12 @@ export default {
method: 'post',
data: { generatedReportId }
})
},
fetchSeverityInfoForIssues(filters, issueIds, endTimeStamp) {
return request({
url: '/api/fetchSeverityInfoForIssues',
method: 'post',
data: {...filters, issueIds, endTimeStamp}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const ReportSummary = ({ reportSummaryItems, severityMap, graphData, organizatio

<Box width='100%' paddingBlockStart={4} paddingBlockEnd={2}>
<VerticalStack gap={3}>
<Text variant="headingSm">Vulnerable APIs by Severity</Text>
<Text variant="headingSm">Issues by Severity</Text>

<ChartypeComponent
data={severityMap}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ const VulnerabilityReport = () => {
})
issuesFilters.filterCollectionsId = issuesFilters.filterCollectionsId.filter((x) => x !== undefined)


await issuesApi.fetchVulnerableTestingRunResultsFromIssues(issuesFilters, issueIdsFromFilter, resultsCount).then(resp => {
vulnerableTestingRunResults = [...vulnerableTestingRunResults, ...resp.testingRunResults]
testingRunCountsFromDB = resp.testingRunResults.length
Expand Down Expand Up @@ -207,7 +208,7 @@ const VulnerabilityReport = () => {
setAktoRecommendations(vulMap.aktoRecommendations)
setGraphData(vulMap.graphData)

const severityMapRes = reportTransform.createVulnerableAPIsSeverity(vulnerableTestingRunResults, categoryMap)
const severityMapRes = reportTransform.createVulnerableAPIsSeverity(vulnerableTestingRunResults)
setSeverityMap(severityMapRes)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,18 @@ import { Badge, Box, Link } from "@shopify/polaris"
import func from "@/util/func"

const reportTransform = {
createVulnerableAPIsSeverity: (vulnerableTestingRunResults, categoryMap) => {

const severityOrder = func.getAktoSeverities().reverse()
createVulnerableAPIsSeverity: (vulnerableTestingRunResults) => {
const countMap = {
HIGH: 0,
MEDIUM: 0,
LOW: 0,
}

const endpointMap = {}

vulnerableTestingRunResults.forEach(item => {
const endpointKey = `${item.apiInfoKey.apiCollectionId}-${item.apiInfoKey.method}-${item.apiInfoKey.url}`
const highestConfidence = item.testResults.reduce((max, result) => {
return severityOrder.indexOf(result.confidence) > severityOrder.indexOf(max)
? result.confidence
: max
}, 'LOW')
if (!endpointMap[endpointKey] || severityOrder.indexOf(highestConfidence) > severityOrder.indexOf(endpointMap[endpointKey])) {
endpointMap[endpointKey] = highestConfidence
}
})

Object.values(endpointMap).forEach(severity => {
if(countMap[severity] !== undefined) {
countMap[severity]++
}
const confidence = item.testResults.filter((result) => {
return result.vulnerable
}).map((result) => result.confidence)[0]
countMap[confidence]++
})

const result = {
Expand All @@ -48,7 +33,6 @@ const reportTransform = {
"filterKey": "Low"
}
}

return result
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,18 @@ public void createIndicesIfAbsent() {
}

public Map<Integer,Map<String,Integer>> getSeveritiesMapForCollections(){
return getSeveritiesMapForCollections(null, true);
}

public Map<Integer,Map<String,Integer>> getSeveritiesMapForCollections(Bson filter, boolean expandApiGroups){
Map<Integer,Map<String,Integer>> resultMap = new HashMap<>() ;
List<Bson> pipeline = new ArrayList<>();
pipeline.add(Aggregates.match(Filters.eq(TestingRunIssues.TEST_RUN_ISSUES_STATUS, "OPEN")));

if(filter!=null){
pipeline.add(Aggregates.match(filter));
}

try {
List<Integer> collectionIds = UsersCollectionsList.getCollectionsIdForUser(Context.userId.get(), Context.accountId.get());
if(collectionIds != null) {
Expand All @@ -76,12 +84,16 @@ public Map<Integer,Map<String,Integer>> getSeveritiesMapForCollections(){
} catch(Exception e){
}

UnwindOptions unwindOptions = new UnwindOptions();
unwindOptions.preserveNullAndEmptyArrays(false);
pipeline.add(Aggregates.unwind("$collectionIds", unwindOptions));
BasicDBObject groupedId = new BasicDBObject("apiCollectionId", "$_id.apiInfoKey.apiCollectionId")
.append("severity", "$severity");

BasicDBObject groupedId = new BasicDBObject("apiCollectionId", "$collectionIds")
.append("severity", "$severity") ;
if (expandApiGroups) {
UnwindOptions unwindOptions = new UnwindOptions();
unwindOptions.preserveNullAndEmptyArrays(false);
pipeline.add(Aggregates.unwind("$collectionIds", unwindOptions));
groupedId = new BasicDBObject("apiCollectionId", "$collectionIds")
.append("severity", "$severity");
}

pipeline.add(Aggregates.group(groupedId, Accumulators.sum("count", 1)));

Expand Down

0 comments on commit ec52ec3

Please sign in to comment.