Skip to content

Commit 0ca1964

Browse files
authored
Merge pull request #606 from akto-api-security/develop
Develop
2 parents e3f99ca + f25e35b commit 0ca1964

File tree

25 files changed

+863
-96
lines changed

25 files changed

+863
-96
lines changed

apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.akto.action.testing_issues;
22

3+
import com.akto.action.ExportSampleDataAction;
34
import com.akto.action.UserAction;
45
import com.akto.dao.demo.VulnerableRequestForTemplateDao;
56
import com.akto.dao.test_editor.YamlTemplateDao;
@@ -13,6 +14,7 @@
1314
import com.akto.dto.test_editor.YamlTemplate;
1415
import com.akto.dto.test_run_findings.TestingIssuesId;
1516
import com.akto.dto.test_run_findings.TestingRunIssues;
17+
import com.akto.dto.testing.TestResult;
1618
import com.akto.dto.testing.TestingRunResult;
1719
import com.akto.dto.testing.sources.TestSourceConfig;
1820
import com.akto.util.enums.GlobalEnums;
@@ -26,6 +28,7 @@
2628
import org.bson.conversions.Bson;
2729

2830
import java.util.ArrayList;
31+
import java.util.HashMap;
2932
import java.util.List;
3033
import java.util.Map;
3134

