Skip to content

Commit f5f61e1

Browse files
authored
Merge pull request #2533 from akto-api-security/feat/ui-schema-conformance
Feat/UI schema conformance
2 parents 0812e4b + f3c4c90 commit f5f61e1

File tree

10 files changed

+154
-60
lines changed

10 files changed

+154
-60
lines changed

apps/dashboard/src/main/java/com/akto/action/threat_detection/MaliciousPayloadsResponse.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,28 @@
33
public class MaliciousPayloadsResponse {
44

55
private String orig;
6+
private String metadata;
67
private long ts;
8+
9+
public MaliciousPayloadsResponse(String orig, String metadata, long ts) {
10+
this.orig = orig;
11+
this.metadata = metadata;
12+
this.ts = ts;
13+
}
14+
715
public MaliciousPayloadsResponse(String orig, long ts) {
816
this.orig = orig;
917
this.ts = ts;
1018
}
11-
19+
20+
public String getMetadata() {
21+
return metadata;
22+
}
23+
24+
public void setMetadata(String metadata) {
25+
this.metadata = metadata;
26+
}
27+
1228
public String getOrig() {
1329
return orig;
1430
}

apps/dashboard/src/main/java/com/akto/action/threat_detection/ThreatActorAction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ public String fetchAggregateMaliciousRequests() {
225225
smr ->
226226
new MaliciousPayloadsResponse(
227227
smr.getOrig(),
228+
smr.getMetadata(),
228229
smr.getTs()))
229230
.collect(Collectors.toList());
230231
});

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React, { useState, useEffect } from 'react'
22
import {
3+
Banner,
4+
List,
35
Text,
46
VerticalStack,
57
HorizontalStack, Box, LegacyCard, HorizontalGrid,
@@ -8,6 +10,33 @@ import SampleDataComponent from './SampleDataComponent';
810
import SampleData from './SampleData';
911
import func from '../../../../util/func';
1012

13+
function SchemaValidationError({ sampleData}) {
14+
if (!sampleData || !sampleData?.metadata) {
15+
return null;
16+
}
17+
const schemaErrors = JSON.parse(sampleData?.metadata)?.schemaErrors || [];
18+
if (schemaErrors.length === 0) {
19+
return null;
20+
}
21+
22+
23+
return (
24+
<VerticalStack gap={"4"}>
25+
<Banner
26+
title="Schema Validation Errors"
27+
status="critical"
28+
>
29+
<List type="bullet">
30+
{schemaErrors?.map((error, index) => {
31+
return <List.Item key={index}>{error?.message}</List.Item>
32+
})}
33+
</List>
34+
</Banner>
35+
36+
</VerticalStack>
37+
)
38+
}
39+
1140
function SampleDataList(props) {
1241

1342
const {showDiff, sampleData, heading, minHeight, vertical, isVulnerable, isNewDiff, metadata} = props;
@@ -20,6 +49,7 @@ function SampleDataList(props) {
2049

2150
return (
2251
<VerticalStack gap="3">
52+
<SchemaValidationError sampleData={sampleData[Math.min(page, sampleData.length - 1)]} />
2353
<HorizontalStack align='space-between'>
2454
<HorizontalStack gap="2">
2555
<Text variant='headingMd'>

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiDetails.jsx

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ function ApiDetails(props) {
5555
const [prompts, setPrompts] = useState([])
5656
const [isGptScreenActive, setIsGptScreenActive] = useState(false)
5757
const [loading, setLoading] = useState(false)
58-
const [badgeActive, setBadgeActive] = useState(false)
5958
const [showMoreActions, setShowMoreActions] = useState(false)
6059
const setSelectedSampleApi = PersistStore(state => state.setSelectedSampleApi)
6160
const [disabledTabs, setDisabledTabs] = useState([])
@@ -257,36 +256,6 @@ function ApiDetails(props) {
257256
setTimeout(() => {
258257
setLoading(false)
259258
}, 100)
260-
await api.loadParamsOfEndpoint(apiCollectionId, endpoint, method).then(resp => {
261-
api.loadSensitiveParameters(apiCollectionId, endpoint, method).then(allSensitiveFields => {
262-
allSensitiveFields.data.endpoints.filter(x => x.sensitive).forEach(sensitive => {
263-
let index = resp.data.params.findIndex(x =>
264-
x.param === sensitive.param &&
265-
x.isHeader === sensitive.isHeader &&
266-
x.responseCode === sensitive.responseCode
267-
)
268-
269-
if (index > -1 && !sensitive.subType) {
270-
resp.data.params[index].savedAsSensitive = true
271-
if (!resp.data.params[index].subType) {
272-
resp.data.params[index].subType = { "name": "CUSTOM" }
273-
} else {
274-
resp.data.params[index].subType = JSON.parse(JSON.stringify(resp.data.params[index].subType))
275-
}
276-
}
277-
})
278-
279-
try {
280-
resp.data.params?.forEach(x => {
281-
if (!values?.skipList.includes(x.subTypeString) && !x?.savedAsSensitive && !x?.sensitive) {
282-
x.nonSensitiveDataType = true
283-
}
284-
})
285-
} catch (e){
286-
}
287-
setParamList(resp.data.params)
288-
})
289-
})
290259
fetchStats(apiCollectionId, endpoint, method)
291260
fetchDistributionData(); // Fetch distribution data
292261
}
@@ -322,10 +291,6 @@ function ApiDetails(props) {
322291
func.setToast(true, false, "Triggered tests successfully!")
323292
}
324293

325-
const badgeClicked = () => {
326-
setBadgeActive(true)
327-
}
328-
329294
useEffect(() => {
330295
if (
331296
(localCategoryMap && Object.keys(localCategoryMap).length > 0) &&
@@ -456,11 +421,8 @@ function ApiDetails(props) {
456421
const SchemaTab = {
457422
id: 'schema',
458423
content: "Schema",
459-
component: paramList.length > 0 && <Box paddingBlockStart={"4"}>
424+
component: <Box paddingBlockStart={"4"}>
460425
<ApiSchema
461-
data={paramList}
462-
badgeActive={badgeActive}
463-
setBadgeActive={setBadgeActive}
464426
apiInfo={{
465427
apiCollectionId: apiDetail.apiCollectionId,
466428
url: apiDetail.endpoint,
@@ -611,8 +573,6 @@ function ApiDetails(props) {
611573
data={newData}
612574
headers={headers}
613575
getStatus={statusFunc}
614-
isBadgeClickable={true}
615-
badgeClicked={badgeClicked}
616576
/>
617577
</HorizontalStack>
618578
</VerticalStack>

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/ApiSchema.jsx

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ChevronDownMinor, ChevronUpMinor } from "@shopify/polaris-icons"
44
import func from "@/util/func"
55
import transform from "../transform";
66
import { useNavigate } from "react-router-dom";
7+
import api from "../api";
78

89
function prepareTableData (data, handleBadgeClick) {
910
let sensitivePayload = []
@@ -70,11 +71,12 @@ function prepareTableData (data, handleBadgeClick) {
7071
}
7172

7273
function ApiSingleSchema(props) {
73-
const { data, title, badgeActive, setBadgeActive } = props;
74+
const { data, title } = props;
7475

7576
const [open, setOpen] = useState(true);
7677
const handleToggle = useCallback(() => setOpen((open) => !open), []);
7778
const [isHeader, setIsHeader] = useState(true)
79+
const [badgeActive, setBadgeActive] = useState(true)
7880

7981
const [dataObj,setDataObj] = useState({
8082
headerData: [],
@@ -83,7 +85,7 @@ function ApiSingleSchema(props) {
8385
});
8486
useEffect(()=>{
8587
setDataObj(prepareTableData(data, props.handleBadgeClick));
86-
},[])
88+
},[data])
8789
const headerCount = dataObj?.headerData?.length
8890
const payloadCount = dataObj?.payloadData?.length
8991

@@ -149,14 +151,61 @@ function ApiSingleSchema(props) {
149151
)
150152
}
151153

154+
152155
function ApiSchema(props) {
153156

154-
const { data, badgeActive, setBadgeActive, apiInfo } = props
157+
const {badgeActive, setBadgeActive, apiInfo } = props
155158
const navigate = useNavigate()
159+
const [payloadData, setPayloadData] = useState({
160+
reqData: [],
161+
resData: []
162+
})
156163

157-
let reqData = data.filter((item) => item.responseCode === -1)
158-
let resData = data.filter((item) => item.responseCode !== -1)
164+
async function fetchData() {
165+
const { apiCollectionId, url, method } = apiInfo;
166+
await api.loadParamsOfEndpoint(apiCollectionId, url, method).then(resp => {
167+
api.loadSensitiveParameters(apiCollectionId, url, method).then(allSensitiveFields => {
168+
allSensitiveFields.data.endpoints.filter(x => x.sensitive).forEach(sensitive => {
169+
let index = resp.data.params.findIndex(x =>
170+
x.param === sensitive.param &&
171+
x.isHeader === sensitive.isHeader &&
172+
x.responseCode === sensitive.responseCode
173+
)
174+
175+
if (index > -1 && !sensitive.subType) {
176+
resp.data.params[index].savedAsSensitive = true
177+
if (!resp.data.params[index].subType) {
178+
resp.data.params[index].subType = { "name": "CUSTOM" }
179+
} else {
180+
resp.data.params[index].subType = JSON.parse(JSON.stringify(resp.data.params[index].subType))
181+
}
182+
}
183+
})
159184

185+
try {
186+
resp.data.params?.forEach(x => {
187+
if (!values?.skipList.includes(x.subTypeString) && !x?.savedAsSensitive && !x?.sensitive) {
188+
x.nonSensitiveDataType = true
189+
}
190+
})
191+
} catch (e) {
192+
}
193+
let reqData = resp.data.params.filter((item) => item.responseCode === -1)
194+
let resData = resp.data.params.filter((item) => item.responseCode !== -1)
195+
196+
setPayloadData({
197+
reqData: reqData,
198+
resData: resData
199+
})
200+
})
201+
})
202+
}
203+
204+
useEffect(() => {
205+
if (apiInfo) {
206+
fetchData()
207+
}
208+
}, [apiInfo])
160209
const handleBadgeClick = (datatype, position) => {
161210
const navUrl = "/dashboard/observe/sensitive/" + datatype.toUpperCase() + "/" + apiInfo.apiCollectionId + "/" + btoa(apiInfo.url + " " + apiInfo.method)
162211
navigate(navUrl)
@@ -166,7 +215,7 @@ function ApiSchema(props) {
166215
<VerticalStack gap="2">
167216
{
168217
['Request', 'Response'].map((type, index) => {
169-
return <ApiSingleSchema handleBadgeClick={handleBadgeClick} title={type} key={type} data={index == 0 ? reqData : resData} badgeActive={badgeActive} setBadgeActive={setBadgeActive}/>
218+
return <ApiSingleSchema handleBadgeClick={handleBadgeClick} title={type} key={type} data={index == 0 ? payloadData.reqData : payloadData.resData} badgeActive={badgeActive} setBadgeActive={setBadgeActive}/>
170219
})
171220
}
172221

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/ThreatDetectionPage.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ function ThreatDetectionPage() {
108108
setShowDetails(true)
109109
setMoreInfoData({
110110
url: data.url,
111+
method: data.method,
112+
apiCollectionId: data.apiCollectionId,
111113
templateId: data.filterId,
112114
})
113115
} else {

apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/threat_detection/components/SampleDetails.jsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import testingApi from "../../testing/api"
88
import MarkdownViewer from "../../../components/shared/MarkdownViewer";
99
import TooltipText from "../../../components/shared/TooltipText";
1010
import ActivityTracker from "../../dashboard/components/ActivityTracker";
11+
import ApiSchema from "../../observe/api_collections/ApiSchema";
1112

1213
function SampleDetails(props) {
1314
const { showDetails, setShowDetails, data, title, moreInfoData, threatFiltersMap } = props
@@ -80,21 +81,36 @@ function SampleDetails(props) {
8081
component: <ActivityTracker latestActivity={latestActivity} />
8182
}
8283

84+
85+
const SchemaTab = {
86+
id: 'schema',
87+
content: "Schema",
88+
component: <Box paddingBlockStart={"4"}>
89+
<ApiSchema
90+
apiInfo={{
91+
apiCollectionId: moreInfoData?.apiCollectionId,
92+
url: moreInfoData?.url,
93+
method: moreInfoData?.method
94+
}}
95+
/>
96+
</Box>
97+
}
98+
8399
const ValuesTab = {
84100
id: 'values',
85101
content: "Values",
86102
component: (
87-
<Box paddingBlockStart={3} paddingInlineEnd={4} paddingInlineStart={4}>
88-
<SampleDataList
89-
key="Sample values"
90-
heading={"Attempt"}
91-
minHeight={"30vh"}
92-
vertical={true}
93-
sampleData={data?.map((result) => {
94-
return {message:result.orig, highlightPaths:[]}
95-
})}
96-
/>
97-
</Box>)
103+
<Box paddingBlockStart={3} paddingInlineEnd={4} paddingInlineStart={4}>
104+
<SampleDataList
105+
key="Sample values"
106+
heading={"Attempt"}
107+
minHeight={"30vh"}
108+
vertical={true}
109+
sampleData={data?.map((result) => {
110+
return { message: result.orig, highlightPaths: [], metadata: result.metadata }
111+
})}
112+
/>
113+
</Box>)
98114
}
99115

100116
const remediationTab = remediationText.length > 0 && {

apps/threat-detection-backend/src/main/java/com/akto/threat/backend/service/ThreatActorService.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.akto.threat.backend.service;
22

33
import com.akto.dto.HttpResponseParams;
4+
import com.akto.proto.generated.threat_detection.message.sample_request.v1.Metadata;
45
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.DailyActorsCountResponse;
56
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.FetchMaliciousEventsRequest;
67
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.FetchMaliciousEventsResponse;
@@ -17,9 +18,11 @@
1718
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.ThreatActorByCountryRequest;
1819
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.ThreatActorByCountryResponse;
1920
import com.akto.proto.generated.threat_detection.service.dashboard_service.v1.ListThreatActorResponse.ActivityData;
21+
import com.akto.ProtoMessageUtils;
2022
import com.akto.threat.backend.constants.MongoDBCollection;
2123
import com.akto.threat.backend.db.ActorInfoModel;
2224
import com.akto.threat.backend.db.SplunkIntegrationModel;
25+
import com.google.protobuf.TextFormat;
2326
import com.mongodb.client.FindIterable;
2427
import com.mongodb.client.MongoClient;
2528
import com.mongodb.client.MongoCollection;
@@ -378,6 +381,19 @@ public ThreatActivityTimelineResponse getThreatActivityTimeline(String accountId
378381
return ThreatActivityTimelineResponse.newBuilder().addAllThreatActivityTimeline(timeline).build();
379382
}
380383

384+
private String fetchMetadataString(Document doc){
385+
String metadataStr = doc.getString("metadata");
386+
Metadata.Builder metadataBuilder = Metadata.newBuilder();
387+
try {
388+
TextFormat.getParser().merge(metadataStr, metadataBuilder);
389+
} catch (Exception e) {
390+
return metadataStr;
391+
}
392+
Metadata metadataProto = metadataBuilder.build();
393+
metadataStr = ProtoMessageUtils.toString(metadataProto).orElse("");
394+
return metadataStr;
395+
}
396+
381397
public FetchMaliciousEventsResponse fetchAggregateMaliciousRequests(
382398
String accountId, FetchMaliciousEventsRequest request) {
383399

@@ -392,6 +408,7 @@ public FetchMaliciousEventsResponse fetchAggregateMaliciousRequests(
392408
maliciousPayloadsResponse.add(
393409
FetchMaliciousEventsResponse.MaliciousPayloadsResponse.newBuilder().
394410
setOrig(HttpResponseParams.getSampleStringFromProtoString(doc.getString("latestApiOrig"))).
411+
setMetadata(fetchMetadataString(doc)).
395412
setTs(doc.getLong("detectedAt")).build());
396413
}
397414
} else {
@@ -401,6 +418,7 @@ public FetchMaliciousEventsResponse fetchAggregateMaliciousRequests(
401418
maliciousPayloadsResponse.add(
402419
FetchMaliciousEventsResponse.MaliciousPayloadsResponse.newBuilder().
403420
setOrig(HttpResponseParams.getSampleStringFromProtoString(doc.getString("orig"))).
421+
setMetadata(fetchMetadataString(doc)).
404422
setTs(doc.getLong("requestTime")).build());
405423
}
406424
}

libs/utils/src/main/java/com/akto/log/LoggerMaker.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public class LoggerMaker {
3737
System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, System.getenv().getOrDefault("AKTO_LOG_LEVEL", "WARN"));
3838
System.setProperty("org.slf4j.simpleLogger.log.org.apache.kafka", "ERROR");
3939
System.setProperty("org.slf4j.simpleLogger.log.io.lettuce", "ERROR");
40+
System.setProperty("org.slf4j.simpleLogger.log.org.mongodb", "ERROR");
4041
System.setProperty("org.slf4j.simpleLogger.log.io.netty", "ERROR");
4142
System.setProperty("org.slf4j.simpleLogger.log.org.flywaydb", "ERROR");
4243
System.out.printf("AKTO_LOG_LEVEL is set to: %s \n", System.getProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY));

0 commit comments

Comments
 (0)