From 46695478072530c505e6be7d2b44af2cfa1763e0 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Mon, 20 Jan 2025 01:58:51 -0800 Subject: [PATCH 01/38] add compliances to common --- .../java/com/akto/action/AccountAction.java | 7 +- .../java/com/akto/action/ProfileAction.java | 4 +- .../akto/listener/InitializerListener.java | 109 +++++++++++++++++- .../main/java/com/akto/dao/MCollection.java | 8 +- .../dao/test_editor/TestConfigYamlParser.java | 14 +++ .../akto/dao/testing/ComplianceInfosDao.java | 22 ++++ .../dao/testing/ComplianceMappingsDao.java | 22 ++++ .../java/com/akto/dto/AccountSettings.java | 11 ++ .../com/akto/dto/testing/ComplianceInfo.java | 87 ++++++++++++++ .../akto/dto/testing/ComplianceMapping.java | 96 +++++++++++++++ 10 files changed, 370 insertions(+), 10 deletions(-) create mode 100644 libs/dao/src/main/java/com/akto/dao/testing/ComplianceInfosDao.java create mode 100644 libs/dao/src/main/java/com/akto/dao/testing/ComplianceMappingsDao.java create mode 100644 libs/dao/src/main/java/com/akto/dto/testing/ComplianceInfo.java create mode 100644 libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java diff --git a/apps/dashboard/src/main/java/com/akto/action/AccountAction.java b/apps/dashboard/src/main/java/com/akto/action/AccountAction.java index 7075d4ca6d..e8dc8aa264 100644 --- a/apps/dashboard/src/main/java/com/akto/action/AccountAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/AccountAction.java @@ -329,10 +329,9 @@ public void run() { AccountSettingsDao.instance.updateOnboardingFlag(true); InitializerListener.insertPiiSources(); - if (DashboardMode.isMetered()) { - AccountSettings accountSettings = AccountSettingsDao.instance.findOne(AccountSettingsDao.generateFilter()); - InitializerListener.insertAktoTestLibraries(accountSettings); - } + AccountSettings accountSettings = AccountSettingsDao.instance.findOne(AccountSettingsDao.generateFilter()); + InitializerListener.insertStateInAccountSettings(accountSettings); + try { InitializerListener.executePIISourceFetch(); } catch (Exception e) { diff --git a/apps/dashboard/src/main/java/com/akto/action/ProfileAction.java b/apps/dashboard/src/main/java/com/akto/action/ProfileAction.java index 3184e3dd19..d3b1172817 100644 --- a/apps/dashboard/src/main/java/com/akto/action/ProfileAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/ProfileAction.java @@ -135,9 +135,7 @@ public static void executeMeta1(Utility utility, User user, HttpServletRequest r } catch (Exception e) { } - if (DashboardMode.isMetered()) { - InitializerListener.insertAktoTestLibraries(accountSettings); - } + InitializerListener.insertStateInAccountSettings(accountSettings); Organization organization = OrganizationsDao.instance.findOne( Filters.in(Organization.ACCOUNTS, sessionAccId) diff --git a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java index c0345cbcaf..340e017a96 100644 --- a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java +++ b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java @@ -2441,7 +2441,23 @@ public static Set getAktoDefaultTestLibs() { return new HashSet<>(Arrays.asList("akto-api-security/tests-library:standard", "akto-api-security/tests-library:pro")); } - public static void insertAktoTestLibraries(AccountSettings accountSettings) { + public static void insertStateInAccountSettings(AccountSettings accountSettings) { + if (DashboardMode.isMetered()) { + insertAktoTestLibraries(accountSettings); + } + + insertCompliances(accountSettings); + } + + private static void insertCompliances(AccountSettings accountSettings) { + boolean complianceInfosAlreadyPresent = accountSettings != null && accountSettings.getComplianceInfosUpdatedTs() > 0; + + if (!complianceInfosAlreadyPresent) { + AccountSettingsDao.instance.updateOne(AccountSettingsDao.generateFilter(), Updates.set(AccountSettings.COMPLIANCE_INFOS_UPDATED_TS, 1)); + } + } + + private static void insertAktoTestLibraries(AccountSettings accountSettings) { List testLibraries = accountSettings == null ? new ArrayList<>() : accountSettings.getTestLibraries(); Set aktoTestLibraries = getAktoDefaultTestLibs(); @@ -3170,6 +3186,7 @@ public void run() { try { processRemedationFilesZip(testingTemplates); + processComplianceInfosFromZip(testingTemplates); } catch (Exception e) { e.printStackTrace(); loggerMaker.infoAndAddToDb("Unable to import remediations", LogDb.DASHBOARD); @@ -3289,6 +3306,96 @@ public static void processRemedationFilesZip(byte[] zipFile) { } } + public static void processComplianceInfosFromZip(byte[] zipFile) { + if (zipFile != null) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(zipFile); + ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { + + ZipEntry entry; + + int countUnchangedCompliances = 0; + int countTotalCompliances = 0; + + Bson emptyFilter = Filters.empty(); + List complianceInfosInDb = ComplianceInfosDao.instance.findAll(emptyFilter, Projections.exclude(ComplianceInfo.MAP_COMPLIANCE_TO_LIST_CLAUSES)); + Map mapIdToComplianceInDb = complianceInfosInDb.stream().collect(Collectors.toMap(ComplianceInfo::getId, Function.identity())); + + while ((entry = zipInputStream.getNextEntry()) != null) { + if (!entry.isDirectory()) { + String entryName = entry.getName(); + + boolean isCompliance = entryName.contains("compliance/"); + if (!isCompliance) { + loggerMaker.infoAndAddToDb( + String.format("%s not a compliance file, skipping", entryName), + LogDb.DASHBOARD); + continue; + } + + if (!entryName.endsWith(".yml") && !entryName.endsWith(".yaml")) { + loggerMaker.infoAndAddToDb( + String.format("%s not a yaml file, skipping", entryName), + LogDb.DASHBOARD); + continue; + } + + String[] filePathTokens = entryName.split("/"); + + if (filePathTokens.length <= 1) { + loggerMaker.infoAndAddToDb( + String.format("%s has no directory patthern", entryName), + LogDb.DASHBOARD); + continue; + } + + String fileSourceId = "compliance/"+filePathTokens[filePathTokens.length-1]; + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = zipInputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + + String templateContent = new String(outputStream.toByteArray(), "UTF-8"); + int templateHashCode = templateContent.hashCode(); + + Map> contentMap = TestConfigYamlParser.parseComplianceTemplate(templateContent); + + ComplianceInfo complianceInfoInDb = mapIdToComplianceInDb.get(fileSourceId); + countTotalCompliances++; + + if (complianceInfoInDb == null) { + ComplianceInfo newComplianceInfo = new ComplianceInfo(fileSourceId, contentMap, Constants._AKTO, templateHashCode, ""); + loggerMaker.infoAndAddToDb("Inserting compliance content: " + entryName, LogDb.DASHBOARD); + ComplianceInfosDao.instance.insertOne(newComplianceInfo); + + } else if (complianceInfoInDb.getHash() == templateHashCode ) { + countUnchangedCompliances++; + } else { + Bson updates = Updates.combine(Updates.set(ComplianceInfo.MAP_COMPLIANCE_TO_LIST_CLAUSES, contentMap), Updates.set(ComplianceInfo.HASH, templateHashCode)); + loggerMaker.infoAndAddToDb("Updating compliance content: " + entryName, LogDb.DASHBOARD); + ComplianceInfosDao.instance.updateOne(Constants.ID, fileSourceId, updates); + } + } + + zipInputStream.closeEntry(); + } + + if (countTotalCompliances != countUnchangedCompliances) { + loggerMaker.infoAndAddToDb(countUnchangedCompliances + "/" + countTotalCompliances + "compliances unchanged", LogDb.DASHBOARD); + } + + } catch (Exception ex) { + cacheLoggerMaker.errorAndAddToDb(ex, + String.format("Error while processing Test template files zip. Error %s", ex.getMessage()), + LogDb.DASHBOARD); + } + } else { + loggerMaker.infoAndAddToDb("Received null zip file"); + } + } + public static void processTemplateFilesZip(byte[] zipFile, String author, String source, String repositoryUrl) { if (zipFile != null) { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(zipFile); diff --git a/libs/dao/src/main/java/com/akto/dao/MCollection.java b/libs/dao/src/main/java/com/akto/dao/MCollection.java index f166276f14..99d02e6b7d 100644 --- a/libs/dao/src/main/java/com/akto/dao/MCollection.java +++ b/libs/dao/src/main/java/com/akto/dao/MCollection.java @@ -394,8 +394,12 @@ public void trimCollection(int maxDocuments) { } public Document getCollectionStats(){ - MongoDatabase mongoDatabase = clients[0].getDatabase(getDBName()); - return mongoDatabase.runCommand(new Document("collStats",getCollName())); + return getCollectionStatsFromNames(getDBName(), getCollName()); + } + + public static Document getCollectionStatsFromNames(String dbName, String collName){ + MongoDatabase mongoDatabase = clients[0].getDatabase(dbName); + return mongoDatabase.runCommand(new Document("collStats",collName)); } public Document compactCollection() { diff --git a/libs/dao/src/main/java/com/akto/dao/test_editor/TestConfigYamlParser.java b/libs/dao/src/main/java/com/akto/dao/test_editor/TestConfigYamlParser.java index c7b9aff6f3..4c00de7d11 100644 --- a/libs/dao/src/main/java/com/akto/dao/test_editor/TestConfigYamlParser.java +++ b/libs/dao/src/main/java/com/akto/dao/test_editor/TestConfigYamlParser.java @@ -26,6 +26,20 @@ public static TestConfig parseTemplate(String content) throws Exception { return parseConfig(config); } + public static Map> parseComplianceTemplate(String content) throws Exception { + + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + + Map config = mapper.readValue(content, Map.class); + + Map> ret = new HashMap<>(); + for(String complianceName: config.keySet()) { + ret.put(complianceName.toUpperCase(), (List) config.get(complianceName)); + } + + return ret; + } + public static TestConfig parseConfig(Map config) throws Exception { TestConfig testConfig = null; diff --git a/libs/dao/src/main/java/com/akto/dao/testing/ComplianceInfosDao.java b/libs/dao/src/main/java/com/akto/dao/testing/ComplianceInfosDao.java new file mode 100644 index 0000000000..ef18590078 --- /dev/null +++ b/libs/dao/src/main/java/com/akto/dao/testing/ComplianceInfosDao.java @@ -0,0 +1,22 @@ +package com.akto.dao.testing; + +import com.akto.dao.CommonContextDao; +import com.akto.dto.testing.ComplianceInfo; + +public class ComplianceInfosDao extends CommonContextDao { + + public static final ComplianceInfosDao instance = new ComplianceInfosDao(); + + private ComplianceInfosDao() {} + + @Override + public String getCollName() { + return "compliance_infos"; + } + + @Override + public Class getClassT() { + return ComplianceInfo.class; + } + +} diff --git a/libs/dao/src/main/java/com/akto/dao/testing/ComplianceMappingsDao.java b/libs/dao/src/main/java/com/akto/dao/testing/ComplianceMappingsDao.java new file mode 100644 index 0000000000..507cb3d618 --- /dev/null +++ b/libs/dao/src/main/java/com/akto/dao/testing/ComplianceMappingsDao.java @@ -0,0 +1,22 @@ +package com.akto.dao.testing; + +import com.akto.dao.AccountsContextDao; +import com.akto.dto.testing.ComplianceMapping; + +public class ComplianceMappingsDao extends AccountsContextDao { + + public static final ComplianceMappingsDao instance = new ComplianceMappingsDao(); + + private ComplianceMappingsDao() {} + + @Override + public String getCollName() { + return "compliance_mappings"; + } + + @Override + public Class getClassT() { + return ComplianceMapping.class; + } + +} diff --git a/libs/dao/src/main/java/com/akto/dto/AccountSettings.java b/libs/dao/src/main/java/com/akto/dto/AccountSettings.java index 5c6e6c2a07..137d3a942d 100644 --- a/libs/dao/src/main/java/com/akto/dto/AccountSettings.java +++ b/libs/dao/src/main/java/com/akto/dto/AccountSettings.java @@ -120,6 +120,9 @@ public class AccountSettings { public static final String HANDLE_APIS_CASE_INSENSITIVE = "handleApisCaseInsensitive"; private boolean handleApisCaseInsensitive; + public static final String COMPLIANCE_INFOS_UPDATED_TS = "complianceInfosUpdatedTs"; + private int complianceInfosUpdatedTs; + public AccountSettings() { } @@ -456,4 +459,12 @@ public boolean getAllowOptionsAPIs() { public void setAllowOptionsAPIs(boolean allowOptionsAPIs) { this.allowOptionsAPIs = allowOptionsAPIs; } + + public int getComplianceInfosUpdatedTs() { + return this.complianceInfosUpdatedTs; + } + + public void setcCmplianceInfosUpdatedTs(int complianceInfosUpdatedTs) { + this.complianceInfosUpdatedTs = complianceInfosUpdatedTs; + } } diff --git a/libs/dao/src/main/java/com/akto/dto/testing/ComplianceInfo.java b/libs/dao/src/main/java/com/akto/dto/testing/ComplianceInfo.java new file mode 100644 index 0000000000..cadea82804 --- /dev/null +++ b/libs/dao/src/main/java/com/akto/dto/testing/ComplianceInfo.java @@ -0,0 +1,87 @@ +package com.akto.dto.testing; + +import java.util.List; +import java.util.Map; + +import org.bson.codecs.pojo.annotations.BsonId; + +public class ComplianceInfo { + + @BsonId + private String id; + + public static final String MAP_COMPLIANCE_TO_LIST_CLAUSES = "mapComplianceToListClauses"; + private Map> mapComplianceToListClauses; + + public static final String AUTHOR = "author"; + private String author; + + public static final String HASH = "hash"; + private int hash; + + private String sourcePath; + + public ComplianceInfo() { + } + + public ComplianceInfo(String id, Map> mapComplianceToListClauses, String author, int hash, String sourcePath) { + this.id = id; + this.mapComplianceToListClauses = mapComplianceToListClauses; + this.author = author; + this.hash = hash; + this.sourcePath = sourcePath; + } + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public Map> getMapComplianceToListClauses() { + return this.mapComplianceToListClauses; + } + + public void setMapComplianceToListClauses(Map> mapComplianceToListClauses) { + this.mapComplianceToListClauses = mapComplianceToListClauses; + } + + public String getAuthor() { + return this.author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public int getHash() { + return this.hash; + } + + public void setHash(int hash) { + this.hash = hash; + } + + public String getSourcePath() { + return this.sourcePath; + } + + public void setSourcePath(String sourcePath) { + this.sourcePath = sourcePath; + } + + @Override + public String toString() { + return "{" + + " id='" + getId() + "'" + + ", mapComplianceToListClauses='" + getMapComplianceToListClauses() + "'" + + ", author='" + getAuthor() + "'" + + ", hash='" + getHash() + "'" + + ", sourcePath='" + getSourcePath() + "'" + + "}"; + } + +} + diff --git a/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java b/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java new file mode 100644 index 0000000000..c7aa8d49b1 --- /dev/null +++ b/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java @@ -0,0 +1,96 @@ +package com.akto.dto.testing; + +import java.util.List; +import java.util.Map; + +import org.bson.codecs.pojo.annotations.BsonId; + +public class ComplianceMapping { + + @BsonId + String id; + + private Map> mapComplianceToListClauses; + + String author; + + int lastUpdatedTs; + + String source; + + int hash; + + + public ComplianceMapping() { + } + + public ComplianceMapping(String id, Map> mapComplianceToListClauses, String author, int lastUpdatedTs, String source, int hash) { + this.id = id; + this.mapComplianceToListClauses = mapComplianceToListClauses; + this.author = author; + this.lastUpdatedTs = lastUpdatedTs; + this.source = source; + this.hash = hash; + } + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public Map> getMapComplianceToListClauses() { + return this.mapComplianceToListClauses; + } + + public void setMapComplianceToListClauses(Map> mapComplianceToListClauses) { + this.mapComplianceToListClauses = mapComplianceToListClauses; + } + + public String getAuthor() { + return this.author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public int getLastUpdatedTs() { + return this.lastUpdatedTs; + } + + public void setLastUpdatedTs(int lastUpdatedTs) { + this.lastUpdatedTs = lastUpdatedTs; + } + + public String getSource() { + return this.source; + } + + public void setSource(String source) { + this.source = source; + } + + public int getHash() { + return this.hash; + } + + public void setHash(int hash) { + this.hash = hash; + } + + @Override + public String toString() { + return "{" + + " id='" + getId() + "'" + + ", mapComplianceToListClauses='" + getMapComplianceToListClauses() + "'" + + ", author='" + getAuthor() + "'" + + ", lastUpdatedTs='" + getLastUpdatedTs() + "'" + + ", source='" + getSource() + "'" + + ", hash='" + getHash() + "'" + + "}"; + } + +} From e350e95bb00606e2b0aef14af0c4af3fd43ab4cf Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Mon, 20 Jan 2025 05:13:35 -0800 Subject: [PATCH 02/38] sync compliance with account --- .../akto/listener/InitializerListener.java | 90 ++++++++++++++++++- .../akto/dto/testing/ComplianceMapping.java | 31 +------ 2 files changed, 91 insertions(+), 30 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java index 340e017a96..9fc2ff36c8 100644 --- a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java +++ b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java @@ -53,6 +53,7 @@ import com.akto.dto.pii.PIIType; import com.akto.dto.settings.DefaultPayload; import com.akto.dto.sso.SAMLConfig; +import com.akto.dto.test_editor.Category; import com.akto.dto.test_editor.TestConfig; import com.akto.dto.test_editor.TestLibrary; import com.akto.dto.test_editor.YamlTemplate; @@ -127,6 +128,7 @@ import com.mongodb.client.MongoCursor; import com.mongodb.client.model.*; import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; import com.slack.api.Slack; import com.slack.api.util.http.SlackHttpClient; import com.slack.api.webhook.WebhookResponse; @@ -3213,6 +3215,11 @@ public void run() { processTemplateFilesZip(zipFile, Constants._AKTO, YamlTemplateSource.AKTO_TEMPLATES.toString(), ""); } } + + if (accountSettings.getComplianceInfosUpdatedTs() > 0) { + addComplianceFromCommonToAccount(getFromCommonDb()); + replaceComplianceFromCommonToAccount(getFromCommonDb()); + } } catch (Exception e) { cacheLoggerMaker.errorAndAddToDb(e, @@ -3306,6 +3313,85 @@ public static void processRemedationFilesZip(byte[] zipFile) { } } + private static Map getFromCommonDb() { + Bson emptyFilter = Filters.empty(); + List complianceInfosInDb = ComplianceInfosDao.instance.findAll(emptyFilter, Projections.exclude(ComplianceInfo.MAP_COMPLIANCE_TO_LIST_CLAUSES)); + Map mapIdToComplianceInDb = complianceInfosInDb.stream().collect(Collectors.toMap(ComplianceInfo::getId, Function.identity())); + return mapIdToComplianceInDb; + } + + public static void replaceComplianceFromCommonToAccount(Map mapIdToComplianceInCommon) { + try { + String ic = "info.compliance."; + for(String fileSourceId: mapIdToComplianceInCommon.keySet()) { + ComplianceInfo complianceInfoInCommon = mapIdToComplianceInCommon.get(fileSourceId); + + Bson filters = + Filters.and( + Filters.eq("info.compliance.source", fileSourceId), + Filters.ne(ic+ComplianceInfo.HASH, complianceInfoInCommon.getHash()) + ); + + Bson updates = Updates.combine( + Updates.set(ic+ComplianceInfo.HASH, complianceInfoInCommon.getHash()), + Updates.set(ic+ComplianceInfo.MAP_COMPLIANCE_TO_LIST_CLAUSES, complianceInfoInCommon.getMapComplianceToListClauses()) + ); + UpdateResult updateResult = ComplianceMappingsDao.instance.updateMany(filters, updates); + loggerMaker.infoAndAddToDb("replaceComplianceFromCommonToAccount: " + Context.accountId.get() + " : " +fileSourceId+" "+ updateResult); + } + } catch (Exception e) { + loggerMaker.errorAndAddToDb("Error in replaceComplianceFromCommonToAccount: " + Context.accountId.get() + " : " + e.getMessage()); + e.printStackTrace(); + } + } + + public static void addComplianceFromCommonToAccount(Map mapIdToComplianceInCommon) { + try { + + for(String fileSourceId: mapIdToComplianceInCommon.keySet()) { + ComplianceInfo complianceInfoInCommon = mapIdToComplianceInCommon.get(fileSourceId); + String compId = complianceInfoInCommon.getId().split("/")[1].split("\\.")[0].toUpperCase(); + + boolean isCategoryTemplate = TestCategory.valueOf(compId.toUpperCase()) != null; + + if (isCategoryTemplate) continue; + + Bson filters = + Filters.and( + Filters.eq(Constants.ID, compId), + Filters.exists("info.compliance", false) + ); + + ComplianceMapping complianceMapping = ComplianceMapping.createFromInfo(complianceInfoInCommon); + UpdateResult updateResult = YamlTemplateDao.instance.updateMany(filters, Updates.set("compliance", complianceMapping)); + loggerMaker.infoAndAddToDb("addComplianceFromCommonToAccount for test id: " + Context.accountId.get() + " : " + compId + " " + updateResult); + } + + + for(String fileSourceId: mapIdToComplianceInCommon.keySet()) { + ComplianceInfo complianceInfoInCommon = mapIdToComplianceInCommon.get(fileSourceId); + String compId = complianceInfoInCommon.getId().split("/")[1].split("\\.")[0].toUpperCase(); + + boolean isCategoryTemplate = TestCategory.valueOf(compId.toUpperCase()) != null; + + if (!isCategoryTemplate) continue; + + Bson filters = + Filters.and( + Filters.eq("info.category.shortName", compId.toUpperCase()), + Filters.exists("info.compliance", false) + ); + + ComplianceMapping complianceMapping = ComplianceMapping.createFromInfo(complianceInfoInCommon); + UpdateResult updateResult = YamlTemplateDao.instance.updateMany(filters, Updates.set("compliance", complianceMapping)); + loggerMaker.infoAndAddToDb("addComplianceFromCommonToAccount for category: " + Context.accountId.get() + " : " + compId + " " + updateResult); + } + } catch (Exception e) { + loggerMaker.errorAndAddToDb("Error in addComplianceFromCommonToAccount: " + Context.accountId.get() + " : " + e.getMessage()); + e.printStackTrace(); + } + } + public static void processComplianceInfosFromZip(byte[] zipFile) { if (zipFile != null) { try (ByteArrayInputStream inputStream = new ByteArrayInputStream(zipFile); @@ -3316,9 +3402,7 @@ public static void processComplianceInfosFromZip(byte[] zipFile) { int countUnchangedCompliances = 0; int countTotalCompliances = 0; - Bson emptyFilter = Filters.empty(); - List complianceInfosInDb = ComplianceInfosDao.instance.findAll(emptyFilter, Projections.exclude(ComplianceInfo.MAP_COMPLIANCE_TO_LIST_CLAUSES)); - Map mapIdToComplianceInDb = complianceInfosInDb.stream().collect(Collectors.toMap(ComplianceInfo::getId, Function.identity())); + Map mapIdToComplianceInDb = getFromCommonDb(); while ((entry = zipInputStream.getNextEntry()) != null) { if (!entry.isDirectory()) { diff --git a/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java b/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java index c7aa8d49b1..dd17dff649 100644 --- a/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java +++ b/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java @@ -3,42 +3,29 @@ import java.util.List; import java.util.Map; -import org.bson.codecs.pojo.annotations.BsonId; - public class ComplianceMapping { - @BsonId - String id; - private Map> mapComplianceToListClauses; String author; - int lastUpdatedTs; - + public static final String SOURCE = "source"; String source; int hash; - public ComplianceMapping() { } - public ComplianceMapping(String id, Map> mapComplianceToListClauses, String author, int lastUpdatedTs, String source, int hash) { - this.id = id; + public ComplianceMapping(Map> mapComplianceToListClauses, String author, String source, int hash) { this.mapComplianceToListClauses = mapComplianceToListClauses; this.author = author; - this.lastUpdatedTs = lastUpdatedTs; this.source = source; this.hash = hash; } - public String getId() { - return this.id; - } - - public void setId(String id) { - this.id = id; + public static ComplianceMapping createFromInfo(ComplianceInfo complianceInfo) { + return new ComplianceMapping(complianceInfo.getMapComplianceToListClauses(), complianceInfo.getAuthor(), complianceInfo.getId(), complianceInfo.getHash()); } public Map> getMapComplianceToListClauses() { @@ -57,14 +44,6 @@ public void setAuthor(String author) { this.author = author; } - public int getLastUpdatedTs() { - return this.lastUpdatedTs; - } - - public void setLastUpdatedTs(int lastUpdatedTs) { - this.lastUpdatedTs = lastUpdatedTs; - } - public String getSource() { return this.source; } @@ -84,10 +63,8 @@ public void setHash(int hash) { @Override public String toString() { return "{" + - " id='" + getId() + "'" + ", mapComplianceToListClauses='" + getMapComplianceToListClauses() + "'" + ", author='" + getAuthor() + "'" + - ", lastUpdatedTs='" + getLastUpdatedTs() + "'" + ", source='" + getSource() + "'" + ", hash='" + getHash() + "'" + "}"; From ce502867deb3be43e989664d54c22cda7acd75a8 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Mon, 20 Jan 2025 05:18:21 -0800 Subject: [PATCH 03/38] add compliance field to test templates --- .../akto/listener/InitializerListener.java | 2 +- .../dao/testing/ComplianceMappingsDao.java | 22 ------------------- .../java/com/akto/dto/test_editor/Info.java | 15 ++++++++++++- 3 files changed, 15 insertions(+), 24 deletions(-) delete mode 100644 libs/dao/src/main/java/com/akto/dao/testing/ComplianceMappingsDao.java diff --git a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java index 9fc2ff36c8..dfe2d88b66 100644 --- a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java +++ b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java @@ -3336,7 +3336,7 @@ public static void replaceComplianceFromCommonToAccount(Map { - - public static final ComplianceMappingsDao instance = new ComplianceMappingsDao(); - - private ComplianceMappingsDao() {} - - @Override - public String getCollName() { - return "compliance_mappings"; - } - - @Override - public Class getClassT() { - return ComplianceMapping.class; - } - -} diff --git a/libs/dao/src/main/java/com/akto/dto/test_editor/Info.java b/libs/dao/src/main/java/com/akto/dto/test_editor/Info.java index 7661fdf744..a64f28ad3b 100644 --- a/libs/dao/src/main/java/com/akto/dto/test_editor/Info.java +++ b/libs/dao/src/main/java/com/akto/dto/test_editor/Info.java @@ -2,6 +2,8 @@ import java.util.List; +import com.akto.dto.testing.ComplianceMapping; + public class Info { private String name; @@ -28,8 +30,10 @@ public class Info { private List cve; + private ComplianceMapping compliance; + public Info(String name, String description, String details, String impact, String remediation, Category category, String subCategory, - String severity, List tags, List references, List cwe, List cve) { + String severity, List tags, List references, List cwe, List cve, ComplianceMapping compliance) { this.name = name; this.description = description; this.details = details; @@ -42,6 +46,7 @@ public Info(String name, String description, String details, String impact, Stri this.references = references; this.cwe = cwe; this.cve = cve; + this.compliance = compliance; } public Info() { } @@ -143,4 +148,12 @@ public void setCve(List cve) { this.cve = cve; } + public ComplianceMapping getCompliance() { + return this.compliance; + } + + public void setCompliance(ComplianceMapping compliance) { + this.compliance = compliance; + } + } From 9356181b9329d7dcc5135bfeb6551b25a47b5fcc Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Tue, 21 Jan 2025 02:46:48 -0800 Subject: [PATCH 04/38] sync compliance with each account --- .../akto/listener/InitializerListener.java | 26 ++++++++++--------- libs/dao/src/main/java/com/akto/DaoInit.java | 4 ++- .../dao/test_editor/TestConfigYamlParser.java | 6 ++++- .../java/com/akto/dto/AccountSettings.java | 2 +- .../akto/dto/testing/ComplianceMapping.java | 6 ++--- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java index dfe2d88b66..8a5795f703 100644 --- a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java +++ b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java @@ -138,6 +138,7 @@ import org.apache.commons.csv.CSVRecord; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; import org.bson.conversions.Bson; import org.bson.types.ObjectId; @@ -3193,7 +3194,7 @@ public void run() { e.printStackTrace(); loggerMaker.infoAndAddToDb("Unable to import remediations", LogDb.DASHBOARD); } - + Map complianceCommonMap = getFromCommonDb(); Map allYamlTemplates = TestTemplateUtils.getZipFromMultipleRepoAndBranch(getAktoDefaultTestLibs()); AccountTask.instance.executeTask((account) -> { try { @@ -3216,9 +3217,9 @@ public void run() { } } - if (accountSettings.getComplianceInfosUpdatedTs() > 0) { - addComplianceFromCommonToAccount(getFromCommonDb()); - replaceComplianceFromCommonToAccount(getFromCommonDb()); + if (accountSettings.getComplianceInfosUpdatedTs() > 0) { + addComplianceFromCommonToAccount(complianceCommonMap); + replaceComplianceFromCommonToAccount(complianceCommonMap); } } catch (Exception e) { @@ -3315,7 +3316,7 @@ public static void processRemedationFilesZip(byte[] zipFile) { private static Map getFromCommonDb() { Bson emptyFilter = Filters.empty(); - List complianceInfosInDb = ComplianceInfosDao.instance.findAll(emptyFilter, Projections.exclude(ComplianceInfo.MAP_COMPLIANCE_TO_LIST_CLAUSES)); + List complianceInfosInDb = ComplianceInfosDao.instance.findAll(emptyFilter); Map mapIdToComplianceInDb = complianceInfosInDb.stream().collect(Collectors.toMap(ComplianceInfo::getId, Function.identity())); return mapIdToComplianceInDb; } @@ -3352,7 +3353,7 @@ public static void addComplianceFromCommonToAccount(Map ComplianceInfo complianceInfoInCommon = mapIdToComplianceInCommon.get(fileSourceId); String compId = complianceInfoInCommon.getId().split("/")[1].split("\\.")[0].toUpperCase(); - boolean isCategoryTemplate = TestCategory.valueOf(compId.toUpperCase()) != null; + boolean isCategoryTemplate = EnumUtils.getEnum(TestCategory.class, compId.toUpperCase()) != null; if (isCategoryTemplate) continue; @@ -3363,7 +3364,7 @@ public static void addComplianceFromCommonToAccount(Map ); ComplianceMapping complianceMapping = ComplianceMapping.createFromInfo(complianceInfoInCommon); - UpdateResult updateResult = YamlTemplateDao.instance.updateMany(filters, Updates.set("compliance", complianceMapping)); + UpdateResult updateResult = YamlTemplateDao.instance.updateMany(filters, Updates.set("info.compliance", complianceMapping)); loggerMaker.infoAndAddToDb("addComplianceFromCommonToAccount for test id: " + Context.accountId.get() + " : " + compId + " " + updateResult); } @@ -3372,18 +3373,18 @@ public static void addComplianceFromCommonToAccount(Map ComplianceInfo complianceInfoInCommon = mapIdToComplianceInCommon.get(fileSourceId); String compId = complianceInfoInCommon.getId().split("/")[1].split("\\.")[0].toUpperCase(); - boolean isCategoryTemplate = TestCategory.valueOf(compId.toUpperCase()) != null; + boolean isCategoryTemplate = EnumUtils.getEnum(TestCategory.class, compId.toUpperCase()) != null; if (!isCategoryTemplate) continue; Bson filters = Filters.and( - Filters.eq("info.category.shortName", compId.toUpperCase()), + Filters.eq("info.category.name", compId.toUpperCase()), Filters.exists("info.compliance", false) ); ComplianceMapping complianceMapping = ComplianceMapping.createFromInfo(complianceInfoInCommon); - UpdateResult updateResult = YamlTemplateDao.instance.updateMany(filters, Updates.set("compliance", complianceMapping)); + UpdateResult updateResult = YamlTemplateDao.instance.updateMany(filters, Updates.set("info.compliance", complianceMapping)); loggerMaker.infoAndAddToDb("addComplianceFromCommonToAccount for category: " + Context.accountId.get() + " : " + compId + " " + updateResult); } } catch (Exception e) { @@ -3416,7 +3417,7 @@ public static void processComplianceInfosFromZip(byte[] zipFile) { continue; } - if (!entryName.endsWith(".yml") && !entryName.endsWith(".yaml")) { + if (!entryName.endsWith(".conf") && !entryName.endsWith(".yml") && !entryName.endsWith(".yaml")) { loggerMaker.infoAndAddToDb( String.format("%s not a yaml file, skipping", entryName), LogDb.DASHBOARD); @@ -3451,7 +3452,7 @@ public static void processComplianceInfosFromZip(byte[] zipFile) { if (complianceInfoInDb == null) { ComplianceInfo newComplianceInfo = new ComplianceInfo(fileSourceId, contentMap, Constants._AKTO, templateHashCode, ""); - loggerMaker.infoAndAddToDb("Inserting compliance content: " + entryName, LogDb.DASHBOARD); + loggerMaker.infoAndAddToDb("Inserting compliance content: " + entryName + " c=" + templateContent + " ci: " + newComplianceInfo, LogDb.DASHBOARD); ComplianceInfosDao.instance.insertOne(newComplianceInfo); } else if (complianceInfoInDb.getHash() == templateHashCode ) { @@ -3474,6 +3475,7 @@ public static void processComplianceInfosFromZip(byte[] zipFile) { cacheLoggerMaker.errorAndAddToDb(ex, String.format("Error while processing Test template files zip. Error %s", ex.getMessage()), LogDb.DASHBOARD); + ex.printStackTrace(); } } else { loggerMaker.infoAndAddToDb("Received null zip file"); diff --git a/libs/dao/src/main/java/com/akto/DaoInit.java b/libs/dao/src/main/java/com/akto/DaoInit.java index 86413486e2..ce396d0791 100644 --- a/libs/dao/src/main/java/com/akto/DaoInit.java +++ b/libs/dao/src/main/java/com/akto/DaoInit.java @@ -266,6 +266,8 @@ public static CodecRegistry createCodecRegistry(){ ClassModel allApisGroupClassModel = ClassModel.builder(AllAPIsGroup.class).enableDiscriminator(true).build(); ClassModel remediationClassModel = ClassModel.builder(Remediation.class).enableDiscriminator(true).build(); + ClassModel complianceMappingModel = ClassModel.builder(ComplianceMapping.class).enableDiscriminator(true).build(); + ClassModel complianceInfoModel = ClassModel.builder(ComplianceInfo.class).enableDiscriminator(true).build(); ClassModel RuntimeMetricsClassModel = ClassModel.builder(RuntimeMetrics.class).enableDiscriminator(true).build(); ClassModel codeAnalysisApiModel = ClassModel.builder(CodeAnalysisApi.class).enableDiscriminator(true).build(); ClassModel codeAnalysisRepoModel = ClassModel.builder(CodeAnalysisRepo.class).enableDiscriminator(true).build(); @@ -301,7 +303,7 @@ public static CodecRegistry createCodecRegistry(){ nodeClassModel, connectionClassModel, edgeClassModel, replaceDetailClassModel, modifyHostDetailClassModel, fileUploadClassModel ,fileUploadLogClassModel, codeAnalysisCollectionClassModel, codeAnalysisApiLocationClassModel, codeAnalysisApiInfoClassModel, codeAnalysisApiInfoKeyClassModel, riskScoreTestingEndpointsClassModel, OrganizationFlagsClassModel, sensitiveDataEndpointsClassModel, unauthenticatedEndpointsClassModel, allApisGroupClassModel, - eventsExampleClassModel, remediationClassModel, RuntimeMetricsClassModel, codeAnalysisRepoModel, codeAnalysisApiModel, historicalDataClassModel, configSettingClassModel, configSettingsConditionTypeClassModel).automatic(true).build()); + eventsExampleClassModel, remediationClassModel, complianceInfoModel, complianceMappingModel, RuntimeMetricsClassModel, codeAnalysisRepoModel, codeAnalysisApiModel, historicalDataClassModel, configSettingClassModel, configSettingsConditionTypeClassModel).automatic(true).build()); final CodecRegistry customEnumCodecs = CodecRegistries.fromCodecs( new EnumCodec<>(Conditions.Operator.class), diff --git a/libs/dao/src/main/java/com/akto/dao/test_editor/TestConfigYamlParser.java b/libs/dao/src/main/java/com/akto/dao/test_editor/TestConfigYamlParser.java index 4c00de7d11..c38c4ccbb2 100644 --- a/libs/dao/src/main/java/com/akto/dao/test_editor/TestConfigYamlParser.java +++ b/libs/dao/src/main/java/com/akto/dao/test_editor/TestConfigYamlParser.java @@ -4,6 +4,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; + import com.akto.dao.test_editor.auth.Parser; import com.akto.dao.test_editor.filter.ConfigParser; import com.akto.dao.test_editor.info.InfoParser; @@ -34,7 +36,9 @@ public static Map> parseComplianceTemplate(String content) Map> ret = new HashMap<>(); for(String complianceName: config.keySet()) { - ret.put(complianceName.toUpperCase(), (List) config.get(complianceName)); + List listObj = (List) config.get(complianceName); + List listStr = listObj.stream().map(x -> x.toString()).collect(Collectors.toList()); + ret.put(complianceName.toUpperCase(), listStr); } return ret; diff --git a/libs/dao/src/main/java/com/akto/dto/AccountSettings.java b/libs/dao/src/main/java/com/akto/dto/AccountSettings.java index 137d3a942d..a904553d59 100644 --- a/libs/dao/src/main/java/com/akto/dto/AccountSettings.java +++ b/libs/dao/src/main/java/com/akto/dto/AccountSettings.java @@ -464,7 +464,7 @@ public int getComplianceInfosUpdatedTs() { return this.complianceInfosUpdatedTs; } - public void setcCmplianceInfosUpdatedTs(int complianceInfosUpdatedTs) { + public void setComplianceInfosUpdatedTs(int complianceInfosUpdatedTs) { this.complianceInfosUpdatedTs = complianceInfosUpdatedTs; } } diff --git a/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java b/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java index dd17dff649..5d8013b1b8 100644 --- a/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java +++ b/libs/dao/src/main/java/com/akto/dto/testing/ComplianceMapping.java @@ -7,12 +7,12 @@ public class ComplianceMapping { private Map> mapComplianceToListClauses; - String author; + private String author; public static final String SOURCE = "source"; - String source; + private String source; - int hash; + private int hash; public ComplianceMapping() { } From 169ddb4f09f562abc29bcebad8fa0d9067052dd8 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Sat, 25 Jan 2025 00:41:03 -0800 Subject: [PATCH 05/38] show compliance info on dashboard --- .../action/testing_issues/IssuesAction.java | 10 ++++++ .../pages/issues/IssuesPage/IssuesPage.jsx | 13 +++++++- .../src/apps/dashboard/pages/issues/api.js | 4 +-- .../apps/dashboard/pages/issues/transform.js | 6 +++- .../apps/dashboard/pages/testing/transform.js | 31 ++++++++++++++++++- .../testing/vulnerability_report/Category.jsx | 2 +- .../testing/vulnerability_report/Issue.jsx | 22 +++++++++++-- .../web/polaris_web/web/src/util/func.js | 3 ++ apps/dashboard/web/public/CIS CONTROLS.svg | 9 ++++++ apps/dashboard/web/public/CSA CCM.svg | 9 ++++++ ...TY MATURITY MODEL CERTIFICATION (CMMC).svg | 9 ++++++ apps/dashboard/web/public/FEDRAMP.svg | 9 ++++++ apps/dashboard/web/public/FISMA.svg | 9 ++++++ apps/dashboard/web/public/GDPR.svg | 9 ++++++ apps/dashboard/web/public/HIPAA.svg | 16 ++++++++++ apps/dashboard/web/public/ISO 27001.svg | 9 ++++++ apps/dashboard/web/public/NIST 800-171.svg | 9 ++++++ apps/dashboard/web/public/NIST 800-53.svg | 9 ++++++ apps/dashboard/web/public/PCI DSS.svg | 9 ++++++ apps/dashboard/web/public/SOC 2.svg | 9 ++++++ .../akto/dao/test_editor/YamlTemplateDao.java | 5 +++ 21 files changed, 203 insertions(+), 8 deletions(-) create mode 100644 apps/dashboard/web/public/CIS CONTROLS.svg create mode 100644 apps/dashboard/web/public/CSA CCM.svg create mode 100644 apps/dashboard/web/public/CYBERSECURITY MATURITY MODEL CERTIFICATION (CMMC).svg create mode 100644 apps/dashboard/web/public/FEDRAMP.svg create mode 100644 apps/dashboard/web/public/FISMA.svg create mode 100644 apps/dashboard/web/public/GDPR.svg create mode 100644 apps/dashboard/web/public/HIPAA.svg create mode 100644 apps/dashboard/web/public/ISO 27001.svg create mode 100644 apps/dashboard/web/public/NIST 800-171.svg create mode 100644 apps/dashboard/web/public/NIST 800-53.svg create mode 100644 apps/dashboard/web/public/PCI DSS.svg create mode 100644 apps/dashboard/web/public/SOC 2.svg diff --git a/apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java b/apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java index 91f7a1102b..ebd6ff5faf 100644 --- a/apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java @@ -75,6 +75,7 @@ public class IssuesAction extends UserAction { private List filterStatus; private List filterCollectionsId; private List filterSeverity; + private List filterCompliance; private List filterSubCategory; private List similarlyAffectedIssues; private boolean activeCollections; @@ -483,6 +484,7 @@ public static BasicDBObject createSubcategoriesInfoObj(TestConfig testConfig) { infoObj.put("issueDetails", info.getDetails()); infoObj.put("issueImpact", info.getImpact()); infoObj.put("issueTags", info.getTags()); + infoObj.put("compliance", info.getCompliance()); infoObj.put("testName", info.getName()); infoObj.put("references", info.getReferences()); infoObj.put("cwe", info.getCwe()); @@ -1052,4 +1054,12 @@ public List getRemovedRunResultsIssuesList() { public void setActiveCollections(boolean activeCollections) { this.activeCollections = activeCollections; } + + public List getFilterCompliance() { + return filterCompliance; + } + + public void setFilterCompliance(List filterCompliance) { + this.filterCompliance = filterCompliance; + } } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx index 4621139d96..4a121320eb 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx @@ -125,6 +125,12 @@ function IssuesPage() { text: "Domains", value: "domains" }, + { + title: "Compliance", + text: "Compliance", + value: "compliance", + sortActive: true + }, { title: "Discovered", text: "Discovered", @@ -353,6 +359,8 @@ function IssuesPage() { case "issueStatus": case "severity": return func.convertToDisambiguateLabel(value, func.toSentenceCase, 2) + case "compliance": + return func.convertToDisambiguateLabel(value, func.toUpperCase(), 2) case "issueCategory": return func.convertToDisambiguateLabelObj(value, null, 3) case "collectionIds": @@ -408,6 +416,7 @@ function IssuesPage() { setTableLoading(true) let filterStatus = [selectedTab.toUpperCase()] let filterSeverity = filters.severity + let filterCompliance = filters.compliance const activeCollections = (filters?.activeCollections !== undefined && filters?.activeCollections.length > 0) ? filters?.activeCollections[0] : initialValForResponseFilter; const apiCollectionId = filters.apiCollectionId || [] let filterCollectionsId = apiCollectionId.concat(filters.collectionIds) @@ -422,6 +431,7 @@ function IssuesPage() { 'filterStatus': filterStatus, 'filterCollectionsId': collectionIdsArray, 'filterSeverity': filterSeverity, + 'filterCompliance': filterCompliance, filterSubCategory: filterSubCategory, startEpoch: [startTimestamp.toString()], endTimeStamp: [endTimestamp.toString()], @@ -434,7 +444,7 @@ function IssuesPage() { let issueItem = [] - await api.fetchIssues(skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, sortKey, sortOrder, startTimestamp, endTimestamp, activeCollections).then((issuesDataRes) => { + await api.fetchIssues(skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, sortKey, sortOrder, startTimestamp, endTimestamp, activeCollections, filterCompliance).then((issuesDataRes) => { const uniqueIssuesMap = new Map() issuesDataRes.issues.forEach(item => { const key = `${item?.id?.testSubCategory}|${item?.severity}|${item?.unread.toString()}` @@ -442,6 +452,7 @@ function IssuesPage() { uniqueIssuesMap.set(key, { id: item?.id, severity: func.toSentenceCase(item?.severity), + compliance: Object.keys(subCategoryMap[item?.id?.testSubCategory].compliance?.mapComplianceToListClauses || {}), severityType: item?.severity, issueName: item?.id?.testSubCategory, category: item?.id?.testSubCategory, diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js index 018687fc09..9f7b8782d5 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/api.js @@ -1,11 +1,11 @@ import request from "../../../../util/request" export default { - fetchIssues(skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, sortKey, sortOrder, startEpoch, endTimeStamp, activeCollections) { + fetchIssues(skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, sortKey, sortOrder, startEpoch, endTimeStamp, activeCollections, filterCompliance) { return request({ url: 'api/fetchAllIssues', method: 'post', - data: {skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, sortKey, sortOrder, startEpoch, endTimeStamp, activeCollections} + data: {skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, sortKey, sortOrder, startEpoch, endTimeStamp, activeCollections, filterCompliance} }) }, fetchVulnerableTestingRunResultsFromIssues(filters, issuesIds , skip) { diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/transform.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/transform.js index 4bda8d38fb..09805e09b4 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/transform.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/transform.js @@ -1,6 +1,6 @@ import func from "@/util/func" import ShowListInBadge from "../../components/shared/ShowListInBadge" -import { Badge, Box, HorizontalStack, Link, Tag, Text } from "@shopify/polaris" +import { Badge, Box, HorizontalStack, Link, Tag, Text, Avatar } from "@shopify/polaris" import api from "./api" import testingTransform from "../testing/transform.js" import { history } from "@/util/history"; @@ -53,6 +53,9 @@ const transform = { const processedData = await Promise.all( await Promise.all(rawData.map(async (issue, idx) => { const key = `${issue.id.testSubCategory}|${issue.severity}|${issue.testRunIssueStatus}|${idx}` + let totalCompliance = issue.compliance.length + let maxShowCompliance = 2 + let badge = totalCompliance > maxShowCompliance ? +{totalCompliance - maxShowCompliance} : null return { key: key, id: issue.urls.map((x) => x.id), @@ -62,6 +65,7 @@ const transform = { issueName: subCategoryMap[issue.issueName]?.testName, category: subCategoryMap[issue.issueName]?.superCategory?.shortName, numberOfEndpoints: issue.numberOfEndpoints, + compliance: {issue.compliance.slice(0, maxShowCompliance).map(x => )}{badge}, creationTime: func.prettifyEpoch(issue.creationTime), issueStatus: (
diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/transform.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/transform.js index 4d7fd2925b..acc02a4515 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/transform.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/transform.js @@ -9,6 +9,7 @@ import {ResourcesMajor, CalendarMinor, ReplayMinor, PlayMinor, + LockMajor } from '@shopify/polaris-icons'; import React from 'react' import { Text,HorizontalStack, Badge, Link, List, Box, Icon, Avatar, Tag, Tooltip} from '@shopify/polaris'; @@ -171,6 +172,7 @@ function getCveLink(item) { } const transform = { + tagList: (list, linkType) => { let ret = list?.map((tag, index) => { @@ -475,6 +477,27 @@ const transform = { ) break; + case "Compliance": + if (category?.compliance?.mapComplianceToListClauses && Object.keys(category?.compliance?.mapComplianceToListClauses).length > 0) { + sectionLocal.content = ( + + { + Object.keys(category?.compliance?.mapComplianceToListClauses).map((compliance, index) => { + return ( + + + + {compliance} + + + ) + }) + } + + ) + } + break; + case "References": if (category?.references == null || category?.references == undefined || category?.references.length == 0) { return; @@ -674,6 +697,12 @@ getInfoSectionsHeaders(){ content: "", tooltipContent: 'Category info about the test.' }, + { + icon: LockMajor, + title: "Compliance", + content: "", + tooltipContent: "Compliances for the above test" + }, { icon: CreditCardSecureMajor, title: "CWE", @@ -696,7 +725,7 @@ getInfoSectionsHeaders(){ icon: ResourcesMajor, title: "References", content: "", - tooltipContent: "References for the above test." + tooltipContent: "References for the above test" }, { icon: ResourcesMajor, diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/Category.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/Category.jsx index 9fd81328f9..ac108f173c 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/Category.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/Category.jsx @@ -32,7 +32,7 @@ function Category({ index, issue, subCategoryMap }) { { issue.vulnerableTestingRunResults.map((vulnerableTestingRunResult, index) => { - return ( ) + return ( ) }) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/Issue.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/Issue.jsx index 24c5a63aac..dc180d1e52 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/Issue.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/vulnerability_report/Issue.jsx @@ -1,10 +1,11 @@ import React, { useEffect, useState } from 'react' import GithubSimpleTable from '../../../components/tables/GithubSimpleTable' -import { Badge, Box, HorizontalStack, Link, List, Text, VerticalStack } from '@shopify/polaris' +import { Badge, Tag, Avatar, Box, HorizontalStack, Link, List, Text, VerticalStack } from '@shopify/polaris' import GetPrettifyEndpoint from '../../observe/GetPrettifyEndpoint' import HttpRequestResponseViewer from './HttpRequestResponseViewer' +import func from '@/util/func' -const Issue = ({ vulnerableApi, references, cwes }) => { +const Issue = ({ vulnerableApi, references, cwes, compliance }) => { const [vulnerableApisState, setVulnerableApisState] = useState([]) const [vulnerableResultSampleData, setVulnerableResultSampleData] = useState({}) @@ -153,6 +154,23 @@ const Issue = ({ vulnerableApi, references, cwes }) => { + + Compliance + + { + Object.keys(compliance?.mapComplianceToListClauses).map((compliance, index) => { + return ( + + + + {compliance} + + + ) + }) + } + + CWE diff --git a/apps/dashboard/web/polaris_web/web/src/util/func.js b/apps/dashboard/web/polaris_web/web/src/util/func.js index cbec2e4a84..abc6c91213 100644 --- a/apps/dashboard/web/polaris_web/web/src/util/func.js +++ b/apps/dashboard/web/polaris_web/web/src/util/func.js @@ -1272,6 +1272,9 @@ getDeprecatedEndpoints(apiInfoList, unusedEndpoints, apiCollectionId) { return combinedArr }, + getComplianceIcon: (complianceName) => { + return "/public/"+complianceName.toUpperCase()+".svg"; +}, convertToDisambiguateLabel(value, convertFunc, maxAllowed){ if (value.length > maxAllowed) { diff --git a/apps/dashboard/web/public/CIS CONTROLS.svg b/apps/dashboard/web/public/CIS CONTROLS.svg new file mode 100644 index 0000000000..73a618d89b --- /dev/null +++ b/apps/dashboard/web/public/CIS CONTROLS.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/CSA CCM.svg b/apps/dashboard/web/public/CSA CCM.svg new file mode 100644 index 0000000000..2dbc82c8c5 --- /dev/null +++ b/apps/dashboard/web/public/CSA CCM.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/CYBERSECURITY MATURITY MODEL CERTIFICATION (CMMC).svg b/apps/dashboard/web/public/CYBERSECURITY MATURITY MODEL CERTIFICATION (CMMC).svg new file mode 100644 index 0000000000..c4b2029b06 --- /dev/null +++ b/apps/dashboard/web/public/CYBERSECURITY MATURITY MODEL CERTIFICATION (CMMC).svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/FEDRAMP.svg b/apps/dashboard/web/public/FEDRAMP.svg new file mode 100644 index 0000000000..e9c04636e8 --- /dev/null +++ b/apps/dashboard/web/public/FEDRAMP.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/FISMA.svg b/apps/dashboard/web/public/FISMA.svg new file mode 100644 index 0000000000..99a665f3e6 --- /dev/null +++ b/apps/dashboard/web/public/FISMA.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/GDPR.svg b/apps/dashboard/web/public/GDPR.svg new file mode 100644 index 0000000000..a8f31dd4db --- /dev/null +++ b/apps/dashboard/web/public/GDPR.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/HIPAA.svg b/apps/dashboard/web/public/HIPAA.svg new file mode 100644 index 0000000000..3f96f59e85 --- /dev/null +++ b/apps/dashboard/web/public/HIPAA.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/apps/dashboard/web/public/ISO 27001.svg b/apps/dashboard/web/public/ISO 27001.svg new file mode 100644 index 0000000000..ca2c76d754 --- /dev/null +++ b/apps/dashboard/web/public/ISO 27001.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/NIST 800-171.svg b/apps/dashboard/web/public/NIST 800-171.svg new file mode 100644 index 0000000000..287ea2e080 --- /dev/null +++ b/apps/dashboard/web/public/NIST 800-171.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/NIST 800-53.svg b/apps/dashboard/web/public/NIST 800-53.svg new file mode 100644 index 0000000000..35615203e6 --- /dev/null +++ b/apps/dashboard/web/public/NIST 800-53.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/PCI DSS.svg b/apps/dashboard/web/public/PCI DSS.svg new file mode 100644 index 0000000000..178e97cc1f --- /dev/null +++ b/apps/dashboard/web/public/PCI DSS.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/dashboard/web/public/SOC 2.svg b/apps/dashboard/web/public/SOC 2.svg new file mode 100644 index 0000000000..953b8c91ff --- /dev/null +++ b/apps/dashboard/web/public/SOC 2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/libs/dao/src/main/java/com/akto/dao/test_editor/YamlTemplateDao.java b/libs/dao/src/main/java/com/akto/dao/test_editor/YamlTemplateDao.java index 31f8335e17..f5e6627121 100644 --- a/libs/dao/src/main/java/com/akto/dao/test_editor/YamlTemplateDao.java +++ b/libs/dao/src/main/java/com/akto/dao/test_editor/YamlTemplateDao.java @@ -52,6 +52,11 @@ public Map fetchTestConfigMap(boolean includeYamlContent, bo testConfig.setInactive(yamlTemplate.getInactive()); testConfig.setAuthor(yamlTemplate.getAuthor()); testConfigMap.put(testConfig.getId(), testConfig); + + if (testConfig.getInfo() != null && yamlTemplate.getInfo() != null && yamlTemplate.getInfo().getCompliance() != null) { + testConfig.getInfo().setCompliance(yamlTemplate.getInfo().getCompliance()); + } + } catch (Exception e) { e.printStackTrace(); } From b0e71fd0a00b96c7635086dbdb3162b311f5f8e3 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Sun, 2 Feb 2025 06:12:48 -0800 Subject: [PATCH 06/38] Add compliance report UI --- .../components/layouts/leftnav/LeftNav.js | 43 +- .../issues/IssuesPage/CompliancePage.jsx | 625 ++++++++++++++++++ .../IssuesPage/CriticalFindingsGraph.jsx | 22 +- .../web/polaris_web/web/src/apps/main/App.js | 14 + 4 files changed, 694 insertions(+), 10 deletions(-) create mode 100644 apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CompliancePage.jsx diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/leftnav/LeftNav.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/leftnav/LeftNav.js index d7dcadd158..0424ead757 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/leftnav/LeftNav.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/leftnav/LeftNav.js @@ -187,18 +187,49 @@ export default function LeftNav() { key: "5", }, { + url: "#", label: ( - - Issues + + Reports ), - icon: AnalyticsFilledMinor, + icon: ReportFilledMinor, onClick: () => { - handleSelect("dashboard_issues"); - navigate("/dashboard/issues"); + navigate("/dashboard/reports/issues"); + handleSelect("dashboard_reports_issues"); setActive("normal"); }, - selected: leftNavSelected === "dashboard_issues", + selected: leftNavSelected.includes("_reports"), + subNavigationItems: [ + { + label: "Issues", + onClick: () => { + navigate("/dashboard/reports/issues"); + handleSelect("dashboard_reports_issues"); + setActive("active"); + }, + selected: leftNavSelected === "dashboard_reports_issues", + }, + { + label: "Compliance", + onClick: () => { + navigate("/dashboard/reports/compliance"); + handleSelect("dashboard_reports_compliance"); + setActive("active"); + }, + selected: leftNavSelected === "dashboard_reports_compliance", + } + ], key: "6", }, window?.STIGG_FEATURE_WISE_ALLOWED?.THREAT_DETECTION?.isGranted diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CompliancePage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CompliancePage.jsx new file mode 100644 index 0000000000..c7af46d817 --- /dev/null +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CompliancePage.jsx @@ -0,0 +1,625 @@ +import PageWithMultipleCards from "../../../components/layouts/PageWithMultipleCards" +import GithubServerTable from "../../../components/tables/GithubServerTable" +import { useReducer, useState } from "react"; +import api from "../api" +import Store from "../../../store"; +import func from "@/util/func"; +import { MarkFulfilledMinor, ReportMinor, ExternalMinor } from '@shopify/polaris-icons'; +import PersistStore from "../../../../main/PersistStore"; +import { Button, Popover, Box, Avatar, Text, HorizontalGrid, HorizontalStack, IndexFiltersMode, VerticalStack } from "@shopify/polaris"; +import EmptyScreensLayout from "../../../components/banners/EmptyScreensLayout"; +import { ISSUES_PAGE_DOCS_URL } from "../../../../main/onboardingData"; +import {SelectCollectionComponent} from "../../testing/TestRunsPage/TestrunsBannerComponent" +import { useEffect } from "react"; +import TitleWithInfo from "@/apps/dashboard/components/shared/TitleWithInfo"; +import { useSearchParams } from "react-router-dom"; +import TestRunResultPage from "../../testing/TestRunResultPage/TestRunResultPage"; +import LocalStore from "../../../../main/LocalStorageStore"; +import { CellType } from "../../../components/tables/rows/GithubRow.js"; +import DateRangeFilter from "../../../components/layouts/DateRangeFilter.jsx"; +import { produce } from "immer"; +import "./style.css" +import transform from "../transform.js"; +import SummaryInfo from "./SummaryInfo.jsx"; +import useTable from "../../../components/tables/TableContext.js"; +import values from "@/util/values"; +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 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 }, + { label: 'Severity', value: 'severity desc', directionLabel: 'Lowest', sortKey: 'severity', columnIndex: 2 }, + { label: 'Number of endpoints', value: 'numberOfEndpoints asc', directionLabel: 'More', sortKey: 'numberOfEndpoints', columnIndex: 5 }, + { label: 'Number of endpoints', value: 'numberOfEndpoints desc', directionLabel: 'Less', sortKey: 'numberOfEndpoints', columnIndex: 5 }, + { label: 'Discovered time', value: 'creationTime asc', directionLabel: 'Newest', sortKey: 'creationTime', columnIndex: 7 }, + { label: 'Discovered time', value: 'creationTime desc', directionLabel: 'Oldest', sortKey: 'creationTime', columnIndex: 7 }, +]; + +const allCompliances = ["CIS Controls", "CMMC", "CSA CCM", "Cybersecurity Maturity Model Certification (CMMC)", "FISMA", "FedRAMP", "GDPR", "HIPAA", "ISO 27001", "NIST 800-171", "NIST 800-53", "PCI DSS", "SOC 2"]; + +let filtersOptions = [ + { + key: 'apiCollectionId', + label: 'Collection', + title: 'Collection', + choices: [], + }, + { + key: 'severity', + label: 'Severity', + title: 'Severity', + choices: [ + {label: 'Critical', value: 'CRITICAL'}, + { label: "High", value: "HIGH" }, + { label: "Medium", value: "MEDIUM" }, + { label: "Low", value: "LOW" } + ], + }, + { + key:"issueCategory", + label: "Issue category", + title:"Issue category", + choices:[] + }, + { + key: 'collectionIds', + label: 'API groups', + title: 'API groups', + choices: [], + }, + { + key: 'activeCollections', + label: 'Active collections', + title: 'Active collections', + choices: [ + { + label:"Active collections", + value:true + }, + { + label:"All collections", + value:false + }], + singleSelect:true + } +] + +const resourceName = { + singular: 'issue', + plural: 'issues', +}; + +function CompliancePage() { + const [headers, setHeaders] = useState([ + { + title: '', + type: CellType.COLLAPSIBLE + }, + { + title: "Severity", + text: "Severity", + value: "severity", + sortActive: true + }, + { + title: "Issue name", + text: "Issue name", + value: "issueName", + }, + { + title: "Category", + text: "Category", + value: "category" + }, + { + title: "Number of endpoints", + text: "Number of endpoints", + value: "numberOfEndpoints", + sortActive: true + }, + { + title: "Domains", + text: "Domains", + value: "domains" + }, + { + title: "Compliance", + text: "Compliance", + value: "compliance", + sortActive: true + }, + { + title: "Discovered", + text: "Discovered", + value: "creationTime", + sortActive: true + }, + { + value: 'collectionIds' + }, + ]) + + + function calcFilteredTestIds(complianceView) { + let ret = Object.entries(subCategoryMap).filter(([_, v]) => {return !!v.compliance?.mapComplianceToListClauses[complianceView]}).map(([k, _]) => k) + + return ret + } + + const subCategoryMap = LocalStore(state => state.subCategoryMap); + const [issuesFilters, setIssuesFilters] = useState({}) + const [key, setKey] = useState(false); + const apiCollectionMap = PersistStore(state => state.collectionsMap); + const [showEmptyScreen, setShowEmptyScreen] = useState(true) + const [selectedTab, setSelectedTab] = useState("open") + const [loading, setLoading] = useState(true) + const [selected, setSelected] = useState(0) + const [tableLoading, setTableLoading] = useState(false) + const [issuesDataCount, setIssuesDataCount] = useState([]) + const [jiraModalActive, setJiraModalActive] = useState(false) + const [selectedIssuesItems, setSelectedIssuesItems] = useState([]) + const [jiraProjectMaps,setJiraProjectMap] = useState({}) + const [issueType, setIssueType] = useState(''); + const [projId, setProjId] = useState('') + const [moreActions, setMoreActions] = useState(false); + const [complianceView, setComplianceView] = useState('SOC 2'); + const [filteredTestIds, setFilteredTestIds] = useState([]); + + + const [currDateRange, dispatchCurrDateRange] = useReducer(produce((draft, action) => func.dateRangeReducer(draft, action)), values.ranges[5]) + + const getTimeEpoch = (key) => { + return Math.floor(Date.parse(currDateRange.period[key]) / 1000) + } + + const startTimestamp = getTimeEpoch("since") + const endTimestamp = getTimeEpoch("until") + + const hostNameMap = PersistStore.getState().hostNameMap + + const setToastConfig = Store(state => state.setToastConfig) + const setToast = (isActive, isError, message) => { + setToastConfig({ + isActive: isActive, + isError: isError, + message: message + }) + } + + const handleSelectedTab = (selectedIndex) => { + setTableLoading(true) + setSelected(selectedIndex) + setTimeout(()=> { + setTableLoading(false) + }, 200) + } + + const definedTableTabs = ["Open", "Fixed", "Ignored"] + + const { tabsInfo, selectItems } = useTable() + const tableCountObj = func.getTabsCount(definedTableTabs, {}, issuesDataCount) + const tableTabs = func.getTableTabsContent(definedTableTabs, tableCountObj, setSelectedTab, selectedTab, tabsInfo) + + const resetResourcesSelected = () => { + TableStore.getState().setSelectedItems([]) + selectItems([]) + setKey(!key) + setSelectedIssuesItems([]) + } + + useEffect(() => { + const statusHeader = { + title: "Status", + text: "Status", + value: "issueStatus" + } + + if (selectedTab.toUpperCase() === 'OPEN') { + if (!headers.some(header => header.value === "issueStatus")) { + setHeaders(prevHeaders => { + const newHeaders = [...prevHeaders, statusHeader] + return newHeaders + }) + } + } else { + setHeaders(prevHeaders => prevHeaders.filter(header => header.value !== "issueStatus")) + } + resetResourcesSelected(); + }, [selectedTab]) + + useEffect(() => { + setKey(!key) + }, [startTimestamp, endTimestamp]) + + const [searchParams, setSearchParams] = useSearchParams(); + const resultId = searchParams.get("result") + + const filterParams = searchParams.get('filters') + let initialValForResponseFilter = true + if(filterParams && filterParams !== undefined &&filterParams.split('activeCollections').length > 1){ + let isRequestVal = filterParams.split("activeCollections__")[1].split('&')[0] + if(isRequestVal.length > 0){ + initialValForResponseFilter = (isRequestVal === 'true' || isRequestVal.includes('true')) + } + } + + const appliedFilters = [ + { + key: 'activeCollections', + value: [initialValForResponseFilter], + onRemove: () => {} + } + ] + + filtersOptions = func.getCollectionFilters(filtersOptions) + + const handleSaveJiraAction = () => { + setToast(true, false, "Please wait while we create your Jira ticket.") + setJiraModalActive(false) + api.bulkCreateJiraTickets(selectedIssuesItems, window.location.origin, projId, issueType).then((res) => { + if(res?.errorMessage) { + setToast(true, false, res?.errorMessage) + } else { + setToast(true, false, `${selectedIssuesItems.length} jira ticket${selectedIssuesItems.length === 1 ? "" : "s"} created.`) + } + resetResourcesSelected() + }) + } + + let promotedBulkActions = (selectedResources) => { + let items + if(selectedResources.length > 0 && typeof selectedResources[0][0] === 'string') { + const flatSelectedResources = selectedResources.flat() + items = flatSelectedResources.map((item) => JSON.parse(item)) + } else { + items = selectedResources.map((item) => JSON.parse(item)) + } + + function ignoreAction(ignoreReason){ + api.bulkUpdateIssueStatus(items, "IGNORED", ignoreReason, {} ).then((res) => { + setToast(true, false, `Issue${items.length==1 ? "" : "s"} ignored`) + resetResourcesSelected() + }) + } + + function reopenAction(){ + api.bulkUpdateIssueStatus(items, "OPEN", "" ).then((res) => { + setToast(true, false, `Issue${items.length==1 ? "" : "s"} re-opened`) + resetResourcesSelected() + }) + } + + function createJiraTicketBulk () { + setSelectedIssuesItems(items) + settingFunctions.fetchJiraIntegration().then((jirIntegration) => { + if(jirIntegration.projectIdsMap !== null && Object.keys(jirIntegration.projectIdsMap).length > 0){ + setJiraProjectMap(jirIntegration.projectIdsMap) + if(Object.keys(jirIntegration.projectIdsMap).length > 0){ + setProjId(Object.keys(jirIntegration.projectIdsMap)[0]) + } + }else{ + setProjId(jirIntegration.projId) + setIssueType(jirIntegration.issueType) + } + setJiraModalActive(true) + }) + } + + let issues = [{ + content: 'False positive', + onAction: () => { ignoreAction("False positive") } + }, + { + content: 'Acceptable risk', + onAction: () => { ignoreAction("Acceptable risk") } + }, + { + content: 'No time to fix', + onAction: () => { ignoreAction("No time to fix") } + }, + { + content: 'Export selected Issues', + onAction: () => { openVulnerabilityReport(items) } + }, + { + content: 'Create jira ticket', + onAction: () => { createJiraTicketBulk() } + }] + + let reopen = [{ + content: 'Reopen', + onAction: () => { reopenAction() } + }] + + let ret = []; + let status = selectedTab.toUpperCase() + + switch (status) { + case "OPEN": ret = [].concat(issues); break; + case "IGNORED": + ret = ret.concat(reopen); + break; + case "FIXED": + default: + ret = [] + } + + return ret; + } + + let store = {} + let result = [] + Object.values(subCategoryMap).forEach((x) => { + let superCategory = x.superCategory + if (!store[superCategory.name]) { + result.push({ "label": superCategory.displayName, "value": superCategory.name }) + store[superCategory.name] = [] + } + store[superCategory.name].push(x._name); + }) + filtersOptions[2].choices = [].concat(result) + let categoryToSubCategories = store + + function disambiguateLabel(key, value) { + switch (key) { + case "startTimestamp": + return func.convertToDisambiguateLabel(value, func.prettifyEpoch, 2) + case "issueStatus": + case "severity": + return func.convertToDisambiguateLabel(value, func.toSentenceCase, 2) + case "compliance": + return func.convertToDisambiguateLabel(value, func.toUpperCase(), 2) + case "issueCategory": + return func.convertToDisambiguateLabelObj(value, null, 3) + case "collectionIds": + case "apiCollectionId": + return func.convertToDisambiguateLabelObj(value, apiCollectionMap, 2) + case "activeCollections": + if(value[0]){ + return "Active collections only" + }else{ + return "All collections" + } + default: + return value; + } + } + + 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 = [ + { + title: "Triage", + icon: MarkFulfilledMinor, + description: "Prioritize, assign them to team members and manage API issues effectively.", + }, + { + title: "Download vulnerability report", + icon: ReportMinor, + description: "Export and share detailed report of vulnerabilities in your APIs.", + }, + { + title: "Send them to GitHub", + icon: ExternalMinor, + description: "Integrate Akto with GitHub to send all issues to your developers on GitHub." + } + ] + + useEffect(() => { + if (subCategoryMap && Object.keys(subCategoryMap).length > 0 && apiCollectionMap && Object.keys(apiCollectionMap).length > 0) { + setShowEmptyScreen(false) + setLoading(false) + } + }, [subCategoryMap, apiCollectionMap]) + + const onSelectCompliance = (compliance) => { + setComplianceView(compliance) + resetResourcesSelected() + setMoreActions(false) + } + + const fetchTableData = async (sortKey, sortOrder, skip, limit, filters, filterOperators, queryValue) => { + setTableLoading(true) + let filterStatus = [selectedTab.toUpperCase()] + let filterSeverity = filters.severity + let filterCompliance = filters.compliance + const activeCollections = (filters?.activeCollections !== undefined && filters?.activeCollections.length > 0) ? filters?.activeCollections[0] : initialValForResponseFilter; + const apiCollectionId = filters.apiCollectionId || [] + let filterCollectionsId = apiCollectionId.concat(filters.collectionIds) + let filterSubCategory = calcFilteredTestIds(complianceView) + + const collectionIdsArray = filterCollectionsId.map((x) => {return x.toString()}) + + let obj = { + 'filterStatus': filterStatus, + 'filterCollectionsId': collectionIdsArray, + 'filterSeverity': filterSeverity, + 'filterCompliance': filterCompliance, + filterSubCategory: filterSubCategory, + startEpoch: [startTimestamp.toString()], + endTimeStamp: [endTimestamp.toString()], + activeCollections: [activeCollections.toString()] + } + setIssuesFilters(obj) + + let ret = [] + let total = 0 + + let issueItem = [] + + await api.fetchIssues(skip, limit, filterStatus, filterCollectionsId, filterSeverity, filterSubCategory, sortKey, sortOrder, startTimestamp, endTimestamp, activeCollections, filterCompliance).then((issuesDataRes) => { + const uniqueIssuesMap = new Map() + issuesDataRes.issues.forEach(item => { + const key = `${item?.id?.testSubCategory}|${item?.severity}|${item?.unread.toString()}` + if (!uniqueIssuesMap.has(key)) { + uniqueIssuesMap.set(key, { + id: item?.id, + severity: func.toSentenceCase(item?.severity), + compliance: Object.keys(subCategoryMap[item?.id?.testSubCategory].compliance?.mapComplianceToListClauses || {}), + severityType: item?.severity, + issueName: item?.id?.testSubCategory, + category: item?.id?.testSubCategory, + numberOfEndpoints: 1, + creationTime: item?.creationTime, + issueStatus: item?.unread.toString(), + testRunName: "Test Run", + domains: [(hostNameMap[item?.id?.apiInfoKey?.apiCollectionId] !== null ? hostNameMap[item?.id?.apiInfoKey?.apiCollectionId] : apiCollectionMap[item?.id?.apiInfoKey?.apiCollectionId])], + urls: [{ + method: item?.id?.apiInfoKey?.method, + url: item?.id?.apiInfoKey?.url, + id: JSON.stringify(item?.id), + }], + urlsKey: [''] + }) + } else { + const existingIssue = uniqueIssuesMap.get(key) + const domain = (hostNameMap[item?.id?.apiInfoKey?.apiCollectionId] !== null ? hostNameMap[item?.id?.apiInfoKey?.apiCollectionId] : apiCollectionMap[item?.id?.apiInfoKey?.apiCollectionId]) + if (!existingIssue.domains.includes(domain)) { + existingIssue.domains.push(domain) + } + existingIssue.urls.push({ + method: item?.id?.apiInfoKey?.method, + url: item?.id?.apiInfoKey?.url, + id: JSON.stringify(item?.id), + }) + existingIssue.numberOfEndpoints += 1 + } + }) + issueItem = Array.from(uniqueIssuesMap.values()) + + total = selectedTab.toUpperCase() === 'OPEN' ? issuesDataRes.openIssuesCount : selectedTab.toUpperCase() === 'FIXED' ? issuesDataRes.fixedIssuesCount : issuesDataRes.ignoredIssuesCount + setIssuesDataCount([issuesDataRes.openIssuesCount, issuesDataRes.fixedIssuesCount, issuesDataRes.ignoredIssuesCount]) + }).catch((e) => { + func.setToast(true, true, e.message) + setTableLoading(false) + setLoading(false) + }) + + const sortedIssueItem = transform.sortIssues(issueItem, sortKey, sortOrder) + + const issueTableData = await transform.convertToIssueTableData(sortedIssueItem, subCategoryMap) + ret.push(...issueTableData) + setTableLoading(false) + setLoading(false) + + return {value: ret, total: total} + } + + const components = ( + <> + + + + + + { return "warning" }} + selected={selected} + onRowClick={() => {}} + onSelect={handleSelectedTab} + getFilteredItems={()=>{}} + mode={IndexFiltersMode.Default} + headings={headers} + useNewRow={true} + condensedHeight={true} + tableTabs={tableTabs} + selectable={true} + promotedBulkActions={promotedBulkActions} + loading={loading || tableLoading} + hideQueryField={true} + isMultipleItemsSelected={true} + /> + + ) + + return ( + <> + + + setMoreActions(!moreActions)} disclosure removeUnderline> + + + + {complianceView} + + + + )} + autofocusTarget="first-node" + onClose={() => { setMoreActions(false) }} + preferredAlignment="right" + > + + + + {allCompliances.map(compliance => {return } )} + + + + + + + } + isFirstPage={true} + components = {loading ? [] : [ + showEmptyScreen ? + } + /> + + + : components + ]} + primaryAction={} + secondaryActions={ dispatchCurrDateRange({ type: "update", period: dateObj.period, title: dateObj.title, alias: dateObj.alias })} />} + /> + {(resultId !== null && resultId.length > 0) ? : null} + + ) +} + +export default CompliancePage \ No newline at end of file diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx index 1121e85e3e..ec79cf9f80 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx @@ -6,8 +6,11 @@ import testingApi from "../../testing/api.js" import testingFunc from "../../testing/transform.js" import func from "@/util/func"; import BarGraph from '../../../components/charts/BarGraph.jsx'; +import LocalStore from "../../../../main/LocalStorageStore"; + +const CriticalFindingsGraph = ({ linkText, linkUrl, complianceMode }) => { + const subCategoryMap = LocalStore(state => state.subCategoryMap); -const CriticalFindingsGraph = ({ linkText, linkUrl }) => { const [criticalFindingsData, setCriticalFindingsData] = useState([]) const [showTestingComponents, setShowTestingComponents] = useState(false) @@ -26,8 +29,19 @@ const CriticalFindingsGraph = ({ linkText, linkUrl }) => { const fetchGraphData = async () => { setShowTestingComponents(false) const subcategoryDataResp = await testingApi.getSummaryInfo(0, func.timeNow()) - const tempResult = testingFunc.convertSubIntoSubcategory(subcategoryDataResp) - convertSubCategoryInfo(tempResult.subCategoryMap) + let tempResultSubCategoryMap = {} + if (complianceMode) { + Object.entries(subcategoryDataResp).forEach(([testId, count]) => { + let clauses = subCategoryMap[testId]?.compliance.mapComplianceToListClauses[complianceMode] + clauses.forEach(clause => { + tempResultSubCategoryMap[clause] = tempResultSubCategoryMap[clause] || {text: 0, color: 'red', key: clause} + tempResultSubCategoryMap[clause].text += count + }); + }) + } else { + tempResultSubCategoryMap = testingFunc.convertSubIntoSubcategory(subcategoryDataResp).subCategoryMap + } + convertSubCategoryInfo(tempResultSubCategoryMap) setShowTestingComponents(true) } @@ -58,7 +72,7 @@ const CriticalFindingsGraph = ({ linkText, linkUrl }) => { barWidth={30} /> } - title="Vulnerabilities findings" + title={complianceMode ? (complianceMode + " clauses") : "Vulnerabilities findings"} titleToolTip="Overview of the most critical security issues detected, including the number of issues and APIs affected for each type of vulnerability." linkText={linkText} linkUrl={linkUrl} diff --git a/apps/dashboard/web/polaris_web/web/src/apps/main/App.js b/apps/dashboard/web/polaris_web/web/src/apps/main/App.js index c533d47eb4..b7eba74cc9 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/main/App.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/main/App.js @@ -30,6 +30,7 @@ import Metrics from "../dashboard/pages/settings/metrics/Metrics"; import TestEditor from "../dashboard/pages/test_editor/TestEditor"; import DataTypes from "../dashboard/pages/observe/data_types/DataTypes"; import IssuesPage from "../dashboard/pages/issues/IssuesPage/IssuesPage"; +import CompliancePage from "../dashboard/pages/issues/IssuesPage/CompliancePage"; import QuickStart from "../dashboard/pages/quick_start/QuickStart"; import Webhooks from "../dashboard/pages/settings/integrations/webhooks/Webhooks"; import Webhook from "../dashboard/pages/settings/integrations/webhooks/Webhook"; @@ -175,6 +176,19 @@ const router = createBrowserRouter([ path: "issues", element: }, + { + path: "reports", + children: [ + { + path: "issues", + element: + }, + { + path: "compliance", + element: + } + ] + }, { path: "protection", children: [ From a488d170a9696d634436743e3840d79a4415a138 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Sun, 2 Feb 2025 17:48:22 -0800 Subject: [PATCH 07/38] add compliance details if source changes --- .../src/main/java/com/akto/listener/InitializerListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java index 8a5795f703..e61d238820 100644 --- a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java +++ b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java @@ -3360,7 +3360,7 @@ public static void addComplianceFromCommonToAccount(Map Bson filters = Filters.and( Filters.eq(Constants.ID, compId), - Filters.exists("info.compliance", false) + Filters.or(Filters.exists("info.compliance", false), Filters.ne("info.compliance.source", fileSourceId)) ); ComplianceMapping complianceMapping = ComplianceMapping.createFromInfo(complianceInfoInCommon); From 2f4d4a296c14aded5ad93d3eb44a7aca145bd254 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Sun, 2 Feb 2025 22:10:31 -0800 Subject: [PATCH 08/38] draw categories from input compliance mode and fix colors --- .../pages/issues/IssuesPage/CriticalFindingsGraph.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx index ec79cf9f80..0cbfd945b9 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx @@ -34,7 +34,7 @@ const CriticalFindingsGraph = ({ linkText, linkUrl, complianceMode }) => { Object.entries(subcategoryDataResp).forEach(([testId, count]) => { let clauses = subCategoryMap[testId]?.compliance.mapComplianceToListClauses[complianceMode] clauses.forEach(clause => { - tempResultSubCategoryMap[clause] = tempResultSubCategoryMap[clause] || {text: 0, color: 'red', key: clause} + tempResultSubCategoryMap[clause] = tempResultSubCategoryMap[clause] || {text: 0, key: clause} tempResultSubCategoryMap[clause].text += count }); }) @@ -47,7 +47,7 @@ const CriticalFindingsGraph = ({ linkText, linkUrl, complianceMode }) => { useEffect(() => { fetchGraphData() - }, []) + }, [complianceMode]) const defaultChartOptions = { "legend": { From 22620e85bcf2e7b7a04c6fb59c982a7a9e3b8b44 Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Wed, 5 Feb 2025 16:15:22 +0530 Subject: [PATCH 09/38] Sending slack alert only for non-demo collections --- .../src/main/java/com/akto/testing/Main.java | 9 +-- .../testing/kafka_utils/ConsumerUtil.java | 4 +- .../java/com/akto/runtime/RuntimeUtil.java | 7 ++ .../src/main/java/com/akto/testing/Utils.java | 66 +++++++++++++++++-- 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/apps/testing/src/main/java/com/akto/testing/Main.java b/apps/testing/src/main/java/com/akto/testing/Main.java index 0df5da1213..c524289f89 100644 --- a/apps/testing/src/main/java/com/akto/testing/Main.java +++ b/apps/testing/src/main/java/com/akto/testing/Main.java @@ -63,6 +63,7 @@ import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; +import static com.akto.testing.Utils.isTestingRunForDemoCollection; import static com.akto.testing.Utils.readJsonContentFromFile; import java.util.*; @@ -402,10 +403,10 @@ public static void main(String[] args) throws InterruptedException { // mark the test completed here testCompletion.markTestAsCompleteAndRunFunctions(testingRun, summaryId); - if (StringUtils.hasLength(AKTO_SLACK_WEBHOOK) ) { + if (StringUtils.hasLength(AKTO_SLACK_WEBHOOK) && !isTestingRunForDemoCollection(testingRun)) { try { CustomTextAlert customTextAlert = new CustomTextAlert("Test completed for accountId=" + accountId + " testingRun=" + testingRun.getHexId() + " summaryId=" + summaryId.toHexString() + " : @Arjun you are up now. Make your time worth it. :)"); - SLACK_INSTANCE.send(AKTO_SLACK_WEBHOOK, customTextAlert.toJson()); + SLACK_INSTANCE.send(AKTO_SLACK_WEBHOOK, customTextAlert.toJson()); } catch (Exception e) { logger.error("Error sending slack alert for completion of test", e); } @@ -676,7 +677,7 @@ public void run() { RequiredConfigs.initiate(); int maxRunTime = testingRun.getTestRunTime() <= 0 ? 30*60 : testingRun.getTestRunTime(); - if (StringUtils.hasLength(AKTO_SLACK_WEBHOOK) ) { + if (StringUtils.hasLength(AKTO_SLACK_WEBHOOK) && !isTestingRunForDemoCollection(testingRun)) { CustomTextAlert customTextAlert = new CustomTextAlert("Test started: accountId=" + Context.accountId.get() + " testingRun=" + testingRun.getHexId() + " summaryId=" + summaryId.toHexString() + " time=" + maxRunTime); SLACK_INSTANCE.send(AKTO_SLACK_WEBHOOK, customTextAlert.toJson()); } @@ -697,7 +698,7 @@ public void run() { loggerMaker.errorAndAddToDb(e, "Error in init " + e); } testCompletion.markTestAsCompleteAndRunFunctions(testingRun, summaryId); - if (StringUtils.hasLength(AKTO_SLACK_WEBHOOK) ) { + if (StringUtils.hasLength(AKTO_SLACK_WEBHOOK) && !isTestingRunForDemoCollection(testingRun)) { try { CustomTextAlert customTextAlert = new CustomTextAlert("Test completed for accountId=" + accountId + " testingRun=" + testingRun.getHexId() + " summaryId=" + summaryId.toHexString() + " : @Arjun you are up now. Make your time worth it. :)"); SLACK_INSTANCE.send(AKTO_SLACK_WEBHOOK, customTextAlert.toJson()); diff --git a/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java b/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java index cadaca26b6..40dcd92324 100644 --- a/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java +++ b/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java @@ -121,9 +121,9 @@ public void init(int maxRunTimeInSeconds) { parallelConsumer = ParallelStreamProcessor.createEosStreamProcessor(options); parallelConsumer.subscribe(Arrays.asList(topicName)); - if (StringUtils.hasLength(Main.AKTO_SLACK_WEBHOOK) ) { + if (StringUtils.hasLength(Main.AKTO_SLACK_WEBHOOK)) { try { - CustomTextAlert customTextAlert = new CustomTextAlert("Tests being picked for execution" + currentTestInfo.getInt("accountId") + " summaryId=" + summaryIdForTest); + CustomTextAlert customTextAlert = new CustomTextAlert("Tests being picked for execution through consumer for account: " + currentTestInfo.getInt("accountId") + " summaryId=" + summaryIdForTest); Main.SLACK_INSTANCE.send(Main.AKTO_SLACK_WEBHOOK, customTextAlert.toJson()); } catch (Exception e) { logger.error("Error sending slack alert for completion of test", e); diff --git a/libs/utils/src/main/java/com/akto/runtime/RuntimeUtil.java b/libs/utils/src/main/java/com/akto/runtime/RuntimeUtil.java index dc312e1723..4e4d9598ab 100644 --- a/libs/utils/src/main/java/com/akto/runtime/RuntimeUtil.java +++ b/libs/utils/src/main/java/com/akto/runtime/RuntimeUtil.java @@ -16,6 +16,13 @@ import java.util.*; public class RuntimeUtil { + + public static final String JUICE_SHOP_DEMO_COLLECTION_NAME = "juice_shop_demo"; + public static final String VULNERABLE_API_COLLECTION_NAME = "vulnerable_apis"; + public static final String LLM_API_COLLECTION_NAME = "llm_apis"; + public static final int VULNERABLE_API_COLLECTION_ID = 1111111111; + public static final int LLM_API_COLLECTION_ID = 1222222222; + private static final LoggerMaker loggerMaker = new LoggerMaker(RuntimeUtil.class); public static boolean matchesDefaultPayload(HttpResponseParams httpResponseParams, Map defaultPayloadMap) { try { diff --git a/libs/utils/src/main/java/com/akto/testing/Utils.java b/libs/utils/src/main/java/com/akto/testing/Utils.java index c91fc351f4..4f58222272 100644 --- a/libs/utils/src/main/java/com/akto/testing/Utils.java +++ b/libs/utils/src/main/java/com/akto/testing/Utils.java @@ -1,5 +1,10 @@ package com.akto.testing; +import static com.akto.runtime.RuntimeUtil.extractAllValuesFromPayload; +import static com.akto.test_editor.Utils.deleteKeyFromPayload; +import static com.akto.test_editor.execution.Operations.deleteCookie; +import static com.akto.test_editor.execution.Operations.modifyCookie; + import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -15,28 +20,35 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; +import com.akto.dao.ApiCollectionsDao; import com.akto.dao.context.Context; import com.akto.dao.testing.TestingRunResultDao; import com.akto.dao.testing.VulnerableTestingRunResultDao; import com.akto.dao.testing_run_findings.TestingRunIssuesDao; +import com.akto.dto.ApiCollection; import com.akto.dto.ApiInfo.ApiInfoKey; -import com.akto.dto.CollectionConditions.ConditionsType; import com.akto.dto.OriginalHttpRequest; import com.akto.dto.RawApi; +import com.akto.dto.CollectionConditions.ConditionsType; import com.akto.dto.test_editor.DataOperandsFilterResponse; import com.akto.dto.test_editor.FilterNode; import com.akto.dto.test_editor.Util; import com.akto.dto.test_run_findings.TestingIssuesId; import com.akto.dto.test_run_findings.TestingRunIssues; +import com.akto.dto.testing.CollectionWiseTestingEndpoints; +import com.akto.dto.testing.CustomTestingEndpoints; import com.akto.dto.testing.GenericTestResult; import com.akto.dto.testing.TestResult; import com.akto.dto.testing.TestResult.Confidence; import com.akto.dto.testing.TestResult.TestError; +import com.akto.dto.testing.TestingEndpoints; +import com.akto.dto.testing.TestingRun; import com.akto.dto.testing.TestingRunResult; import com.akto.dto.testing.WorkflowUpdatedSampleData; import com.akto.dto.type.RequestTemplate; import com.akto.log.LoggerMaker; import com.akto.log.LoggerMaker.LogDb; +import com.akto.runtime.RuntimeUtil; import com.akto.test_editor.filter.Filter; import com.akto.test_editor.filter.data_operands_impl.ValidationResult; import com.akto.testing_utils.TestingUtils; @@ -56,11 +68,6 @@ import okhttp3.MediaType; -import static com.akto.runtime.RuntimeUtil.extractAllValuesFromPayload; -import static com.akto.test_editor.Utils.deleteKeyFromPayload; -import static com.akto.test_editor.execution.Operations.deleteCookie; -import static com.akto.test_editor.execution.Operations.modifyCookie; - public class Utils { private static final LoggerMaker loggerMaker = new LoggerMaker(Utils.class); @@ -606,6 +613,53 @@ public static T readJsonContentFromFile(String folderName, String fileName, } return result; } + + private static boolean isCollectionDemo(int apiCollectionId){ + try { + if(apiCollectionId == RuntimeUtil.VULNERABLE_API_COLLECTION_ID || apiCollectionId == RuntimeUtil.LLM_API_COLLECTION_ID){ + return true; + } + ApiCollection collection = ApiCollectionsDao.instance.findOne( + Filters.eq(Constants.ID, apiCollectionId), Projections.include(Constants.ID) + ); + if(collection.getName() == null){ + return false; + } + return collection.getName().equals(RuntimeUtil.JUICE_SHOP_DEMO_COLLECTION_NAME); + } catch (Exception e) { + return false; + } + } + + public static boolean isTestingRunForDemoCollection(TestingRun testingRun){ + TestingEndpoints endpoints = testingRun.getTestingEndpoints(); + try { + if(endpoints.getType().equals(TestingEndpoints.Type.COLLECTION_WISE)){ + CollectionWiseTestingEndpoints testingEndpoints = (CollectionWiseTestingEndpoints) endpoints; + int apiCollectionId = testingEndpoints.getApiCollectionId(); + return isCollectionDemo(apiCollectionId); + }else{ + int apiCollectionId = -1; + CustomTestingEndpoints testingEndpoints = (CustomTestingEndpoints) endpoints; + for(ApiInfoKey apiInfoKey : testingEndpoints.getApisList()){ + if(apiCollectionId != -1 && apiCollectionId != apiInfoKey.getApiCollectionId()){ + // case of groups{ multiple collections in single test} + return false; + }else{ + apiCollectionId = apiInfoKey.getApiCollectionId(); + } + } + + if(apiCollectionId != -1){ + return isCollectionDemo(apiCollectionId); + }else{ + return false; + } + } + } catch (Exception e) { + return false; + } + } } From d14c4a6fd6bd61b504106fada3456263fc057da3 Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Thu, 6 Feb 2025 12:48:47 +0530 Subject: [PATCH 10/38] Adding string check to activate new testing --- libs/dao/src/main/java/com/akto/util/Constants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/dao/src/main/java/com/akto/util/Constants.java b/libs/dao/src/main/java/com/akto/util/Constants.java index 1b4693409c..b5f14ebb40 100644 --- a/libs/dao/src/main/java/com/akto/util/Constants.java +++ b/libs/dao/src/main/java/com/akto/util/Constants.java @@ -31,7 +31,7 @@ private Constants() {} public static final int AKTO_KAFKA_MAX_POLL_RECORDS_CONFIG = 1; // read one message at a time public static final String TESTING_STATE_FOLDER_PATH = System.getenv("TESTING_STATE_FOLDER_PATH") != null ? System.getenv("TESTING_STATE_FOLDER_PATH") : "testing-info"; public static final String TESTING_STATE_FILE_NAME = "testing-state.json"; - public static final boolean IS_NEW_TESTING_ENABLED = StringUtils.hasLength(System.getenv("NEW_TESTING_ENABLED")); + public static final boolean IS_NEW_TESTING_ENABLED = (StringUtils.hasLength(System.getenv("NEW_TESTING_ENABLED")) && System.getenv("NEW_TESTING_ENABLED").equals("true")); public static final String UNDERSCORE = "_"; From f037ac2e0f079b52942f480f5ce2881b75dfacc6 Mon Sep 17 00:00:00 2001 From: notshivansh Date: Thu, 6 Feb 2025 16:41:34 +0530 Subject: [PATCH 11/38] re-enable test pre-script and grpc request handling --- .../com/akto/action/testing/ScriptAction.java | 24 +++++ apps/dashboard/src/main/resources/struts.xml | 84 +++++++++++++++ .../akto/action/testing/TestScriptAction.java | 34 ++++++ .../pages/testing/user_config/UserConfig.jsx | 20 ++-- .../web/polaris_web/web/src/util/func.js | 13 +++ .../com/akto/testing/StatusCodeAnalyser.java | 43 ++++---- .../java/com/akto/testing/TestExecutor.java | 33 ++++-- .../com/akto/testing/TestExecutorTest.java | 7 +- .../com/akto/billing/UsageMetricUtils.java | 29 ++++- .../java/com/akto/testing/ApiExecutor.java | 15 ++- .../com/akto/testing/ApiExecutorUtil.java | 101 ++++++++++++++++++ .../java/com/akto/testing/HostValidator.java | 23 ++-- .../java/com/akto/util/DashboardMode.java | 17 ++- 13 files changed, 391 insertions(+), 52 deletions(-) create mode 100644 apps/dashboard/src/test/java/com/akto/action/testing/TestScriptAction.java create mode 100644 libs/utils/src/main/java/com/akto/testing/ApiExecutorUtil.java diff --git a/apps/dashboard/src/main/java/com/akto/action/testing/ScriptAction.java b/apps/dashboard/src/main/java/com/akto/action/testing/ScriptAction.java index da9eff7fe9..95b79150d6 100644 --- a/apps/dashboard/src/main/java/com/akto/action/testing/ScriptAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/testing/ScriptAction.java @@ -4,10 +4,12 @@ import org.apache.commons.lang3.NotImplementedException; import org.bson.conversions.Bson; +import org.springframework.security.access.method.P; import com.akto.action.UserAction; import com.akto.dao.context.Context; import com.akto.dao.testing.config.TestScriptsDao; +import com.akto.dto.User; import com.akto.dto.testing.config.TestScript; import com.akto.util.DashboardMode; import com.mongodb.BasicDBObject; @@ -24,11 +26,29 @@ public String execute() throws Exception { private TestScript testScript; + public boolean aktoUser(){ + User user = getSUser(); + + if(user==null || user.getLogin()==null || user.getLogin().isEmpty()){ + return false; + } + + if(user.getLogin().contains("@akto.io")){ + return true; + } + return false; + } + public String addScript() { if (!DashboardMode.isSaasDeployment()) { return Action.ERROR.toUpperCase(); } + + if (!aktoUser()) { + return Action.ERROR.toUpperCase(); + } + if (this.testScript == null || this.testScript.getJavascript() == null) { return Action.ERROR.toUpperCase(); } @@ -56,6 +76,10 @@ public String updateScript() { if (!DashboardMode.isSaasDeployment()) { return Action.ERROR.toUpperCase(); } + + if (!aktoUser()) { + return Action.ERROR.toUpperCase(); + } if (this.testScript == null || this.testScript.getJavascript() == null) { return Action.ERROR.toUpperCase(); diff --git a/apps/dashboard/src/main/resources/struts.xml b/apps/dashboard/src/main/resources/struts.xml index 8bb6eb56f0..96d884ce72 100644 --- a/apps/dashboard/src/main/resources/struts.xml +++ b/apps/dashboard/src/main/resources/struts.xml @@ -5882,6 +5882,90 @@ + + + + + ADMIN_ACTIONS + READ_WRITE + User updated a script + + + TEST_PRE_SCRIPT + + + 403 + false + ^actionErrors.* + + + + 422 + false + ^actionErrors.* + + + 403 + false + ^actionErrors.* + + + + + + + + USER_ACTIONS + READ + + + 403 + false + ^actionErrors.* + + + + 422 + false + ^actionErrors.* + + + 403 + false + ^actionErrors.* + + + + + + + + ADMIN_ACTIONS + READ_WRITE + User added a script + + + TEST_PRE_SCRIPT + + + 403 + false + ^actionErrors.* + + + + 422 + false + ^actionErrors.* + + + 403 + false + ^actionErrors.* + + + + diff --git a/apps/dashboard/src/test/java/com/akto/action/testing/TestScriptAction.java b/apps/dashboard/src/test/java/com/akto/action/testing/TestScriptAction.java new file mode 100644 index 0000000000..33c01c5d51 --- /dev/null +++ b/apps/dashboard/src/test/java/com/akto/action/testing/TestScriptAction.java @@ -0,0 +1,34 @@ +package com.akto.action.testing; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import com.akto.dto.User; + +public class TestScriptAction { + + @Test + public void testAktoUser(){ + + ScriptAction action = new ScriptAction(); + Map session = new HashMap<>(); + User user = new User(); + user.setLogin("test@akto.io"); + session.put("user",user); + action.setSession(session); + + assertTrue(action.aktoUser()); + + user.setLogin("test@notakto.com"); + session.put("user",user); + action.setSession(session); + assertFalse(action.aktoUser()); + + } + +} diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/user_config/UserConfig.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/user_config/UserConfig.jsx index 114ee0265d..86c4e06811 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/user_config/UserConfig.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/testing/user_config/UserConfig.jsx @@ -14,6 +14,7 @@ import settingRequests from "../../settings/api"; import TestCollectionConfiguration from '../configurations/TestCollectionConfiguration' import InfoCard from "../../dashboard/new_components/InfoCard"; import LocalStore from "../../../../main/LocalStorageStore"; +import func from "@/util/func" function UserConfig() { @@ -49,12 +50,15 @@ function UserConfig() { LocalStore.getState().setDefaultIgnoreSummaryTime(val) }) } + try { + await api.fetchScript().then((resp)=> { + if (resp && resp.testScript) { + setPreRequestScript(resp.testScript) + } + }); + } catch(e){ + } - await api.fetchScript().then((resp)=> { - if (resp) { - setPreRequestScript(resp.testScript) - } - }); setIsLoading(false) } @@ -222,7 +226,11 @@ function UserConfig() { ) - const components = [, rateLimit, updateDeltaPeriodTime] + let components = [, rateLimit, updateDeltaPeriodTime] + + if (func.checkForFeatureSaas("TEST_PRE_SCRIPT")) { + components.push(preRequestScriptComponent) + } return ( isLoading ? diff --git a/apps/dashboard/web/polaris_web/web/src/util/func.js b/apps/dashboard/web/polaris_web/web/src/util/func.js index 2b102c4dd0..25554e4954 100644 --- a/apps/dashboard/web/polaris_web/web/src/util/func.js +++ b/apps/dashboard/web/polaris_web/web/src/util/func.js @@ -1685,6 +1685,19 @@ showConfirmationModal(modalContent, primaryActionContent, primaryAction) { } return rbacAccess; }, + + checkForFeatureSaas(featureLabel) { + const stiggFeatures = window.STIGG_FEATURE_WISE_ALLOWED + let access = false; + if (!stiggFeatures || Object.keys(stiggFeatures).length === 0) { + // for feature map not present, no access. For saas only. + access = false; + } else if (stiggFeatures && stiggFeatures[featureLabel]) { + access = stiggFeatures[featureLabel].isGranted + } + return access; + }, + checkUserValidForIntegrations(){ const rbacAccess = this.checkForRbacFeatureBasic(); if(!rbacAccess){ diff --git a/apps/testing/src/main/java/com/akto/testing/StatusCodeAnalyser.java b/apps/testing/src/main/java/com/akto/testing/StatusCodeAnalyser.java index 3dba970e41..286758efa7 100644 --- a/apps/testing/src/main/java/com/akto/testing/StatusCodeAnalyser.java +++ b/apps/testing/src/main/java/com/akto/testing/StatusCodeAnalyser.java @@ -1,9 +1,6 @@ package com.akto.testing; -import com.akto.DaoInit; import com.akto.dao.ApiCollectionsDao; -import com.akto.dao.AuthMechanismsDao; -import com.akto.dao.context.Context; import com.akto.dto.*; import com.akto.dto.testing.AuthMechanism; import com.akto.dto.testing.TestingRunConfig; @@ -17,13 +14,9 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.mongodb.BasicDBObject; -import com.mongodb.ConnectionString; - import java.util.*; import static com.akto.runtime.RelationshipSync.extractAllValuesFromPayload; -import static com.akto.testing.TestExecutor.findHost; public class StatusCodeAnalyser { @@ -48,7 +41,7 @@ public String toString() { } } - public static void run(Map> sampleDataMap, SampleMessageStore sampleMessageStore, AuthMechanismStore authMechanismStore, TestingRunConfig testingRunConfig, Set hosts) { + public static void run(Map> sampleDataMap, SampleMessageStore sampleMessageStore, AuthMechanismStore authMechanismStore, TestingRunConfig testingRunConfig, Map hostAndContentType) { defaultPayloadsMap = new HashMap<>(); result = new ArrayList<>(); if (sampleDataMap == null) { @@ -57,31 +50,35 @@ public static void run(Map> sampleDataMap, Samp } loggerMaker.infoAndAddToDb("started calc default payloads", LogDb.TESTING); - calculateDefaultPayloads(sampleMessageStore, sampleDataMap, testingRunConfig, hosts); + calculateDefaultPayloads(sampleMessageStore, sampleDataMap, testingRunConfig, hostAndContentType); loggerMaker.infoAndAddToDb("started fill result", LogDb.TESTING); fillResult(sampleMessageStore, sampleDataMap, authMechanismStore, testingRunConfig); } - public static Set findAllHosts(SampleMessageStore sampleMessageStore, Map> sampleDataMap){ - Set hosts = new HashSet<>(); + public static Map findAllHosts(SampleMessageStore sampleMessageStore, Map> sampleDataMap){ + Map hostAndContentType = new HashMap<>(); for (ApiInfo.ApiInfoKey apiInfoKey: sampleDataMap.keySet()) { String host; + String contentType; try { loggerMaker.infoAndAddToDb("Finding host for apiInfoKey: " + apiInfoKey.toString()); - host = findHost(apiInfoKey, sampleDataMap, sampleMessageStore); + OriginalHttpRequest request = TestExecutor.findOriginalHttpRequest(apiInfoKey, sampleDataMap, sampleMessageStore); + host = TestExecutor.findHostFromOriginalHttpRequest(request); + contentType = TestExecutor.findContentTypeFromOriginalHttpRequest(request); } catch (Exception e) { loggerMaker.errorAndAddToDb("Error while finding host in status code analyser: " + e, LogDb.TESTING); continue; } - - hosts.add(host); + if(host != null ){ + hostAndContentType.put(host, contentType); + } } - return hosts; + return hostAndContentType; } - public static void calculateDefaultPayloads(SampleMessageStore sampleMessageStore, Map> sampleDataMap, TestingRunConfig testingRunConfig, Set hosts) { - for (String host: hosts) { + public static void calculateDefaultPayloads(SampleMessageStore sampleMessageStore, Map> sampleDataMap, TestingRunConfig testingRunConfig, Map hostAndContentType) { + for (String host: hostAndContentType.keySet()) { loggerMaker.infoAndAddToDb("calc default payload for host: " + host, LogDb.TESTING); for (int idx=0; idx<11;idx++) { try { @@ -89,7 +86,17 @@ public static void calculateDefaultPayloads(SampleMessageStore sampleMessageStor if (!url.endsWith("/")) url += "/"; if (idx > 0) url += "akto-"+idx; // we want to hit host url once too - OriginalHttpRequest request = new OriginalHttpRequest(url, null, URLMethods.Method.GET.name(), null, new HashMap<>(), ""); + String contentType = hostAndContentType.get(host); + Map> headers = new HashMap<>(); + + if (contentType != null) { + headers.put("content-type", Arrays.asList(contentType)); + } + if (host != null && !host.isEmpty()) { + headers.put("host", Arrays.asList(host)); + } + + OriginalHttpRequest request = new OriginalHttpRequest(url, null, URLMethods.Method.GET.name(), null, headers, ""); OriginalHttpResponse response = ApiExecutor.sendRequest(request, true, testingRunConfig, false, new ArrayList<>(), Main.SKIP_SSRF_CHECK); boolean isStatusGood = TestPlugin.isStatusGood(response.getStatusCode()); if (!isStatusGood) continue; diff --git a/apps/testing/src/main/java/com/akto/testing/TestExecutor.java b/apps/testing/src/main/java/com/akto/testing/TestExecutor.java index 399867d9ae..3addd0d7db 100644 --- a/apps/testing/src/main/java/com/akto/testing/TestExecutor.java +++ b/apps/testing/src/main/java/com/akto/testing/TestExecutor.java @@ -223,10 +223,10 @@ public void apiWiseInit(TestingRun testingRun, ObjectId summaryId, boolean debug } int currentTime = Context.now(); - Set hosts = new HashSet<>(); + Map hostAndContentType = new HashMap<>(); try { loggerMaker.infoAndAddToDb("Starting findAllHosts at: " + currentTime, LogDb.TESTING); - hosts = StatusCodeAnalyser.findAllHosts(sampleMessageStore, sampleDataMapForStatusCodeAnalyser); + hostAndContentType = StatusCodeAnalyser.findAllHosts(sampleMessageStore, sampleDataMapForStatusCodeAnalyser); loggerMaker.infoAndAddToDb("Completing findAllHosts in: " + (Context.now() - currentTime) + " at: " + Context.now(), LogDb.TESTING); } catch (Exception e){ loggerMaker.errorAndAddToDb("Error while running findAllHosts " + e.getMessage(), LogDb.TESTING); @@ -234,7 +234,7 @@ public void apiWiseInit(TestingRun testingRun, ObjectId summaryId, boolean debug try { currentTime = Context.now(); loggerMaker.infoAndAddToDb("Starting HostValidator at: " + currentTime, LogDb.TESTING); - HostValidator.compute(hosts,testingRun.getTestingRunConfig()); + HostValidator.compute(hostAndContentType,testingRun.getTestingRunConfig()); loggerMaker.infoAndAddToDb("Completing HostValidator in: " + (Context.now() - currentTime) + " at: " + Context.now(), LogDb.TESTING); } catch (Exception e){ loggerMaker.errorAndAddToDb("Error while running HostValidator " + e.getMessage(), LogDb.TESTING); @@ -242,7 +242,7 @@ public void apiWiseInit(TestingRun testingRun, ObjectId summaryId, boolean debug try { currentTime = Context.now(); loggerMaker.infoAndAddToDb("Starting StatusCodeAnalyser at: " + currentTime, LogDb.TESTING); - StatusCodeAnalyser.run(sampleDataMapForStatusCodeAnalyser, sampleMessageStore , authMechanismStore, testingRun.getTestingRunConfig(), hosts); + StatusCodeAnalyser.run(sampleDataMapForStatusCodeAnalyser, sampleMessageStore , authMechanismStore, testingRun.getTestingRunConfig(), hostAndContentType); loggerMaker.infoAndAddToDb("Completing StatusCodeAnalyser in: " + (Context.now() - currentTime) + " at: " + Context.now(), LogDb.TESTING); } catch (Exception e) { loggerMaker.errorAndAddToDb("Error while running status code analyser " + e.getMessage(), LogDb.TESTING); @@ -460,7 +460,7 @@ public static Severity getSeverityFromTestingRunResult(TestingRunResult testingR return severity; } - public static String findHost(ApiInfo.ApiInfoKey apiInfoKey, Map> sampleMessagesMap, SampleMessageStore sampleMessageStore) throws URISyntaxException { + public static OriginalHttpRequest findOriginalHttpRequest(ApiInfo.ApiInfoKey apiInfoKey, Map> sampleMessagesMap, SampleMessageStore sampleMessageStore){ List sampleMessages = sampleMessagesMap.get(apiInfoKey); if (sampleMessages == null || sampleMessagesMap.isEmpty()) return null; @@ -469,18 +469,37 @@ public static String findHost(ApiInfo.ApiInfoKey apiInfoKey, Map messages = sampleMessageStore.fetchAllOriginalMessages(apiInfoKey); if (messages.isEmpty()) return null; - OriginalHttpRequest originalHttpRequest = messages.get(0).getRequest(); + return messages.get(0).getRequest(); + } + public static String findHostFromOriginalHttpRequest(OriginalHttpRequest originalHttpRequest) + throws URISyntaxException { String baseUrl = originalHttpRequest.getUrl(); if (baseUrl.startsWith("http")) { URI uri = new URI(baseUrl); String host = uri.getScheme() + "://" + uri.getHost(); - return (uri.getPort() != -1) ? host + ":" + uri.getPort() : host; + return (uri.getPort() != -1) ? host + ":" + uri.getPort() : host; } else { return "https://" + originalHttpRequest.findHostFromHeader(); } } + public static String findContentTypeFromOriginalHttpRequest(OriginalHttpRequest originalHttpRequest) { + Map> headers = originalHttpRequest.getHeaders(); + if (headers == null || headers.isEmpty()) { + return null; + } + final String CONTENT_TYPE = "content-type"; + if (headers.containsKey(CONTENT_TYPE)) { + List headerValues = headers.get(CONTENT_TYPE); + if (headerValues == null || headerValues.isEmpty()) { + return null; + } + return headerValues.get(0); + } + return null; + } + private LoginFlowResponse triggerLoginFlow(AuthMechanism authMechanism, int retries) { LoginFlowResponse loginFlowResponse = null; for (int i=0; i(), "", new ArrayList<>()); - String host = TestExecutor.findHost(apiInfoKey, testingUtil.getSampleMessages(), messageStore); - assertEquals(answer,host); + OriginalHttpRequest request = TestExecutor.findOriginalHttpRequest(apiInfoKey, testingUtil.getSampleMessages(), + messageStore); + String host = TestExecutor.findHostFromOriginalHttpRequest(request); + assertEquals(answer, host); } @Test diff --git a/libs/utils/src/main/java/com/akto/billing/UsageMetricUtils.java b/libs/utils/src/main/java/com/akto/billing/UsageMetricUtils.java index 4d558cce39..3b2db386d1 100644 --- a/libs/utils/src/main/java/com/akto/billing/UsageMetricUtils.java +++ b/libs/utils/src/main/java/com/akto/billing/UsageMetricUtils.java @@ -1,7 +1,6 @@ package com.akto.billing; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -28,7 +27,6 @@ import com.akto.util.EmailAccountName; import com.akto.util.UsageUtils; import com.akto.util.http_util.CoreHTTPClient; -import com.google.api.client.json.Json; import com.google.gson.Gson; import com.mongodb.BasicDBObject; import com.mongodb.client.model.Filters; @@ -45,7 +43,7 @@ public class UsageMetricUtils { private static final Logger logger = LoggerFactory.getLogger(UsageMetricUtils.class); - private static final LoggerMaker loggerMaker = new LoggerMaker(UsageMetricUtils.class); + private static final LoggerMaker loggerMaker = new LoggerMaker(UsageMetricUtils.class, LogDb.DASHBOARD); private static final CacheLoggerMaker cacheLoggerMaker = new CacheLoggerMaker(UsageMetricUtils.class); private static final OkHttpClient client = CoreHTTPClient.client.newBuilder().build(); @@ -246,6 +244,31 @@ public static FeatureAccess getFeatureAccess(int accountId, MetricTypes metricTy return featureAccess; } + public static FeatureAccess getFeatureAccessSaas(int accountId, String featureLabel) { + /* + * No access in case of billing service down. + * For selected features only. + */ + FeatureAccess featureAccess = FeatureAccess.noAccess; + try { + if (!DashboardMode.isMetered()) { + return featureAccess; + } + Organization organization = OrganizationsDao.instance.findOneByAccountId(accountId); + if (organization == null) { + return featureAccess; + } + HashMap featureWiseAllowed = organization.getFeatureWiseAllowed(); + if (featureWiseAllowed == null || featureWiseAllowed.isEmpty()) { + return featureAccess; + } + featureAccess = featureWiseAllowed.getOrDefault(featureLabel, FeatureAccess.noAccess); + } catch (Exception e) { + loggerMaker.errorAndAddToDb(e, "Error in fetching featureLabel acc: " + accountId, LogDb.DASHBOARD); + } + return featureAccess; + } + public static FeatureAccess getFeatureAccess(Organization organization, MetricTypes metricType) { FeatureAccess featureAccess = FeatureAccess.fullAccess; try { diff --git a/libs/utils/src/main/java/com/akto/testing/ApiExecutor.java b/libs/utils/src/main/java/com/akto/testing/ApiExecutor.java index 50abfa6035..ef3718438a 100644 --- a/libs/utils/src/main/java/com/akto/testing/ApiExecutor.java +++ b/libs/utils/src/main/java/com/akto/testing/ApiExecutor.java @@ -8,7 +8,6 @@ import com.akto.dto.CollectionConditions.TestConfigsAdvancedSettings; import com.akto.dto.testing.TestingRunConfig; import com.akto.dto.testing.TestingRunResult; -import com.akto.dto.testing.config.TestScript; import com.akto.dto.testing.rate_limit.RateLimitHandler; import com.akto.dto.type.URLMethods; import com.akto.log.LoggerMaker; @@ -29,12 +28,10 @@ import java.util.*; public class ApiExecutor { - private static final LoggerMaker loggerMaker = new LoggerMaker(ApiExecutor.class); + private static final LoggerMaker loggerMaker = new LoggerMaker(ApiExecutor.class, LogDb.TESTING); // Load only first 1 MiB of response body into memory. private static final int MAX_RESPONSE_SIZE = 1024*1024; - private static Map lastFetchedMap = new HashMap<>(); - private static Map testScriptMap = new HashMap<>(); private static OriginalHttpResponse common(Request request, boolean followRedirects, boolean debug, List testLogs, boolean skipSSRFCheck, String requestProtocol) throws Exception { @@ -258,13 +255,13 @@ public static OriginalHttpResponse sendRequest(OriginalHttpRequest request, bool } public static Request buildRequest(OriginalHttpRequest request, TestingRunConfig testingRunConfig) throws Exception{ + boolean executeScript = testingRunConfig != null; + ApiExecutorUtil.calculateHashAndAddAuth(request, executeScript); String url = prepareUrl(request, testingRunConfig); request.setUrl(url); Request.Builder builder = new Request.Builder(); addHeaders(request, builder); builder = builder.url(request.getFullUrlWithParams()); - boolean executeScript = testingRunConfig != null; - //calculateHashAndAddAuth(request, executeScript); Request okHttpRequest = builder.build(); return okHttpRequest; } @@ -294,6 +291,9 @@ public static OriginalHttpResponse sendRequest(OriginalHttpRequest request, bool calculateFinalRequestFromAdvancedSettings(request, testingRunConfig.getConfigsAdvancedSettings()); } + boolean executeScript = testingRunConfig != null; + ApiExecutorUtil.calculateHashAndAddAuth(request, executeScript); + String url = prepareUrl(request, testingRunConfig); if (!(url.contains("insertRuntimeLog") || url.contains("insertTestingLog") || url.contains("insertProtectionLog"))) { @@ -309,9 +309,6 @@ public static OriginalHttpResponse sendRequest(OriginalHttpRequest request, bool builder = builder.url(request.getFullUrlWithParams()); - boolean executeScript = testingRunConfig != null; - //calculateHashAndAddAuth(request, executeScript); - OriginalHttpResponse response = null; HostValidator.validate(url); diff --git a/libs/utils/src/main/java/com/akto/testing/ApiExecutorUtil.java b/libs/utils/src/main/java/com/akto/testing/ApiExecutorUtil.java new file mode 100644 index 0000000000..56d9ff6d72 --- /dev/null +++ b/libs/utils/src/main/java/com/akto/testing/ApiExecutorUtil.java @@ -0,0 +1,101 @@ +package com.akto.testing; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.SimpleScriptContext; + +import com.akto.billing.UsageMetricUtils; +import com.akto.dao.context.Context; +import com.akto.dao.testing.config.TestScriptsDao; +import com.akto.dto.OriginalHttpRequest; +import com.akto.dto.billing.FeatureAccess; +import com.akto.dto.testing.config.TestScript; +import com.akto.log.LoggerMaker; +import com.akto.log.LoggerMaker.LogDb; +import jdk.nashorn.api.scripting.ScriptObjectMirror; + +public class ApiExecutorUtil { + + private static final LoggerMaker loggerMaker = new LoggerMaker(ApiExecutorUtil.class, LogDb.TESTING); + + private static Map lastFetchedMap = new HashMap<>(); + private static Map testScriptMap = new HashMap<>(); + + public static void calculateHashAndAddAuth(OriginalHttpRequest originalHttpRequest, boolean executeScript) { + if (!executeScript) { + return; + } + try { + int accountId = Context.accountId.get(); + FeatureAccess featureAccess = UsageMetricUtils.getFeatureAccessSaas(accountId, "TEST_PRE_SCRIPT"); + if (!featureAccess.getIsGranted()) { + return; + } + + String script; + TestScript testScript = testScriptMap.getOrDefault(accountId, null); + int lastTestScriptFetched = lastFetchedMap.getOrDefault(accountId, 0); + if (Context.now() - lastTestScriptFetched > 5 * 60) { + testScript = TestScriptsDao.instance.fetchTestScript(); + lastTestScriptFetched = Context.now(); + testScriptMap.put(accountId, testScript); + lastFetchedMap.put(accountId, Context.now()); + } + if (testScript != null && testScript.getJavascript() != null) { + script = testScript.getJavascript(); + } else { + return; + } + loggerMaker.infoAndAddToDb("Starting calculateHashAndAddAuth"); + + ScriptEngineManager manager = new ScriptEngineManager(); + ScriptEngine engine = manager.getEngineByName("nashorn"); + + SimpleScriptContext sctx = ((SimpleScriptContext) engine.get("context")); + sctx.setAttribute("method", originalHttpRequest.getMethod(), ScriptContext.ENGINE_SCOPE); + sctx.setAttribute("headers", originalHttpRequest.getHeaders(), ScriptContext.ENGINE_SCOPE); + sctx.setAttribute("url", originalHttpRequest.getPath(), ScriptContext.ENGINE_SCOPE); + sctx.setAttribute("payload", originalHttpRequest.getBody(), ScriptContext.ENGINE_SCOPE); + sctx.setAttribute("queryParams", originalHttpRequest.getQueryParams(), ScriptContext.ENGINE_SCOPE); + engine.eval(script); + + String method = (String) sctx.getAttribute("method"); + Map headers = (Map) sctx.getAttribute("headers"); + String url = (String) sctx.getAttribute("url"); + String payload = (String) sctx.getAttribute("payload"); + String queryParams = (String) sctx.getAttribute("queryParams"); + + Map> hs = new HashMap<>(); + for (String key: headers.keySet()) { + try { + ScriptObjectMirror scm = ((ScriptObjectMirror) headers.get(key)); + List val = new ArrayList<>(); + for (int i = 0; i < scm.size(); i++) { + val.add((String) scm.get(Integer.toString(i))); + } + hs.put(key, val); + } catch (Exception e) { + hs.put(key, (List) headers.get(key)); + } + } + + originalHttpRequest.setBody(payload); + originalHttpRequest.setMethod(method); + originalHttpRequest.setUrl(url); + originalHttpRequest.setHeaders(hs); + originalHttpRequest.setQueryParams(queryParams); + + } catch (Exception e) { + loggerMaker.errorAndAddToDb("error in calculateHashAndAddAuth " + e.getMessage() + " url " + originalHttpRequest.getUrl()); + e.printStackTrace(); + return; + } + } + +} diff --git a/libs/utils/src/main/java/com/akto/testing/HostValidator.java b/libs/utils/src/main/java/com/akto/testing/HostValidator.java index c64a35b8ec..033a00ee56 100644 --- a/libs/utils/src/main/java/com/akto/testing/HostValidator.java +++ b/libs/utils/src/main/java/com/akto/testing/HostValidator.java @@ -1,7 +1,9 @@ package com.akto.testing; import java.io.IOException; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -84,24 +86,33 @@ private static String getUniformUrlUtil(HttpUrl url){ return String.format("%s://%s:%s", url.scheme(), url.host(), url.port()); } - public static void compute(Set hosts, TestingRunConfig testingRunConfig) { + public static void compute(Map hostAndContentType, TestingRunConfig testingRunConfig) { hostReachabilityMap = new HashMap<>(); - if (hosts == null) { + if (hostAndContentType == null) { return; } - for (String host : hosts) { + for (String host : hostAndContentType.keySet()) { try { String url = host; if (!url.endsWith("/")) url += "/"; - + + String contentType = hostAndContentType.get(host); + Map> headers = new HashMap<>(); + + if (contentType != null) { + headers.put("content-type", Arrays.asList(contentType)); + } + if (host != null && !host.isEmpty()) { + headers.put("host", Arrays.asList(host)); + } + OriginalHttpRequest request = new OriginalHttpRequest(url, null, URLMethods.Method.GET.name(), null, new HashMap<>(), ""); - String type = request.findContentType(); Request actualRequest = ApiExecutor.buildRequest(request, testingRunConfig); String attemptUrl = getUniformUrlUtil(actualRequest.url()); loggerMaker.infoAndAddToDb("checking reachability for host: " + attemptUrl); if(!hostReachabilityMap.containsKey(attemptUrl)){ - boolean reachable = checkDomainReach(actualRequest, false, type); + boolean reachable = checkDomainReach(actualRequest, false, contentType); hostReachabilityMap.put(attemptUrl, reachable); } } catch (Exception e) { diff --git a/libs/utils/src/main/java/com/akto/util/DashboardMode.java b/libs/utils/src/main/java/com/akto/util/DashboardMode.java index b72a4971ef..6db41c4a21 100644 --- a/libs/utils/src/main/java/com/akto/util/DashboardMode.java +++ b/libs/utils/src/main/java/com/akto/util/DashboardMode.java @@ -1,6 +1,10 @@ package com.akto.util; +import com.akto.dao.SetupDao; +import com.akto.dto.Setup; import com.akto.onprem.Constants; +import com.mongodb.BasicDBObject; + import org.apache.commons.lang3.StringUtils; public enum DashboardMode { @@ -53,6 +57,17 @@ public static boolean isSaasDeployment(){ } public static boolean isMetered() { - return isSaasDeployment() || isOnPremDeployment(); + + boolean isSaasDeployment = isSaasDeployment(); + try { + Setup setup = SetupDao.instance.findOne(new BasicDBObject()); + if(setup!=null){ + String dashboardMode = setup.getDashboardMode(); + isSaasDeployment = dashboardMode.equalsIgnoreCase(DashboardMode.SAAS.name()); + } + } catch(Exception e){ + + } + return isSaasDeployment || isOnPremDeployment(); } } From 407e18542cf3a13f08dbb5727475077d769cc076 Mon Sep 17 00:00:00 2001 From: notshivansh Date: Thu, 6 Feb 2025 16:49:56 +0530 Subject: [PATCH 12/38] remove unused import --- .../src/main/java/com/akto/action/testing/ScriptAction.java | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/testing/ScriptAction.java b/apps/dashboard/src/main/java/com/akto/action/testing/ScriptAction.java index 95b79150d6..db40389307 100644 --- a/apps/dashboard/src/main/java/com/akto/action/testing/ScriptAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/testing/ScriptAction.java @@ -4,7 +4,6 @@ import org.apache.commons.lang3.NotImplementedException; import org.bson.conversions.Bson; -import org.springframework.security.access.method.P; import com.akto.action.UserAction; import com.akto.dao.context.Context; From 9fa36f0ada973cfd522d8c90edf60b959e29f4a0 Mon Sep 17 00:00:00 2001 From: notshivansh Date: Thu, 6 Feb 2025 17:04:07 +0530 Subject: [PATCH 13/38] add cache --- .../java/com/akto/util/DashboardMode.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/libs/utils/src/main/java/com/akto/util/DashboardMode.java b/libs/utils/src/main/java/com/akto/util/DashboardMode.java index 6db41c4a21..bd05660de5 100644 --- a/libs/utils/src/main/java/com/akto/util/DashboardMode.java +++ b/libs/utils/src/main/java/com/akto/util/DashboardMode.java @@ -1,12 +1,11 @@ package com.akto.util; import com.akto.dao.SetupDao; +import com.akto.dao.context.Context; import com.akto.dto.Setup; import com.akto.onprem.Constants; import com.mongodb.BasicDBObject; -import org.apache.commons.lang3.StringUtils; - public enum DashboardMode { LOCAL_DEPLOY, ON_PREM, STAIRWAY, SAAS; @@ -56,18 +55,25 @@ public static boolean isSaasDeployment(){ return dashboardMode.equals(LOCAL_DEPLOY) && "true".equalsIgnoreCase(System.getenv("IS_SAAS")); } + private static boolean isSaasDeploymentGlobal = false; + private static int lastSaasFetched = 0; + public static boolean isMetered() { - boolean isSaasDeployment = isSaasDeployment(); - try { - Setup setup = SetupDao.instance.findOne(new BasicDBObject()); - if(setup!=null){ - String dashboardMode = setup.getDashboardMode(); - isSaasDeployment = dashboardMode.equalsIgnoreCase(DashboardMode.SAAS.name()); + if (lastSaasFetched == 0 || lastSaasFetched < Context.now() - 30 * 60) { + boolean isSaasDeployment = isSaasDeployment(); + try { + Setup setup = SetupDao.instance.findOne(new BasicDBObject()); + if (setup != null) { + String dashboardMode = setup.getDashboardMode(); + isSaasDeployment = dashboardMode.equalsIgnoreCase(DashboardMode.SAAS.name()); + } + } catch (Exception e) { } - } catch(Exception e){ - + isSaasDeploymentGlobal = isSaasDeployment; + lastSaasFetched = Context.now(); } - return isSaasDeployment || isOnPremDeployment(); + + return isSaasDeploymentGlobal || isOnPremDeployment(); } } From f21a266844eb227bc85afe33c8e0892474bf57ee Mon Sep 17 00:00:00 2001 From: SHIVAM RAWAT Date: Fri, 7 Feb 2025 09:27:29 +0530 Subject: [PATCH 14/38] fixing multiple issues on issues-page and api security posture page. --- .../java/com/akto/action/DashboardAction.java | 30 +++++++++++++++--- apps/dashboard/src/main/resources/struts.xml | 2 +- .../pages/dashboard/HomeDashboard.jsx | 4 +-- .../new_components/ProgressBarChart.jsx | 3 +- .../CriticalUnsecuredAPIsOverTimeGraph.jsx | 31 +++++++++++++------ .../pages/observe/api_collections/RunTest.jsx | 8 ++--- 6 files changed, 55 insertions(+), 23 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/DashboardAction.java b/apps/dashboard/src/main/java/com/akto/action/DashboardAction.java index 121806bc22..e22b50b528 100644 --- a/apps/dashboard/src/main/java/com/akto/action/DashboardAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/DashboardAction.java @@ -28,6 +28,8 @@ import com.mongodb.client.MongoCursor; import com.opensymphony.xwork2.Action; +import static com.akto.dto.test_run_findings.TestingRunIssues.KEY_SEVERITY; + public class DashboardAction extends UserAction { private int startTimeStamp; @@ -100,6 +102,7 @@ public String fetchHistoricalData() { } private List severityToFetch; + private final Map> severityWiseTrendData= new HashMap<>(); private final Map trendData = new HashMap<>(); public String fetchCriticalIssuesTrend(){ if(endTimeStamp == 0) endTimeStamp = Context.now(); @@ -115,7 +118,7 @@ public String fetchCriticalIssuesTrend(){ List allowedStatus = Arrays.asList(GlobalEnums.TestRunIssueStatus.OPEN, GlobalEnums.TestRunIssueStatus.FIXED); Bson issuesFilter = Filters.and( - Filters.in(TestingRunIssues.KEY_SEVERITY, severityToFetch), + Filters.in(KEY_SEVERITY, severityToFetch), Filters.gte(TestingRunIssues.CREATION_TIME, startTimeStamp), Filters.lte(TestingRunIssues.CREATION_TIME, endTimeStamp), Filters.in(TestingRunIssues.TEST_RUN_ISSUES_STATUS, allowedStatus), @@ -135,14 +138,23 @@ public String fetchCriticalIssuesTrend(){ } } catch(Exception e){ } - pipeline.add(Aggregates.project(Projections.computed(dayOfYearFloat, new BasicDBObject("$divide", new Object[]{"$" + TestingRunIssues.CREATION_TIME, 86400})))); - - pipeline.add(Aggregates.project(Projections.computed(dayOfYear, new BasicDBObject("$floor", new Object[]{"$" + dayOfYearFloat})))); + pipeline.add(Aggregates.project( + Projections.fields( + Projections.computed(dayOfYearFloat, new BasicDBObject("$divide", new Object[]{"$" + TestingRunIssues.CREATION_TIME, 86400})), + Projections.include(KEY_SEVERITY) + ))); + + pipeline.add(Aggregates.project( + Projections.fields( + Projections.computed(dayOfYear, new BasicDBObject("$floor", new Object[]{"$" + dayOfYearFloat})), + Projections.include(KEY_SEVERITY) + ))); BasicDBObject groupedId = new BasicDBObject(dayOfYear, "$"+dayOfYear) .append("url", "$_id.apiInfoKey.url") .append("method", "$_id.apiInfoKey.method") - .append("apiCollectionId", "$_id.apiInfoKey.apiCollectionId"); + .append("apiCollectionId", "$_id.apiInfoKey.apiCollectionId") + .append(KEY_SEVERITY, "$" + KEY_SEVERITY); pipeline.add(Aggregates.group(groupedId, Accumulators.sum("count", 1))); MongoCursor issuesCursor = TestingRunIssuesDao.instance.getMCollection().aggregate(pipeline, BasicDBObject.class).cursor(); @@ -150,9 +162,13 @@ public String fetchCriticalIssuesTrend(){ while(issuesCursor.hasNext()){ BasicDBObject basicDBObject = issuesCursor.next(); BasicDBObject o = (BasicDBObject) basicDBObject.get("_id"); + String severity = o.getString(KEY_SEVERITY, GlobalEnums.Severity.LOW.name()); + Map trendData = severityWiseTrendData.computeIfAbsent(severity, k -> new HashMap<>()); int date = o.getInt(dayOfYear); int count = trendData.getOrDefault(date,0); trendData.put(date, count+1); + count = this.trendData.getOrDefault(date,0); + this.trendData.put(date, count+1); } return SUCCESS.toUpperCase(); @@ -378,4 +394,8 @@ public String getOrganization() { public void setOrganization(String organization) { this.organization = organization; } + + public Map> getSeverityWiseTrendData() { + return severityWiseTrendData; + } } diff --git a/apps/dashboard/src/main/resources/struts.xml b/apps/dashboard/src/main/resources/struts.xml index 8bb6eb56f0..5b85b05d20 100644 --- a/apps/dashboard/src/main/resources/struts.xml +++ b/apps/dashboard/src/main/resources/struts.xml @@ -7601,7 +7601,7 @@ READ - trendData + severityWiseTrendData 422 diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/HomeDashboard.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/HomeDashboard.jsx index 7759dd89bf..a56f464a52 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/HomeDashboard.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/HomeDashboard.jsx @@ -237,9 +237,9 @@ function HomeDashboard() { totalTestedApis += x.apisTested }) - const tempRiskScore = totalAPIs ? (totalRiskScore / totalApis).toFixed(2) : 0 + const tempRiskScore = totalApis ? (totalRiskScore / totalApis).toFixed(2) : 0 setOldRiskScore(parseFloat(tempRiskScore)) - const tempTestCoverate = totalAPIs ? (100 * totalTestedApis / totalApis).toFixed(2) : 0 + const tempTestCoverate = totalApis ? (100 * totalTestedApis / totalApis).toFixed(2) : 0 setOldTestCoverage(parseFloat(tempTestCoverate)) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/new_components/ProgressBarChart.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/new_components/ProgressBarChart.jsx index e4893cef68..a6ddb3a6b8 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/new_components/ProgressBarChart.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/dashboard/new_components/ProgressBarChart.jsx @@ -1,5 +1,6 @@ import { Badge, DataTable, HorizontalStack, Text } from '@shopify/polaris'; import React from 'react'; +import transform from '../../observe/transform'; import CustomProgressBar from './CustomProgressBar'; function ProgressBarChart({ data }) { @@ -16,7 +17,7 @@ function ProgressBarChart({ data }) { backgroundColor={item["backgroundColor"]} />
- {item.text} + {parseInt(item.text) !== NaN ? transform.formatNumberWithCommas(parseInt(item.text)): item.text} , ]); }; diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalUnsecuredAPIsOverTimeGraph.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalUnsecuredAPIsOverTimeGraph.jsx index c0feb24b19..afc1670e69 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalUnsecuredAPIsOverTimeGraph.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalUnsecuredAPIsOverTimeGraph.jsx @@ -8,18 +8,29 @@ import dashboardApi from "../../dashboard/api.js" const CriticalUnsecuredAPIsOverTimeGraph = ({ linkText, linkUrl }) => { const [unsecuredAPIs, setUnsecuredAPIs] = useState([]) const [showTestingComponents, setShowTestingComponents] = useState(false) + const [isDataAvailable, setIsDataAvailable] = useState(false) function buildUnsecuredAPIs(input) { - const CRITICAL_COLOR = "#E45357" + + const SEVERITY_CONFIG = { + CRITICAL: { color: "#E45357", name: "Critical Issues", data: [] }, + HIGH: { color: "#EF864C", name: "High Issues", data: [] } + }; + const transformed = [] - const criticalData = { data: [], color: CRITICAL_COLOR, name: "Critical Issues" } - for (const epoch in input) { - const epochMillis = Number(epoch) * 86400000 - criticalData.data.push([epochMillis, input[epoch]]) + let dataAvailability = false + for (const [severity, epochs] of Object.entries(input)) { + const dataset = SEVERITY_CONFIG[severity] || SEVERITY_CONFIG.HIGH; + + for (const epoch in epochs) { + dataset.data.push([Number(epoch) * 86400000, epochs[epoch]]); + dataAvailability = true + } } - transformed.push(criticalData) + transformed.push(SEVERITY_CONFIG.CRITICAL, SEVERITY_CONFIG.HIGH); setUnsecuredAPIs(transformed) + setIsDataAvailable(dataAvailability) } @@ -43,7 +54,7 @@ const CriticalUnsecuredAPIsOverTimeGraph = ({ linkText, linkUrl }) => { const runTestEmptyCardComponent = There’s no data to show. Run test to get data populated. - const criticalUnsecuredAPIsOverTime = (unsecuredAPIs && unsecuredAPIs.length > 0 && unsecuredAPIs[0].data && unsecuredAPIs[0].data.length > 0) ? 0 && isDataAvailable) ? { exportingDisabled={true} /> } - title="Critical Unsecured APIs Over Time" - titleToolTip="Chart showing the number of APIs detected(risk score >= 4) each month over the past year. Helps track security trends over time." + title="Critical or high severity Unsecured APIs Over Time" + titleToolTip="Chart showing the number of APIs detected(risk score >= 3) each month over the past year. Helps track security trends over time." linkText={linkText} linkUrl={linkUrl} - /> : No Unsecured APIs found: runTestEmptyCardComponent} /> + /> : No Unsecured APIs found: runTestEmptyCardComponent} /> return ( {...criticalUnsecuredAPIsOverTime} diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/RunTest.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/RunTest.jsx index 9cd30ad6ea..7d1458dc06 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/RunTest.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/observe/api_collections/RunTest.jsx @@ -31,7 +31,7 @@ function RunTest({ endpoints, filtered, apiCollectionId, disabled, runTestFromOu hourlyLabel: "Now", testRunTime: -1, testRunTimeLabel: "30 minutes", - runTypeLabel: "Now", + runTypeLabel: "Once", maxConcurrentRequests: -1, testName: "", authMechanismPresent: false, @@ -205,8 +205,8 @@ function RunTest({ endpoints, filtered, apiCollectionId, disabled, runTestFromOu handleAddSettings(parentAdvanceSettingsConfig); const getRunTypeLabel = (runType) => { - if (!runType) return "Now"; - if (runType === "CI-CD" || runType === "ONE_TIME") return "Now"; + if (!runType) return "Once"; + if (runType === "CI-CD" || runType === "ONE_TIME") return "Once"; else if (runType === "RECURRING") return "Daily"; else if (runType === "CONTINUOUS_TESTING") return "Continuously"; } @@ -430,7 +430,7 @@ function RunTest({ endpoints, filtered, apiCollectionId, disabled, runTestFromOu const testRunTimeOptions = [...runTimeMinutes, ...runTimeHours] - const runTypeOptions = [{ label: "Daily", value: "Daily" }, { label: "Continuously", value: "Continuously" }, { label: "Now", value: "Now" }] + const runTypeOptions = [{ label: "Daily", value: "Daily" }, { label: "Continuously", value: "Continuously" }, { label: "Once", value: "Once" }] const maxRequests = hours.reduce((abc, x) => { if (x < 11) { From 9eb4237c3024f3ed4e745e1d08ac2643d6e1e1a9 Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Fri, 7 Feb 2025 10:55:11 +0530 Subject: [PATCH 15/38] Fixing consumer getting rejected due to executor shutdown --- .../java/com/akto/testing/kafka_utils/ConsumerUtil.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java b/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java index 40dcd92324..869c4c3fee 100644 --- a/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java +++ b/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java @@ -10,7 +10,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.kafka.clients.consumer.*; import org.bson.types.ObjectId; @@ -98,8 +98,8 @@ public void init(int maxRunTimeInSeconds) { final String summaryIdForTest = currentTestInfo.getString("summaryId"); final ObjectId summaryObjectId = new ObjectId(summaryIdForTest); final int startTime = Context.now(); - AtomicInteger lastRecordRead = new AtomicInteger(Context.now()); boolean isConsumerRunning = false; + AtomicBoolean firstRecordRead = new AtomicBoolean(false); if(currentTestInfo != null){ isConsumerRunning = currentTestInfo.getBoolean("CONSUMER_RUNNING"); } @@ -138,8 +138,8 @@ public void init(int maxRunTimeInSeconds) { String message = record.value(); logger.info("Thread [" + threadName + "] picked up record: " + message); try { - lastRecordRead.set(Context.now()); Future future = executor.submit(() -> runTestFromMessage(message)); + firstRecordRead.set(true); try { future.get(4, TimeUnit.MINUTES); } catch (InterruptedException e) { @@ -163,7 +163,7 @@ else if ((Context.now() - startTime > maxRunTimeInSeconds)) { logger.info("Max run time reached. Stopping consumer."); executor.shutdownNow(); break; - }else if((Context.now() - lastRecordRead.get() > 10)){ + }else if(firstRecordRead.get() && parallelConsumer.workRemaining() == 0){ logger.info("Records are empty now, thus executing final tests"); executor.shutdown(); executor.awaitTermination(maxRunTimeInSeconds, TimeUnit.SECONDS); From b36f92c5c9055fa74ddb374e6533252dc0cd2ccf Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Fri, 7 Feb 2025 12:41:43 +0530 Subject: [PATCH 16/38] Error handling for timed out exception --- .../testing/kafka_utils/ConsumerUtil.java | 37 ++++++++++++++++++- .../java/com/akto/dto/testing/TestResult.java | 3 +- .../src/main/java/com/akto/testing/Utils.java | 5 ++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java b/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java index 869c4c3fee..bc9edb0969 100644 --- a/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java +++ b/apps/testing/src/main/java/com/akto/testing/kafka_utils/ConsumerUtil.java @@ -2,6 +2,7 @@ import static com.akto.testing.Utils.readJsonContentFromFile; import static com.akto.testing.Utils.writeJsonContentInFile; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -10,6 +11,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.kafka.clients.consumer.*; @@ -21,14 +23,19 @@ import com.akto.DaoInit; import com.akto.crons.GetRunningTestsStatus; import com.akto.dao.context.Context; +import com.akto.dao.testing.TestingRunResultDao; +import com.akto.dao.testing.TestingRunResultSummariesDao; import com.akto.dto.ApiInfo; import com.akto.dto.ApiInfo.ApiInfoKey; import com.akto.dto.test_editor.TestConfig; import com.akto.dto.testing.TestingRunResult; +import com.akto.dto.testing.TestingRunResultSummary; +import com.akto.dto.testing.TestResult.TestError; import com.akto.dto.testing.info.SingleTestPayload; import com.akto.notifications.slack.CustomTextAlert; import com.akto.testing.Main; import com.akto.testing.TestExecutor; +import com.akto.testing.Utils; import com.akto.util.Constants; import com.akto.util.DashboardMode; import com.alibaba.fastjson2.JSON; @@ -37,6 +44,8 @@ import com.mongodb.ConnectionString; import com.mongodb.ReadPreference; import com.mongodb.WriteConcern; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; import io.confluent.parallelconsumer.ParallelConsumerOptions; import io.confluent.parallelconsumer.ParallelStreamProcessor; @@ -91,6 +100,25 @@ public void runTestFromMessage(String message){ executor.insertResultsAndMakeIssues(Collections.singletonList(runResult), singleTestPayload.getTestingRunResultSummaryId()); } } + + private void createTimedOutResultFromMessage(String message){ + SingleTestPayload singleTestPayload = parseTestMessage(message); + Context.accountId.set(singleTestPayload.getAccountId()); + + String subCategory = singleTestPayload.getSubcategory(); + TestConfig testConfig = TestingConfigurations.getInstance().getTestConfigMap().get(subCategory); + + String testSuperType = testConfig.getInfo().getCategory().getName(); + String testSubType = testConfig.getInfo().getSubCategory(); + + TestingRunResult runResult = Utils.generateFailedRunResultForMessage(singleTestPayload.getTestingRunId(), singleTestPayload.getApiInfoKey(), testSuperType, testSubType, singleTestPayload.getTestingRunResultSummaryId(), new ArrayList<>(), TestError.TEST_TIMED_OUT.getMessage()); + TestExecutor.trim(runResult); + TestingRunResultSummariesDao.instance.getMCollection().withWriteConcern(WriteConcern.W1).findOneAndUpdate( + Filters.eq(Constants.ID, singleTestPayload.getTestingRunResultSummaryId()), + Updates.inc(TestingRunResultSummary.TEST_RESULTS_COUNT, 1) + ); + TestingRunResultDao.instance.insertOne(runResult); + } public void init(int maxRunTimeInSeconds) { executor = Executors.newFixedThreadPool(100); @@ -141,10 +169,15 @@ public void init(int maxRunTimeInSeconds) { Future future = executor.submit(() -> runTestFromMessage(message)); firstRecordRead.set(true); try { - future.get(4, TimeUnit.MINUTES); + future.get(5, TimeUnit.MINUTES); } catch (InterruptedException e) { - logger.error("Task timed out: " + message); + logger.error("Task timed out"); + future.cancel(true); + createTimedOutResultFromMessage(message); + } catch(TimeoutException e){ + logger.error("Task timed out"); future.cancel(true); + createTimedOutResultFromMessage(message); } catch (Exception e) { logger.error("Error in task execution: " + message, e); } diff --git a/libs/dao/src/main/java/com/akto/dto/testing/TestResult.java b/libs/dao/src/main/java/com/akto/dto/testing/TestResult.java index c55c4e259e..519b414cb7 100644 --- a/libs/dao/src/main/java/com/akto/dto/testing/TestResult.java +++ b/libs/dao/src/main/java/com/akto/dto/testing/TestResult.java @@ -56,7 +56,8 @@ public enum TestError { SKIPPING_EXECUTION_BECAUSE_FILTERS("Request API failed to satisfy api_selection_filters block, skipping execution", true), DEACTIVATED_ENDPOINT("This is a deactivated endpoint", true), USAGE_EXCEEDED("You have exceeded the limit of this feature, skipping execution", true), - ROLE_NOT_FOUND("config doesn't exist, skipping execution", false); + ROLE_NOT_FOUND("config doesn't exist, skipping execution", false), + TEST_TIMED_OUT("Test took too long for execution, exited out", true); private final String message; private final boolean skipTest; diff --git a/libs/utils/src/main/java/com/akto/testing/Utils.java b/libs/utils/src/main/java/com/akto/testing/Utils.java index 4f58222272..8a1d6424f3 100644 --- a/libs/utils/src/main/java/com/akto/testing/Utils.java +++ b/libs/utils/src/main/java/com/akto/testing/Utils.java @@ -19,6 +19,7 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; +import org.springframework.util.StringUtils; import com.akto.dao.ApiCollectionsDao; import com.akto.dao.context.Context; @@ -561,9 +562,9 @@ public static TestingRunResult generateFailedRunResultForMessage(ObjectId testin List testResults = new ArrayList<>(); String failMessage = errorMessage; - if(deactivatedCollections.contains(apiInfoKey.getApiCollectionId())){ + if(!StringUtils.hasLength(errorMessage) && deactivatedCollections.contains(apiInfoKey.getApiCollectionId())){ failMessage = TestError.DEACTIVATED_ENDPOINT.getMessage(); - }else if(messages == null || messages.isEmpty()){ + }else if(!StringUtils.hasLength(errorMessage) && (messages == null || messages.isEmpty())){ failMessage = TestError.NO_PATH.getMessage(); } From a2f0e7bc9ae70b0db60318533bfe7cbd90532a7d Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Fri, 7 Feb 2025 14:37:29 +0530 Subject: [PATCH 17/38] Fixing token datatype operator --- .../akto/listener/InitializerListener.java | 30 +++++++++++++++++-- .../com/akto/dto/BackwardCompatibility.java | 15 +++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java index c6554a3f5a..8ffcc71ba3 100644 --- a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java +++ b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java @@ -723,7 +723,7 @@ private static CustomDataType getCustomDataTypeFromPiiType(PIISource piiSource, active, ((piiType.getOnKey() || piiType.getOnKeyAndPayload()) ? keyConditions : null), ((piiType.getOnKey() && !piiType.getOnKeyAndPayload()) ? null : valueConditions), - Operator.OR, + piiType.getOnKeyAndPayload() ? Operator.AND : Operator.OR, ignoreData, false, true @@ -2999,13 +2999,39 @@ private static void markSummariesAsVulnerable(BackwardCompatibility backwardComp } } + private static void updateCustomDataTypeOperator(BackwardCompatibility backwardCompatibility){ + if(backwardCompatibility.getChangeOperatorConditionInCDT() == 0){ + CustomDataTypeDao.instance.updateOneNoUpsert( + Filters.and( + Filters.eq(CustomDataType.NAME, "TOKEN"), + Filters.or( + Filters.exists(CustomDataType.USER_MODIFIED_TIMESTAMP, false), + Filters.eq(CustomDataType.USER_MODIFIED_TIMESTAMP, 0) + ) + ), + Updates.set(CustomDataType.OPERATOR, Operator.AND) + ); + + // trigger fix for token here + CustomDataTypeAction dataTypeAction = new CustomDataTypeAction(); + dataTypeAction.setName("TOKEN"); + String temp = dataTypeAction.resetDataTypeRetro(); + + BackwardCompatibilityDao.instance.updateOne( + Filters.eq("_id", backwardCompatibility.getId()), + Updates.set(BackwardCompatibility.CHANGE_OPERATOR_CONDITION_IN_CDT, Context.now()) + ); + } + } + public static void setBackwardCompatibilities(BackwardCompatibility backwardCompatibility){ if (DashboardMode.isMetered()) { initializeOrganizationAccountBelongsTo(backwardCompatibility); setOrganizationsInBilling(backwardCompatibility); } - markSummariesAsVulnerable(backwardCompatibility); setAktoDefaultNewUI(backwardCompatibility); + updateCustomDataTypeOperator(backwardCompatibility); + markSummariesAsVulnerable(backwardCompatibility); dropLastCronRunInfoField(backwardCompatibility); fetchIntegratedConnections(backwardCompatibility); dropFilterSampleDataCollection(backwardCompatibility); diff --git a/libs/dao/src/main/java/com/akto/dto/BackwardCompatibility.java b/libs/dao/src/main/java/com/akto/dto/BackwardCompatibility.java index 59c95fe474..9cad84f580 100644 --- a/libs/dao/src/main/java/com/akto/dto/BackwardCompatibility.java +++ b/libs/dao/src/main/java/com/akto/dto/BackwardCompatibility.java @@ -106,6 +106,9 @@ public class BackwardCompatibility { public static final String MARK_SUMMARIES_NEW_FOR_VULNERABLE = "markSummariesVulnerable"; private int markSummariesVulnerable; + public static final String CHANGE_OPERATOR_CONDITION_IN_CDT = "changeOperatorConditionInCDT"; + private int changeOperatorConditionInCDT; + public BackwardCompatibility(int id, int dropFilterSampleData, int resetSingleTypeInfoCount, int dropWorkflowTestResult, int readyForNewTestingFramework,int addAktoDataTypes, boolean deploymentStatusUpdated, int authMechanismData, boolean mirroringLambdaTriggered, int deleteAccessListFromApiToken, @@ -115,7 +118,8 @@ public BackwardCompatibility(int id, int dropFilterSampleData, int resetSingleTy int loginSignupGroups, int vulnerableApiUpdationVersionV1, int riskScoreGroups, int deactivateCollections, int disableAwsSecretPii, int apiCollectionAutomatedField, int automatedApiGroups, int addAdminRoleIfAbsent, int dropSpecialCharacterApiCollections, int fixApiAccessType, - int addDefaultFilters, int moveAzureSamlToNormalSaml, int deleteOptionsAPIs, int moveOktaOidcSSO, int markSummariesVulnerable) { + int addDefaultFilters, int moveAzureSamlToNormalSaml, int deleteOptionsAPIs, int moveOktaOidcSSO, int markSummariesVulnerable, + int changeOperatorConditionInCDT) { this.id = id; this.dropFilterSampleData = dropFilterSampleData; this.resetSingleTypeInfoCount = resetSingleTypeInfoCount; @@ -149,6 +153,7 @@ public BackwardCompatibility(int id, int dropFilterSampleData, int resetSingleTy this.deleteOptionsAPIs = deleteOptionsAPIs; this.moveOktaOidcSSO = moveOktaOidcSSO; this.markSummariesVulnerable = markSummariesVulnerable; + this.changeOperatorConditionInCDT = changeOperatorConditionInCDT; } public BackwardCompatibility() { @@ -449,4 +454,12 @@ public int getMarkSummariesVulnerable() { public void setMarkSummariesVulnerable(int markSummariesVulnerable) { this.markSummariesVulnerable = markSummariesVulnerable; } + + public int getChangeOperatorConditionInCDT() { + return changeOperatorConditionInCDT; + } + + public void setChangeOperatorConditionInCDT(int changeOperatorConditionInCDT) { + this.changeOperatorConditionInCDT = changeOperatorConditionInCDT; + } } From 1eb8beb8ce8946a1a3f545e47c85fa4f0c26a9a6 Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Fri, 7 Feb 2025 14:43:08 +0530 Subject: [PATCH 18/38] Removing retro fix --- .../main/java/com/akto/listener/InitializerListener.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java index 8ffcc71ba3..596ed13893 100644 --- a/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java +++ b/apps/dashboard/src/main/java/com/akto/listener/InitializerListener.java @@ -13,7 +13,6 @@ import com.akto.dao.context.Context; import com.akto.dao.loaders.LoadersDao; import com.akto.dao.notifications.CustomWebhooksDao; -import com.akto.dao.notifications.CustomWebhooksResultDao; import com.akto.dao.notifications.EventsMetricsDao; import com.akto.dao.notifications.SlackWebhooksDao; import com.akto.dao.pii.PIISourceDao; @@ -167,7 +166,6 @@ import static com.akto.task.Cluster.callDibs; import static com.akto.utils.billing.OrganizationUtils.syncOrganizationWithAkto; import static com.mongodb.client.model.Filters.eq; -import static com.akto.runtime.utils.Utils.convertOriginalReqRespToString; import static com.akto.utils.Utils.deleteApis; public class InitializerListener implements ServletContextListener { @@ -3012,11 +3010,6 @@ private static void updateCustomDataTypeOperator(BackwardCompatibility backwardC Updates.set(CustomDataType.OPERATOR, Operator.AND) ); - // trigger fix for token here - CustomDataTypeAction dataTypeAction = new CustomDataTypeAction(); - dataTypeAction.setName("TOKEN"); - String temp = dataTypeAction.resetDataTypeRetro(); - BackwardCompatibilityDao.instance.updateOne( Filters.eq("_id", backwardCompatibility.getId()), Updates.set(BackwardCompatibility.CHANGE_OPERATOR_CONDITION_IN_CDT, Context.now()) From c5a0e683d1b85ec27f715bde18bd8b497b24fc20 Mon Sep 17 00:00:00 2001 From: notshivansh Date: Fri, 7 Feb 2025 16:06:43 +0530 Subject: [PATCH 19/38] fix data type reset --- .../src/main/java/com/akto/action/CustomDataTypeAction.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/dashboard/src/main/java/com/akto/action/CustomDataTypeAction.java b/apps/dashboard/src/main/java/com/akto/action/CustomDataTypeAction.java index feeadca335..30b7a9a35f 100644 --- a/apps/dashboard/src/main/java/com/akto/action/CustomDataTypeAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/CustomDataTypeAction.java @@ -600,6 +600,11 @@ public String resetDataTypeRetro() { Bson sort = Sorts.ascending("_id.apiCollectionId", "_id.url", "_id.method"); List responses = new ArrayList<>(); this.customSubTypeMatches = new ArrayList<>(); + + SensitiveSampleDataDao.instance.getMCollection().deleteMany(Filters.eq("_id.subType", name)); + SingleTypeInfoDao.instance.updateMany(Filters.eq(SingleTypeInfo.SUB_TYPE, name), + Updates.set(SingleTypeInfo.SUB_TYPE, SingleTypeInfo.GENERIC)); + do { sampleDataList = SampleDataDao.instance.findAll(Filters.empty(), skip, LIMIT, sort); skip += LIMIT; From a50d99687a93f736b8c8e1c79b0c56ee811abf55 Mon Sep 17 00:00:00 2001 From: notshivansh Date: Fri, 7 Feb 2025 16:10:11 +0530 Subject: [PATCH 20/38] fix query --- .../src/main/java/com/akto/action/CustomDataTypeAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/CustomDataTypeAction.java b/apps/dashboard/src/main/java/com/akto/action/CustomDataTypeAction.java index 30b7a9a35f..932b04e3a1 100644 --- a/apps/dashboard/src/main/java/com/akto/action/CustomDataTypeAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/CustomDataTypeAction.java @@ -603,7 +603,7 @@ public String resetDataTypeRetro() { SensitiveSampleDataDao.instance.getMCollection().deleteMany(Filters.eq("_id.subType", name)); SingleTypeInfoDao.instance.updateMany(Filters.eq(SingleTypeInfo.SUB_TYPE, name), - Updates.set(SingleTypeInfo.SUB_TYPE, SingleTypeInfo.GENERIC)); + Updates.set(SingleTypeInfo.SUB_TYPE, SingleTypeInfo.GENERIC.getName())); do { sampleDataList = SampleDataDao.instance.findAll(Filters.empty(), skip, LIMIT, sort); From 1fc88fb2e7525601fbaf83f9121a3f5c198f060c Mon Sep 17 00:00:00 2001 From: notshivansh Date: Fri, 7 Feb 2025 20:43:41 +0530 Subject: [PATCH 21/38] testing read preference set to primary --- apps/testing/src/main/java/com/akto/testing/Main.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/testing/src/main/java/com/akto/testing/Main.java b/apps/testing/src/main/java/com/akto/testing/Main.java index c524289f89..ba0c9b7b49 100644 --- a/apps/testing/src/main/java/com/akto/testing/Main.java +++ b/apps/testing/src/main/java/com/akto/testing/Main.java @@ -334,10 +334,7 @@ public void run() { public static void main(String[] args) throws InterruptedException { String mongoURI = System.getenv("AKTO_MONGO_CONN"); - ReadPreference readPreference = ReadPreference.secondary(); - if(DashboardMode.isOnPremDeployment()){ - readPreference = ReadPreference.primary(); - } + ReadPreference readPreference = ReadPreference.primary(); WriteConcern writeConcern = WriteConcern.W1; DaoInit.init(new ConnectionString(mongoURI), readPreference, writeConcern); From 94127fbdedd74c10fbc0d1c31261b9ddac3b1df3 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Fri, 7 Feb 2025 21:10:09 -0800 Subject: [PATCH 22/38] show compliance to akto users only --- .../components/layouts/leftnav/LeftNav.js | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/leftnav/LeftNav.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/leftnav/LeftNav.js index 0424ead757..15d0430f89 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/leftnav/LeftNav.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/components/layouts/leftnav/LeftNav.js @@ -30,6 +30,30 @@ export default function LeftNav() { setLeftNavSelected(selectedId); }; + let reportsSubNavigationItems = [ + { + label: "Issues", + onClick: () => { + navigate("/dashboard/reports/issues"); + handleSelect("dashboard_reports_issues"); + setActive("active"); + }, + selected: leftNavSelected === "dashboard_reports_issues", + } + ] + + if (window.USER_NAME.indexOf("@akto.io")) { + reportsSubNavigationItems.push({ + label: "Compliance", + onClick: () => { + navigate("/dashboard/reports/compliance"); + handleSelect("dashboard_reports_compliance"); + setActive("active"); + }, + selected: leftNavSelected === "dashboard_reports_compliance", + }) + } + const navigationMarkup = (
@@ -210,26 +234,7 @@ export default function LeftNav() { setActive("normal"); }, selected: leftNavSelected.includes("_reports"), - subNavigationItems: [ - { - label: "Issues", - onClick: () => { - navigate("/dashboard/reports/issues"); - handleSelect("dashboard_reports_issues"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_reports_issues", - }, - { - label: "Compliance", - onClick: () => { - navigate("/dashboard/reports/compliance"); - handleSelect("dashboard_reports_compliance"); - setActive("active"); - }, - selected: leftNavSelected === "dashboard_reports_compliance", - } - ], + subNavigationItems: reportsSubNavigationItems, key: "6", }, window?.STIGG_FEATURE_WISE_ALLOWED?.THREAT_DETECTION?.isGranted From e50067eb396a6272e239568cdf61bf69591271a1 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Sat, 8 Feb 2025 01:26:29 -0800 Subject: [PATCH 23/38] add handling for absent compliance --- .../dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx index 0cbfd945b9..00b0e86dcf 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/CriticalFindingsGraph.jsx @@ -32,7 +32,7 @@ const CriticalFindingsGraph = ({ linkText, linkUrl, complianceMode }) => { let tempResultSubCategoryMap = {} if (complianceMode) { Object.entries(subcategoryDataResp).forEach(([testId, count]) => { - let clauses = subCategoryMap[testId]?.compliance.mapComplianceToListClauses[complianceMode] + let clauses = (subCategoryMap[testId]?.compliance?.mapComplianceToListClauses || {})[complianceMode] || [] clauses.forEach(clause => { tempResultSubCategoryMap[clause] = tempResultSubCategoryMap[clause] || {text: 0, key: clause} tempResultSubCategoryMap[clause].text += count From 0444b84d51de2dc4c06b7f3ea4ce2e0f68119220 Mon Sep 17 00:00:00 2001 From: notshivansh Date: Sat, 8 Feb 2025 16:04:17 +0530 Subject: [PATCH 24/38] calculate xmx memory dynamically --- apps/dashboard/Dockerfile | 9 ++++++++- apps/dashboard/entrypoint.sh | 7 +++++++ apps/dashboard/set_xmx.sh | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 apps/dashboard/entrypoint.sh create mode 100644 apps/dashboard/set_xmx.sh diff --git a/apps/dashboard/Dockerfile b/apps/dashboard/Dockerfile index 3f577c0ec8..c03e6a77eb 100644 --- a/apps/dashboard/Dockerfile +++ b/apps/dashboard/Dockerfile @@ -6,5 +6,12 @@ ADD ./target/dashboard.war /var/lib/jetty/webapps/root.war RUN echo "--module=http-forwarded" > /var/lib/jetty/start.d/http-forwarded.ini RUN echo "jetty.httpConfig.sendServerVersion=false" > /var/lib/jetty/start.d/server.ini RUN echo "org.slf4j.simpleLogger.log.org.eclipse.jetty.annotations.AnnotationParser=ERROR" >> /var/lib/jetty/start.d/server.ini -ENV JAVA_OPTIONS="-XX:+ExitOnOutOfMemoryError" + +COPY set_xmx.sh /var/lib/jetty/set_xmx.sh +RUN chmod +x /var/lib/jetty/set_xmx.sh + +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] EXPOSE 8080 \ No newline at end of file diff --git a/apps/dashboard/entrypoint.sh b/apps/dashboard/entrypoint.sh new file mode 100644 index 0000000000..871668e716 --- /dev/null +++ b/apps/dashboard/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Run the memory calculation script to set JAVA_OPTIONS +source /var/lib/jetty/set_xmx.sh + +# Start Jetty normally +exec /docker-entrypoint.sh "$@" diff --git a/apps/dashboard/set_xmx.sh b/apps/dashboard/set_xmx.sh new file mode 100644 index 0000000000..c1305b7897 --- /dev/null +++ b/apps/dashboard/set_xmx.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +echo "Running memory detection script..." + +# 1. Detect and read cgroup memory limits +if [ -f /sys/fs/cgroup/memory.max ]; then + # cgroup v2 + MEM_LIMIT_BYTES=$(cat /sys/fs/cgroup/memory.max) +elif [ -f /sys/fs/cgroup/memory/memory.limit_in_bytes ]; then + # cgroup v1 + MEM_LIMIT_BYTES=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes) +else + # Fallback to free -b (bytes) if cgroup file not found + echo "Neither cgroup v2 nor v1 memory file found, defaulting to free -m" + MEM_LIMIT_BYTES=$(free -b | awk '/Mem:/ {print $2}') +fi + +# 2. Handle edge cases: "max" means no strict limit or a very large limit +if [ "$MEM_LIMIT_BYTES" = "max" ]; then + echo "Cgroup memory limit set to 'max', defaulting to free memory" + MEM_LIMIT_BYTES=$(free -b | awk '/Mem:/ {print $2}') +fi + +# 3. Convert the memory limit from bytes to MB (integer division) +MEM_LIMIT_MB=$((MEM_LIMIT_BYTES / 1024 / 1024)) +echo "Detected container memory limit: ${MEM_LIMIT_MB} MB" + +# 4. Calculate 80% of that limit for Xmx +XMX_MEM=$((MEM_LIMIT_MB * 80 / 100)) +echo "Calculated -Xmx value: ${XMX_MEM} MB" + +# Export JAVA_OPTIONS so Jetty picks it up +export JAVA_OPTIONS="-XX:+ExitOnOutOfMemoryError -Xmx${XMX_MEM}m" + +# Log the final JAVA_OPTIONS value +echo "JAVA_OPTIONS set to: $JAVA_OPTIONS" From ec3b0ab416a66a9836f3316664c3407b53243534 Mon Sep 17 00:00:00 2001 From: notshivansh Date: Sat, 8 Feb 2025 16:20:25 +0530 Subject: [PATCH 25/38] add utility --- apps/dashboard/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/Dockerfile b/apps/dashboard/Dockerfile index c03e6a77eb..816470fc32 100644 --- a/apps/dashboard/Dockerfile +++ b/apps/dashboard/Dockerfile @@ -1,7 +1,7 @@ FROM jetty:9.4-jre8 USER root RUN apt-get update -y -RUN apt-get install -y --no-install-recommends libpcap-dev +RUN apt-get install -y --no-install-recommends libpcap-dev procps ADD ./target/dashboard.war /var/lib/jetty/webapps/root.war RUN echo "--module=http-forwarded" > /var/lib/jetty/start.d/http-forwarded.ini RUN echo "jetty.httpConfig.sendServerVersion=false" > /var/lib/jetty/start.d/server.ini From 5fd7dc9fdfbbb8517df145d08118f51a8ed81402 Mon Sep 17 00:00:00 2001 From: Ankush Jain Date: Sat, 8 Feb 2025 15:01:15 -0800 Subject: [PATCH 26/38] check compliance not null --- .../com/akto/action/testing_issues/IssuesAction.java | 9 ++++++++- .../dashboard/pages/issues/IssuesPage/IssuesPage.jsx | 2 +- .../web/src/apps/dashboard/pages/issues/transform.js | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java b/apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java index ebd6ff5faf..778ce8fd0a 100644 --- a/apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/testing_issues/IssuesAction.java @@ -480,11 +480,18 @@ public static BasicDBObject createSubcategoriesInfoObj(TestConfig testConfig) { BasicDBObject infoObj = new BasicDBObject(); BasicDBObject superCategory = new BasicDBObject(); BasicDBObject severity = new BasicDBObject(); + + ComplianceMapping complianceMapping = info.getCompliance(); + + if (complianceMapping == null) { + complianceMapping = new ComplianceMapping(new HashMap<>(), "", "", 0); + } + infoObj.put("issueDescription", info.getDescription()); infoObj.put("issueDetails", info.getDetails()); infoObj.put("issueImpact", info.getImpact()); infoObj.put("issueTags", info.getTags()); - infoObj.put("compliance", info.getCompliance()); + infoObj.put("compliance", complianceMapping); infoObj.put("testName", info.getName()); infoObj.put("references", info.getReferences()); infoObj.put("cwe", info.getCwe()); diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx index 4a121320eb..bcf75bfb5e 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/IssuesPage/IssuesPage.jsx @@ -452,7 +452,7 @@ function IssuesPage() { uniqueIssuesMap.set(key, { id: item?.id, severity: func.toSentenceCase(item?.severity), - compliance: Object.keys(subCategoryMap[item?.id?.testSubCategory].compliance?.mapComplianceToListClauses || {}), + compliance: Object.keys(subCategoryMap[item?.id?.testSubCategory]?.compliance?.mapComplianceToListClauses || {}), severityType: item?.severity, issueName: item?.id?.testSubCategory, category: item?.id?.testSubCategory, diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/transform.js b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/transform.js index 09805e09b4..72a3a28c2e 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/transform.js +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/issues/transform.js @@ -53,7 +53,7 @@ const transform = { const processedData = await Promise.all( await Promise.all(rawData.map(async (issue, idx) => { const key = `${issue.id.testSubCategory}|${issue.severity}|${issue.testRunIssueStatus}|${idx}` - let totalCompliance = issue.compliance.length + let totalCompliance = (issue.compliance || []).length let maxShowCompliance = 2 let badge = totalCompliance > maxShowCompliance ? +{totalCompliance - maxShowCompliance} : null return { From 31ea676983ae3be408e1951d8edc1589cf66c214 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Sun, 9 Feb 2025 15:41:36 +0530 Subject: [PATCH 27/38] fix: not merging template graphql urls --- .../main/java/com/akto/runtime/APICatalogSync.java | 11 +++++++++++ .../java/com/akto/hybrid_runtime/APICatalogSync.java | 11 +++++++++++ .../src/main/java/com/akto/dto/type/URLTemplate.java | 5 +++++ .../src/main/java/com/akto/graphql/GraphQLUtils.java | 7 +++++++ 4 files changed, 34 insertions(+) diff --git a/apps/api-runtime/src/main/java/com/akto/runtime/APICatalogSync.java b/apps/api-runtime/src/main/java/com/akto/runtime/APICatalogSync.java index 3f0d5ee645..51c4c7967b 100644 --- a/apps/api-runtime/src/main/java/com/akto/runtime/APICatalogSync.java +++ b/apps/api-runtime/src/main/java/com/akto/runtime/APICatalogSync.java @@ -27,6 +27,7 @@ import com.akto.dto.type.SingleTypeInfo.SuperType; import com.akto.dto.type.URLMethods.Method; import com.akto.dto.usage.MetricTypes; +import com.akto.graphql.GraphQLUtils; import com.akto.log.LoggerMaker; import com.akto.log.LoggerMaker.LogDb; import com.akto.util.filter.DictionaryFilter; @@ -739,6 +740,11 @@ public static URLTemplate tryParamteresingUrl(URLStatic newUrl){ SuperType[] newTypes = new SuperType[tokens.length]; int start = newUrl.getUrl().startsWith("http") ? 3 : 0; + + if(GraphQLUtils.isGraphQLEndpoint(newUrl.getUrl())) { + return null; // Don't merge GraphQL endpoints + } + for(int i = start; i < tokens.length; i ++) { String tempToken = tokens[i]; if(DictionaryFilter.isEnglishWord(tempToken)) continue; @@ -804,6 +810,11 @@ public static URLTemplate tryMergeUrls(URLStatic dbUrl, URLStatic newUrl) { SuperType[] newTypes = new SuperType[newTokens.length]; int templatizedStrTokens = 0; + + if(GraphQLUtils.isGraphQLEndpoint(dbUrl.getUrl()) || GraphQLUtils.isGraphQLEndpoint(newUrl.getUrl())) { + return null; // Don't merge GraphQL endpoints + } + for(int i = 0; i < newTokens.length; i ++) { String tempToken = newTokens[i]; String dbToken = dbTokens[i]; diff --git a/apps/mini-runtime/src/main/java/com/akto/hybrid_runtime/APICatalogSync.java b/apps/mini-runtime/src/main/java/com/akto/hybrid_runtime/APICatalogSync.java index c0b529dd37..e871cc65a6 100644 --- a/apps/mini-runtime/src/main/java/com/akto/hybrid_runtime/APICatalogSync.java +++ b/apps/mini-runtime/src/main/java/com/akto/hybrid_runtime/APICatalogSync.java @@ -19,6 +19,7 @@ import com.akto.dto.type.SingleTypeInfo.SubType; import com.akto.dto.type.SingleTypeInfo.SuperType; import com.akto.dto.type.URLMethods.Method; +import com.akto.graphql.GraphQLUtils; import com.akto.log.LoggerMaker; import com.akto.log.LoggerMaker.LogDb; import com.akto.data_actor.DataActor; @@ -525,6 +526,11 @@ public static URLTemplate tryParamteresingUrl(URLStatic newUrl){ SuperType[] newTypes = new SuperType[tokens.length]; int start = newUrl.getUrl().startsWith("http") ? 3 : 0; + + if(GraphQLUtils.isGraphQLEndpoint(newUrl.getUrl())) { + return null; // Don't merge GraphQL endpoints + } + for(int i = start; i < tokens.length; i ++) { String tempToken = tokens[i]; if(DictionaryFilter.isEnglishWord(tempToken)) continue; @@ -590,6 +596,11 @@ public static URLTemplate tryMergeUrls(URLStatic dbUrl, URLStatic newUrl) { SuperType[] newTypes = new SuperType[newTokens.length]; int templatizedStrTokens = 0; + + if(GraphQLUtils.isGraphQLEndpoint(dbUrl.getUrl()) || GraphQLUtils.isGraphQLEndpoint(newUrl.getUrl())) { + return null; // Don't merge GraphQL endpoints + } + for(int i = 0; i < newTokens.length; i ++) { String tempToken = newTokens[i]; String dbToken = dbTokens[i]; diff --git a/libs/dao/src/main/java/com/akto/dto/type/URLTemplate.java b/libs/dao/src/main/java/com/akto/dto/type/URLTemplate.java index 0430ccebf6..ade8a9eaee 100644 --- a/libs/dao/src/main/java/com/akto/dto/type/URLTemplate.java +++ b/libs/dao/src/main/java/com/akto/dto/type/URLTemplate.java @@ -1,5 +1,6 @@ package com.akto.dto.type; +import java.util.Arrays; import java.util.UUID; import com.akto.dao.context.Context; @@ -65,6 +66,10 @@ public boolean match(String[] url, Method urlMethod) { String[] thatTokens = url; if (thatTokens.length != this.tokens.length) return false; + if(Arrays.toString(url).contains("graphql") || Arrays.toString(url).contains("graph")) { + return false; + } + for (int i = 0; i < thatTokens.length; i++) { String thatToken = thatTokens[i]; String thisToken = this.tokens[i]; diff --git a/libs/utils/src/main/java/com/akto/graphql/GraphQLUtils.java b/libs/utils/src/main/java/com/akto/graphql/GraphQLUtils.java index 5a742b13a4..ded895dce9 100644 --- a/libs/utils/src/main/java/com/akto/graphql/GraphQLUtils.java +++ b/libs/utils/src/main/java/com/akto/graphql/GraphQLUtils.java @@ -320,4 +320,11 @@ public List parseGraphQLRequest(Map requestPayload) { } return result; } + + public static boolean isGraphQLEndpoint(String url) { + if(url.contains("graphql") || url.contains("graph")) { + return true; + } + return false; + } } From b0719eaf3454b4057eae6341f0c43036b89c5c16 Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Mon, 10 Feb 2025 11:03:33 +0530 Subject: [PATCH 28/38] Fixed npe to load results --- .../akto/dao/testing/TestingRunResultDao.java | 84 ++++++++++--------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/libs/dao/src/main/java/com/akto/dao/testing/TestingRunResultDao.java b/libs/dao/src/main/java/com/akto/dao/testing/TestingRunResultDao.java index 1eea09acb0..36cb68df58 100644 --- a/libs/dao/src/main/java/com/akto/dao/testing/TestingRunResultDao.java +++ b/libs/dao/src/main/java/com/akto/dao/testing/TestingRunResultDao.java @@ -140,49 +140,55 @@ public List fetchLatestTestingRunResult(Bson filters, int limi while (cursor.hasNext()) { TestingRunResult testingRunResult = new TestingRunResult(); BasicDBObject doc = cursor.next(); - testingRunResult.setId(new ObjectId(doc.getString(Constants.ID))); - testingRunResult.setTestRunId(new ObjectId(doc.getString(TestingRunResult.TEST_RUN_ID))); - BasicDBObject apiInfoKeyObj = (BasicDBObject) doc.get(TestingRunResult.API_INFO_KEY); - ApiInfoKey apiInfoKey = new ApiInfoKey(apiInfoKeyObj.getInt(ApiInfoKey.API_COLLECTION_ID), - apiInfoKeyObj.getString(ApiInfoKey.URL), - URLMethods.Method.valueOf(apiInfoKeyObj.getString(ApiInfoKey.METHOD))); - testingRunResult.setApiInfoKey(apiInfoKey); - testingRunResult.setTestSuperType(doc.getString(TestingRunResult.TEST_SUPER_TYPE)); - testingRunResult.setTestSubType(doc.getString(TestingRunResult.TEST_SUB_TYPE)); - testingRunResult.setVulnerable(doc.getBoolean(TestingRunResult.VULNERABLE)); - testingRunResult.setConfidencePercentage(doc.getInt(TestingRunResult.CONFIDENCE_PERCENTAGE)); - testingRunResult.setStartTimestamp(doc.getInt(TestingRunResult.START_TIMESTAMP)); - testingRunResult.setEndTimestamp(doc.getInt(TestingRunResult.END_TIMESTAMP)); - testingRunResult.setTestRunResultSummaryId( - new ObjectId(doc.getString(TestingRunResult.TEST_RUN_RESULT_SUMMARY_ID))); - - BasicDBList testResultsList = (BasicDBList)doc.get(TestingRunResult.TEST_RESULTS); - - List errors = new ArrayList<>(); - List testResults = new ArrayList<>(); - if (testResultsList != null && !testResultsList.isEmpty()) { - BasicDBObject genericTestResult = (BasicDBObject)testResultsList.get(0); - String confidence = ""; - if (genericTestResult.get(GenericTestResult._CONFIDENCE)!=null) { - TestResult testResult = new TestResult(); - confidence = genericTestResult.getString(GenericTestResult._CONFIDENCE); - try { - testResult.setConfidence(Confidence.valueOf(confidence)); - testResults.add(testResult); - } catch(Exception e){ + try { + testingRunResult.setId(new ObjectId(doc.getString(Constants.ID))); + testingRunResult.setTestRunId(new ObjectId(doc.getString(TestingRunResult.TEST_RUN_ID))); + BasicDBObject apiInfoKeyObj = (BasicDBObject) doc.get(TestingRunResult.API_INFO_KEY); + ApiInfoKey apiInfoKey = new ApiInfoKey(apiInfoKeyObj.getInt(ApiInfoKey.API_COLLECTION_ID), + apiInfoKeyObj.getString(ApiInfoKey.URL), + URLMethods.Method.valueOf(apiInfoKeyObj.getString(ApiInfoKey.METHOD))); + testingRunResult.setApiInfoKey(apiInfoKey); + testingRunResult.setTestSuperType(doc.getString(TestingRunResult.TEST_SUPER_TYPE)); + testingRunResult.setTestSubType(doc.getString(TestingRunResult.TEST_SUB_TYPE)); + testingRunResult.setVulnerable(doc.getBoolean(TestingRunResult.VULNERABLE)); + testingRunResult.setConfidencePercentage(doc.getInt(TestingRunResult.CONFIDENCE_PERCENTAGE)); + testingRunResult.setStartTimestamp(doc.getInt(TestingRunResult.START_TIMESTAMP)); + testingRunResult.setEndTimestamp(doc.getInt(TestingRunResult.END_TIMESTAMP)); + testingRunResult.setTestRunResultSummaryId( + new ObjectId(doc.getString(TestingRunResult.TEST_RUN_RESULT_SUMMARY_ID))); + + BasicDBList testResultsList = (BasicDBList)doc.get(TestingRunResult.TEST_RESULTS); + + List errors = new ArrayList<>(); + List testResults = new ArrayList<>(); + if (testResultsList != null && !testResultsList.isEmpty()) { + BasicDBObject genericTestResult = (BasicDBObject)testResultsList.get(0); + String confidence = ""; + if (genericTestResult.get(GenericTestResult._CONFIDENCE)!=null) { + TestResult testResult = new TestResult(); + confidence = genericTestResult.getString(GenericTestResult._CONFIDENCE); + try { + testResult.setConfidence(Confidence.valueOf(confidence)); + testResults.add(testResult); + } catch(Exception e){ + } } - } - if (genericTestResult.get(TestResult._ERRORS)!=null) { - try { - errors = (List)genericTestResult.get(TestResult._ERRORS); - } catch(Exception e){ + if (genericTestResult.get(TestResult._ERRORS)!=null) { + try { + errors = (List)genericTestResult.get(TestResult._ERRORS); + } catch(Exception e){ + } } } + testingRunResult.setErrorsList(errors); + testingRunResult.setTestResults(testResults); + testingRunResult.setHexId(testingRunResult.getId().toHexString()); + testingRunResults.add(testingRunResult); + } catch (Exception e) { + e.printStackTrace();; + continue; } - testingRunResult.setErrorsList(errors); - testingRunResult.setTestResults(testResults); - testingRunResult.setHexId(testingRunResult.getId().toHexString()); - testingRunResults.add(testingRunResult); + } return testingRunResults; From 790958642ffa5f4b4912b50e835a18003cf1698c Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Thu, 30 Jan 2025 17:08:12 +0530 Subject: [PATCH 29/38] feat: enhance SSO integration with additional authentication URLs and email handling --- .../main/java/com/akto/action/HomeAction.java | 49 ++++++++++++++++--- .../java/com/akto/action/SignupAction.java | 23 ++++++--- .../com/akto/action/user/AzureSsoAction.java | 4 +- .../main/java/com/akto/utils/GithubLogin.java | 38 ++++++++++++++ .../main/java/com/akto/utils/OktaLogin.java | 4 +- .../java/com/akto/utils/sso/SsoUtils.java | 2 +- apps/dashboard/web/pages/login.jsp | 6 ++- .../pages/settings/integrations/GithubSso.jsx | 3 +- .../settings/integrations/sso/AzureSso.jsx | 4 ++ .../integrations/sso/CustomSamlSso.jsx | 4 +- .../integrations/sso/GoogleSamlSso.jsx | 4 ++ .../web/src/apps/signup/components/SignUp.jsx | 33 +++++++++---- .../src/main/java/com/akto/dto/Config.java | 22 +++++++++ .../main/java/com/akto/dto/SignupInfo.java | 12 ++++- 14 files changed, 175 insertions(+), 33 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java index 5d84c442f7..0c913a901d 100644 --- a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java @@ -1,14 +1,19 @@ package com.akto.action; import com.akto.dao.UsersDao; +import com.akto.dto.Config; import com.akto.dto.User; +import com.akto.dto.sso.SAMLConfig; import com.akto.listener.InitializerListener; import com.akto.utils.*; import com.akto.util.DashboardMode; +import com.akto.utils.sso.CustomSamlSettings; import com.auth0.AuthorizeUrl; import com.auth0.SessionUtils; import com.mongodb.BasicDBObject; import com.mongodb.client.model.Filters; +import com.onelogin.saml2.authn.AuthnRequest; +import com.onelogin.saml2.settings.Saml2Settings; import com.opensymphony.xwork2.Action; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; @@ -20,9 +25,13 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Map; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; import static com.akto.action.SignupAction.*; import static com.akto.filter.UserDetailsFilter.LOGIN_URI; @@ -50,14 +59,38 @@ public String verifyEmail(){ public String execute() { servletRequest.setAttribute("isSaas", InitializerListener.isSaas); - if (GithubLogin.getClientId() != null) { - servletRequest.setAttribute("githubClientId", new String(Base64.getEncoder().encode(GithubLogin.getClientId().getBytes()))); - } - if (GithubLogin.getGithubUrl() != null) { - servletRequest.setAttribute("githubUrl", GithubLogin.getGithubUrl()); - } - if(DashboardMode.isOnPremDeployment() && OktaLogin.getAuthorisationUrl() != null){ - servletRequest.setAttribute("oktaAuthUrl", new String(Base64.getEncoder().encode(OktaLogin.getAuthorisationUrl().getBytes()))); + if(DashboardMode.isOnPremDeployment()) { + if (GithubLogin.getGithubUrl() != null) { + servletRequest.setAttribute("githubAuthUrl", GithubLogin.getGithubUrl() + "/login/oauth/authorize?client_id=" + GithubLogin.getClientId() + "&scope=user&state=1000000"); + servletRequest.setAttribute("activeSso", Config.ConfigType.GITHUB); + } else if (OktaLogin.getAuthorisationUrl() != null) { + servletRequest.setAttribute("oktaAuthUrl", OktaLogin.getAuthorisationUrl()); + servletRequest.setAttribute("activeSso", Config.ConfigType.OKTA); + } else if (Config.AzureConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) { + try { + SAMLConfig samlConfig = Config.AzureConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.AZURE); + Saml2Settings samlSettings = CustomSamlSettings.getSamlSettings(samlConfig); + String samlRequestXml = new AuthnRequest(samlSettings).getAuthnRequestXml(); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + Deflater deflater = new Deflater(Deflater.DEFLATED, true); + DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater); + deflaterOutputStream.write(samlRequestXml.getBytes(StandardCharsets.UTF_8)); + deflaterOutputStream.close(); + String base64Encoded = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); + String urlEncoded = URLEncoder.encode(base64Encoded, "UTF-8"); + + servletRequest.setAttribute("azureAuthUrl", samlConfig.getLoginUrl() + "?SAMLRequest=" + urlEncoded + "&RelayState=" + 1000000); + servletRequest.setAttribute("activeSso", Config.ConfigType.AZURE); + } catch (Exception e) { + e.printStackTrace(); + logger.error(e.getMessage()); + } + } else if (Config.GoogleConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML) != null) { + Config.GoogleConfig googleSamlConfig = (Config.GoogleConfig) Config.GoogleConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML); + servletRequest.setAttribute("googleSamlAuthUrl", googleSamlConfig.getAuthURI()); + servletRequest.setAttribute("activeSso", Config.ConfigType.GOOGLE_SAML); + } } if (InitializerListener.aktoVersion != null && InitializerListener.aktoVersion.contains("akto-release-version")) { servletRequest.setAttribute("AktoVersionGlobal", ""); diff --git a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java index 816139f502..042f5581b6 100644 --- a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java @@ -457,6 +457,7 @@ public String registerViaGithub() { params.put("client_id", githubConfig.getClientId()); params.put("client_secret", githubConfig.getClientSecret()); params.put("code", this.code); + params.put("scope", "user"); logger.info("Github code length: {}", this.code.length()); try { String githubUrl = githubConfig.getGithubUrl(); @@ -491,12 +492,17 @@ public String registerViaGithub() { int refreshTokenExpiry = (int) Double.parseDouble(tokenData.getOrDefault("refresh_token_expires_in", "0").toString()); Map userData = CustomHttpRequest.getRequest(githubApiUrl + "/user", "Bearer " + accessToken); logger.info("Get request to {} success", githubApiUrl); - String company = "sso"; - String username = userData.get("login").toString() + "@" + company; + + List> emailResp = GithubLogin.getEmailRequest(accessToken); + String username = userData.get("name").toString(); + String email = GithubLogin.getPrimaryGithubEmail(emailResp); + if(email == null || email.isEmpty()) { + email = username + "@sso"; + } logger.info("username {}", username); - SignupInfo.GithubSignupInfo ghSignupInfo = new SignupInfo.GithubSignupInfo(accessToken, refreshToken, refreshTokenExpiry, username); + SignupInfo.GithubSignupInfo ghSignupInfo = new SignupInfo.GithubSignupInfo(accessToken, refreshToken, refreshTokenExpiry, email, username); shouldLogin = "true"; - createUserAndRedirect(username, username, ghSignupInfo, 1000000, Config.ConfigType.GITHUB.toString()); + createUserAndRedirect(email, username, ghSignupInfo, 1000000, Config.ConfigType.GITHUB.toString(), RBAC.Role.MEMBER); code = ""; logger.info("Executed registerViaGithub"); @@ -587,7 +593,7 @@ public String fetchDefaultInviteRole(int accountId, String fallbackDefault){ public String sendRequestToSamlIdP() throws IOException{ String queryString = servletRequest.getQueryString(); String emailId = Util.getValueFromQueryString(queryString, "email"); - if(emailId.isEmpty()){ + if(!DashboardMode.isOnPremDeployment() && emailId.isEmpty()){ code = "Error, user email cannot be empty"; logger.error(code); servletResponse.sendRedirect("/login"); @@ -595,7 +601,12 @@ public String sendRequestToSamlIdP() throws IOException{ } logger.info("Trying to sign in for: " + emailId); setUserEmail(emailId); - SAMLConfig samlConfig = SSOConfigsDao.instance.getSSOConfig(userEmail); + SAMLConfig samlConfig = null; + if(userEmail != null && !userEmail.isEmpty()) { + samlConfig = SSOConfigsDao.instance.getSSOConfig(userEmail); + } else if(!DashboardMode.isOnPremDeployment()) { + samlConfig = Config.AzureConfig.getSSOConfigByAccountId(1000000, ConfigType.AZURE); + } if(samlConfig == null) { code = "Error, cannot login via SSO, trying to login with okta sso"; logger.error(code); diff --git a/apps/dashboard/src/main/java/com/akto/action/user/AzureSsoAction.java b/apps/dashboard/src/main/java/com/akto/action/user/AzureSsoAction.java index cdd7197be0..6e8f90822a 100644 --- a/apps/dashboard/src/main/java/com/akto/action/user/AzureSsoAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/user/AzureSsoAction.java @@ -41,7 +41,7 @@ private SAMLConfig getConfig(ConfigType configType, String domain){ public String addSamlSsoInfo(){ String userLogin = getSUser().getLogin(); String domain = userLogin.split("@")[1]; - if (SsoUtils.isAnySsoActive(Context.accountId.get())) { + if (SsoUtils.isAnySsoActive()) { addActionError("A SSO Integration already exists."); return ERROR.toUpperCase(); } @@ -79,7 +79,7 @@ public String execute() throws Exception { Filters.eq("configType", configType.name()) ) ); - if (SsoUtils.isAnySsoActive(Context.accountId.get()) && samlConfig == null) { + if (SsoUtils.isAnySsoActive() && samlConfig == null) { addActionError("A different SSO Integration already exists."); return ERROR.toUpperCase(); } diff --git a/apps/dashboard/src/main/java/com/akto/utils/GithubLogin.java b/apps/dashboard/src/main/java/com/akto/utils/GithubLogin.java index 7637b81959..bb9a05bcad 100644 --- a/apps/dashboard/src/main/java/com/akto/utils/GithubLogin.java +++ b/apps/dashboard/src/main/java/com/akto/utils/GithubLogin.java @@ -4,6 +4,13 @@ import com.akto.dao.context.Context; import com.akto.dto.Config; import com.akto.dto.Config.GithubConfig; +import com.akto.dto.OriginalHttpRequest; +import com.akto.dto.OriginalHttpResponse; +import com.akto.testing.ApiExecutor; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.*; public class GithubLogin { @@ -11,6 +18,7 @@ public class GithubLogin { private static GithubLogin instance = null; private GithubConfig githubConfig = null; private int lastProbeTs = 0; + public static final String GET_GITHUB_EMAILS_URL = "https://api.github.com/user/emails"; public static GithubLogin getInstance() { boolean shouldProbeAgain = true; @@ -52,6 +60,36 @@ public static String getGithubUrl() { return githubUrl; } + public static List> getEmailRequest(String accessToken){ + ObjectMapper objectMapper = new ObjectMapper(); + Map> headers = new HashMap<>(); + headers.put("Content-Type", Collections.singletonList("application/vnd.github+json")); + headers.put("Authorization", Collections.singletonList("Bearer " + accessToken)); + headers.put("X-GitHub-Api-Version", Collections.singletonList("2022-11-28")); + + OriginalHttpRequest request = new OriginalHttpRequest(GET_GITHUB_EMAILS_URL, "", "GET", null, headers, ""); + OriginalHttpResponse response = null; + try { + response = ApiExecutor.sendRequest(request, false, null, false, new ArrayList<>()); + return objectMapper.readValue(response.getBody(), new TypeReference>>() {}); + }catch(Exception e){ + return null; + } + } + + public static String getPrimaryGithubEmail(List> emailResp){ + if(emailResp == null){ + return ""; + }else{ + for (Map entryMap : emailResp) { + if(entryMap.get("primary").equals("true")){ + return entryMap.get("email"); + } + } + } + return null; + } + private GithubLogin() { } diff --git a/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java b/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java index 320040d9fa..a89d3bcbc3 100644 --- a/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java +++ b/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java @@ -24,7 +24,7 @@ public static OktaLogin getInstance() { } if (shouldProbeAgain) { - OktaConfig oktaConfig = (Config.OktaConfig) ConfigsDao.instance.findOne(Constants.ID, OktaConfig.getOktaId(Context.accountId.get())); + OktaConfig oktaConfig = (Config.OktaConfig) ConfigsDao.instance.findOne(Constants.ID, OktaConfig.getOktaId(1000000)); if (instance == null) { instance = new OktaLogin(); } @@ -47,7 +47,7 @@ public static String getAuthorisationUrl() { paramMap.put("redirect_uri",oktaConfig.getRedirectUri()); paramMap.put("response_type", "code"); paramMap.put("scope", "openid%20email%20profile"); - paramMap.put("state", "login"); + paramMap.put("state", String.valueOf(oktaConfig.getAccountId())); String queryString = SsoUtils.getQueryString(paramMap); diff --git a/apps/dashboard/src/main/java/com/akto/utils/sso/SsoUtils.java b/apps/dashboard/src/main/java/com/akto/utils/sso/SsoUtils.java index b2e4b89254..1a131fd586 100644 --- a/apps/dashboard/src/main/java/com/akto/utils/sso/SsoUtils.java +++ b/apps/dashboard/src/main/java/com/akto/utils/sso/SsoUtils.java @@ -49,7 +49,7 @@ public static boolean isAnySsoActive(){ }else{ List ssoList = Arrays.asList(oktaIdString, "GITHUB-ankush", "AZURE-ankush"); Bson filter = Filters.in("_id", ssoList); - return ConfigsDao.instance.count(filter) > 0; + return ConfigsDao.instance.count(filter) > 0 || isAnySsoActive(1000000); } } diff --git a/apps/dashboard/web/pages/login.jsp b/apps/dashboard/web/pages/login.jsp index 817493ed68..2a38088e38 100644 --- a/apps/dashboard/web/pages/login.jsp +++ b/apps/dashboard/web/pages/login.jsp @@ -72,7 +72,11 @@ window.TIME_ZONE = '${requestScope.currentTimeZone}' window.USER_FULL_NAME = '${requestScope.userFullName}' window.ORGANIZATION_NAME = '${requestScope.organizationName}' - window.GOOGLE_SSO_URL=atob('${requestScope.googleSsoUrl}') + window.GOOGLE_SAML_AUTH_URL=atob('${requestScope.googleSamlAuthUrl}') + window.OKTA_AUTH_URL = '${requestScope.oktaAuthUrl}' + window.AZURE_AUTH_URL = '${requestScope.azureAuthUrl}' + window.GITHUB_AUTH_URL = '${requestScope.githubAuthUrl}' + window.ACTIVE_SSO = '${requestScope.activeSso}' window.STIGG_IS_OVERAGE='${requestScope.stiggIsOverage}' window.USAGE_PAUSED=JSON.parse('${requestScope.usagePaused}' || '{}'); diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx index a13631a2c4..47fdef50a0 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx @@ -13,7 +13,7 @@ function GithubSso() { const [showGithubSsoModal, setShowGithubSsoModal] = useState(false) const [githubPresent, setGithubPresent] = useState("") const [componentType, setComponentType] = useState(0) ; - const [nextButtonActive,setNextButtonActive] = useState(window.DASHBOARD_MODE === "ON_PREM"); + const [nextButtonActive,setNextButtonActive] = useState(); const [githubUrl, setGithubUrl] = useState("https://github.com") const [githubApiUrl, setGithubApiUrl] = useState("https://api.github.com") @@ -61,6 +61,7 @@ function GithubSso() { setGithubClientId(githubClientId) if (githubUrl) setGithubUrl(githubUrl) if (githubApiUrl) setGithubApiUrl(githubApiUrl) + setNextButtonActive(true) } catch (error) { setNextButtonActive(false) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/AzureSso.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/AzureSso.jsx index 3ef51af866..1865e7af94 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/AzureSso.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/AzureSso.jsx @@ -15,6 +15,7 @@ function AzureSso() { const [loginUrl, setLoginUrl] = useState('') const [azureIdentity, setAzureIdentity] = useState('') + const [nextButtonActive, setNextButtonActive] = useState() const cardContent = "Enable Login via Azure AD on your Akto dashboard"; @@ -62,10 +63,12 @@ function AzureSso() { await settingRequests.fetchAzureSso("AZURE").then((resp)=> { setLoginUrl(resp.loginUrl) setAzureIdentity(resp.ssoEntityId) + setNextButtonActive(true) }) setLoading(false) } catch (error) { setLoading(false) + setNextButtonActive(false) } } @@ -92,6 +95,7 @@ function AzureSso() { pageTitle={"Azure AD SSO SAML"} loading={loading} certificateName={"Federation Metadata XML"} + isButtonActive={nextButtonActive} /> ) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/CustomSamlSso.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/CustomSamlSso.jsx index d34d18399e..c28756fab7 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/CustomSamlSso.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/CustomSamlSso.jsx @@ -9,7 +9,7 @@ import func from "@/util/func" import Details from '../components/Details'; import { CancelMajor } from "@shopify/polaris-icons" -function CustomSamlSso({ssoType,entityTitle, entityId, loginURL,pageTitle, signinUrl, integrationSteps, cardContent, handleSubmitOutSide, handleDeleteOutside, samlUrlDocs, loading, showCustomInputs, certificateName}) { +function CustomSamlSso({ssoType,entityTitle, entityId, loginURL,pageTitle, signinUrl, integrationSteps, cardContent, handleSubmitOutSide, handleDeleteOutside, samlUrlDocs, loading, showCustomInputs, certificateName, isButtonActive}) { const [componentType, setComponentType] = useState(0) ; const [showDeleteModal, setShowDeleteModal] = useState(false); const [files, setFiles] = useState(null) @@ -17,7 +17,7 @@ function CustomSamlSso({ssoType,entityTitle, entityId, loginURL,pageTitle, signi const [identifier, setIdentifier] = useState('') const stepsComponent = ( - setComponentType(1)} buttonActive={true}/> + setComponentType(1)} buttonActive={isButtonActive}/> ) const setFilesCheck = (file) => { diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/GoogleSamlSso.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/GoogleSamlSso.jsx index 6b9f776a67..7111c07eec 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/GoogleSamlSso.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/GoogleSamlSso.jsx @@ -15,6 +15,7 @@ function GoogleSamlSso() { const [loginUrl, setLoginUrl] = useState('') const [ssoIdentity, setSsoIdentity] = useState('') + const [nextButtonActive, setNextButtonActive] = useState() const cardContent = "Enable Login via Google Workspace on your Akto dashboard"; @@ -60,10 +61,12 @@ function GoogleSamlSso() { await settingRequests.fetchAzureSso("GOOGLE_SAML").then((resp)=> { setLoginUrl(resp.loginUrl) setSsoIdentity(resp.ssoEntityId) + setNextButtonActive(true) }) setLoading(false) } catch (error) { setLoading(false) + setNextButtonActive(false) } } @@ -91,6 +94,7 @@ function GoogleSamlSso() { showCustomInputs={true} certificateName={"X509 certificate"} signinUrl={AcsUrl} + isButtonActive={nextButtonActive} /> ) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx index 8434ea93bd..35cabca907 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx @@ -35,34 +35,49 @@ function SignUp() { })) } - const oktaUrl = window.OKTA_AUTH_URL - const githubId = window.GITHUB_CLIENT_ID - const githubUrl = window.GITHUB_URL ? window.GITHUB_URL : "https://github.com" + const activeSSO = window.ACTIVE_SSO + const githubAuthUrl = window.GITHUB_AUTH_URL + const oktaAuthUrl = window.OKTA_AUTH_URL + const azureAuthUrl = window.AZURE_AUTH_URL const resetAll = PersistStore(state => state.resetAll) const { clearPollingInterval } = usePolling(); const githubAuthObj = { logo: '/public/github_icon.svg', text: 'Continue with Github SSO', - onClickFunc: () => { window.location.href = (githubUrl + "/login/oauth/authorize?client_id=" + githubId); } + onClickFunc: () => { window.location.href = githubAuthUrl } } const oktaAuthObj = { logo: '/public/okta_logo.svg', text: 'Continue with Okta SSO', - onClickFunc: () => { window.location.href = oktaUrl } + onClickFunc: () => { window.location.href = oktaAuthUrl } + } + + const azureAuthObj = { + logo: '/public/azure_logo.svg', + text: 'Continue with Azure SSO', + onClickFunc: () => { window.location.href = azureAuthUrl } + } + + const googleSamlAuthObj = { + logo: '/public/gcp.svg', + text: 'Continue with Google SAML SSO', + onClickFunc: () => { window.location.href = "" } } useEffect(() => { resetAll() clearPollingInterval() let copySsoList = [] - if (githubId !== undefined && githubId.length > 0) { + if (activeSSO?.toLowerCase() === "github" && githubAuthUrl?.length > 0) { copySsoList.push(githubAuthObj) - } - - if (oktaUrl !== undefined && oktaUrl.length > 0) { + } else if(activeSSO?.toLowerCase() === "okta" && oktaAuthUrl?.length > 0) { copySsoList.push(oktaAuthObj) + } else if(activeSSO?.toLowerCase() === "azure" && azureAuthUrl?.length > 0) { + copySsoList.push(azureAuthObj) + } else if(activeSSO?.toLowerCase() === "google_saml") { + copySsoList.push(googleSamlAuthObj) } setSsoList(copySsoList) diff --git a/libs/dao/src/main/java/com/akto/dto/Config.java b/libs/dao/src/main/java/com/akto/dto/Config.java index 63c67da9da..41af492eb4 100644 --- a/libs/dao/src/main/java/com/akto/dto/Config.java +++ b/libs/dao/src/main/java/com/akto/dto/Config.java @@ -5,6 +5,9 @@ import java.util.Set; import com.akto.dao.ConfigsDao; +import com.akto.dao.SSOConfigsDao; +import com.akto.dto.sso.SAMLConfig; +import com.akto.util.Constants; import com.mongodb.client.model.Filters; import org.bson.codecs.pojo.annotations.BsonDiscriminator; @@ -101,6 +104,16 @@ public GoogleConfig() { this.id = configType.name()+"-ankush"; } + public static Config getSSOConfigByAccountId(int accountId, ConfigType configType) { + return ConfigsDao.instance.findOne( + Filters.and( + Filters.eq(Constants.ID, configType.name()+CONFIG_SALT), + Filters.eq(OktaConfig.ACCOUNT_ID, accountId), + Filters.eq("configType", configType.name()) + ) + ); + } + public String getClientId() { return clientId; } @@ -539,6 +552,15 @@ public AzureConfig() { this.id = CONFIG_ID; } + public static SAMLConfig getSSOConfigByAccountId(int accountId, ConfigType configType) { + return SSOConfigsDao.instance.findOne( + Filters.and( + Filters.eq(Constants.ID, String.valueOf(accountId)), + Filters.eq("configType", configType.name()) + ) + ); + } + public String getX509Certificate() { return x509Certificate; } diff --git a/libs/dao/src/main/java/com/akto/dto/SignupInfo.java b/libs/dao/src/main/java/com/akto/dto/SignupInfo.java index 15024de460..722e4e35f9 100644 --- a/libs/dao/src/main/java/com/akto/dto/SignupInfo.java +++ b/libs/dao/src/main/java/com/akto/dto/SignupInfo.java @@ -389,16 +389,18 @@ public static class GithubSignupInfo extends SignupInfo { private String accessToken; private String refreshToken; private int refreshTokenExpiry; + private String email; private String username; public GithubSignupInfo() { } - public GithubSignupInfo(String accessToken, String refreshToken, int refreshTokenExpiry, String username) { + public GithubSignupInfo(String accessToken, String refreshToken, int refreshTokenExpiry, String username, String email) { this.accessToken = accessToken; this.refreshToken = refreshToken; this.refreshTokenExpiry = refreshTokenExpiry; + this.email = email; this.username = username; this.configType = Config.ConfigType.GITHUB; this.key = this.configType.name(); @@ -435,6 +437,14 @@ public String getUsername() { public void setUsername(String username) { this.username = username; } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } } public static class OktaSignupInfo extends SignupInfo { From 798b02543d10352a0613552c7bcd6da59715897f Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Fri, 31 Jan 2025 00:35:54 +0530 Subject: [PATCH 30/38] fix: getting default role based on the rbac feature for all sso functions --- .../java/com/akto/action/SignupAction.java | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java index 042f5581b6..f30f81663c 100644 --- a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java @@ -502,7 +502,7 @@ public String registerViaGithub() { logger.info("username {}", username); SignupInfo.GithubSignupInfo ghSignupInfo = new SignupInfo.GithubSignupInfo(accessToken, refreshToken, refreshTokenExpiry, email, username); shouldLogin = "true"; - createUserAndRedirect(email, username, ghSignupInfo, 1000000, Config.ConfigType.GITHUB.toString(), RBAC.Role.MEMBER); + createUserAndRedirectWithDefaultRole(email, username, ghSignupInfo, 1000000, Config.ConfigType.GITHUB.toString()); code = ""; logger.info("Executed registerViaGithub"); @@ -557,14 +557,8 @@ public String registerViaOkta() throws IOException{ String username = userInfo.get("preferred_username").toString(); SignupInfo.OktaSignupInfo oktaSignupInfo= new SignupInfo.OktaSignupInfo(accessToken, username); - - String defaultRole = RBAC.Role.MEMBER.name(); - if (UsageMetricCalculator.isRbacFeatureAvailable(accountId)) { - defaultRole = fetchDefaultInviteRole(accountId, RBAC.Role.GUEST.name()); - } - shouldLogin = "true"; - createUserAndRedirect(email, username, oktaSignupInfo, accountId, Config.ConfigType.OKTA.toString(), defaultRole); + createUserAndRedirectWithDefaultRole(email, username, oktaSignupInfo, accountId, Config.ConfigType.OKTA.toString()); code = ""; } catch (Exception e) { loggerMaker.errorAndAddToDb("Error while signing in via okta sso \n" + e.getMessage(), LogDb.DASHBOARD); @@ -692,12 +686,7 @@ public String registerViaAzure() throws Exception{ logger.info("Successful signing with Azure Idp for: "+ useremail); SignupInfo.SamlSsoSignupInfo signUpInfo = new SignupInfo.SamlSsoSignupInfo(username, useremail, Config.ConfigType.AZURE); - String defaultRole = RBAC.Role.MEMBER.name(); - if (UsageMetricCalculator.isRbacFeatureAvailable(this.accountId)) { - defaultRole = fetchDefaultInviteRole(this.accountId,RBAC.Role.GUEST.name()); - } - - createUserAndRedirect(useremail, username, signUpInfo, this.accountId, Config.ConfigType.AZURE.toString(), defaultRole); + createUserAndRedirectWithDefaultRole(useremail, username, signUpInfo, this.accountId, Config.ConfigType.AZURE.toString()); } catch (Exception e1) { loggerMaker.errorAndAddToDb("Error while signing in via azure sso \n" + e1.getMessage(), LogDb.DASHBOARD); servletResponse.sendRedirect("/login"); @@ -747,12 +736,7 @@ public String registerViaGoogleSamlSso() throws IOException{ shouldLogin = "true"; SignupInfo.SamlSsoSignupInfo signUpInfo = new SignupInfo.SamlSsoSignupInfo(username, userEmail, Config.ConfigType.GOOGLE_SAML); - String defaultRole = RBAC.Role.MEMBER.name(); - if (UsageMetricCalculator.isRbacFeatureAvailable(this.accountId)) { - defaultRole = fetchDefaultInviteRole(this.accountId, RBAC.Role.GUEST.name()); - } - - createUserAndRedirect(userEmail, username, signUpInfo, this.accountId, Config.ConfigType.GOOGLE_SAML.toString(), defaultRole); + createUserAndRedirectWithDefaultRole(userEmail, username, signUpInfo, this.accountId, Config.ConfigType.GOOGLE_SAML.toString()); } catch (Exception e1) { loggerMaker.errorAndAddToDb("Error while signing in via google workspace sso \n" + e1.getMessage(), LogDb.DASHBOARD); servletResponse.sendRedirect("/login"); @@ -839,6 +823,15 @@ private void createUserAndRedirect(String userEmail, String username, SignupInfo createUserAndRedirect(userEmail, username, signupInfo, invitationToAccount, method, null); } + private void createUserAndRedirectWithDefaultRole(String userEmail, String username, SignupInfo signupInfo, + int invitationToAccount, String method) throws IOException { + String defaultRole = RBAC.Role.MEMBER.name(); + if (UsageMetricCalculator.isRbacFeatureAvailable(invitationToAccount)) { + defaultRole = fetchDefaultInviteRole(invitationToAccount, RBAC.Role.GUEST.name()); + } + createUserAndRedirect(userEmail, username, signupInfo, invitationToAccount, method, defaultRole); + } + private void createUserAndRedirect(String userEmail, String username, SignupInfo signupInfo, int invitationToAccount, String method, String invitedRole) throws IOException { loggerMaker.infoAndAddToDb("createUserAndRedirect called"); From 8ed5d64c9342b51d1b6252f27e28710dd7dc9aa2 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:31:48 +0530 Subject: [PATCH 31/38] fixed sso bugs and cleaned code --- .../main/java/com/akto/action/HomeAction.java | 67 ++++++++++--------- .../java/com/akto/action/SignupAction.java | 12 ++-- .../main/java/com/akto/utils/OktaLogin.java | 4 +- .../java/com/akto/utils/sso/SsoUtils.java | 3 +- .../pages/settings/integrations/GithubSso.jsx | 11 ++- .../src/main/java/com/akto/dto/Config.java | 19 ------ .../java/com/akto/dto/sso/SAMLConfig.java | 12 ++++ 7 files changed, 71 insertions(+), 57 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java index 0c913a901d..255551f428 100644 --- a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java @@ -59,39 +59,44 @@ public String verifyEmail(){ public String execute() { servletRequest.setAttribute("isSaas", InitializerListener.isSaas); - if(DashboardMode.isOnPremDeployment()) { - if (GithubLogin.getGithubUrl() != null) { - servletRequest.setAttribute("githubAuthUrl", GithubLogin.getGithubUrl() + "/login/oauth/authorize?client_id=" + GithubLogin.getClientId() + "&scope=user&state=1000000"); - servletRequest.setAttribute("activeSso", Config.ConfigType.GITHUB); - } else if (OktaLogin.getAuthorisationUrl() != null) { - servletRequest.setAttribute("oktaAuthUrl", OktaLogin.getAuthorisationUrl()); - servletRequest.setAttribute("activeSso", Config.ConfigType.OKTA); - } else if (Config.AzureConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) { - try { - SAMLConfig samlConfig = Config.AzureConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.AZURE); - Saml2Settings samlSettings = CustomSamlSettings.getSamlSettings(samlConfig); - String samlRequestXml = new AuthnRequest(samlSettings).getAuthnRequestXml(); - - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - Deflater deflater = new Deflater(Deflater.DEFLATED, true); - DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater); - deflaterOutputStream.write(samlRequestXml.getBytes(StandardCharsets.UTF_8)); - deflaterOutputStream.close(); - String base64Encoded = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); - String urlEncoded = URLEncoder.encode(base64Encoded, "UTF-8"); - - servletRequest.setAttribute("azureAuthUrl", samlConfig.getLoginUrl() + "?SAMLRequest=" + urlEncoded + "&RelayState=" + 1000000); - servletRequest.setAttribute("activeSso", Config.ConfigType.AZURE); - } catch (Exception e) { - e.printStackTrace(); - logger.error(e.getMessage()); - } - } else if (Config.GoogleConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML) != null) { - Config.GoogleConfig googleSamlConfig = (Config.GoogleConfig) Config.GoogleConfig.getSSOConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML); - servletRequest.setAttribute("googleSamlAuthUrl", googleSamlConfig.getAuthURI()); - servletRequest.setAttribute("activeSso", Config.ConfigType.GOOGLE_SAML); + if (GithubLogin.getGithubUrl() != null) { + servletRequest.setAttribute("githubAuthUrl", GithubLogin.getGithubUrl() + "/login/oauth/authorize?client_id=" + GithubLogin.getClientId() + "&scope=user&state=1000000"); + servletRequest.setAttribute("activeSso", Config.ConfigType.GITHUB); + } + + if (OktaLogin.getAuthorisationUrl() != null) { + servletRequest.setAttribute("oktaAuthUrl", OktaLogin.getAuthorisationUrl()); + servletRequest.setAttribute("activeSso", Config.ConfigType.OKTA); + } + + if (SAMLConfig.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) { + try { + SAMLConfig samlConfig = SAMLConfig.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE); + Saml2Settings samlSettings = CustomSamlSettings.getSamlSettings(samlConfig); + String samlRequestXml = new AuthnRequest(samlSettings).getAuthnRequestXml(); + + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + Deflater deflater = new Deflater(Deflater.DEFLATED, true); + DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater); + deflaterOutputStream.write(samlRequestXml.getBytes(StandardCharsets.UTF_8)); + deflaterOutputStream.close(); + String base64Encoded = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); + String urlEncoded = URLEncoder.encode(base64Encoded, "UTF-8"); + + servletRequest.setAttribute("azureAuthUrl", samlConfig.getLoginUrl() + "?SAMLRequest=" + urlEncoded + "&RelayState=" + 1000000); + servletRequest.setAttribute("activeSso", Config.ConfigType.AZURE); + } catch (Exception e) { + e.printStackTrace(); + logger.error(e.getMessage()); } } + + // TODO("Haven't tested Google SAML SSO") + if (SAMLConfig.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML) != null) { + SAMLConfig googleSamlConfig = SAMLConfig.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML); + servletRequest.setAttribute("googleSamlAuthUrl", googleSamlConfig.getLoginUrl()); + servletRequest.setAttribute("activeSso", Config.ConfigType.GOOGLE_SAML); + } if (InitializerListener.aktoVersion != null && InitializerListener.aktoVersion.contains("akto-release-version")) { servletRequest.setAttribute("AktoVersionGlobal", ""); } else { diff --git a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java index f30f81663c..7f00904eae 100644 --- a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java @@ -526,8 +526,12 @@ public String registerViaOkta() throws IOException{ servletResponse.sendRedirect("/login"); return ERROR.toUpperCase(); } - - setAccountId(1000000); + try { + setAccountId(Integer.parseInt(state)); + } catch (NumberFormatException e) { + servletResponse.sendRedirect("/login"); + return ERROR.toUpperCase(); + } oktaConfig = OktaLogin.getInstance().getOktaConfig(); } else { setAccountId(Integer.parseInt(state)); @@ -598,8 +602,8 @@ public String sendRequestToSamlIdP() throws IOException{ SAMLConfig samlConfig = null; if(userEmail != null && !userEmail.isEmpty()) { samlConfig = SSOConfigsDao.instance.getSSOConfig(userEmail); - } else if(!DashboardMode.isOnPremDeployment()) { - samlConfig = Config.AzureConfig.getSSOConfigByAccountId(1000000, ConfigType.AZURE); + } else if(DashboardMode.isOnPremDeployment()) { + samlConfig = SAMLConfig.getSAMLConfigByAccountId(1000000, ConfigType.AZURE); } if(samlConfig == null) { code = "Error, cannot login via SSO, trying to login with okta sso"; diff --git a/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java b/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java index a89d3bcbc3..c7512f5888 100644 --- a/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java +++ b/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java @@ -24,7 +24,9 @@ public static OktaLogin getInstance() { } if (shouldProbeAgain) { - OktaConfig oktaConfig = (Config.OktaConfig) ConfigsDao.instance.findOne(Constants.ID, OktaConfig.getOktaId(1000000)); + int accountId = Context.accountId.get() != null ? Context.accountId.get() : 1_000_000; + OktaConfig oktaConfig = (Config.OktaConfig) ConfigsDao.instance.findOne(Constants.ID, OktaConfig.getOktaId(accountId)); + if(oktaConfig == null) return null; if (instance == null) { instance = new OktaLogin(); } diff --git a/apps/dashboard/src/main/java/com/akto/utils/sso/SsoUtils.java b/apps/dashboard/src/main/java/com/akto/utils/sso/SsoUtils.java index 1a131fd586..d96eb251d1 100644 --- a/apps/dashboard/src/main/java/com/akto/utils/sso/SsoUtils.java +++ b/apps/dashboard/src/main/java/com/akto/utils/sso/SsoUtils.java @@ -49,7 +49,8 @@ public static boolean isAnySsoActive(){ }else{ List ssoList = Arrays.asList(oktaIdString, "GITHUB-ankush", "AZURE-ankush"); Bson filter = Filters.in("_id", ssoList); - return ConfigsDao.instance.count(filter) > 0 || isAnySsoActive(1000000); + accountId = Context.accountId.get() != null ? Context.accountId.get() : 1_000_000; + return ConfigsDao.instance.count(filter) > 0 || isAnySsoActive(accountId); } } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx index 47fdef50a0..7cb3a829c6 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx @@ -17,6 +17,8 @@ function GithubSso() { const [githubUrl, setGithubUrl] = useState("https://github.com") const [githubApiUrl, setGithubApiUrl] = useState("https://api.github.com") + const [isModalDisabled, setIsModalDisabled] = useState(false) + const location = window.location ; const hostname = location.origin; @@ -44,10 +46,13 @@ function GithubSso() { const addText = "Are you sure you want to add Github SSO Integration? This will enable all members of your GitHub account to access Akto dashboard." const handleDeleteGithubSso = async () => { + setIsModalDisabled(true) const response = await settingRequests.deleteGithubSso() if (response) { func.setToast(true, false, "Github SSO deleted successfully!") setComponentType(0); + setShowGithubSsoModal(false) + setIsModalDisabled(false) } } @@ -72,6 +77,7 @@ function GithubSso() { }, []) const handleAddGithubSso = async () => { + setIsModalDisabled(true) const response = await settingRequests.addGithubSso(githubClientId, githubClientSecret, githubUrl, githubApiUrl) if (response) { if (response.error) { @@ -80,6 +86,8 @@ function GithubSso() { func.setToast(true, false, "Github SSO added successfully!") window.location.reload() } + setShowGithubSsoModal(false) + setIsModalDisabled(false) } } @@ -124,7 +132,8 @@ function GithubSso() { title="Are you sure?" primaryAction={{ content: githubPresent ? 'Delete Github SSO' : 'Add GitHub SSO', - onAction: githubPresent ? handleDeleteGithubSso : handleAddGithubSso + onAction: githubPresent ? handleDeleteGithubSso : handleAddGithubSso, + disabled: isModalDisabled }} > diff --git a/libs/dao/src/main/java/com/akto/dto/Config.java b/libs/dao/src/main/java/com/akto/dto/Config.java index 41af492eb4..31f0c82c65 100644 --- a/libs/dao/src/main/java/com/akto/dto/Config.java +++ b/libs/dao/src/main/java/com/akto/dto/Config.java @@ -104,16 +104,6 @@ public GoogleConfig() { this.id = configType.name()+"-ankush"; } - public static Config getSSOConfigByAccountId(int accountId, ConfigType configType) { - return ConfigsDao.instance.findOne( - Filters.and( - Filters.eq(Constants.ID, configType.name()+CONFIG_SALT), - Filters.eq(OktaConfig.ACCOUNT_ID, accountId), - Filters.eq("configType", configType.name()) - ) - ); - } - public String getClientId() { return clientId; } @@ -552,15 +542,6 @@ public AzureConfig() { this.id = CONFIG_ID; } - public static SAMLConfig getSSOConfigByAccountId(int accountId, ConfigType configType) { - return SSOConfigsDao.instance.findOne( - Filters.and( - Filters.eq(Constants.ID, String.valueOf(accountId)), - Filters.eq("configType", configType.name()) - ) - ); - } - public String getX509Certificate() { return x509Certificate; } diff --git a/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java b/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java index b6c6a73947..d036257a08 100644 --- a/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java +++ b/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java @@ -1,4 +1,7 @@ package com.akto.dto.sso; +import com.akto.dao.SSOConfigsDao; +import com.akto.util.Constants; +import com.mongodb.client.model.Filters; import org.bson.codecs.pojo.annotations.BsonDiscriminator; import com.akto.dto.Config; @@ -43,6 +46,15 @@ public static SAMLConfig convertAzureConfigToSAMLConfig(Config.AzureConfig azure return samlConfig; } + public static SAMLConfig getSAMLConfigByAccountId(int accountId, ConfigType configType) { + return SSOConfigsDao.instance.findOne( + Filters.and( + Filters.eq(Constants.ID, String.valueOf(accountId)), + Filters.eq("configType", configType.name()) + ) + ); + } + public String getApplicationIdentifier() { return applicationIdentifier; } From 7ccc49e97898386f1c3cf4daf0a40e5656bd1dac Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Fri, 7 Feb 2025 00:23:20 +0530 Subject: [PATCH 32/38] moved getSAMLConfigByAccountId method from SAMLConfig to it's DAO --- .../src/main/java/com/akto/action/HomeAction.java | 9 +++++---- .../src/main/java/com/akto/action/SignupAction.java | 2 +- .../dao/src/main/java/com/akto/dao/SSOConfigsDao.java | 11 +++++++++++ .../src/main/java/com/akto/dto/sso/SAMLConfig.java | 9 --------- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java index 255551f428..bca170fb79 100644 --- a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java @@ -1,5 +1,6 @@ package com.akto.action; +import com.akto.dao.SSOConfigsDao; import com.akto.dao.UsersDao; import com.akto.dto.Config; import com.akto.dto.User; @@ -69,9 +70,9 @@ public String execute() { servletRequest.setAttribute("activeSso", Config.ConfigType.OKTA); } - if (SAMLConfig.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) { + if (SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) { try { - SAMLConfig samlConfig = SAMLConfig.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE); + SAMLConfig samlConfig = SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE); Saml2Settings samlSettings = CustomSamlSettings.getSamlSettings(samlConfig); String samlRequestXml = new AuthnRequest(samlSettings).getAuthnRequestXml(); @@ -92,8 +93,8 @@ public String execute() { } // TODO("Haven't tested Google SAML SSO") - if (SAMLConfig.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML) != null) { - SAMLConfig googleSamlConfig = SAMLConfig.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML); + if (SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML) != null) { + SAMLConfig googleSamlConfig = SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML); servletRequest.setAttribute("googleSamlAuthUrl", googleSamlConfig.getLoginUrl()); servletRequest.setAttribute("activeSso", Config.ConfigType.GOOGLE_SAML); } diff --git a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java index 7f00904eae..1f0075f249 100644 --- a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java @@ -603,7 +603,7 @@ public String sendRequestToSamlIdP() throws IOException{ if(userEmail != null && !userEmail.isEmpty()) { samlConfig = SSOConfigsDao.instance.getSSOConfig(userEmail); } else if(DashboardMode.isOnPremDeployment()) { - samlConfig = SAMLConfig.getSAMLConfigByAccountId(1000000, ConfigType.AZURE); + samlConfig = SSOConfigsDao.getSAMLConfigByAccountId(1000000, ConfigType.AZURE); } if(samlConfig == null) { code = "Error, cannot login via SSO, trying to login with okta sso"; diff --git a/libs/dao/src/main/java/com/akto/dao/SSOConfigsDao.java b/libs/dao/src/main/java/com/akto/dao/SSOConfigsDao.java index 68b2c68a07..328a48c114 100644 --- a/libs/dao/src/main/java/com/akto/dao/SSOConfigsDao.java +++ b/libs/dao/src/main/java/com/akto/dao/SSOConfigsDao.java @@ -1,6 +1,8 @@ package com.akto.dao; +import com.akto.dto.Config; import com.akto.dto.sso.SAMLConfig; +import com.akto.util.Constants; import com.mongodb.client.model.Filters; public class SSOConfigsDao extends CommonContextDao { @@ -22,6 +24,15 @@ public SAMLConfig getSSOConfig(String userEmail){ return config; } + public static SAMLConfig getSAMLConfigByAccountId(int accountId, Config.ConfigType configType) { + return SSOConfigsDao.instance.findOne( + Filters.and( + Filters.eq(Constants.ID, String.valueOf(accountId)), + Filters.eq("configType", configType.name()) + ) + ); + } + @Override public String getCollName() { return "sso_configs"; diff --git a/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java b/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java index d036257a08..67d25b6f42 100644 --- a/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java +++ b/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java @@ -46,15 +46,6 @@ public static SAMLConfig convertAzureConfigToSAMLConfig(Config.AzureConfig azure return samlConfig; } - public static SAMLConfig getSAMLConfigByAccountId(int accountId, ConfigType configType) { - return SSOConfigsDao.instance.findOne( - Filters.and( - Filters.eq(Constants.ID, String.valueOf(accountId)), - Filters.eq("configType", configType.name()) - ) - ); - } - public String getApplicationIdentifier() { return applicationIdentifier; } From fbd6eeeb6b6e6960546b493f240309c939bcdce2 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Fri, 7 Feb 2025 00:37:13 +0530 Subject: [PATCH 33/38] improved code quality --- .../web/src/apps/signup/components/SignUp.jsx | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx index 35cabca907..14fe93c060 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx @@ -70,14 +70,25 @@ function SignUp() { resetAll() clearPollingInterval() let copySsoList = [] - if (activeSSO?.toLowerCase() === "github" && githubAuthUrl?.length > 0) { - copySsoList.push(githubAuthObj) - } else if(activeSSO?.toLowerCase() === "okta" && oktaAuthUrl?.length > 0) { - copySsoList.push(oktaAuthObj) - } else if(activeSSO?.toLowerCase() === "azure" && azureAuthUrl?.length > 0) { - copySsoList.push(azureAuthObj) - } else if(activeSSO?.toLowerCase() === "google_saml") { - copySsoList.push(googleSamlAuthObj) + switch (activeSSO?.toLowerCase()) { + case "github": + if (githubAuthUrl?.length > 0) { + copySsoList.push(githubAuthObj); + } + break; + case "okta": + if (oktaAuthUrl?.length > 0) { + copySsoList.push(oktaAuthObj); + } + break; + case "azure": + if (azureAuthUrl?.length > 0) { + copySsoList.push(azureAuthObj); + } + break; + case "google_saml": + copySsoList.push(googleSamlAuthObj); + break; } setSsoList(copySsoList) From 879e95d0d223bf2f6ed688c149f0f0dd97fdc5c4 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 10 Feb 2025 12:08:28 +0530 Subject: [PATCH 34/38] fix: handling azure sso auth url creation in backend --- .../main/java/com/akto/action/HomeAction.java | 28 +------------------ .../web/src/apps/signup/components/SignUp.jsx | 6 ++-- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java index bca170fb79..a8b1ac787d 100644 --- a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java @@ -8,13 +8,10 @@ import com.akto.listener.InitializerListener; import com.akto.utils.*; import com.akto.util.DashboardMode; -import com.akto.utils.sso.CustomSamlSettings; import com.auth0.AuthorizeUrl; import com.auth0.SessionUtils; import com.mongodb.BasicDBObject; import com.mongodb.client.model.Filters; -import com.onelogin.saml2.authn.AuthnRequest; -import com.onelogin.saml2.settings.Saml2Settings; import com.opensymphony.xwork2.Action; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; @@ -26,13 +23,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayOutputStream; -import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.Map; -import java.util.zip.Deflater; -import java.util.zip.DeflaterOutputStream; import static com.akto.action.SignupAction.*; import static com.akto.filter.UserDetailsFilter.LOGIN_URI; @@ -71,25 +63,7 @@ public String execute() { } if (SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) { - try { - SAMLConfig samlConfig = SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE); - Saml2Settings samlSettings = CustomSamlSettings.getSamlSettings(samlConfig); - String samlRequestXml = new AuthnRequest(samlSettings).getAuthnRequestXml(); - - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - Deflater deflater = new Deflater(Deflater.DEFLATED, true); - DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater); - deflaterOutputStream.write(samlRequestXml.getBytes(StandardCharsets.UTF_8)); - deflaterOutputStream.close(); - String base64Encoded = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); - String urlEncoded = URLEncoder.encode(base64Encoded, "UTF-8"); - - servletRequest.setAttribute("azureAuthUrl", samlConfig.getLoginUrl() + "?SAMLRequest=" + urlEncoded + "&RelayState=" + 1000000); - servletRequest.setAttribute("activeSso", Config.ConfigType.AZURE); - } catch (Exception e) { - e.printStackTrace(); - logger.error(e.getMessage()); - } + servletRequest.setAttribute("activeSso", Config.ConfigType.AZURE); } // TODO("Haven't tested Google SAML SSO") diff --git a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx index 14fe93c060..5d9b1c5341 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx @@ -57,7 +57,7 @@ function SignUp() { const azureAuthObj = { logo: '/public/azure_logo.svg', text: 'Continue with Azure SSO', - onClickFunc: () => { window.location.href = azureAuthUrl } + onClickFunc: () => { window.location.href = "/trigger-saml-sso" } } const googleSamlAuthObj = { @@ -82,9 +82,7 @@ function SignUp() { } break; case "azure": - if (azureAuthUrl?.length > 0) { - copySsoList.push(azureAuthObj); - } + copySsoList.push(azureAuthObj); break; case "google_saml": copySsoList.push(googleSamlAuthObj); From 9ec176047cc79b3a06be3493cbc904d4689703c3 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 10 Feb 2025 12:27:59 +0530 Subject: [PATCH 35/38] fix: removed nextButtonActive state and handling it in backend --- .../apps/dashboard/pages/settings/integrations/GithubSso.jsx | 3 +-- .../dashboard/pages/settings/integrations/sso/AzureSso.jsx | 4 ---- .../pages/settings/integrations/sso/GoogleSamlSso.jsx | 4 ---- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx index 7cb3a829c6..60cd9d9aa4 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/GithubSso.jsx @@ -13,7 +13,7 @@ function GithubSso() { const [showGithubSsoModal, setShowGithubSsoModal] = useState(false) const [githubPresent, setGithubPresent] = useState("") const [componentType, setComponentType] = useState(0) ; - const [nextButtonActive,setNextButtonActive] = useState(); + const [nextButtonActive,setNextButtonActive] = useState(window.DASHBOARD_MODE === "ON_PREM"); const [githubUrl, setGithubUrl] = useState("https://github.com") const [githubApiUrl, setGithubApiUrl] = useState("https://api.github.com") @@ -66,7 +66,6 @@ function GithubSso() { setGithubClientId(githubClientId) if (githubUrl) setGithubUrl(githubUrl) if (githubApiUrl) setGithubApiUrl(githubApiUrl) - setNextButtonActive(true) } catch (error) { setNextButtonActive(false) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/AzureSso.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/AzureSso.jsx index 1865e7af94..3ef51af866 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/AzureSso.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/AzureSso.jsx @@ -15,7 +15,6 @@ function AzureSso() { const [loginUrl, setLoginUrl] = useState('') const [azureIdentity, setAzureIdentity] = useState('') - const [nextButtonActive, setNextButtonActive] = useState() const cardContent = "Enable Login via Azure AD on your Akto dashboard"; @@ -63,12 +62,10 @@ function AzureSso() { await settingRequests.fetchAzureSso("AZURE").then((resp)=> { setLoginUrl(resp.loginUrl) setAzureIdentity(resp.ssoEntityId) - setNextButtonActive(true) }) setLoading(false) } catch (error) { setLoading(false) - setNextButtonActive(false) } } @@ -95,7 +92,6 @@ function AzureSso() { pageTitle={"Azure AD SSO SAML"} loading={loading} certificateName={"Federation Metadata XML"} - isButtonActive={nextButtonActive} /> ) } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/GoogleSamlSso.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/GoogleSamlSso.jsx index 7111c07eec..6b9f776a67 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/GoogleSamlSso.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/sso/GoogleSamlSso.jsx @@ -15,7 +15,6 @@ function GoogleSamlSso() { const [loginUrl, setLoginUrl] = useState('') const [ssoIdentity, setSsoIdentity] = useState('') - const [nextButtonActive, setNextButtonActive] = useState() const cardContent = "Enable Login via Google Workspace on your Akto dashboard"; @@ -61,12 +60,10 @@ function GoogleSamlSso() { await settingRequests.fetchAzureSso("GOOGLE_SAML").then((resp)=> { setLoginUrl(resp.loginUrl) setSsoIdentity(resp.ssoEntityId) - setNextButtonActive(true) }) setLoading(false) } catch (error) { setLoading(false) - setNextButtonActive(false) } } @@ -94,7 +91,6 @@ function GoogleSamlSso() { showCustomInputs={true} certificateName={"X509 certificate"} signinUrl={AcsUrl} - isButtonActive={nextButtonActive} /> ) } From b7f3f78cc11d769cb3b14c8c4219e39582b74f2f Mon Sep 17 00:00:00 2001 From: Ark2307 Date: Mon, 10 Feb 2025 14:38:35 +0530 Subject: [PATCH 36/38] Fixing basic errors --- .../main/java/com/akto/action/HomeAction.java | 39 +++++++++---------- .../java/com/akto/action/SignupAction.java | 13 ++++--- .../com/akto/action/user/OktaSsoAction.java | 11 ++---- .../main/java/com/akto/utils/OktaLogin.java | 6 ++- .../web/src/apps/signup/components/SignUp.jsx | 5 ++- .../main/java/com/akto/dao/SSOConfigsDao.java | 16 ++++++-- .../src/main/java/com/akto/dto/Config.java | 3 -- .../java/com/akto/dto/sso/SAMLConfig.java | 3 -- 8 files changed, 51 insertions(+), 45 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java index a8b1ac787d..9d52698ebf 100644 --- a/apps/dashboard/src/main/java/com/akto/action/HomeAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/HomeAction.java @@ -4,7 +4,6 @@ import com.akto.dao.UsersDao; import com.akto.dto.Config; import com.akto.dto.User; -import com.akto.dto.sso.SAMLConfig; import com.akto.listener.InitializerListener; import com.akto.utils.*; import com.akto.util.DashboardMode; @@ -52,26 +51,26 @@ public String verifyEmail(){ public String execute() { servletRequest.setAttribute("isSaas", InitializerListener.isSaas); - if (GithubLogin.getGithubUrl() != null) { - servletRequest.setAttribute("githubAuthUrl", GithubLogin.getGithubUrl() + "/login/oauth/authorize?client_id=" + GithubLogin.getClientId() + "&scope=user&state=1000000"); - servletRequest.setAttribute("activeSso", Config.ConfigType.GITHUB); - } - - if (OktaLogin.getAuthorisationUrl() != null) { - servletRequest.setAttribute("oktaAuthUrl", OktaLogin.getAuthorisationUrl()); - servletRequest.setAttribute("activeSso", Config.ConfigType.OKTA); - } - - if (SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) { - servletRequest.setAttribute("activeSso", Config.ConfigType.AZURE); - } - - // TODO("Haven't tested Google SAML SSO") - if (SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML) != null) { - SAMLConfig googleSamlConfig = SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML); - servletRequest.setAttribute("googleSamlAuthUrl", googleSamlConfig.getLoginUrl()); - servletRequest.setAttribute("activeSso", Config.ConfigType.GOOGLE_SAML); + if(DashboardMode.isOnPremDeployment()){ + if (GithubLogin.getGithubUrl() != null) { + servletRequest.setAttribute("githubAuthUrl", GithubLogin.getGithubUrl() + "/login/oauth/authorize?client_id=" + GithubLogin.getClientId() + "&scope=user&state=1000000"); + servletRequest.setAttribute("activeSso", Config.ConfigType.GITHUB); + } + + if (OktaLogin.getAuthorisationUrl() != null) { + servletRequest.setAttribute("oktaAuthUrl", OktaLogin.getAuthorisationUrl()); + servletRequest.setAttribute("activeSso", Config.ConfigType.OKTA); + } + + if (SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.AZURE) != null) { + servletRequest.setAttribute("activeSso", Config.ConfigType.AZURE); + } + + if (SSOConfigsDao.getSAMLConfigByAccountId(1000000, Config.ConfigType.GOOGLE_SAML) != null) { + servletRequest.setAttribute("activeSso", Config.ConfigType.GOOGLE_SAML); + } } + if (InitializerListener.aktoVersion != null && InitializerListener.aktoVersion.contains("akto-release-version")) { servletRequest.setAttribute("AktoVersionGlobal", ""); } else { diff --git a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java index 1f0075f249..4738ee813e 100644 --- a/apps/dashboard/src/main/java/com/akto/action/SignupAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/SignupAction.java @@ -603,7 +603,7 @@ public String sendRequestToSamlIdP() throws IOException{ if(userEmail != null && !userEmail.isEmpty()) { samlConfig = SSOConfigsDao.instance.getSSOConfig(userEmail); } else if(DashboardMode.isOnPremDeployment()) { - samlConfig = SSOConfigsDao.getSAMLConfigByAccountId(1000000, ConfigType.AZURE); + samlConfig = SSOConfigsDao.getSAMLConfigByAccountId(1000000); } if(samlConfig == null) { code = "Error, cannot login via SSO, trying to login with okta sso"; @@ -638,10 +638,13 @@ public String oktaAuthUrlCreator(String emailId) throws IOException { logger.info("Trying to create auth url for okta sso for: " + emailId); Config.OktaConfig oktaConfig = Config.getOktaConfig(emailId); if(oktaConfig == null) { - code= "Error, cannot find okta sso for this organization, redirecting to login"; - logger.error(code); - servletResponse.sendRedirect("/login"); - return ERROR.toUpperCase(); + oktaConfig = OktaLogin.getInstance().getOktaConfig(); + if(oktaConfig == null){ + code= "Error, cannot find okta sso for this organization, redirecting to login"; + logger.error(code); + servletResponse.sendRedirect("/login"); + return ERROR.toUpperCase(); + } } String authorisationUrl = OktaLogin.getAuthorisationUrl(emailId); diff --git a/apps/dashboard/src/main/java/com/akto/action/user/OktaSsoAction.java b/apps/dashboard/src/main/java/com/akto/action/user/OktaSsoAction.java index 94632d8006..7f019986d4 100644 --- a/apps/dashboard/src/main/java/com/akto/action/user/OktaSsoAction.java +++ b/apps/dashboard/src/main/java/com/akto/action/user/OktaSsoAction.java @@ -12,7 +12,6 @@ import com.akto.dto.User; import com.akto.dto.Config.OktaConfig; import com.akto.util.Constants; -import com.akto.util.DashboardMode; import com.akto.utils.sso.SsoUtils; import com.mongodb.BasicDBObject; import com.mongodb.client.model.Filters; @@ -41,12 +40,10 @@ public String addOktaSso() { oktaConfig.setAuthorisationServerId(authorisationServerId); oktaConfig.setOktaDomainUrl(oktaDomain); oktaConfig.setRedirectUri(redirectUri); - if(!DashboardMode.isOnPremDeployment()){ - oktaConfig.setAccountId(Context.accountId.get()); - String userLogin = getSUser().getLogin(); - String domain = userLogin.split("@")[1]; - oktaConfig.setOrganizationDomain(domain); - } + oktaConfig.setAccountId(Context.accountId.get()); + String userLogin = getSUser().getLogin(); + String domain = userLogin.split("@")[1]; + oktaConfig.setOrganizationDomain(domain); ConfigsDao.instance.insertOne(oktaConfig); return SUCCESS.toUpperCase(); diff --git a/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java b/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java index c7512f5888..de4d290a14 100644 --- a/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java +++ b/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java @@ -49,7 +49,11 @@ public static String getAuthorisationUrl() { paramMap.put("redirect_uri",oktaConfig.getRedirectUri()); paramMap.put("response_type", "code"); paramMap.put("scope", "openid%20email%20profile"); - paramMap.put("state", String.valueOf(oktaConfig.getAccountId())); + int accountId = 1000000; + if(oktaConfig.getAccountId() != 0){ + accountId = oktaConfig.getAccountId(); + } + paramMap.put("state", String.valueOf(accountId)); String queryString = SsoUtils.getQueryString(paramMap); diff --git a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx index 5d9b1c5341..1ae1e4cd0a 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx @@ -38,7 +38,6 @@ function SignUp() { const activeSSO = window.ACTIVE_SSO const githubAuthUrl = window.GITHUB_AUTH_URL const oktaAuthUrl = window.OKTA_AUTH_URL - const azureAuthUrl = window.AZURE_AUTH_URL const resetAll = PersistStore(state => state.resetAll) const { clearPollingInterval } = usePolling(); @@ -63,7 +62,7 @@ function SignUp() { const googleSamlAuthObj = { logo: '/public/gcp.svg', text: 'Continue with Google SAML SSO', - onClickFunc: () => { window.location.href = "" } + onClickFunc: () => { window.location.href = "/trigger-saml-sso" } } useEffect(() => { @@ -87,6 +86,8 @@ function SignUp() { case "google_saml": copySsoList.push(googleSamlAuthObj); break; + default: + break; } setSsoList(copySsoList) diff --git a/libs/dao/src/main/java/com/akto/dao/SSOConfigsDao.java b/libs/dao/src/main/java/com/akto/dao/SSOConfigsDao.java index 328a48c114..9435511cf5 100644 --- a/libs/dao/src/main/java/com/akto/dao/SSOConfigsDao.java +++ b/libs/dao/src/main/java/com/akto/dao/SSOConfigsDao.java @@ -1,6 +1,6 @@ package com.akto.dao; -import com.akto.dto.Config; +import com.akto.dto.Config.ConfigType; import com.akto.dto.sso.SAMLConfig; import com.akto.util.Constants; import com.mongodb.client.model.Filters; @@ -24,11 +24,19 @@ public SAMLConfig getSSOConfig(String userEmail){ return config; } - public static SAMLConfig getSAMLConfigByAccountId(int accountId, Config.ConfigType configType) { + public static SAMLConfig getSAMLConfigByAccountId(int accountId) { return SSOConfigsDao.instance.findOne( Filters.and( - Filters.eq(Constants.ID, String.valueOf(accountId)), - Filters.eq("configType", configType.name()) + Filters.eq(Constants.ID, String.valueOf(accountId)) + ) + ); + } + + public static SAMLConfig getSAMLConfigByAccountId(int accountId, ConfigType configType) { + return SSOConfigsDao.instance.findOne( + Filters.and( + Filters.eq(Constants.ID, String.valueOf(accountId)), + Filters.eq("configType", configType.name()) ) ); } diff --git a/libs/dao/src/main/java/com/akto/dto/Config.java b/libs/dao/src/main/java/com/akto/dto/Config.java index 31f0c82c65..63c67da9da 100644 --- a/libs/dao/src/main/java/com/akto/dto/Config.java +++ b/libs/dao/src/main/java/com/akto/dto/Config.java @@ -5,9 +5,6 @@ import java.util.Set; import com.akto.dao.ConfigsDao; -import com.akto.dao.SSOConfigsDao; -import com.akto.dto.sso.SAMLConfig; -import com.akto.util.Constants; import com.mongodb.client.model.Filters; import org.bson.codecs.pojo.annotations.BsonDiscriminator; diff --git a/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java b/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java index 67d25b6f42..b6c6a73947 100644 --- a/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java +++ b/libs/dao/src/main/java/com/akto/dto/sso/SAMLConfig.java @@ -1,7 +1,4 @@ package com.akto.dto.sso; -import com.akto.dao.SSOConfigsDao; -import com.akto.util.Constants; -import com.mongodb.client.model.Filters; import org.bson.codecs.pojo.annotations.BsonDiscriminator; import com.akto.dto.Config; From beadf3d71e780ca18f0c2f450b8972c268d3826c Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:18:23 +0530 Subject: [PATCH 37/38] fixed some bugs --- apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java | 1 - .../pages/settings/integrations/components/StepsComponent.jsx | 2 +- .../web/polaris_web/web/src/apps/signup/components/SignUp.jsx | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java b/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java index de4d290a14..de92315fe6 100644 --- a/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java +++ b/apps/dashboard/src/main/java/com/akto/utils/OktaLogin.java @@ -26,7 +26,6 @@ public static OktaLogin getInstance() { if (shouldProbeAgain) { int accountId = Context.accountId.get() != null ? Context.accountId.get() : 1_000_000; OktaConfig oktaConfig = (Config.OktaConfig) ConfigsDao.instance.findOne(Constants.ID, OktaConfig.getOktaId(accountId)); - if(oktaConfig == null) return null; if (instance == null) { instance = new OktaLogin(); } diff --git a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/components/StepsComponent.jsx b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/components/StepsComponent.jsx index a740b06a5c..7bd16f8511 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/components/StepsComponent.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/dashboard/pages/settings/integrations/components/StepsComponent.jsx @@ -17,7 +17,7 @@ function StepsComponent({integrationSteps, onClickFunc, buttonActive}) { ) })} - + diff --git a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx index 1ae1e4cd0a..f6516fb06b 100644 --- a/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx +++ b/apps/dashboard/web/polaris_web/web/src/apps/signup/components/SignUp.jsx @@ -305,7 +305,6 @@ function SignUp() { {activeObject.headingText} {ssoCard} - {!func.checkLocal() ? window.location.href="/sso-login"} logos={['/public/azure_logo.svg', '/public/gcp.svg']} text={"Sign in with SSO"} /> : null} {signupEmailCard} {loginActive && isOnPrem && resetPasswordComp} From d974db99ceec505098cb46861fa3b685771a1661 Mon Sep 17 00:00:00 2001 From: Umesh Kumar <166806589+TangoBeeAkto@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:42:32 +0530 Subject: [PATCH 38/38] clean code --- .../src/main/java/com/akto/runtime/APICatalogSync.java | 4 ++-- .../java/com/akto/hybrid_runtime/APICatalogSync.java | 4 ++-- .../src/main/java/com/akto/dto/HttpResponseParams.java | 9 +++++++++ .../dao/src/main/java/com/akto/dto/type/URLTemplate.java | 3 ++- .../src/main/java/com/akto/graphql/GraphQLUtils.java | 7 ------- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/api-runtime/src/main/java/com/akto/runtime/APICatalogSync.java b/apps/api-runtime/src/main/java/com/akto/runtime/APICatalogSync.java index 51c4c7967b..d611be5bd5 100644 --- a/apps/api-runtime/src/main/java/com/akto/runtime/APICatalogSync.java +++ b/apps/api-runtime/src/main/java/com/akto/runtime/APICatalogSync.java @@ -741,7 +741,7 @@ public static URLTemplate tryParamteresingUrl(URLStatic newUrl){ int start = newUrl.getUrl().startsWith("http") ? 3 : 0; - if(GraphQLUtils.isGraphQLEndpoint(newUrl.getUrl())) { + if(HttpResponseParams.isGraphQLEndpoint(newUrl.getUrl())) { return null; // Don't merge GraphQL endpoints } @@ -811,7 +811,7 @@ public static URLTemplate tryMergeUrls(URLStatic dbUrl, URLStatic newUrl) { SuperType[] newTypes = new SuperType[newTokens.length]; int templatizedStrTokens = 0; - if(GraphQLUtils.isGraphQLEndpoint(dbUrl.getUrl()) || GraphQLUtils.isGraphQLEndpoint(newUrl.getUrl())) { + if(HttpResponseParams.isGraphQLEndpoint(dbUrl.getUrl()) || HttpResponseParams.isGraphQLEndpoint(newUrl.getUrl())) { return null; // Don't merge GraphQL endpoints } diff --git a/apps/mini-runtime/src/main/java/com/akto/hybrid_runtime/APICatalogSync.java b/apps/mini-runtime/src/main/java/com/akto/hybrid_runtime/APICatalogSync.java index e871cc65a6..45c352055e 100644 --- a/apps/mini-runtime/src/main/java/com/akto/hybrid_runtime/APICatalogSync.java +++ b/apps/mini-runtime/src/main/java/com/akto/hybrid_runtime/APICatalogSync.java @@ -527,7 +527,7 @@ public static URLTemplate tryParamteresingUrl(URLStatic newUrl){ int start = newUrl.getUrl().startsWith("http") ? 3 : 0; - if(GraphQLUtils.isGraphQLEndpoint(newUrl.getUrl())) { + if(HttpResponseParams.isGraphQLEndpoint(newUrl.getUrl())) { return null; // Don't merge GraphQL endpoints } @@ -597,7 +597,7 @@ public static URLTemplate tryMergeUrls(URLStatic dbUrl, URLStatic newUrl) { SuperType[] newTypes = new SuperType[newTokens.length]; int templatizedStrTokens = 0; - if(GraphQLUtils.isGraphQLEndpoint(dbUrl.getUrl()) || GraphQLUtils.isGraphQLEndpoint(newUrl.getUrl())) { + if(HttpResponseParams.isGraphQLEndpoint(dbUrl.getUrl()) || HttpResponseParams.isGraphQLEndpoint(newUrl.getUrl())) { return null; // Don't merge GraphQL endpoints } diff --git a/libs/dao/src/main/java/com/akto/dto/HttpResponseParams.java b/libs/dao/src/main/java/com/akto/dto/HttpResponseParams.java index 7fb7ed7e5b..235645579c 100644 --- a/libs/dao/src/main/java/com/akto/dto/HttpResponseParams.java +++ b/libs/dao/src/main/java/com/akto/dto/HttpResponseParams.java @@ -98,6 +98,15 @@ public static boolean isGraphql(HttpResponseParams responseParams) { return isAllowedForParse && requestPayload.contains(QUERY); } + public static boolean isGraphQLEndpoint(String url) { + for (String keyword : allowedPath) { + if (url.contains(keyword)) { + return true; + } + } + return false; + } + public int getTimeOrNow() { return getTime() == 0 ? Context.now() : getTime(); } diff --git a/libs/dao/src/main/java/com/akto/dto/type/URLTemplate.java b/libs/dao/src/main/java/com/akto/dto/type/URLTemplate.java index ade8a9eaee..456d5f937a 100644 --- a/libs/dao/src/main/java/com/akto/dto/type/URLTemplate.java +++ b/libs/dao/src/main/java/com/akto/dto/type/URLTemplate.java @@ -4,6 +4,7 @@ import java.util.UUID; import com.akto.dao.context.Context; +import com.akto.dto.HttpResponseParams; import com.akto.dto.type.SingleTypeInfo.SuperType; import com.akto.dto.type.URLMethods.Method; @@ -66,7 +67,7 @@ public boolean match(String[] url, Method urlMethod) { String[] thatTokens = url; if (thatTokens.length != this.tokens.length) return false; - if(Arrays.toString(url).contains("graphql") || Arrays.toString(url).contains("graph")) { + if(HttpResponseParams.isGraphQLEndpoint(Arrays.toString(url))) { return false; } diff --git a/libs/utils/src/main/java/com/akto/graphql/GraphQLUtils.java b/libs/utils/src/main/java/com/akto/graphql/GraphQLUtils.java index ded895dce9..5a742b13a4 100644 --- a/libs/utils/src/main/java/com/akto/graphql/GraphQLUtils.java +++ b/libs/utils/src/main/java/com/akto/graphql/GraphQLUtils.java @@ -320,11 +320,4 @@ public List parseGraphQLRequest(Map requestPayload) { } return result; } - - public static boolean isGraphQLEndpoint(String url) { - if(url.contains("graphql") || url.contains("graph")) { - return true; - } - return false; - } }