@@ -37,6 +40,8 @@ public class IssuesAction extends UserAction {
3740
private TestingIssuesId issueId;
3841
private List<TestingIssuesId> issueIdArray;
3942
private TestingRunResult testingRunResult;
43+
private List<TestingRunResult> testingRunResults;
44+
private Map<String, String> sampleDataVsCurlMap;
4045
private TestRunIssueStatus statusToBeUpdated;
4146
private String ignoreReason;
4247
private int skip;
@@ -109,6 +114,46 @@ public String fetchAllIssues() {
109114
}
110115
return SUCCESS.toUpperCase();
111116
}
117+
118+
public String fetchVulnerableTestingRunResultsFromIssues() {
119+
Bson filters = createFilters();
120+
try {
121+
List<TestingRunIssues> issues = TestingRunIssuesDao.instance.findAll(filters, skip, 50, null);
122+
this.totalIssuesCount = issues.size();
123+
List<Bson> andFilters = new ArrayList<>();
124+
for (TestingRunIssues issue : issues) {
125+
andFilters.add(Filters.and(
126+
Filters.eq(TestingRunResult.TEST_RUN_RESULT_SUMMARY_ID, issue.getLatestTestingRunSummaryId()),
127+
Filters.eq(TestingRunResult.TEST_SUB_TYPE, issue.getId().getTestSubCategory()),
128+
Filters.eq(TestingRunResult.API_INFO_KEY, issue.getId().getApiInfoKey()),
129+
Filters.eq(TestingRunResult.VULNERABLE, true)
130+
));
131+
}
132+
if (issues.isEmpty()) {
133+
this.testingRunResults = new ArrayList<>();
134+
this.sampleDataVsCurlMap = new HashMap<>();
135+
return SUCCESS.toUpperCase();
136+
}
137+
Bson orFilters = Filters.or(andFilters);
138+
this.testingRunResults = TestingRunResultDao.instance.findAll(orFilters);
139+
Map<String, String> sampleDataVsCurlMap = new HashMap<>();
140+
for (TestingRunResult runResult: this.testingRunResults) {
141+
List<TestResult> testResults = new ArrayList<>();
142+
for (TestResult testResult : runResult.getTestResults()) {
143+
if (testResult.isVulnerable()) {
144+
testResults.add(testResult);
145+
sampleDataVsCurlMap.put(testResult.getMessage(), ExportSampleDataAction.getCurl(testResult.getMessage()));
146+
sampleDataVsCurlMap.put(testResult.getOriginalMessage(), ExportSampleDataAction.getCurl(testResult.getOriginalMessage()));
147+
}
148+
}
149+
runResult.setTestResults(testResults);
150+
}
151+
this.sampleDataVsCurlMap = sampleDataVsCurlMap;
152+
} catch (Exception e) {
153+
return ERROR.toUpperCase();
154+
}
155+
return SUCCESS.toUpperCase();
156+
}
112157
public String fetchTestingRunResult() {
113158
if (issueId == null) {
114159
throw new IllegalStateException();
@@ -378,4 +423,20 @@ public boolean getFetchOnlyActive() {
378423
public void setFetchOnlyActive(boolean fetchOnlyActive) {
379424
this.fetchOnlyActive = fetchOnlyActive;
380425
}
426+
427+
public List<TestingRunResult> getTestingRunResults() {
428+
return testingRunResults;
429+
}
430+
431+
public void setTestingRunResults(List<TestingRunResult> testingRunResults) {
432+
this.testingRunResults = testingRunResults;
433+
}
434+
435+
public Map<String, String> getSampleDataVsCurlMap() {
436+
return sampleDataVsCurlMap;
437+
}
438+
439+
public void setSampleDataVsCurlMap(Map<String, String> sampleDataVsCurlMap) {
440+
this.sampleDataVsCurlMap = sampleDataVsCurlMap;
441+
}
381442
}

apps/dashboard/src/main/resources/struts.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,15 @@
11821182
</result>
11831183
</action>
11841184

1185+
<action name="api/fetchVulnerableTestingRunResultsFromIssues" class="com.akto.action.testing_issues.IssuesAction" method="fetchVulnerableTestingRunResultsFromIssues">
1186+
<interceptor-ref name="json"/>
1187+
<interceptor-ref name="defaultStack" />
1188+
<result name="SUCCESS" type="json"/>
1189+
<result name="ERROR" type="httpheader">
1190+
<param name="status">401</param>
1191+
</result>
1192+
</action>
1193+
11851194
<action name="api/fetchAffectedEndpoints" class="com.akto.action.testing_issues.IssuesAction" method="fetchAffectedEndpoints">
11861195
<interceptor-ref name="json"/>
11871196
<interceptor-ref name="defaultStack" />

apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/aktoGpt/AktoGptLayout.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ function AktoGptLayout({prompts,closeModal, runCustomTests}) {
131131
<Box padding="5" maxWidth="65vw" background="bg-subdued">
132132
<div className="response-message">
133133
<HorizontalStack gap="6" align="start">
134-
<Avatar name="Akto" source='akto_colored.svg' size="medium"/>
134+
<Avatar name="Akto" source='/public/akto_colored.svg' size="medium"/>
135135
{loading ? <Spinner size="small" />
136136
: <ResponseComponent response={func.getResponse(response,queryType)} chatLogRef={chatLogRef} onCompletion={() => handleCompletion()}/>
137137
}

apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/SampleData.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function highlightPaths(highlightPathMap, ref){
2626
}
2727

2828
function highlightHeaders(data, ref, getLineNumbers){
29-
const diffRange = []
29+
let diffRange = []
3030
const headerKeysMap = data.headersMap
3131

3232
// add classname for first line only
@@ -93,7 +93,7 @@ function highlightHeaders(data, ref, getLineNumbers){
9393
diffRange.sort((a,b) => a.range - b.range)
9494
let currentRange = null
9595
let result = []
96-
96+
diffRange = Array.from(new Set(diffRange.map(i => JSON.stringify(i))), JSON.parse);
9797
for (const obj of diffRange) {
9898
if (!currentRange) {
9999
currentRange = { start: obj.range, end: obj.range, key: obj.key };
@@ -113,7 +113,8 @@ function highlightHeaders(data, ref, getLineNumbers){
113113
ref.createDecorationsCollection([{
114114
range: new monaco.Range(obj.start, 1, obj.end, 100),
115115
options: {
116-
blockClassName: className
116+
blockClassName: className,
117+
isWholeLine: true
117118
}
118119
}])
119120
})

apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/SampleDataComponent.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ function SampleDataComponent(props) {
153153
})
154154
}
155155
if (data.originalMessage) {
156+
if(items.length==2){
157+
items[0].content = "Copy attempt request as curl"
158+
items[1].content = "Copy attempt request as burp"
159+
}
156160
items.push({
157161
content: 'Copy original request as curl',
158162
onAction: () => { copyRequest(type, "CURL", data.originalMessage) },
@@ -170,6 +174,9 @@ function SampleDataComponent(props) {
170174
})
171175
}
172176
if (data.originalMessage) {
177+
if(items.length==1){
178+
items[0].content = "Copy attempt response"
179+
}
173180
items.push({
174181
content: 'Copy original response',
175182
onAction: () => { copyRequest(type, "RESPONSE", data.originalMessage) },

apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/shared/customDiffEditor.js

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,23 @@ const transform = {
6969
}
7070
},
7171

72+
processArrayJson(input) {
73+
try {
74+
let parsedJson = JSON.parse(input);
75+
let ret = []
76+
Object.keys(parsedJson).forEach((key)=>{
77+
if(!isNaN(key)){
78+
ret.push(parsedJson[key])
79+
}else{
80+
ret.push({key: parsedJson[key]})
81+
}
82+
})
83+
return JSON.stringify(ret, null, 2)
84+
} catch (error) {
85+
return input
86+
}
87+
},
88+
7289
getPayloadData(original,current){
7390
let changedKeys = []
7491
let insertedKeys = []
@@ -81,7 +98,15 @@ const transform = {
8198
for(const key in ogFlat){
8299
let mainKey = '"' + key.split(".")?.pop() + '": '
83100
if(!currFlat?.hasOwnProperty(key)){
84-
deletedKeys.push({header: mainKey + '"' + ogFlat[key] + '"', className: 'deleted-content'})
101+
let searchKey = "";
102+
if(typeof(ogFlat[key]) === "string"){
103+
searchKey = mainKey + '"' + ogFlat[key] + '"'
104+
} else if(typeof(ogFlat[key]) === 'object'){
105+
searchKey = mainKey
106+
} else{
107+
searchKey = mainKey + ogFlat[key]
108+
}
109+
deletedKeys.push({header: searchKey, className: 'deleted-content'})
85110
finalUnflatObj[key] = ogFlat[key]
86111
}else if(!func.deepComparison(ogFlat[key],currFlat[key])){
87112
let searchKey = typeof(ogFlat[key]) === "string" ? mainKey + '"' + currFlat[key] + '"' : mainKey + currFlat[key]
@@ -105,14 +130,44 @@ const transform = {
105130
return result;
106131
}, {});
107132

108-
return{
109-
json: func.unflattenObject(finalUnflatObj),
110-
headersMap: mergedObject,
111-
}
133+
let ret = {};
134+
ret.headersMap = mergedObject
135+
ret.json = "";
136+
137+
let ogArr = typeof(original)==='string' ? original.split("\n") : []
138+
let curArr = typeof(current)==='string' ? current.split("\n") : []
139+
140+
let retArr = []
141+
for(let i in Array.from(Array(Math.max(ogArr.length, curArr.length)))){
142+
if(ogArr[i] && curArr[i]){
143+
if(ogArr[i]!==curArr[i]){
144+
ret.headersMap[curArr[i]] = {className: 'updated-content', data: ogArr[i].replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;') + "->" + curArr[i].replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'), keyLength: -2}
145+
}
146+
retArr.push(curArr[i]);
147+
} else if(ogArr[i]) {
148+
ret.headersMap[curArr[i]] = {className:'deleted-content'};
149+
retArr.push(ogArr[i]);
150+
} else if(curArr[i]){
151+
ret.headersMap[curArr[i]] = {className:'added-content'};
152+
retArr.push(curArr[i]);
153+
}
154+
}
155+
156+
if(typeof(current)!=='string' && typeof(original)!=='string'){
157+
ret.json = this.formatJson(func.unflattenObject(finalUnflatObj));
158+
} else if(typeof(current)==='string' && typeof(original)==='string'){
159+
ret.json = retArr.join("\n")
160+
} else if(typeof(current)==='string'){
161+
ret.json = retArr.join("\n") + "\n" + this.formatJson(original);
162+
} else {
163+
ret.json = this.formatJson(current) + "\n" + retArr.join("\n");
164+
}
165+
166+
return ret;
112167
},
113168

114169
mergeDataObjs(lineObj, jsonObj, payloadObj){
115-
let finalMessage = (lineObj.firstLine ? lineObj.firstLine: "") + "\n" + (jsonObj.message ? jsonObj.message: "") + "\n" + this.formatJson(payloadObj.json)
170+
let finalMessage = (lineObj.firstLine ? lineObj.firstLine: "") + "\n" + (jsonObj.message ? jsonObj.message: "") + "\n" + this.processArrayJson(payloadObj.json)
116171
return{
117172
message: finalMessage,
118173
firstLine: lineObj.original + "->" + lineObj.firstLine,

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import transform from "../transform";
77
import func from "@/util/func";
88
import { ClockMinor,DynamicSourceMinor,LinkMinor } from '@shopify/polaris-icons';
99
import PersistStore from "../../../../main/PersistStore";
10+
import { Button } from "@shopify/polaris";
1011

1112
const headers = [
1213
{
@@ -136,6 +137,7 @@ function IssuesPage(){
136137
const subCategoryMap = PersistStore(state => state.subCategoryMap);
137138
const subCategoryFromSourceConfigMap = PersistStore(state => state.subCategoryFromSourceConfigMap);
138139
const [issueStatus, setIssueStatus] = useState([]);
140+
const [issuesFilters, setIssuesFilters] = useState({})
139141
const [key, setKey] = useState(false);
140142
const apiCollectionMap = PersistStore(state => state.collectionsMap);
141143

@@ -253,7 +255,14 @@ function IssuesPage(){
253255
let filterStatus = filters.issueStatus
254256
setIssueStatus(filterStatus);
255257
let startTimestamp = filters?.startTimestamp?.[0] || 0;
256-
258+
let obj = {
259+
'filterStatus': filterStatus,
260+
'filterCollectionsId': filterCollectionsId,
261+
'filterSeverity': filterSeverity,
262+
filterSubCategory: filterSubCategory,
263+
startEpoch: startTimestamp
264+
}
265+
setIssuesFilters(obj)
257266
await api.fetchIssues(skip, limit,filterStatus,filterCollectionsId,filterSeverity,filterSubCategory,startTimestamp).then((res) => {
258267
total = res.totalIssuesCount;
259268
ret = transform.prepareIssues(res, subCategoryMap, subCategoryFromSourceConfigMap, apiCollectionMap);
@@ -263,6 +272,11 @@ function IssuesPage(){
263272
return {value:ret , total:total};
264273
}
265274

275+
const openVulnerabilityReport = () => {
276+
let summaryId = btoa(JSON.stringify(issuesFilters))
277+
window.open('/dashboard/issues/summary/' + summaryId, '_blank');
278+
}
279+
266280
return (
267281
<PageWithMultipleCards
268282
title="Issues"
@@ -286,7 +300,8 @@ function IssuesPage(){
286300
getStatus={func.getTestResultStatus}
287301
/>
288302
]}
289-
/>
303+
primaryAction={<Button monochrome removeUnderline plain onClick={() => openVulnerabilityReport()}>Export vulnerability report</Button>}
304+
/>
290305
)
291306
}
292307

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ export default {
88
data: {skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, startEpoch}
99
})
1010
},
11+
fetchVulnerableTestingRunResultsFromIssues(filters, skip) {
12+
filters['skip'] = skip
13+
return request({
14+
url: 'api/fetchVulnerableTestingRunResultsFromIssues',
15+
method: 'post',
16+
data: filters
17+
})
18+
},
1119
bulkUpdateIssueStatus (issueIdArray, statusToBeUpdated, ignoreReason) {
1220
return request({
1321
url: 'api/bulkUpdateIssueStatus',

0 commit comments

Comments
 (0)