Skip to content

Commit 15be43e

Browse files
authored
Merge pull request #3727 from akto-api-security/feature/support_auth_types_filter_explore_mode
Feature/support auth types filter explore mode
2 parents fe20b3b + b906c61 commit 15be43e

File tree

5 files changed

+227
-2
lines changed

5 files changed

+227
-2
lines changed

apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/CollectionComponent.jsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,32 @@ const HTTP_METHODS = [
2222
{'label': 'TRACK', 'value': 'TRACK'}
2323
]
2424

25+
// Auth types matching ApiInfo.AuthType enum
26+
const AUTH_TYPES = [
27+
{ label: 'Unauthenticated', value: 'UNAUTHENTICATED' },
28+
{ label: 'Basic', value: 'BASIC' },
29+
{ label: 'Authorization Header', value: 'AUTHORIZATION_HEADER' },
30+
{ label: 'JWT', value: 'JWT' },
31+
{ label: 'API Token', value: 'API_TOKEN' },
32+
{ label: 'Bearer', value: 'BEARER' },
33+
{ label: 'Custom', value: 'CUSTOM' },
34+
{ label: 'API Key', value: 'API_KEY' },
35+
{ label: 'MTLS', value: 'MTLS' },
36+
{ label: 'Session Token', value: 'SESSION_TOKEN' }
37+
]
38+
2539
function CollectionComponent(props) {
2640

2741
const { condition, index, dispatch, operatorComponent } = props
2842
const [apiEndpoints, setApiEndpoints] = useState({})
2943
const initialRegexText = (condition && condition?.type === 'REGEX') ? (condition?.data?.regex || '') : ''
3044
const initialHostRegexText = (condition && condition?.type === 'HOST_REGEX') ? (condition?.data?.host_regex || '') : ''
3145
const initialTagsText = (condition && condition?.type === 'TAGS') ? (condition?.data?.query || '') : ''
46+
const initialAuthTypes = (condition && condition?.type === 'AUTH_TYPE') ? (condition?.data?.authTypes || []) : []
3247
const [regexText, setRegexText] = useState(initialRegexText)
3348
const [hostRegexText, setHostRegexText] = useState(initialHostRegexText)
3449
const [tagsText, setTagsText] = useState(initialTagsText)
50+
const [selectedAuthTypes, setSelectedAuthTypes] = useState(initialAuthTypes)
3551
const dashboardCategory = PersistStore(state => state.dashboardCategory)
3652

3753
useEffect(() => {
@@ -159,6 +175,8 @@ function CollectionComponent(props) {
159175
return {}
160176
case "TAGS":
161177
return {}
178+
case "AUTH_TYPE":
179+
return {authTypes:[]}
162180
default:
163181
return {}
164182
}
@@ -186,6 +204,10 @@ function CollectionComponent(props) {
186204
{
187205
label: 'Tags',
188206
value: 'TAGS'
207+
},
208+
{
209+
label: 'Auth Type',
210+
value: 'AUTH_TYPE'
189211
}
190212
]}
191213
initial={condition.type}
@@ -210,6 +232,11 @@ function CollectionComponent(props) {
210232
dispatch({ type: "overwrite", index: index, key: "data", obj: {"query":val } })
211233
}
212234

235+
const handleAuthTypesSelected = (authTypes) => {
236+
setSelectedAuthTypes(authTypes)
237+
dispatch({ type: "overwrite", index: index, key: "data", obj: {"authTypes": authTypes } })
238+
}
239+
213240
const component = (condition, index) => {
214241
switch (condition.type) {
215242
case "CUSTOM":
@@ -238,6 +265,18 @@ function CollectionComponent(props) {
238265
return(
239266
<TextField onChange={(val) => handleTagsText(val)} value={tagsText} />
240267
)
268+
case "AUTH_TYPE":
269+
return(
270+
<DropdownSearch
271+
id={`auth-type-${index}`}
272+
placeholder="Select auth types"
273+
optionsList={AUTH_TYPES}
274+
setSelected={(authTypes) => handleAuthTypesSelected(authTypes)}
275+
preSelected={selectedAuthTypes}
276+
value={selectedAuthTypes.length > 0 ? `${selectedAuthTypes.length} auth type${selectedAuthTypes.length === 1 ? '' : 's'} selected` : undefined}
277+
allowMultiple
278+
/>
279+
)
241280
default:
242281
break;
243282
}

libs/dao/src/main/java/com/akto/DaoInit.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ public static CodecRegistry createCodecRegistry(){
258258
ClassModel<RegexTestingEndpoints> regexTestingEndpointsClassModel = ClassModel.builder(RegexTestingEndpoints.class).enableDiscriminator(true).build();
259259
ClassModel<HostRegexTestingEndpoints> hostRegexTestingEndpointsClassModel = ClassModel.builder(HostRegexTestingEndpoints.class).enableDiscriminator(true).build();
260260
ClassModel<TagsTestingEndpoints> tagsTestingEndpointsClassModel = ClassModel.builder(TagsTestingEndpoints.class).enableDiscriminator(true).build();
261+
ClassModel<AuthTypeTestingEndpoints> authTypeTestingEndpointsClassModel = ClassModel.builder(AuthTypeTestingEndpoints.class).enableDiscriminator(true).build();
261262
ClassModel<DependencyNode> dependencyNodeClassModel = ClassModel.builder(DependencyNode.class).enableDiscriminator(true).build();
262263
ClassModel<ParamInfo> paramInfoClassModel = ClassModel.builder(ParamInfo.class).enableDiscriminator(true).build();
263264
ClassModel<Node> nodeClassModel = ClassModel.builder(Node.class).enableDiscriminator(true).build();
@@ -336,7 +337,7 @@ public static CodecRegistry createCodecRegistry(){
336337
setupClassModel,
337338
cronTimersClassModel, connectionInfoClassModel, testLibraryClassModel,
338339
methodConditionClassModel, regexTestingEndpointsClassModel, hostRegexTestingEndpointsClassModel,
339-
tagsTestingEndpointsClassModel, allTestingEndpointsClassModel,
340+
tagsTestingEndpointsClassModel, authTypeTestingEndpointsClassModel, allTestingEndpointsClassModel,
340341
UsageMetricClassModel, UsageMetricInfoClassModel, UsageSyncClassModel, OrganizationClassModel,
341342
yamlNodeDetails, multiExecTestResultClassModel, workflowTestClassModel, dependencyNodeClassModel,
342343
paramInfoClassModel,

libs/dao/src/main/java/com/akto/dto/ApiCollectionUsers.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.akto.dao.demo.VulnerableRequestForTemplateDao;
2929
import com.akto.dao.testing_run_findings.TestingRunIssuesDao;
3030
import com.akto.dto.rbac.UsersCollectionsList;
31+
import com.akto.dto.testing.AuthTypeTestingEndpoints;
3132
import com.akto.dto.testing.CustomTestingEndpoints;
3233
import com.akto.dto.testing.SensitiveDataEndpoints;
3334
import com.akto.dto.testing.TestingEndpoints;
@@ -48,6 +49,8 @@ public class ApiCollectionUsers {
4849
private static final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
4950
private static final Logger logger = LoggerFactory.getLogger(ApiCollectionUsers.class);
5051

52+
private static final int MAX_ALLOWED_API_COUNT = 10000;
53+
5154
public enum CollectionType {
5255
ApiCollectionId, Id_ApiCollectionId, Id_ApiInfoKey_ApiCollectionId
5356
}
@@ -76,6 +79,14 @@ public static List<BasicDBObject> getSingleTypeInfoListFromConditions(List<Testi
7679
return new ArrayList<>();
7780
}
7881
List<Bson> filterList = SingleTypeInfoDao.filterForHostHostHeaderRaw();
82+
boolean hasAuthFilter = checkAuthTypeFilter(conditions);
83+
if (hasAuthFilter) {
84+
int apiInfoCount = getApisCountFromConditions(conditions, deactivatedCollections);
85+
if (apiInfoCount >= MAX_ALLOWED_API_COUNT) {
86+
Bson apiInfoFilter = getFilters(conditions,CollectionType.Id_ApiCollectionId);
87+
return findAllAsBasicDBObject(apiInfoFilter, skip, limit, Projections.include("_id"));
88+
}
89+
}
7990
Bson singleTypeInfoFilters = getFilters(conditions, CollectionType.ApiCollectionId);
8091
Bson filters = Filters.and(filterList);
8192
singleTypeInfoFilters = Filters.and(filters, singleTypeInfoFilters);
@@ -104,6 +115,14 @@ public static int getApisCountFromConditionsWithStis(List<TestingEndpoints> cond
104115
return 0;
105116
}
106117

118+
boolean hasAuthFilter = checkAuthTypeFilter(conditions);
119+
if (hasAuthFilter) {
120+
int apiInfoCount = getApisCountFromConditions(conditions, deactivatedCollections);
121+
if (apiInfoCount >= MAX_ALLOWED_API_COUNT) {
122+
return apiInfoCount;
123+
}
124+
}
125+
107126
List<Bson> filterList = SingleTypeInfoDao.filterForHostHostHeaderRaw();
108127
Bson filters = Filters.and(filterList);
109128
Bson stiFiltes = getFilters(conditions, CollectionType.ApiCollectionId);
@@ -305,4 +324,22 @@ public static void reset(int apiCollectionId) {
305324
removeFromCollectionsForCollectionId(Collections.singletonList(ep), apiCollectionId);
306325
}
307326

327+
private static boolean checkAuthTypeFilter(List<TestingEndpoints> conditions) {
328+
for (TestingEndpoints condition : conditions) {
329+
if (condition instanceof AuthTypeTestingEndpoints) {
330+
return true;
331+
}
332+
}
333+
return false;
334+
}
335+
336+
private static List<BasicDBObject> findAllAsBasicDBObject(Bson filter,int skip,int limit,Bson projection) {
337+
List<BasicDBObject> results = new ArrayList<>();
338+
MongoCursor<BasicDBObject> cursor = ApiInfoDao.instance.getMCollection().find(filter, BasicDBObject.class).projection(projection).skip(skip).limit(limit).iterator();
339+
while (cursor.hasNext()) {
340+
results.add(cursor.next());
341+
}
342+
343+
return results;
344+
}
308345
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.akto.dto.testing;
2+
3+
import com.akto.dao.ApiInfoDao;
4+
import com.akto.dao.MCollection;
5+
import com.akto.dto.ApiCollectionUsers;
6+
import com.akto.dto.ApiInfo;
7+
import com.akto.dto.type.SingleTypeInfo;
8+
import com.mongodb.client.model.Filters;
9+
import org.bson.Document;
10+
import org.bson.conversions.Bson;
11+
12+
import java.util.ArrayList;
13+
import java.util.HashSet;
14+
import java.util.List;
15+
import java.util.Set;
16+
17+
public class AuthTypeTestingEndpoints extends TestingEndpoints {
18+
private List<String> authTypes;
19+
20+
public AuthTypeTestingEndpoints(Operator operator, List<String> authTypes) {
21+
super(Type.AUTH_TYPE, operator);
22+
this.authTypes = authTypes;
23+
}
24+
25+
public AuthTypeTestingEndpoints() {
26+
super(Type.AUTH_TYPE, Operator.OR);
27+
}
28+
29+
@Override
30+
public List<ApiInfo.ApiInfoKey> returnApis() {
31+
// Not used in filtering workflow, only for testing/policy contexts
32+
return new ArrayList<>();
33+
}
34+
35+
@Override
36+
public boolean containsApi(ApiInfo.ApiInfoKey key) {
37+
if (authTypes == null || authTypes.isEmpty()) {
38+
return false;
39+
}
40+
41+
ApiInfo apiInfo = ApiInfoDao.instance.findOne(ApiInfoDao.getFilter(key));
42+
if (apiInfo == null || apiInfo.getAllAuthTypesFound() == null) {
43+
return false;
44+
}
45+
46+
// Check if any of the selected auth types exist in the API's auth types
47+
Set<ApiInfo.AuthType> selectedAuthTypes = new HashSet<>();
48+
for (String authTypeStr : authTypes) {
49+
try {
50+
selectedAuthTypes.add(ApiInfo.AuthType.valueOf(authTypeStr));
51+
} catch (IllegalArgumentException e) {
52+
// Skip invalid auth types
53+
}
54+
}
55+
56+
// Check if any set in allAuthTypesFound contains any of our selected auth types
57+
for (Set<ApiInfo.AuthType> authTypeSet : apiInfo.getAllAuthTypesFound()) {
58+
for (ApiInfo.AuthType authType : authTypeSet) {
59+
if (selectedAuthTypes.contains(authType)) {
60+
return true;
61+
}
62+
}
63+
}
64+
65+
return false;
66+
}
67+
68+
private static Bson createApiFilters(ApiCollectionUsers.CollectionType type, ApiInfo.ApiInfoKey apiKey) {
69+
String prefix = getFilterPrefix(type);
70+
71+
return Filters.and(
72+
Filters.eq(prefix + SingleTypeInfo._URL, apiKey.getUrl()),
73+
Filters.eq(prefix + SingleTypeInfo._METHOD, apiKey.getMethod().toString()),
74+
Filters.in(SingleTypeInfo._COLLECTION_IDS, apiKey.getApiCollectionId())
75+
);
76+
}
77+
78+
@Override
79+
public Bson createFilters(ApiCollectionUsers.CollectionType type) {
80+
if (authTypes == null || authTypes.isEmpty()) {
81+
return MCollection.noMatchFilter;
82+
}
83+
84+
// Convert string auth types to AuthType enum
85+
List<ApiInfo.AuthType> authTypeEnums = new ArrayList<>();
86+
for (String authTypeStr : authTypes) {
87+
try {
88+
authTypeEnums.add(ApiInfo.AuthType.valueOf(authTypeStr));
89+
} catch (IllegalArgumentException e) {
90+
// Skip invalid auth types
91+
}
92+
}
93+
94+
if (authTypeEnums.isEmpty()) {
95+
return MCollection.noMatchFilter;
96+
}
97+
98+
List<Bson> authTypeFilters = new ArrayList<>();
99+
for (ApiInfo.AuthType authType : authTypeEnums) {
100+
// Nested elemMatch: outer for array-of-arrays, inner for elements in sub-array
101+
// Java Filters API doesn't support nested elemMatch, so use Document
102+
authTypeFilters.add(
103+
new Document(ApiInfo.ALL_AUTH_TYPES_FOUND,
104+
new Document("$elemMatch",
105+
new Document("$elemMatch",
106+
new Document("$eq", authType.name())
107+
)
108+
)
109+
)
110+
);
111+
}
112+
Bson authFilter = Filters.or(authTypeFilters);
113+
114+
// For ApiInfo-based collections, use the auth filter directly
115+
if (type == ApiCollectionUsers.CollectionType.Id_ApiCollectionId) {
116+
return authFilter;
117+
}
118+
119+
// For SingleTypeInfo-based collections (ApiCollectionId),
120+
// fetch matching ApiInfos and create filters based on URL/method/collectionId
121+
// This is necessary because SingleTypeInfo doesn't have allAuthTypesFound field
122+
List<ApiInfo> matchedApis = ApiInfoDao.instance.findAll(authFilter);
123+
124+
if (matchedApis.isEmpty()) {
125+
return MCollection.noMatchFilter;
126+
}
127+
128+
List<Bson> apiFilters = new ArrayList<>();
129+
for (ApiInfo apiInfo : matchedApis) {
130+
apiFilters.add(createApiFilters(type, apiInfo.getId()));
131+
}
132+
133+
return Filters.or(apiFilters);
134+
}
135+
136+
public List<String> getAuthTypes() {
137+
return authTypes;
138+
}
139+
140+
public void setAuthTypes(List<String> authTypes) {
141+
this.authTypes = authTypes;
142+
}
143+
}

libs/dao/src/main/java/com/akto/dto/testing/TestingEndpoints.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public enum Operator {
3939

4040

4141
public enum Type {
42-
CUSTOM, COLLECTION_WISE, WORKFLOW, LOGICAL_GROUP, METHOD, ALL, REGEX, RISK_SCORE, SENSITIVE_DATA, UNAUTHENTICATED, HOST_REGEX, TAGS
42+
CUSTOM, COLLECTION_WISE, WORKFLOW, LOGICAL_GROUP, METHOD, ALL, REGEX, RISK_SCORE, SENSITIVE_DATA, UNAUTHENTICATED, HOST_REGEX, TAGS, AUTH_TYPE
4343
}
4444

4545
public Type getType() {
@@ -87,6 +87,11 @@ public static TestingEndpoints generateCondition(Type type, Operator operator, B
8787
String q = (data != null) ? data.getString("query") : null;
8888
condition = new TagsTestingEndpoints(operator, q);
8989
break;
90+
case AUTH_TYPE:
91+
List<String> authTypes = (data != null && data.get("authTypes") != null) ?
92+
(List<String>) data.get("authTypes") : new ArrayList<>();
93+
condition = new AuthTypeTestingEndpoints(operator, authTypes);
94+
break;
9095
default:
9196
break;
9297
}

0 commit comments

Comments
 (0)