From af0ff2be6e0f4a4ebeae0d5d936ae84c680ece2b Mon Sep 17 00:00:00 2001 From: Seb-MIGUEL Date: Wed, 25 Feb 2026 14:17:45 +0100 Subject: [PATCH 01/12] [backend/frontend] feat: add 12 new ContractOutputType for NetExec findings --- ...ithPasswordNotRequiredOutputProcessor.java | 65 ++++++++++ .../AdminUsernameOutputProcessor.java | 56 ++++++++ .../AsreproastableAccountOutputProcessor.java | 58 +++++++++ .../ComputerOutputProcessor.java | 56 ++++++++ .../CredentialsOutputProcessor.java | 35 ++++- .../DelegationOutputProcessor.java | 72 +++++++++++ .../GroupOutputProcessor.java | 67 ++++++++++ .../KerberoastableAccountOutputProcessor.java | 58 +++++++++ .../PasswordPolicyOutputProcessor.java | 60 +++++++++ .../ShareOutputProcessor.java | 64 +++++++++ .../output_processor/SidOutputProcessor.java | 56 ++++++++ .../UsernameOutputProcessor.java | 63 +++++++++ .../VulnerabilityOutputProcessor.java | 62 +++++++++ .../catalog/catalog-integrators.json | 65 ++++++++++ ...asswordNotRequiredOutputProcessorTest.java | 99 ++++++++++++++ .../AdminUsernameOutputProcessorTest.java | 82 ++++++++++++ ...eproastableAccountOutputProcessorTest.java | 95 ++++++++++++++ .../ComputerOutputProcessorTest.java | 85 ++++++++++++ .../DelegationOutputProcessorTest.java | 121 ++++++++++++++++++ .../GroupOutputProcessorTest.java | 95 ++++++++++++++ ...beroastableAccountOutputProcessorTest.java | 95 ++++++++++++++ .../PasswordPolicyOutputProcessorTest.java | 95 ++++++++++++++ .../ShareOutputProcessorTest.java | 106 +++++++++++++++ .../SidOutputProcessorTest.java | 89 +++++++++++++ .../UsernameOutputProcessorTest.java | 98 ++++++++++++++ .../VulnerabilityOutputProcessorTest.java | 94 ++++++++++++++ .../inject/StructuredOutputUtilsTest.java | 2 +- .../findings/ContractOutputElementType.ts | 33 +++++ .../src/admin/components/findings/Finding.ts | 12 -- .../admin/components/payloads/PayloadForm.tsx | 3 +- .../form/ContractOutputElementCard.tsx | 26 ++-- .../vulnerabilities/RelatedInjectsTab.tsx | 2 +- openaev-front/src/components/FindingIcon.tsx | 37 +++++- openaev-front/src/utils/api-types.d.ts | 99 +++++++++++++- .../InjectorContractUtils.ts | 2 +- openaev-front/src/utils/lang/de.json | 14 ++ openaev-front/src/utils/lang/en.json | 14 ++ openaev-front/src/utils/lang/es.json | 14 ++ openaev-front/src/utils/lang/fr.json | 14 ++ openaev-front/src/utils/lang/it.json | 14 ++ openaev-front/src/utils/lang/ja.json | 14 ++ openaev-front/src/utils/lang/ko.json | 14 ++ openaev-front/src/utils/lang/ru.json | 14 ++ openaev-front/src/utils/lang/zh.json | 14 ++ .../database/model/ContractOutputType.java | 36 ++++++ .../enums/ExternalServiceDependency.java | 5 + 46 files changed, 2337 insertions(+), 37 deletions(-) create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/AdminUsernameOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/AsreproastableAccountOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/ComputerOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/DelegationOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/GroupOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/KerberoastableAccountOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/PasswordPolicyOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/ShareOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/SidOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/UsernameOutputProcessor.java create mode 100644 openaev-api/src/main/java/io/openaev/output_processor/VulnerabilityOutputProcessor.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/AdminUsernameOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/AsreproastableAccountOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/ComputerOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/DelegationOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/GroupOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/KerberoastableAccountOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/PasswordPolicyOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/ShareOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/SidOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/UsernameOutputProcessorTest.java create mode 100644 openaev-api/src/test/java/io/openaev/output_processor/VulnerabilityOutputProcessorTest.java create mode 100644 openaev-front/src/admin/components/findings/ContractOutputElementType.ts delete mode 100644 openaev-front/src/admin/components/findings/Finding.ts diff --git a/openaev-api/src/main/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessor.java new file mode 100644 index 00000000000..040251cfdfb --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessor.java @@ -0,0 +1,65 @@ +package io.openaev.output_processor; + +import static org.springframework.util.StringUtils.hasText; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class AccountWithPasswordNotRequiredOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String ACCOUNT = "account"; + private static final String STATUS = "status"; + private static final String HOST = "host"; + + public AccountWithPasswordNotRequiredOutputProcessor(FindingService findingService) { + super( + ContractOutputType.AccountWithPasswordNotRequired, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(ACCOUNT, ContractOutputTechnicalType.Text, true), + new ContractOutputField(STATUS, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(ACCOUNT); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + String account = buildString(jsonNode, ACCOUNT); + String status = buildString(jsonNode, STATUS); + if (hasText(status)) { + return account + " (" + status + ")"; + } + return account; + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/AdminUsernameOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/AdminUsernameOutputProcessor.java new file mode 100644 index 00000000000..4a2591782ac --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/AdminUsernameOutputProcessor.java @@ -0,0 +1,56 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class AdminUsernameOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String USERNAME = "username"; + private static final String HOST = "host"; + + public AdminUsernameOutputProcessor(FindingService findingService) { + super( + ContractOutputType.AdminUsername, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(USERNAME, ContractOutputTechnicalType.Text, true), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(USERNAME); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + return buildString(jsonNode, USERNAME); + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/AsreproastableAccountOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/AsreproastableAccountOutputProcessor.java new file mode 100644 index 00000000000..f2886a65cc4 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/AsreproastableAccountOutputProcessor.java @@ -0,0 +1,58 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class AsreproastableAccountOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String USERNAME = "username"; + private static final String HASH = "hash"; + private static final String HOST = "host"; + + public AsreproastableAccountOutputProcessor(FindingService findingService) { + super( + ContractOutputType.AsreproastableAccount, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(USERNAME, ContractOutputTechnicalType.Text, true), + new ContractOutputField(HASH, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(USERNAME); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + return buildString(jsonNode, USERNAME); + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/ComputerOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/ComputerOutputProcessor.java new file mode 100644 index 00000000000..8e3b61bc092 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/ComputerOutputProcessor.java @@ -0,0 +1,56 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class ComputerOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String COMPUTER_NAME = "computer_name"; + private static final String HOST = "host"; + + public ComputerOutputProcessor(FindingService findingService) { + super( + ContractOutputType.Computer, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(COMPUTER_NAME, ContractOutputTechnicalType.Text, true), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(COMPUTER_NAME); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + return buildString(jsonNode, COMPUTER_NAME); + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/CredentialsOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/CredentialsOutputProcessor.java index 90aebac604b..c4c64b1c12b 100644 --- a/openaev-api/src/main/java/io/openaev/output_processor/CredentialsOutputProcessor.java +++ b/openaev-api/src/main/java/io/openaev/output_processor/CredentialsOutputProcessor.java @@ -5,34 +5,61 @@ import io.openaev.database.model.ContractOutputTechnicalType; import io.openaev.database.model.ContractOutputType; import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.springframework.stereotype.Component; @Component public class CredentialsOutputProcessor extends FindingCapableOutputProcessor { + private static final String ASSET_ID = "asset_id"; private static final String USERNAME = "username"; private static final String PASSWORD = "password"; + private static final String HASH = "hash"; + private static final String HOST = "host"; public CredentialsOutputProcessor(FindingService findingService) { super( ContractOutputType.Credentials, ContractOutputTechnicalType.Object, List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), new ContractOutputField(USERNAME, ContractOutputTechnicalType.Text, true), - new ContractOutputField(PASSWORD, ContractOutputTechnicalType.Text, true)), + new ContractOutputField(PASSWORD, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HASH, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), findingService); } @Override public boolean validate(JsonNode jsonNode) { - return jsonNode.hasNonNull(USERNAME) && jsonNode.hasNonNull(PASSWORD); + return jsonNode.hasNonNull(USERNAME) + && (jsonNode.hasNonNull(PASSWORD) || jsonNode.hasNonNull(HASH)); } @Override public String toFindingValue(JsonNode jsonNode) { String username = buildString(jsonNode, USERNAME); - String password = buildString(jsonNode, PASSWORD); - return username + ":" + password; + if (jsonNode.hasNonNull(PASSWORD)) { + return username + ":" + buildString(jsonNode, PASSWORD); + } + return username + ":" + buildString(jsonNode, HASH); + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); } } diff --git a/openaev-api/src/main/java/io/openaev/output_processor/DelegationOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/DelegationOutputProcessor.java new file mode 100644 index 00000000000..40ac19c8c95 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/DelegationOutputProcessor.java @@ -0,0 +1,72 @@ +package io.openaev.output_processor; + +import static org.springframework.util.StringUtils.hasText; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class DelegationOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String ACCOUNT = "account"; + private static final String DELEGATION_TYPE = "delegation_type"; + private static final String RIGHTS_TO = "rights_to"; + private static final String HOST = "host"; + + public DelegationOutputProcessor(FindingService findingService) { + super( + ContractOutputType.Delegation, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(ACCOUNT, ContractOutputTechnicalType.Text, true), + new ContractOutputField(DELEGATION_TYPE, ContractOutputTechnicalType.Text, false), + new ContractOutputField(RIGHTS_TO, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(ACCOUNT); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + String account = buildString(jsonNode, ACCOUNT); + String delegationType = buildString(jsonNode, DELEGATION_TYPE); + String rightsTo = buildString(jsonNode, RIGHTS_TO); + StringBuilder sb = new StringBuilder(account); + if (hasText(delegationType)) { + sb.append(" [").append(delegationType).append("]"); + } + if (hasText(rightsTo)) { + sb.append(" -> ").append(rightsTo); + } + return sb.toString(); + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/GroupOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/GroupOutputProcessor.java new file mode 100644 index 00000000000..f0db6838013 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/GroupOutputProcessor.java @@ -0,0 +1,67 @@ +package io.openaev.output_processor; + +import static org.springframework.util.StringUtils.hasText; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class GroupOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String GROUP_NAME = "group_name"; + private static final String MEMBER_COUNT = "member_count"; + private static final String RID = "rid"; + private static final String HOST = "host"; + + public GroupOutputProcessor(FindingService findingService) { + super( + ContractOutputType.Group, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(GROUP_NAME, ContractOutputTechnicalType.Text, true), + new ContractOutputField(MEMBER_COUNT, ContractOutputTechnicalType.Text, false), + new ContractOutputField(RID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(GROUP_NAME); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + String groupName = buildString(jsonNode, GROUP_NAME); + String memberCount = buildString(jsonNode, MEMBER_COUNT); + if (hasText(memberCount)) { + return groupName + " (" + memberCount + " members)"; + } + return groupName; + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/KerberoastableAccountOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/KerberoastableAccountOutputProcessor.java new file mode 100644 index 00000000000..e2b8fe0af81 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/KerberoastableAccountOutputProcessor.java @@ -0,0 +1,58 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class KerberoastableAccountOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String USERNAME = "username"; + private static final String HASH = "hash"; + private static final String HOST = "host"; + + public KerberoastableAccountOutputProcessor(FindingService findingService) { + super( + ContractOutputType.KerberoastableAccount, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(USERNAME, ContractOutputTechnicalType.Text, true), + new ContractOutputField(HASH, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(USERNAME); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + return buildString(jsonNode, USERNAME); + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/PasswordPolicyOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/PasswordPolicyOutputProcessor.java new file mode 100644 index 00000000000..c0b3d4e0a1b --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/PasswordPolicyOutputProcessor.java @@ -0,0 +1,60 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class PasswordPolicyOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String KEY = "key"; + private static final String VALUE = "value"; + private static final String HOST = "host"; + + public PasswordPolicyOutputProcessor(FindingService findingService) { + super( + ContractOutputType.PasswordPolicy, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(KEY, ContractOutputTechnicalType.Text, true), + new ContractOutputField(VALUE, ContractOutputTechnicalType.Text, true), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(KEY) && jsonNode.hasNonNull(VALUE); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + String key = buildString(jsonNode, KEY); + String value = buildString(jsonNode, VALUE); + return key + ": " + value; + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/ShareOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/ShareOutputProcessor.java new file mode 100644 index 00000000000..19899e13f03 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/ShareOutputProcessor.java @@ -0,0 +1,64 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class ShareOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String SHARE_NAME = "share_name"; + private static final String PERMISSIONS = "permissions"; + private static final String HOST = "host"; + + public ShareOutputProcessor(FindingService findingService) { + super( + ContractOutputType.Share, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(SHARE_NAME, ContractOutputTechnicalType.Text, true), + new ContractOutputField(PERMISSIONS, ContractOutputTechnicalType.Text, true), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(SHARE_NAME) && jsonNode.hasNonNull(PERMISSIONS); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + String shareName = buildString(jsonNode, SHARE_NAME); + String permissions = buildString(jsonNode, PERMISSIONS); + String host = buildString(jsonNode, HOST); + if (!host.isEmpty()) { + return "\\\\" + host + "\\" + shareName + " (" + permissions + ")"; + } + return shareName + " (" + permissions + ")"; + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/SidOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/SidOutputProcessor.java new file mode 100644 index 00000000000..58dd6fe0af0 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/SidOutputProcessor.java @@ -0,0 +1,56 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class SidOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String SID = "sid"; + private static final String HOST = "host"; + + public SidOutputProcessor(FindingService findingService) { + super( + ContractOutputType.Sid, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(SID, ContractOutputTechnicalType.Text, true), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(SID); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + return buildString(jsonNode, SID); + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/UsernameOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/UsernameOutputProcessor.java new file mode 100644 index 00000000000..a1cf1b63839 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/UsernameOutputProcessor.java @@ -0,0 +1,63 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class UsernameOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String USERNAME = "username"; + private static final String DOMAIN = "domain"; + private static final String HOST = "host"; + + public UsernameOutputProcessor(FindingService findingService) { + super( + ContractOutputType.Username, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(USERNAME, ContractOutputTechnicalType.Text, true), + new ContractOutputField(DOMAIN, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(USERNAME); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + String username = buildString(jsonNode, USERNAME); + String domain = buildString(jsonNode, DOMAIN); + if (!domain.isEmpty()) { + return domain + "\\" + username; + } + return username; + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/java/io/openaev/output_processor/VulnerabilityOutputProcessor.java b/openaev-api/src/main/java/io/openaev/output_processor/VulnerabilityOutputProcessor.java new file mode 100644 index 00000000000..15f26a1b3a9 --- /dev/null +++ b/openaev-api/src/main/java/io/openaev/output_processor/VulnerabilityOutputProcessor.java @@ -0,0 +1,62 @@ +package io.openaev.output_processor; + +import com.fasterxml.jackson.databind.JsonNode; +import io.openaev.database.model.ContractOutputField; +import io.openaev.database.model.ContractOutputTechnicalType; +import io.openaev.database.model.ContractOutputType; +import io.openaev.rest.finding.FindingService; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class VulnerabilityOutputProcessor extends FindingCapableOutputProcessor { + + private static final String ASSET_ID = "asset_id"; + private static final String NAME = "name"; + private static final String STATUS = "status"; + private static final String DETAILS = "details"; + private static final String HOST = "host"; + + public VulnerabilityOutputProcessor(FindingService findingService) { + super( + ContractOutputType.Vulnerability, + ContractOutputTechnicalType.Object, + List.of( + new ContractOutputField(ASSET_ID, ContractOutputTechnicalType.Text, false), + new ContractOutputField(NAME, ContractOutputTechnicalType.Text, true), + new ContractOutputField(STATUS, ContractOutputTechnicalType.Text, true), + new ContractOutputField(DETAILS, ContractOutputTechnicalType.Text, false), + new ContractOutputField(HOST, ContractOutputTechnicalType.Text, false)), + findingService); + } + + @Override + public boolean validate(JsonNode jsonNode) { + return jsonNode.hasNonNull(NAME) && jsonNode.hasNonNull(STATUS); + } + + @Override + public String toFindingValue(JsonNode jsonNode) { + String name = buildString(jsonNode, NAME); + String status = buildString(jsonNode, STATUS); + return name + " [" + status + "]"; + } + + @Override + public List toFindingAssets(JsonNode jsonNode) { + JsonNode assetIdNode = jsonNode.get(ASSET_ID); + if (assetIdNode == null) { + return Collections.emptyList(); + } + if (assetIdNode.isArray()) { + List result = new ArrayList<>(); + for (JsonNode idNode : assetIdNode) { + result.add(idNode.asText()); + } + return result; + } + return List.of(assetIdNode.asText()); + } +} diff --git a/openaev-api/src/main/resources/catalog/catalog-integrators.json b/openaev-api/src/main/resources/catalog/catalog-integrators.json index 8433bc4c110..dea3d93d4c7 100644 --- a/openaev-api/src/main/resources/catalog/catalog-integrators.json +++ b/openaev-api/src/main/resources/catalog/catalog-integrators.json @@ -2098,6 +2098,71 @@ ], "additionalProperties": true } + }, + { + "title": "NetExec", + "slug": "openaev_netexec", + "description": "Allow OAEV to perform network execution and credential testing with NetExec", + "short_description": "Allow OAEV to perform network execution with NetExec", + "use_cases": [ + "Technical" + ], + "verified": true, + "last_verified_date": "", + "playbook_supported": false, + "max_confidence_level": 80, + "support_version": "", + "subscription_link": "https://www.netexec.wiki/", + "source_code": "", + "manager_supported": true, + "container_version": "rolling", + "container_image": "openaev/injector-netexec", + "container_type": "INJECTOR", + "logo": "data:image/png;base64,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", + "config_schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "config.schema.json", + "type": "object", + "properties": { + "OPENAEV_URL": { + "description": "The OpenAEV platform URL.", + "format": "uri", + "maxLength": 2083, + "minLength": 1, + "type": "string" + }, + "OPENAEV_TOKEN": { + "description": "The token for the OpenAEV platform.", + "type": "string" + }, + "INJECTOR_ID": { + "description": "A unique UUIDv4 identifier for this injector instance.", + "type": "string" + }, + "INJECTOR_NAME": { + "default": "NetExec", + "description": "Name of the injector.", + "type": "string" + }, + "INJECTOR_LOG_LEVEL": { + "default": "error", + "description": "Determines the verbosity of the logs.", + "enum": [ + "debug", + "info", + "warn", + "error" + ], + "type": "string" + } + }, + "required": [ + "OPENAEV_URL", + "OPENAEV_TOKEN", + "INJECTOR_ID" + ], + "additionalProperties": true + } } ] } diff --git a/openaev-api/src/test/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessorTest.java new file mode 100644 index 00000000000..0b01738de53 --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessorTest.java @@ -0,0 +1,99 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AccountWithPasswordNotRequiredOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final AccountWithPasswordNotRequiredOutputProcessor processor = + new AccountWithPasswordNotRequiredOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when account is present") + void shouldReturnTrueWhenAccountPresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"account\": \"guest\", \"status\": \"enabled\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return true when account is present without optional fields") + void shouldReturnTrueWhenAccountPresentWithoutOptionalFields() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"guest\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when account is missing") + void shouldReturnFalseWhenAccountMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"status\": \"enabled\", \"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when account is null") + void shouldReturnFalseWhenAccountNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return account with status when status is present") + void shouldReturnAccountWithStatusWhenStatusPresent() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"guest\", \"status\": \"enabled\"}"); + String result = processor.toFindingValue(node); + assertEquals("guest (enabled)", result); + } + + @Test + @DisplayName("should return account only when status is absent") + void shouldReturnAccountOnlyWhenStatusAbsent() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"guest\"}"); + String result = processor.toFindingValue(node); + assertEquals("guest", result); + } + + @Test + @DisplayName("should return account only when status is empty") + void shouldReturnAccountOnlyWhenStatusEmpty() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"guest\", \"status\": \"\"}"); + String result = processor.toFindingValue(node); + assertEquals("guest", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"guest\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"guest\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree("{\"account\": \"guest\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/AdminUsernameOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/AdminUsernameOutputProcessorTest.java new file mode 100644 index 00000000000..2697d99a4b3 --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/AdminUsernameOutputProcessorTest.java @@ -0,0 +1,82 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AdminUsernameOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final AdminUsernameOutputProcessor processor = new AdminUsernameOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when username is present") + void shouldReturnTrueWhenUsernamePresent() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"Administrator\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when username is missing") + void shouldReturnFalseWhenUsernameMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when username is null") + void shouldReturnFalseWhenUsernameNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return username as finding value") + void shouldReturnUsernameAsFindingValue() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"Administrator\", \"host\": \"dc01\"}"); + String result = processor.toFindingValue(node); + assertEquals("Administrator", result); + } + + @Test + @DisplayName("should return empty string when username field is missing for finding value") + void shouldReturnEmptyStringWhenUsernameMissingForFindingValue() throws Exception { + JsonNode node = objectMapper.readTree("{\"host\": \"dc01\"}"); + String result = processor.toFindingValue(node); + assertEquals("", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"Administrator\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree("{\"username\": \"Administrator\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree("{\"username\": \"Administrator\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/AsreproastableAccountOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/AsreproastableAccountOutputProcessorTest.java new file mode 100644 index 00000000000..d256c7a54c8 --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/AsreproastableAccountOutputProcessorTest.java @@ -0,0 +1,95 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AsreproastableAccountOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final AsreproastableAccountOutputProcessor processor = + new AsreproastableAccountOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when username is present") + void shouldReturnTrueWhenUsernamePresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"username\": \"svc_backup\", \"hash\": \"$krb5asrep$23$svc_backup@CORP\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return true when username is present without optional fields") + void shouldReturnTrueWhenUsernamePresentWithoutOptionalFields() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"svc_backup\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when username is missing") + void shouldReturnFalseWhenUsernameMissing() throws Exception { + JsonNode node = + objectMapper.readTree("{\"hash\": \"$krb5asrep$23$svc_backup@CORP\", \"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when username is null") + void shouldReturnFalseWhenUsernameNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return username as finding value") + void shouldReturnUsernameAsFindingValue() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"username\": \"svc_backup\", \"hash\": \"$krb5asrep$23$svc_backup@CORP\"}"); + String result = processor.toFindingValue(node); + assertEquals("svc_backup", result); + } + + @Test + @DisplayName("should return empty string when username is missing for finding value") + void shouldReturnEmptyStringWhenUsernameMissingForFindingValue() throws Exception { + JsonNode node = objectMapper.readTree("{\"hash\": \"somehash\"}"); + String result = processor.toFindingValue(node); + assertEquals("", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"svc_backup\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree("{\"username\": \"svc_backup\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree("{\"username\": \"svc_backup\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/ComputerOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/ComputerOutputProcessorTest.java new file mode 100644 index 00000000000..8fa60f48f9d --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/ComputerOutputProcessorTest.java @@ -0,0 +1,85 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ComputerOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final ComputerOutputProcessor processor = new ComputerOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when computer_name is present") + void shouldReturnTrueWhenComputerNamePresent() throws Exception { + JsonNode node = + objectMapper.readTree("{\"computer_name\": \"WORKSTATION01\", \"host\": \"192.168.1.10\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when computer_name is missing") + void shouldReturnFalseWhenComputerNameMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"host\": \"192.168.1.10\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when computer_name is null") + void shouldReturnFalseWhenComputerNameNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"computer_name\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return computer name as finding value") + void shouldReturnComputerNameAsFindingValue() throws Exception { + JsonNode node = + objectMapper.readTree("{\"computer_name\": \"WORKSTATION01\", \"host\": \"192.168.1.10\"}"); + String result = processor.toFindingValue(node); + assertEquals("WORKSTATION01", result); + } + + @Test + @DisplayName("should return empty string when computer_name is missing for finding value") + void shouldReturnEmptyStringWhenComputerNameMissingForFindingValue() throws Exception { + JsonNode node = objectMapper.readTree("{\"host\": \"192.168.1.10\"}"); + String result = processor.toFindingValue(node); + assertEquals("", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"computer_name\": \"WORKSTATION01\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree("{\"computer_name\": \"WORKSTATION01\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"computer_name\": \"WORKSTATION01\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/DelegationOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/DelegationOutputProcessorTest.java new file mode 100644 index 00000000000..aa823e0a083 --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/DelegationOutputProcessorTest.java @@ -0,0 +1,121 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DelegationOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final DelegationOutputProcessor processor = new DelegationOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when account is present") + void shouldReturnTrueWhenAccountPresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"account\": \"svc_sql\", \"delegation_type\": \"Unconstrained\", \"rights_to\": \"CIFS/dc01\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return true when account is present without optional fields") + void shouldReturnTrueWhenAccountPresentWithoutOptionalFields() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"svc_sql\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when account is missing") + void shouldReturnFalseWhenAccountMissing() throws Exception { + JsonNode node = + objectMapper.readTree("{\"delegation_type\": \"Unconstrained\", \"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when account is null") + void shouldReturnFalseWhenAccountNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return account with delegation type and rights_to when all present") + void shouldReturnFullFindingValueWhenAllFieldsPresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"account\": \"svc_sql\", \"delegation_type\": \"Unconstrained\", \"rights_to\": \"CIFS/dc01\"}"); + String result = processor.toFindingValue(node); + assertEquals("svc_sql [Unconstrained] -> CIFS/dc01", result); + } + + @Test + @DisplayName("should return account with delegation type when rights_to is absent") + void shouldReturnAccountWithDelegationTypeWhenRightsToAbsent() throws Exception { + JsonNode node = + objectMapper.readTree("{\"account\": \"svc_sql\", \"delegation_type\": \"Constrained\"}"); + String result = processor.toFindingValue(node); + assertEquals("svc_sql [Constrained]", result); + } + + @Test + @DisplayName("should return account with rights_to when delegation_type is absent") + void shouldReturnAccountWithRightsToWhenDelegationTypeAbsent() throws Exception { + JsonNode node = + objectMapper.readTree("{\"account\": \"svc_sql\", \"rights_to\": \"CIFS/dc01\"}"); + String result = processor.toFindingValue(node); + assertEquals("svc_sql -> CIFS/dc01", result); + } + + @Test + @DisplayName("should return account only when optional fields are absent") + void shouldReturnAccountOnlyWhenOptionalFieldsAbsent() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"svc_sql\"}"); + String result = processor.toFindingValue(node); + assertEquals("svc_sql", result); + } + + @Test + @DisplayName("should return account only when optional fields are empty") + void shouldReturnAccountOnlyWhenOptionalFieldsEmpty() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"account\": \"svc_sql\", \"delegation_type\": \"\", \"rights_to\": \"\"}"); + String result = processor.toFindingValue(node); + assertEquals("svc_sql", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"svc_sql\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = objectMapper.readTree("{\"account\": \"svc_sql\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree("{\"account\": \"svc_sql\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/GroupOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/GroupOutputProcessorTest.java new file mode 100644 index 00000000000..8409870b64b --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/GroupOutputProcessorTest.java @@ -0,0 +1,95 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GroupOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final GroupOutputProcessor processor = new GroupOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when group_name is present") + void shouldReturnTrueWhenGroupNamePresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"group_name\": \"Domain Admins\", \"member_count\": \"5\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when group_name is missing") + void shouldReturnFalseWhenGroupNameMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"member_count\": \"5\", \"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when group_name is null") + void shouldReturnFalseWhenGroupNameNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"group_name\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return group name with member count when member_count is present") + void shouldReturnGroupNameWithMemberCountWhenPresent() throws Exception { + JsonNode node = + objectMapper.readTree("{\"group_name\": \"Domain Admins\", \"member_count\": \"5\"}"); + String result = processor.toFindingValue(node); + assertEquals("Domain Admins (5 members)", result); + } + + @Test + @DisplayName("should return group name only when member_count is absent") + void shouldReturnGroupNameOnlyWhenMemberCountAbsent() throws Exception { + JsonNode node = objectMapper.readTree("{\"group_name\": \"Domain Admins\"}"); + String result = processor.toFindingValue(node); + assertEquals("Domain Admins", result); + } + + @Test + @DisplayName("should return group name only when member_count is empty") + void shouldReturnGroupNameOnlyWhenMemberCountEmpty() throws Exception { + JsonNode node = + objectMapper.readTree("{\"group_name\": \"Domain Admins\", \"member_count\": \"\"}"); + String result = processor.toFindingValue(node); + assertEquals("Domain Admins", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"group_name\": \"Domain Admins\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree("{\"group_name\": \"Domain Admins\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"group_name\": \"Domain Admins\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/KerberoastableAccountOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/KerberoastableAccountOutputProcessorTest.java new file mode 100644 index 00000000000..d5fd14d3a26 --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/KerberoastableAccountOutputProcessorTest.java @@ -0,0 +1,95 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class KerberoastableAccountOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final KerberoastableAccountOutputProcessor processor = + new KerberoastableAccountOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when username is present") + void shouldReturnTrueWhenUsernamePresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"username\": \"svc_mssql\", \"hash\": \"$krb5tgs$23$*svc_mssql$CORP\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return true when username is present without optional fields") + void shouldReturnTrueWhenUsernamePresentWithoutOptionalFields() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"svc_mssql\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when username is missing") + void shouldReturnFalseWhenUsernameMissing() throws Exception { + JsonNode node = + objectMapper.readTree("{\"hash\": \"$krb5tgs$23$*svc_mssql$CORP\", \"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when username is null") + void shouldReturnFalseWhenUsernameNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return username as finding value") + void shouldReturnUsernameAsFindingValue() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"username\": \"svc_mssql\", \"hash\": \"$krb5tgs$23$*svc_mssql$CORP\"}"); + String result = processor.toFindingValue(node); + assertEquals("svc_mssql", result); + } + + @Test + @DisplayName("should return empty string when username is missing for finding value") + void shouldReturnEmptyStringWhenUsernameMissingForFindingValue() throws Exception { + JsonNode node = objectMapper.readTree("{\"hash\": \"somehash\"}"); + String result = processor.toFindingValue(node); + assertEquals("", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"svc_mssql\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree("{\"username\": \"svc_mssql\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree("{\"username\": \"svc_mssql\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/PasswordPolicyOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/PasswordPolicyOutputProcessorTest.java new file mode 100644 index 00000000000..3b169afa084 --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/PasswordPolicyOutputProcessorTest.java @@ -0,0 +1,95 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PasswordPolicyOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final PasswordPolicyOutputProcessor processor = new PasswordPolicyOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when key and value are present") + void shouldReturnTrueWhenKeyAndValuePresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"key\": \"Minimum password length\", \"value\": \"8\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when key is missing") + void shouldReturnFalseWhenKeyMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"value\": \"8\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when value is missing") + void shouldReturnFalseWhenValueMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"key\": \"Minimum password length\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when key is null") + void shouldReturnFalseWhenKeyNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"key\": null, \"value\": \"8\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when value is null") + void shouldReturnFalseWhenValueNull() throws Exception { + JsonNode node = + objectMapper.readTree("{\"key\": \"Minimum password length\", \"value\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return key: value as finding value") + void shouldReturnKeyColonValueAsFindingValue() throws Exception { + JsonNode node = + objectMapper.readTree("{\"key\": \"Minimum password length\", \"value\": \"8\"}"); + String result = processor.toFindingValue(node); + assertEquals("Minimum password length: 8", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = + objectMapper.readTree("{\"key\": \"Minimum password length\", \"value\": \"8\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"key\": \"Minimum password length\", \"value\": \"8\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"key\": \"Minimum password length\", \"value\": \"8\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/ShareOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/ShareOutputProcessorTest.java new file mode 100644 index 00000000000..d95eba39eb4 --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/ShareOutputProcessorTest.java @@ -0,0 +1,106 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ShareOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final ShareOutputProcessor processor = new ShareOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when share_name and permissions are present") + void shouldReturnTrueWhenShareNameAndPermissionsPresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"share_name\": \"ADMIN$\", \"permissions\": \"READ\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when share_name is missing") + void shouldReturnFalseWhenShareNameMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"permissions\": \"READ\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when permissions is missing") + void shouldReturnFalseWhenPermissionsMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"share_name\": \"ADMIN$\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when both required fields are missing") + void shouldReturnFalseWhenBothRequiredFieldsMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return UNC path with host when host is present") + void shouldReturnUncPathWithHostWhenHostPresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"share_name\": \"ADMIN$\", \"permissions\": \"READ,WRITE\", \"host\": \"dc01\"}"); + String result = processor.toFindingValue(node); + assertEquals("\\\\dc01\\ADMIN$ (READ,WRITE)", result); + } + + @Test + @DisplayName("should return share name with permissions when host is absent") + void shouldReturnShareNameWithPermissionsWhenHostAbsent() throws Exception { + JsonNode node = objectMapper.readTree("{\"share_name\": \"C$\", \"permissions\": \"READ\"}"); + String result = processor.toFindingValue(node); + assertEquals("C$ (READ)", result); + } + + @Test + @DisplayName("should return share name with permissions when host is empty") + void shouldReturnShareNameWithPermissionsWhenHostEmpty() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"share_name\": \"C$\", \"permissions\": \"READ\", \"host\": \"\"}"); + String result = processor.toFindingValue(node); + assertEquals("C$ (READ)", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = + objectMapper.readTree("{\"share_name\": \"ADMIN$\", \"permissions\": \"READ\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"share_name\": \"ADMIN$\", \"permissions\": \"READ\", \"asset_id\": \"asset1\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset1"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"share_name\": \"ADMIN$\", \"permissions\": \"READ\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/SidOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/SidOutputProcessorTest.java new file mode 100644 index 00000000000..65f834d5b2f --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/SidOutputProcessorTest.java @@ -0,0 +1,89 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SidOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final SidOutputProcessor processor = new SidOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when sid is present") + void shouldReturnTrueWhenSidPresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"sid\": \"S-1-5-21-3623811015-3361044348-30300820-1013\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when sid is missing") + void shouldReturnFalseWhenSidMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when sid is null") + void shouldReturnFalseWhenSidNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"sid\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return sid as finding value") + void shouldReturnSidAsFindingValue() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"sid\": \"S-1-5-21-3623811015-3361044348-30300820-1013\", \"host\": \"dc01\"}"); + String result = processor.toFindingValue(node); + assertEquals("S-1-5-21-3623811015-3361044348-30300820-1013", result); + } + + @Test + @DisplayName("should return empty string when sid field is missing for finding value") + void shouldReturnEmptyStringWhenSidMissingForFindingValue() throws Exception { + JsonNode node = objectMapper.readTree("{\"host\": \"dc01\"}"); + String result = processor.toFindingValue(node); + assertEquals("", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = + objectMapper.readTree("{\"sid\": \"S-1-5-21-3623811015-3361044348-30300820-1013\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"sid\": \"S-1-5-21-3623811015-3361044348-30300820-1013\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"sid\": \"S-1-5-21-3623811015-3361044348-30300820-1013\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/UsernameOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/UsernameOutputProcessorTest.java new file mode 100644 index 00000000000..63897353042 --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/UsernameOutputProcessorTest.java @@ -0,0 +1,98 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class UsernameOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final UsernameOutputProcessor processor = new UsernameOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when username is present") + void shouldReturnTrueWhenUsernamePresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"username\": \"alice\", \"domain\": \"CORP\", \"host\": \"dc01\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return true when username is present without optional fields") + void shouldReturnTrueWhenUsernamePresentWithoutOptionalFields() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"alice\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when username is missing") + void shouldReturnFalseWhenUsernameMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"domain\": \"CORP\", \"host\": \"dc01\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when username is null") + void shouldReturnFalseWhenUsernameNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return domain\\username when domain is present") + void shouldReturnDomainBackslashUsernameWhenDomainPresent() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"alice\", \"domain\": \"CORP\"}"); + String result = processor.toFindingValue(node); + assertEquals("CORP\\alice", result); + } + + @Test + @DisplayName("should return username only when domain is absent") + void shouldReturnUsernameOnlyWhenDomainAbsent() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"bob\"}"); + String result = processor.toFindingValue(node); + assertEquals("bob", result); + } + + @Test + @DisplayName("should return username only when domain is empty") + void shouldReturnUsernameOnlyWhenDomainEmpty() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"bob\", \"domain\": \"\"}"); + String result = processor.toFindingValue(node); + assertEquals("bob", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"alice\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = objectMapper.readTree("{\"username\": \"alice\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree("{\"username\": \"alice\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/output_processor/VulnerabilityOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/VulnerabilityOutputProcessorTest.java new file mode 100644 index 00000000000..d3a8b32d0af --- /dev/null +++ b/openaev-api/src/test/java/io/openaev/output_processor/VulnerabilityOutputProcessorTest.java @@ -0,0 +1,94 @@ +package io.openaev.output_processor; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; + +import io.openaev.rest.finding.FindingService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class VulnerabilityOutputProcessorTest { + + private final FindingService findingService = mock(FindingService.class); + private final VulnerabilityOutputProcessor processor = new VulnerabilityOutputProcessor(findingService); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + @DisplayName("should return true when name and status are present") + void shouldReturnTrueWhenNameAndStatusPresent() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"name\": \"EternalBlue\", \"status\": \"vulnerable\", \"details\": \"MS17-010\", \"host\": \"192.168.1.1\"}"); + assertTrue(processor.validate(node)); + } + + @Test + @DisplayName("should return false when name is missing") + void shouldReturnFalseWhenNameMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"status\": \"vulnerable\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when status is missing") + void shouldReturnFalseWhenStatusMissing() throws Exception { + JsonNode node = objectMapper.readTree("{\"name\": \"EternalBlue\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when name is null") + void shouldReturnFalseWhenNameNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"name\": null, \"status\": \"vulnerable\"}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return false when status is null") + void shouldReturnFalseWhenStatusNull() throws Exception { + JsonNode node = objectMapper.readTree("{\"name\": \"EternalBlue\", \"status\": null}"); + assertFalse(processor.validate(node)); + } + + @Test + @DisplayName("should return name [status] as finding value") + void shouldReturnNameBracketStatusAsFindingValue() throws Exception { + JsonNode node = + objectMapper.readTree("{\"name\": \"EternalBlue\", \"status\": \"vulnerable\"}"); + String result = processor.toFindingValue(node); + assertEquals("EternalBlue [vulnerable]", result); + } + + @Test + @DisplayName("should return empty list when asset_id is missing") + void shouldReturnEmptyListWhenAssetIdMissing() throws Exception { + JsonNode node = + objectMapper.readTree("{\"name\": \"EternalBlue\", \"status\": \"vulnerable\"}"); + List result = processor.toFindingAssets(node); + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("should return single asset id when asset_id is a string") + void shouldReturnSingleAssetIdWhenAssetIdIsString() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"name\": \"EternalBlue\", \"status\": \"vulnerable\", \"asset_id\": \"asset42\"}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("asset42"), result); + } + + @Test + @DisplayName("should return multiple asset ids when asset_id is an array") + void shouldReturnMultipleAssetIdsWhenAssetIdIsArray() throws Exception { + JsonNode node = + objectMapper.readTree( + "{\"name\": \"EternalBlue\", \"status\": \"vulnerable\", \"asset_id\": [\"a1\", \"a2\"]}"); + List result = processor.toFindingAssets(node); + assertEquals(List.of("a1", "a2"), result); + } +} diff --git a/openaev-api/src/test/java/io/openaev/rest/inject/StructuredOutputUtilsTest.java b/openaev-api/src/test/java/io/openaev/rest/inject/StructuredOutputUtilsTest.java index 1390ab624a5..da650b991d5 100644 --- a/openaev-api/src/test/java/io/openaev/rest/inject/StructuredOutputUtilsTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/inject/StructuredOutputUtilsTest.java @@ -275,7 +275,7 @@ void given_raw_output_credentials_should_return_credentials() { ContractOutputType.Credentials, "Credentials", "(\\S+)\\\\(\\S+):(\\S+)", - "[{\"username\":\"savacano\",\"password\":\"savacano\"}]"); + "[{\"asset_id\":null,\"username\":\"savacano\",\"password\":\"savacano\",\"hash\":null,\"host\":null}]"); } private void testRegexExtraction( diff --git a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts new file mode 100644 index 00000000000..15db2de9ecb --- /dev/null +++ b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts @@ -0,0 +1,33 @@ +enum ContractOutputElementType { + text = 'Text', + number = 'Number', + port = 'Port', + portscan = 'PortsScan', + ipv4 = 'IPv4', + ipv6 = 'IPv6', + credentials = 'Credentials', + cve = 'CVE', + username = 'Username', + share = 'Share', + admin_username = 'AdminUsername', + group = 'Group', + computer = 'Computer', + password_policy = 'PasswordPolicy', + delegation = 'Delegation', + sid = 'Sid', + account_with_password_not_required = 'AccountWithPasswordNotRequired', + asreproastable_account = 'AsreproastableAccount', + kerberoastable_account = 'KerberoastableAccount', + vulnerability = 'Vulnerability', +} + +export const CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS = [ + 'text', 'number', 'port', 'portscan', 'ipv4', 'ipv6', 'credentials', 'cve', + 'vulnerability', 'username', 'share', 'admin_username', 'group', 'computer', + 'password_policy', 'delegation', 'sid', 'account_with_password_not_required', + 'asreproastable_account', 'kerberoastable_account', +] as const; + +export type ContractOutputElementTypeKey = typeof CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS[number]; + +export default ContractOutputElementType; diff --git a/openaev-front/src/admin/components/findings/Finding.ts b/openaev-front/src/admin/components/findings/Finding.ts deleted file mode 100644 index d21dec057df..00000000000 --- a/openaev-front/src/admin/components/findings/Finding.ts +++ /dev/null @@ -1,12 +0,0 @@ -enum ContractOutputElementType { - text = 'Text', - number = 'Number', - port = 'Port', - portscan = 'PortsScan', - ipv4 = 'IPv4', - ipv6 = 'IPv6', - credentials = 'Credentials', - vulnerability = 'CVE', -}; - -export default ContractOutputElementType; diff --git a/openaev-front/src/admin/components/payloads/PayloadForm.tsx b/openaev-front/src/admin/components/payloads/PayloadForm.tsx index 41a07f533c9..5b8df56823e 100644 --- a/openaev-front/src/admin/components/payloads/PayloadForm.tsx +++ b/openaev-front/src/admin/components/payloads/PayloadForm.tsx @@ -8,6 +8,7 @@ import { z, type ZodTypeAny } from 'zod'; import Tabs, { type TabsEntry } from '../../../components/common/tabs/Tabs'; import useTabs from '../../../components/common/tabs/useTabs'; import { useFormatter } from '../../../components/i18n'; +import { CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS } from '../findings/ContractOutputElementType'; import { type DetectionRemediationInput } from '../../../utils/api-types'; import { type PayloadCreateInput } from '../../../utils/api-types-custom'; import useEnterpriseEdition from '../../../utils/hooks/useEnterpriseEdition'; @@ -75,7 +76,7 @@ const PayloadForm = ({ contract_output_element_is_finding: z.boolean(), contract_output_element_name: z.string().min(1, { message: t('Should not be empty') }), contract_output_element_key: z.string().min(1, { message: t('Should not be empty') }), - contract_output_element_type: z.enum(['text', 'number', 'port', 'portscan', 'ipv4', 'ipv6', 'credentials', 'cve'], { message: t('Should not be empty') }), + contract_output_element_type: z.enum(CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS, { message: t('Should not be empty') }), contract_output_element_tags: z.string().array().optional(), contract_output_element_rule: z.string().min(1, { message: t('Should not be empty') }), contract_output_element_regex_groups: z.array(regexGroupObject), diff --git a/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx b/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx index 965381b168d..9ed3d2227d1 100644 --- a/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx +++ b/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx @@ -9,7 +9,8 @@ import SelectFieldController from '../../../../components/fields/SelectFieldCont import TagFieldController from '../../../../components/fields/TagFieldController'; import TextFieldController from '../../../../components/fields/TextFieldController'; import { useFormatter } from '../../../../components/i18n'; -import { type ContractOutputElement, type RegexGroup } from '../../../../utils/api-types'; +import ContractOutputElementType, { CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS } from '../../findings/ContractOutputElementType'; +import { type RegexGroup } from '../../../../utils/api-types'; interface Props { prefixName: string; @@ -39,9 +40,21 @@ const ContractOutputElementCard = ({ prefixName, index, remove }: Props) => { const { t } = useFormatter(); const defaultFields = { - credentials: ['username', 'password'], + credentials: ['username', 'password', 'hash'], portscan: ['host', 'port', 'service'], cve: ['id', 'host', 'severity'], + username: ['username', 'domain', 'host'], + share: ['share_name', 'permissions', 'host'], + admin_username: ['username', 'host'], + group: ['group_name', 'member_count', 'host'], + computer: ['computer_name', 'host'], + password_policy: ['key', 'value', 'host'], + delegation: ['account', 'delegation_type', 'rights_to', 'host'], + sid: ['sid', 'host'], + vulnerability: ['name', 'status', 'details', 'host'], + account_with_password_not_required: ['account', 'status', 'host'], + asreproastable_account: ['username', 'hash', 'host'], + kerberoastable_account: ['username', 'hash', 'host'], }; const selectedContractOutputElementType = watch(`${prefixName}.${index}.contract_output_element_type`) as keyof typeof defaultFields | undefined; @@ -51,14 +64,9 @@ const ContractOutputElementCard = ({ prefixName, index, remove }: Props) => { }); const regexGroupsField = fields as (RegexGroup & { id: string })[]; - type ContractOutputElementType = ContractOutputElement['contract_output_element_type']; - const contractOutputElementTypes: ContractOutputElementType[] = [ - 'text', 'number', 'port', 'portscan', 'ipv4', 'ipv6', 'credentials', 'cve', - ]; - - const outputParserTypeList = contractOutputElementTypes.map(type => ({ + const outputParserTypeList = CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS.map(type => ({ value: type, - label: t(type.charAt(0).toUpperCase() + type.slice(1)), + label: ContractOutputElementType[type], })); useEffect(() => { diff --git a/openaev-front/src/admin/components/settings/vulnerabilities/RelatedInjectsTab.tsx b/openaev-front/src/admin/components/settings/vulnerabilities/RelatedInjectsTab.tsx index 2ef429b18c8..2833c329263 100644 --- a/openaev-front/src/admin/components/settings/vulnerabilities/RelatedInjectsTab.tsx +++ b/openaev-front/src/admin/components/settings/vulnerabilities/RelatedInjectsTab.tsx @@ -16,7 +16,7 @@ import FindingIcon from '../../../../components/FindingIcon'; import ItemTargets from '../../../../components/ItemTargets'; import PaginatedListLoader from '../../../../components/PaginatedListLoader'; import type { AggregatedFindingOutput, FilterGroup, RelatedFindingOutput, SearchPaginationInput, TargetSimple } from '../../../../utils/api-types'; -import ContractOutputElementType from '../../findings/Finding'; +import ContractOutputElementType from '../../findings/ContractOutputElementType'; const useStyles = makeStyles()(() => ({ itemHead: { textTransform: 'uppercase' }, diff --git a/openaev-front/src/components/FindingIcon.tsx b/openaev-front/src/components/FindingIcon.tsx index 67106a86418..18a1380e14e 100644 --- a/openaev-front/src/components/FindingIcon.tsx +++ b/openaev-front/src/components/FindingIcon.tsx @@ -1,6 +1,22 @@ import { ReportProblemOutlined } from '@mui/icons-material'; import { Tooltip } from '@mui/material'; -import { CubeScan, FormatText, IpOutline, KeyOutline, MidiPort, Numeric, TagSearchOutline } from 'mdi-material-ui'; +import { + AccountAlertOutline, + AccountArrowRight, + AccountGroupOutline, + AccountOutline, + CubeScan, + DesktopClassic, + FolderNetworkOutline, + FormatText, + Identifier, + IpOutline, + KeyOutline, + MidiPort, + Numeric, + ShieldLockOutline, + TagSearchOutline, +} from 'mdi-material-ui'; import { type FunctionComponent } from 'react'; interface FindingIconProps { @@ -26,6 +42,25 @@ const renderIcon = (findingType: string) => { return ; case 'vulnerability': return ; + case 'username': + case 'admin_username': + return ; + case 'share': + return ; + case 'group': + return ; + case 'computer': + return ; + case 'password_policy': + return ; + case 'delegation': + return ; + case 'sid': + return ; + case 'account_with_password_not_required': + case 'asreproastable_account': + case 'kerberoastable_account': + return ; default: return ; } diff --git a/openaev-front/src/utils/api-types.d.ts b/openaev-front/src/utils/api-types.d.ts index b392aa2b79d..579022730a1 100644 --- a/openaev-front/src/utils/api-types.d.ts +++ b/openaev-front/src/utils/api-types.d.ts @@ -135,7 +135,19 @@ export interface AggregatedFindingOutput { | "ipv4" | "ipv6" | "credentials" - | "cve"; + | "cve" + | "username" + | "share" + | "admin_username" + | "group" + | "computer" + | "password_policy" + | "delegation" + | "sid" + | "vulnerability" + | "account_with_password_not_required" + | "asreproastable_account" + | "kerberoastable_account"; /** Finding Value */ finding_value: string; } @@ -1169,7 +1181,19 @@ export interface ContractOutputElement { | "ipv4" | "ipv6" | "credentials" - | "cve"; + | "cve" + | "username" + | "share" + | "admin_username" + | "group" + | "computer" + | "password_policy" + | "delegation" + | "sid" + | "vulnerability" + | "account_with_password_not_required" + | "asreproastable_account" + | "kerberoastable_account"; /** @format date-time */ contract_output_element_updated_at: string; listened?: boolean; @@ -1202,7 +1226,19 @@ export interface ContractOutputElementInput { | "ipv4" | "ipv6" | "credentials" - | "cve"; + | "cve" + | "username" + | "share" + | "admin_username" + | "group" + | "computer" + | "password_policy" + | "delegation" + | "sid" + | "vulnerability" + | "account_with_password_not_required" + | "asreproastable_account" + | "kerberoastable_account"; } /** Represents the rules for parsing the output of an execution. */ @@ -1229,7 +1265,19 @@ export interface ContractOutputElementSimple { | "ipv4" | "ipv6" | "credentials" - | "cve"; + | "cve" + | "username" + | "share" + | "admin_username" + | "group" + | "computer" + | "password_policy" + | "delegation" + | "sid" + | "vulnerability" + | "account_with_password_not_required" + | "asreproastable_account" + | "kerberoastable_account"; } export interface CreateConnectorInstanceInput { @@ -2790,7 +2838,19 @@ export interface Finding { | "ipv4" | "ipv6" | "credentials" - | "cve"; + | "cve" + | "username" + | "share" + | "admin_username" + | "group" + | "computer" + | "password_policy" + | "delegation" + | "sid" + | "vulnerability" + | "account_with_password_not_required" + | "asreproastable_account" + | "kerberoastable_account"; /** @format date-time */ finding_updated_at: string; finding_users?: string[]; @@ -2810,7 +2870,19 @@ export interface FindingInput { | "ipv4" | "ipv6" | "credentials" - | "cve"; + | "cve" + | "username" + | "share" + | "admin_username" + | "group" + | "computer" + | "password_policy" + | "delegation" + | "sid" + | "vulnerability" + | "account_with_password_not_required" + | "asreproastable_account" + | "kerberoastable_account"; finding_value: string; } @@ -3553,6 +3625,7 @@ export interface Injector { | "IMAP" | "NUCLEI" | "NMAP" + | "NETEXEC" | "OpenAEV Email" | "OpenAEV Implant" )[]; @@ -5630,7 +5703,19 @@ export interface RelatedFindingOutput { | "ipv4" | "ipv6" | "credentials" - | "cve"; + | "cve" + | "username" + | "share" + | "admin_username" + | "group" + | "computer" + | "password_policy" + | "delegation" + | "sid" + | "vulnerability" + | "account_with_password_not_required" + | "asreproastable_account" + | "kerberoastable_account"; /** Finding Value */ finding_value: string; } diff --git a/openaev-front/src/utils/injector_contract/InjectorContractUtils.ts b/openaev-front/src/utils/injector_contract/InjectorContractUtils.ts index 812a4bf717c..a02de14b8d2 100644 --- a/openaev-front/src/utils/injector_contract/InjectorContractUtils.ts +++ b/openaev-front/src/utils/injector_contract/InjectorContractUtils.ts @@ -2,7 +2,7 @@ import * as R from 'ramda'; import { type AttackPattern, type InjectorContractFullOutput } from '../api-types'; -export const externalContractTypesWithFindings = ['openaev_nmap', 'openaev_nuclei', 'openaev_aws']; +export const externalContractTypesWithFindings = ['openaev_nmap', 'openaev_nuclei', 'openaev_aws', 'openaev_netexec']; const computeAttackPatterns = (attackPatternIds: InjectorContractFullOutput['injector_contract_attack_patterns'], attackPatternsMap: Record) => { const attackPatternParents = (attackPatternIds ?? []).flatMap((attackPattern) => { diff --git a/openaev-front/src/utils/lang/de.json b/openaev-front/src/utils/lang/de.json index 24a190e7e39..dd689a2b799 100644 --- a/openaev-front/src/utils/lang/de.json +++ b/openaev-front/src/utils/lang/de.json @@ -27,6 +27,7 @@ "Access": "Zugang", "Access reports": "Zugriff auf Berichte", "Access these injects": "Zugriff auf diese Injektionen", + "AccountWithPasswordNotRequired": "Konto mit Passwort nicht erforderlich", "Action type": "Aktionstyp", "Active": "Aktiv", "Add": "Hinzufügen", @@ -62,6 +63,7 @@ "added an entry on": "einen Eintrag hinzufügen zu", "Admin": "Admin", "Administrator": "Administrator", + "AdminUsername": "Admin-Benutzername", "Advanced Installation": "Erweiterte Installation", "Advanced options": "Erweiterte Optionen", "Advanced search": "Erweiterte Suche", @@ -119,6 +121,7 @@ "Arrogant": "Arrogant", "ARTICLE": "Automatisch - Ausgelöst, wenn das Ziel die Artikel liest", "Ask AI": "KI fragen", + "AsreproastableAccount": "AS-REP röstbares Konto", "Assertive": "Assuré", "Asset": "Asset", "asset": "vermögenswert", @@ -295,6 +298,7 @@ "COMPLETE": "Letzte Spur", "Components": "Bestandteile", "Components version": "Version der Komponenten", + "Computer": "Computer", "Condition": "Bedingung", "Configuration": "Konfiguration", "Configure": "Konfigurieren Sie", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "Visualisierung", "Customization": "Anpassung", "Customize columns": "Anpassen von Spalten", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSS muss mindestens 0.0 sein", "CVSS must be at most 10.0": "CVSS darf höchstens 10.0 sein", "CVSS score is required": "CVSS-Score ist erforderlich", @@ -441,6 +446,7 @@ "Deferred": "Aufgeschoben", "Define structured outputs by parsing the raw output of your payload.": "Definieren Sie strukturierte Ausgaben, indem Sie die Rohausgabe Ihrer Nutzlast parsen.", "Definition": "Definition", + "Delegation": "Delegation", "Delete": "Löschen", "Delete test": "Test löschen", "Deleted": "Gelöscht", @@ -817,6 +823,7 @@ "Global score": "Globale Bewertung", "global-crisis": "Globale Krise", "Granted": "Gewährt", + "Group": "Gruppe", "Groups": "Gruppen", "h": "h", "Header mode": "Kopfzeilen-Modus", @@ -987,6 +994,8 @@ "Ip Address": "Ip-Adresse", "IP Addresses": "IP-Adressen", "Ip Addresses": "Ip-Adressen", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "ist", "is empty": "ist leer", "is not empty": "ist nicht leer", @@ -997,6 +1006,7 @@ "its dedicated page": "seine spezielle Seite", "Japanese": "Japanisch", "JAVA Virtual Machine": "Virtuelle JAVA-Maschine", + "KerberoastableAccount": "Kerberoastbares Konto", "Key": "Schlüssel", "Kill chain name": "Name der Abbruchkette", "Kill Chain Phase": "Phase der Tötungskette", @@ -1327,6 +1337,7 @@ "Partial": "Teilweise", "Password": "Passwort", "Password validation": "Passwort-Überprüfung", + "PasswordPolicy": "Passwort-Richtlinie", "Passwords do no match": "Passwörter stimmen nicht überein", "Paste text to analyse for selected TTPs.": "Text zur Analyse für ausgewählte TTPs einfügen.", "Paste your Filigran OpenAEV Enterprise Edition license": "Fügen Sie Ihre Filigran OpenAEV Enterprise Edition Lizenz ein", @@ -1402,6 +1413,7 @@ "Polls": "Umfragen", "Port": "Hafen", "Portscan": "Hafen-Scan", + "PortsScan": "Ports scannen", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Befehl zur Überprüfung der Voraussetzungen", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "Mehrere Übereinstimmungen gefunden", "Severity": "Schweregrad", "Sh": "Sh", + "Share": "Teilen Sie", "Shares": "Anteile", "Should at least select one dimension": "Sollte mindestens eine Dimension auswählen", "Should be a valid email address": "Sollte eine gültige E-Mail Adresse sein", @@ -1616,6 +1629,7 @@ "Show in findings": "In Befunden anzeigen", "Show Timeline": "Zeitleiste anzeigen", "Show timeline": "Zeitleiste anzeigen", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "Anmelden", "Simulate Now": "Jetzt simulieren", diff --git a/openaev-front/src/utils/lang/en.json b/openaev-front/src/utils/lang/en.json index 98a381d3bba..5685d01a20f 100644 --- a/openaev-front/src/utils/lang/en.json +++ b/openaev-front/src/utils/lang/en.json @@ -27,6 +27,7 @@ "Access": "Access", "Access reports": "Access reports", "Access these injects": "Access these injects", + "AccountWithPasswordNotRequired": "Account with password not required", "Action type": "Action type", "Active": "Active", "Add": "Add", @@ -62,6 +63,7 @@ "added an entry on": "added an entry on", "Admin": "Admin", "Administrator": "Administrator", + "AdminUsername": "Admin username", "Advanced Installation": "Advanced Installation", "Advanced options": "Advanced options", "Advanced search": "Advanced search", @@ -119,6 +121,7 @@ "Arrogant": "Arrogant", "ARTICLE": "Automatic - Triggered when target reads the articles", "Ask AI": "Ask AI", + "AsreproastableAccount": "AS-REP roastable account", "Assertive": "Assertive", "Asset": "Asset", "asset": "asset", @@ -295,6 +298,7 @@ "COMPLETE": "Last trace", "Components": "Components", "Components version": "Components version", + "Computer": "Computer", "Condition": "Condition", "Configuration": "Configuration", "Configure": "Configure", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "Visualization", "Customization": "Customization", "Customize columns": "Customize columns", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSS must be at least 0.0", "CVSS must be at most 10.0": "CVSS must be at most 10.0", "CVSS score is required": "CVSS score is required", @@ -441,6 +446,7 @@ "Deferred": "Deferred", "Define structured outputs by parsing the raw output of your payload.": "Define structured outputs by parsing the raw output of your payload.", "Definition": "Definition", + "Delegation": "Delegation", "Delete": "Delete", "Delete test": "Delete test", "Deleted": "Deleted", @@ -817,6 +823,7 @@ "Global score": "Global score", "global-crisis": "Global Crisis", "Granted": "Granted", + "Group": "Group", "Groups": "Groups", "h": "h", "Header mode": "Header mode", @@ -987,6 +994,8 @@ "Ip Address": "Ip Address", "IP Addresses": "IP Addresses", "Ip Addresses": "Ip Addresses", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "is", "is empty": "is empty", "is not empty": "is not empty", @@ -997,6 +1006,7 @@ "its dedicated page": "its dedicated page", "Japanese": "Japanese", "JAVA Virtual Machine": "JAVA Virtual Machine", + "KerberoastableAccount": "Kerberoastable account", "Key": "Key", "Kill chain name": "Kill chain name", "Kill Chain Phase": "Kill Chain Phase", @@ -1327,6 +1337,7 @@ "Partial": "Partial", "Password": "Password", "Password validation": "Password validation", + "PasswordPolicy": "Password policy", "Passwords do no match": "Passwords do no match", "Paste text to analyse for selected TTPs.": "Paste text to analyse for selected TTPs.", "Paste your Filigran OpenAEV Enterprise Edition license": "Paste your Filigran OpenAEV Enterprise Edition license", @@ -1402,6 +1413,7 @@ "Polls": "Polls", "Port": "Port", "Portscan": "Port scan", + "PortsScan": "Ports scan", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Prerequisite check command", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "Several matches found", "Severity": "Severity", "Sh": "Sh", + "Share": "Share", "Shares": "Shares", "Should at least select one dimension": "Should at least select one dimension", "Should be a valid email address": "Should be a valid email address", @@ -1616,6 +1629,7 @@ "Show in findings": "Show in findings", "Show Timeline": "Show Timeline", "Show timeline": "Show timeline", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "Sign in", "Simulate Now": "Simulate Now", diff --git a/openaev-front/src/utils/lang/es.json b/openaev-front/src/utils/lang/es.json index b7a5e72e675..857d5b9d39f 100644 --- a/openaev-front/src/utils/lang/es.json +++ b/openaev-front/src/utils/lang/es.json @@ -27,6 +27,7 @@ "Access": "Acceso", "Access reports": "Acceder a informes", "Access these injects": "Acceder a estas inyecciones", + "AccountWithPasswordNotRequired": "Cuenta con contraseña no necesaria", "Action type": "Tipo de acción", "Active": "Activo", "Add": "Añadir", @@ -62,6 +63,7 @@ "added an entry on": "añadir una entrada en", "Admin": "Admin", "Administrator": "Administrador", + "AdminUsername": "Admin nombre de usuario", "Advanced Installation": "Instalación avanzada", "Advanced options": "Opciones avanzadas", "Advanced search": "Búsqueda avanzada", @@ -119,6 +121,7 @@ "Arrogant": "Arrogante", "ARTICLE": "Automático - Se activa cuando el objetivo lee los artículos", "Ask AI": "Preguntar AI", + "AsreproastableAccount": "Cuenta AS-REP roastable", "Assertive": "Assuré", "Asset": "Activo", "asset": "activo", @@ -295,6 +298,7 @@ "COMPLETE": "Último rastro", "Components": "Componentes", "Components version": "Versión de componentes", + "Computer": "Ordenador", "Condition": "Condición", "Configuration": "Configuración", "Configure": "Configurar", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "Visualización", "Customization": "Personalización", "Customize columns": "Personalizar columnas", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSS debe ser al menos 0.0", "CVSS must be at most 10.0": "CVSS debe ser como máximo 10.0", "CVSS score is required": "Se requiere la puntuación CVSS", @@ -441,6 +446,7 @@ "Deferred": "Aplazado", "Define structured outputs by parsing the raw output of your payload.": "Define estructuras de salida analizando la salida bruta de su carga útil.", "Definition": "Definición", + "Delegation": "Delegación", "Delete": "Borrar", "Delete test": "Borrar prueba", "Deleted": "Borrado", @@ -817,6 +823,7 @@ "Global score": "Puntuación global", "global-crisis": "Crisis global", "Granted": "Concedido", + "Group": "Grupo", "Groups": "Grupos", "h": "h", "Header mode": "Modo cabecera", @@ -987,6 +994,8 @@ "Ip Address": "Dirección IP", "IP Addresses": "Direcciones IP", "Ip Addresses": "Direcciones IP", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "es", "is empty": "está vacío", "is not empty": "no está vacío", @@ -997,6 +1006,7 @@ "its dedicated page": "su página dedicada", "Japanese": "Japonés", "JAVA Virtual Machine": "Máquina Virtual JAVA", + "KerberoastableAccount": "Cuenta Kerberoastable", "Key": "Clave", "Kill chain name": "Nombre de la cadena de matanza", "Kill Chain Phase": "Fase de la cadena de ataque", @@ -1327,6 +1337,7 @@ "Partial": "Parcial", "Password": "Contraseña", "Password validation": "Validación de contraseña", + "PasswordPolicy": "Política de contraseña", "Passwords do no match": "Las contraseñas no coinciden", "Paste text to analyse for selected TTPs.": "Pegar texto para analizar los Patrones de Attaques seleccionados.", "Paste your Filigran OpenAEV Enterprise Edition license": "Pegue su licencia de Filigran OpenAEV Enterprise Edition", @@ -1402,6 +1413,7 @@ "Polls": "Encuestas", "Port": "Puerto", "Portscan": "Puerto scan", + "PortsScan": "Escaneo de puertos", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Comando de comprobación de requisitos previos", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "Se encontraron varias coincidencias", "Severity": "Gravedad", "Sh": "Sh", + "Share": "Compartir", "Shares": "Acciones", "Should at least select one dimension": "Debe seleccionar al menos una dimensión", "Should be a valid email address": "Debe ser una dirección de correo electrónico válida", @@ -1616,6 +1629,7 @@ "Show in findings": "Mostrar en resultados", "Show Timeline": "Mostrar línea de tiempo", "Show timeline": "Mostrar línea de tiempo", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "Iniciar sesión", "Simulate Now": "Simular ahora", diff --git a/openaev-front/src/utils/lang/fr.json b/openaev-front/src/utils/lang/fr.json index 556534d98c2..841e3656a94 100644 --- a/openaev-front/src/utils/lang/fr.json +++ b/openaev-front/src/utils/lang/fr.json @@ -27,6 +27,7 @@ "Access": "Accéder", "Access reports": "Accéder aux rapports", "Access these injects": "Accéder à ces injections", + "AccountWithPasswordNotRequired": "Compte avec mot de passe non requis", "Action type": "Type d'action", "Active": "Actif", "Add": "Ajouter", @@ -62,6 +63,7 @@ "added an entry on": "a ajouté une entrée le", "Admin": "Administrateur", "Administrator": "Administrateur", + "AdminUsername": "Nom d'utilisateur de l'administrateur", "Advanced Installation": "Installation avancée", "Advanced options": "Options avancées", "Advanced search": "Recherche avancée", @@ -119,6 +121,7 @@ "Arrogant": "Arrogant", "ARTICLE": "Automatique - Déclenché lorsque l'équipe a lu l'article", "Ask AI": "Demander à l'IA", + "AsreproastableAccount": "Compte roastable AS-REP", "Assertive": "Assertif", "Asset": "Actif", "asset": "actif", @@ -295,6 +298,7 @@ "COMPLETE": "Dernière trace", "Components": "Composants", "Components version": "Version des composants", + "Computer": "Ordinateur", "Condition": "Condition", "Configuration": "Configuration", "Configure": "Configurer", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "Visualisation", "Customization": "Personnalisation", "Customize columns": "Personnaliser les colonnes", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSS doit être au moins égal à 0.0", "CVSS must be at most 10.0": "CVSS doit être au plus égal à 10.0", "CVSS score is required": "Le score CVSS est requis", @@ -441,6 +446,7 @@ "Deferred": "Différé", "Define structured outputs by parsing the raw output of your payload.": "Définissez des sorties structurées en analysant la sortie brute de votre payload.", "Definition": "Définition", + "Delegation": "Délégation", "Delete": "Supprimer", "Delete test": "Supprimer le test", "Deleted": "Supprimé", @@ -817,6 +823,7 @@ "Global score": "Score global", "global-crisis": "Crise mondiale", "Granted": "Accordé", + "Group": "Groupe", "Groups": "Groupes", "h": "h", "Header mode": "Mode du bandeau supérieur", @@ -987,6 +994,8 @@ "Ip Address": "Addresse Ip", "IP Addresses": "Adresses IP", "Ip Addresses": "Addresse Ips", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "est", "is empty": "est vide", "is not empty": "n'est pas vide", @@ -997,6 +1006,7 @@ "its dedicated page": "sa page dédiée", "Japanese": "Japonais", "JAVA Virtual Machine": "Machine virtuelle JAVA", + "KerberoastableAccount": "Compte kerberoastable", "Key": "Clé", "Kill chain name": "Nom de la kill chain", "Kill Chain Phase": "Kill chain d'intrusion", @@ -1327,6 +1337,7 @@ "Partial": "Partiel", "Password": "Mot de passe", "Password validation": "Validation du mot de passe", + "PasswordPolicy": "Mot de passe", "Passwords do no match": "Les mots de passe ne correspondent pas", "Paste text to analyse for selected TTPs.": "Coller le texte à analyser pour les TTPs sélectionnés.", "Paste your Filigran OpenAEV Enterprise Edition license": "Collez votre licence Filigran OpenAEV Enterprise Edition", @@ -1402,6 +1413,7 @@ "Polls": "Sondages", "Port": "Port", "Portscan": "Analyse des ports", + "PortsScan": "Balayage des ports", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Commande de vérification des prérequis", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "Plusieurs correspondances trouvées", "Severity": "Severité", "Sh": "Sh", + "Share": "Partager", "Shares": "Nombre de partages", "Should at least select one dimension": "Il faut au moins sélectionner une dimension", "Should be a valid email address": "Doit être une adresse e-mail valide", @@ -1616,6 +1629,7 @@ "Show in findings": "Afficher dans les résultats", "Show Timeline": "Afficher la chronologie", "Show timeline": "Afficher la chronologie", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "S'identifier", "Simulate Now": "Simuler maintenant", diff --git a/openaev-front/src/utils/lang/it.json b/openaev-front/src/utils/lang/it.json index a3ecdffc040..029f7cb56d2 100644 --- a/openaev-front/src/utils/lang/it.json +++ b/openaev-front/src/utils/lang/it.json @@ -27,6 +27,7 @@ "Access": "Accesso", "Access reports": "Rapporti di accesso", "Access these injects": "Accedere a questi injects", + "AccountWithPasswordNotRequired": "Account con password non richiesta", "Action type": "Tipo di azione", "Active": "Attivo", "Add": "Aggiungi", @@ -62,6 +63,7 @@ "added an entry on": "aggiunta una voce su", "Admin": "Admin", "Administrator": "Amministratore", + "AdminUsername": "Nome utente amministratore", "Advanced Installation": "Installazione avanzata", "Advanced options": "Opzioni avanzate", "Advanced search": "Ricerca avanzata", @@ -119,6 +121,7 @@ "Arrogant": "Arrogante", "ARTICLE": "Automatico - Si attiva quando il bersaglio legge gli articoli", "Ask AI": "Chiedi ad AI", + "AsreproastableAccount": "Conto AS-REP roastable", "Assertive": "Assicurato", "Asset": "Asset", "asset": "patrimonio", @@ -295,6 +298,7 @@ "COMPLETE": "Ultima traccia", "Components": "Componenti", "Components version": "Versione dei componenti", + "Computer": "Computer", "Condition": "Condizione", "Configuration": "Configurazione", "Configure": "Configurare", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "Visualizzazione", "Customization": "Personalizzazione", "Customize columns": "Personalizzazione delle colonne", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSS deve essere almeno 0.0", "CVSS must be at most 10.0": "CVSS deve essere al massimo 10.0", "CVSS score is required": "Il punteggio CVSS è richiesto", @@ -441,6 +446,7 @@ "Deferred": "Differito", "Define structured outputs by parsing the raw output of your payload.": "Definire output strutturati analizzando l'output grezzo del payload.", "Definition": "Definizione", + "Delegation": "Delega", "Delete": "Eliminare", "Delete test": "Test di cancellazione", "Deleted": "Eliminato", @@ -817,6 +823,7 @@ "Global score": "Punteggio globale", "global-crisis": "Crisi globale", "Granted": "Concesso", + "Group": "Gruppo", "Groups": "Gruppi", "h": "h", "Header mode": "Modalità di intestazione", @@ -987,6 +994,8 @@ "Ip Address": "Indirizzo IP", "IP Addresses": "Indirizzi IP", "Ip Addresses": "Indirizzi Ip", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "è", "is empty": "è vuoto", "is not empty": "non è vuoto", @@ -997,6 +1006,7 @@ "its dedicated page": "la sua pagina dedicata", "Japanese": "Giapponese", "JAVA Virtual Machine": "Macchina virtuale JAVA", + "KerberoastableAccount": "Conto Kerberoastable", "Key": "Chiave", "Kill chain name": "Nome della catena di uccisione", "Kill Chain Phase": "Fase della catena di uccisione", @@ -1327,6 +1337,7 @@ "Partial": "Parziale", "Password": "Password", "Password validation": "Convalida della password", + "PasswordPolicy": "Politica di password", "Passwords do no match": "Le password non corrispondono", "Paste text to analyse for selected TTPs.": "Incolla il testo da analizzare per i TTP selezionati.", "Paste your Filigran OpenAEV Enterprise Edition license": "Incolla la tua licenza Filigran OpenAEV Enterprise Edition", @@ -1402,6 +1413,7 @@ "Polls": "Sondaggi", "Port": "Porto", "Portscan": "Scansione della porta", + "PortsScan": "Scansione delle porte", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Comando di controllo dei prerequisiti", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "DIVERSE CORRISPONDENZE TROVATE", "Severity": "Gravità", "Sh": "Sh", + "Share": "Condividi", "Shares": "Azioni", "Should at least select one dimension": "Dovrebbe essere selezionata almeno una dimensione", "Should be a valid email address": "Deve essere un indirizzo e-mail valido", @@ -1616,6 +1629,7 @@ "Show in findings": "Mostra nei risultati", "Show Timeline": "Mostra linea temporale", "Show timeline": "Mostra linea temporale", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "Accedi", "Simulate Now": "Simulare ora", diff --git a/openaev-front/src/utils/lang/ja.json b/openaev-front/src/utils/lang/ja.json index 9327c1009d0..aaf022ef436 100644 --- a/openaev-front/src/utils/lang/ja.json +++ b/openaev-front/src/utils/lang/ja.json @@ -27,6 +27,7 @@ "Access": "アクセス", "Access reports": "アクセスレポート", "Access these injects": "これらのインジェクトにアクセスする", + "AccountWithPasswordNotRequired": "パスワード不要のアカウント", "Action type": "アクションタイプ", "Active": "アクティブ", "Add": "追加", @@ -62,6 +63,7 @@ "added an entry on": "のエントリを追加", "Admin": "管理者", "Administrator": "管理者", + "AdminUsername": "管理者ユーザー名", "Advanced Installation": "高度なインストール", "Advanced options": "高度なオプション", "Advanced search": "高度な検索", @@ -119,6 +121,7 @@ "Arrogant": "傲慢", "ARTICLE": "自動 - ターゲットが記事を読むと発動", "Ask AI": "AIに聞く", + "AsreproastableAccount": "AS-REPロースタブルアカウント", "Assertive": "アシュレ", "Asset": "アセット", "asset": "アセット", @@ -295,6 +298,7 @@ "COMPLETE": "最後のトレース", "Components": "コンポーネント", "Components version": "コンポーネント バージョン", + "Computer": "コンピュータ", "Condition": "コンディション", "Configuration": "構成", "Configure": "設定する", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "可視化", "Customization": "カスタマイズ", "Customize columns": "カラムのカスタマイズ", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSSが0.0以上であること", "CVSS must be at most 10.0": "CVSSは10.0以上であること", "CVSS score is required": "CVSSスコアが必要", @@ -441,6 +446,7 @@ "Deferred": "繰延", "Define structured outputs by parsing the raw output of your payload.": "ペイロードの生出力をパースして、構造化出力を定義する。", "Definition": "定義", + "Delegation": "委任", "Delete": "削除", "Delete test": "削除テスト", "Deleted": "削除", @@ -817,6 +823,7 @@ "Global score": "グローバルスコア", "global-crisis": "グローバル危機", "Granted": "\"付与", + "Group": "グループ", "Groups": "グループ", "h": "h", "Header mode": "ヘッダーモード", @@ -987,6 +994,8 @@ "Ip Address": "IP アドレス", "IP Addresses": "IPアドレス", "Ip Addresses": "IPアドレス", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "は", "is empty": "は空", "is not empty": "は空でない", @@ -997,6 +1006,7 @@ "its dedicated page": "専用ページ", "Japanese": "日本語", "JAVA Virtual Machine": "JAVA仮想マシン", + "KerberoastableAccount": "ケベロースター可能アカウント", "Key": "キー", "Kill chain name": "キルチェーン名", "Kill Chain Phase": "キルチェーンフェーズ", @@ -1327,6 +1337,7 @@ "Partial": "部分的", "Password": "パスワード", "Password validation": "パスワード認証", + "PasswordPolicy": "パスワードポリシー", "Passwords do no match": "パスワードが一致しない", "Paste text to analyse for selected TTPs.": "選択されたTTPを分析するためにテキストを貼り付ける。", "Paste your Filigran OpenAEV Enterprise Edition license": "Filigran OpenAEV Enterprise Edition のライセンスを貼り付ける。", @@ -1402,6 +1413,7 @@ "Polls": "世論調査", "Port": "ポート", "Portscan": "ポートスキャン", + "PortsScan": "ポートスキャン", "PostgreSQL": "PostgreSQL", "PowerShell": "パワーシェル", "PREREQUISITE_CHECK": "前提条件チェックコマンド", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "複数の一致が見つかりました", "Severity": "深刻度", "Sh": "シュ", + "Share": "シェア", "Shares": "シェア", "Should at least select one dimension": "少なくとも1つの次元を選択する", "Should be a valid email address": "有効なEメールアドレスであること", @@ -1616,6 +1629,7 @@ "Show in findings": "所見で表示", "Show Timeline": "タイムラインの表示", "Show timeline": "タイムラインを表示する", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "サインイン", "Simulate Now": "今すぐシミュレーション", diff --git a/openaev-front/src/utils/lang/ko.json b/openaev-front/src/utils/lang/ko.json index c232571776f..3e96b08167e 100644 --- a/openaev-front/src/utils/lang/ko.json +++ b/openaev-front/src/utils/lang/ko.json @@ -27,6 +27,7 @@ "Access": "액세스", "Access reports": "보고서 액세스", "Access these injects": "다음 인젝트에 액세스", + "AccountWithPasswordNotRequired": "비밀번호가 필요하지 않은 계정", "Action type": "작업 유형", "Active": "활성", "Add": "추가", @@ -62,6 +63,7 @@ "added an entry on": "에 항목 추가", "Admin": "관리자", "Administrator": "관리자", + "AdminUsername": "관리자 사용자 아이디", "Advanced Installation": "고급 설치", "Advanced options": "고급 옵션", "Advanced search": "고급 검색", @@ -119,6 +121,7 @@ "Arrogant": "오만한", "ARTICLE": "자동 - 타겟이 기사를 읽으면 트리거됩니다", "Ask AI": "AI에게 질문", + "AsreproastableAccount": "AS-REP 로스팅 가능한 계정", "Assertive": "단호한", "Asset": "유산", "asset": "유산", @@ -295,6 +298,7 @@ "COMPLETE": "마지막 추적", "Components": "구성 요소", "Components version": "컴포넌트 버전", + "Computer": "컴퓨터", "Condition": "조건", "Configuration": "구성", "Configure": "구성", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "시각화", "Customization": "사용자 지정", "Customize columns": "열 사용자 지정", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSS는 0.0 이상이어야 합니다", "CVSS must be at most 10.0": "CVSS는 최대 10.0이어야 합니다", "CVSS score is required": "CVSS 점수가 필요합니다", @@ -441,6 +446,7 @@ "Deferred": "연기됨", "Define structured outputs by parsing the raw output of your payload.": "페이로드의 원시 출력을 파싱하여 구조화된 출력을 정의합니다.", "Definition": "정의", + "Delegation": "위임", "Delete": "삭제", "Delete test": "테스트 삭제", "Deleted": "삭제됨", @@ -817,6 +823,7 @@ "Global score": "글로벌 점수", "global-crisis": "글로벌 위기", "Granted": "부여됨", + "Group": "그룹", "Groups": "그룹", "h": "h", "Header mode": "헤더 모드", @@ -987,6 +994,8 @@ "Ip Address": "IP 주소", "IP Addresses": "IP 주소", "Ip Addresses": "IP 주소", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "is", "is empty": "비어 있습니다", "is not empty": "비어 있지 않습니다", @@ -997,6 +1006,7 @@ "its dedicated page": "전용 페이지", "Japanese": "일본어", "JAVA Virtual Machine": "자바 가상 머신", + "KerberoastableAccount": "로스터블 계정", "Key": "키", "Kill chain name": "킬 체인 이름", "Kill Chain Phase": "킬 체인 단계", @@ -1327,6 +1337,7 @@ "Partial": "부분", "Password": "비밀번호", "Password validation": "비밀번호 유효성 검사", + "PasswordPolicy": "비밀번호 정책", "Passwords do no match": "비밀번호가 일치하지 않음", "Paste text to analyse for selected TTPs.": "선택한 TTP에 대해 분석할 텍스트를 붙여넣습니다.", "Paste your Filigran OpenAEV Enterprise Edition license": "필리그란 OpenAEV 엔터프라이즈 에디션 라이선스 붙여넣기", @@ -1402,6 +1413,7 @@ "Polls": "투표", "Port": "포트", "Portscan": "포트 스캔", + "PortsScan": "포트 스캔", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "전제 조건 확인 명령", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "일치하는 항목이 여러 개 발견됨", "Severity": "심각도", "Sh": "Sh", + "Share": "공유", "Shares": "공유", "Should at least select one dimension": "최소한 하나의 차원을 선택해야 합니다", "Should be a valid email address": "유효한 이메일 주소여야 합니다", @@ -1616,6 +1629,7 @@ "Show in findings": "결과에 표시", "Show Timeline": "타임라인 표시", "Show timeline": "타임라인 표시", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "로그인", "Simulate Now": "지금 시뮬레이션", diff --git a/openaev-front/src/utils/lang/ru.json b/openaev-front/src/utils/lang/ru.json index 70d0c35b88f..d41d9b253e3 100644 --- a/openaev-front/src/utils/lang/ru.json +++ b/openaev-front/src/utils/lang/ru.json @@ -27,6 +27,7 @@ "Access": "Доступ", "Access reports": "Отчеты о доступе", "Access these injects": "Доступ к этим инъекциям", + "AccountWithPasswordNotRequired": "Учетная запись с необязательным паролем", "Action type": "Тип действия", "Active": "Активный", "Add": "Добавить", @@ -62,6 +63,7 @@ "added an entry on": "добавлена запись о", "Admin": "Admin", "Administrator": "Администратор", + "AdminUsername": "Имя пользователя", "Advanced Installation": "Расширенная установка", "Advanced options": "Дополнительные опции", "Advanced search": "Расширенный поиск", @@ -119,6 +121,7 @@ "Arrogant": "Arrogant", "ARTICLE": "Автоматически - срабатывает, когда цель читает статьи", "Ask AI": "Спросить АИ", + "AsreproastableAccount": "AS-REP roastable account", "Assertive": "Assuré", "Asset": "Asset", "asset": "актив", @@ -295,6 +298,7 @@ "COMPLETE": "Последний след", "Components": "Компоненты", "Components version": "Версия компонентов", + "Computer": "Компьютер", "Condition": "Условие", "Configuration": "Конфигурация", "Configure": "Настроить", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "Визуализация", "Customization": "Персонализация", "Customize columns": "Настроить колонки", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSS должен быть не менее 0.0", "CVSS must be at most 10.0": "CVSS должен быть не менее 10.0", "CVSS score is required": "Требуется оценка CVSS", @@ -441,6 +446,7 @@ "Deferred": "Отложенный", "Define structured outputs by parsing the raw output of your payload.": "Определите структурированные выходные данные, разобрав необработанный вывод вашей полезной нагрузки.", "Definition": "Определение", + "Delegation": "Делегирование", "Delete": "Удалить", "Delete test": "Удалить тест", "Deleted": "Удалено", @@ -817,6 +823,7 @@ "Global score": "Глобальная оценка", "global-crisis": "Глобальный кризис", "Granted": "Предоставлено", + "Group": "Группа", "Groups": "Группы", "h": "h", "Header mode": "Режим заголовка", @@ -987,6 +994,8 @@ "Ip Address": "Ip-адрес", "IP Addresses": "IP-адреса", "Ip Addresses": "Ip-адреса", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "это", "is empty": "пустой", "is not empty": "не пустой", @@ -997,6 +1006,7 @@ "its dedicated page": "специальная страница", "Japanese": "Японский", "JAVA Virtual Machine": "Виртуальная машина JAVA", + "KerberoastableAccount": "Керберостабильная учетная запись", "Key": "Ключ", "Kill chain name": "Имя цепочки убийств", "Kill Chain Phase": "Фаза цепи убийств", @@ -1327,6 +1337,7 @@ "Partial": "Частичный", "Password": "Пароль", "Password validation": "Проверка пароля", + "PasswordPolicy": "Политика паролей", "Passwords do no match": "Пароли не совпадают", "Paste text to analyse for selected TTPs.": "Вставьте текст для анализа выбранных TTP.", "Paste your Filigran OpenAEV Enterprise Edition license": "Вставьте лицензию Filigran OpenAEV Enterprise Edition", @@ -1402,6 +1413,7 @@ "Polls": "Опросы", "Port": "Порт", "Portscan": "Сканирование порта", + "PortsScan": "Сканирование портов", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Команда проверки предпосылок", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "Найдено несколько совпадений", "Severity": "Тяжесть", "Sh": "Sh", + "Share": "Поделиться", "Shares": "Акции", "Should at least select one dimension": "Выберите хотя бы одно измерение", "Should be a valid email address": "Должен быть действительный адрес электронной почты", @@ -1616,6 +1629,7 @@ "Show in findings": "Показать в выводах", "Show Timeline": "Показать временную шкалу", "Show timeline": "Показать временную шкалу", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "Войти", "Simulate Now": "Смоделируйте сейчас", diff --git a/openaev-front/src/utils/lang/zh.json b/openaev-front/src/utils/lang/zh.json index 219039bc819..6e4b5480704 100644 --- a/openaev-front/src/utils/lang/zh.json +++ b/openaev-front/src/utils/lang/zh.json @@ -27,6 +27,7 @@ "Access": "访问", "Access reports": "访问报告", "Access these injects": "访问这些注入", + "AccountWithPasswordNotRequired": "无需密码的账户", "Action type": "行动类型", "Active": "激活", "Add": "添加", @@ -62,6 +63,7 @@ "added an entry on": "增加了关于", "Admin": "管理", "Administrator": "管理员", + "AdminUsername": "管理员用户名", "Advanced Installation": "高级安装", "Advanced options": "高级选项", "Advanced search": "高级搜索", @@ -119,6 +121,7 @@ "Arrogant": "傲慢", "ARTICLE": "自动 - 团队阅读文章后触发", "Ask AI": "问AI", + "AsreproastableAccount": "AS-REP 可焙烧账户", "Assertive": "自信", "Asset": "资产", "asset": "資產", @@ -295,6 +298,7 @@ "COMPLETE": "最后跟踪", "Components": "组件", "Components version": "组件版本", + "Computer": "计算机", "Condition": "条件", "Configuration": "配置", "Configure": "配置", @@ -400,6 +404,7 @@ "custom_dashboard_step_type": "可视化", "Customization": "自定义", "Customize columns": "专栏标准化", + "CVE": "CVE", "CVSS must be at least 0.0": "CVSS 必须至少为 0.0", "CVSS must be at most 10.0": "CVSS 必须至少为 10.0", "CVSS score is required": "CVSS 分数是必需的", @@ -441,6 +446,7 @@ "Deferred": "推迟", "Define structured outputs by parsing the raw output of your payload.": "通过解析有效载荷的原始输出,定义结构化输出。", "Definition": "定义", + "Delegation": "授权", "Delete": "删除", "Delete test": "删除测试", "Deleted": "已删除", @@ -817,6 +823,7 @@ "Global score": "全局分数", "global-crisis": "全局危机", "Granted": "已批准", + "Group": "群组", "Groups": "组", "h": "h", "Header mode": "头部模式", @@ -987,6 +994,8 @@ "Ip Address": "IP地址", "IP Addresses": "IP地址", "Ip Addresses": "IP地址组", + "IPv4": "IPv4", + "IPv6": "IPv6", "is": "是", "is empty": "为空", "is not empty": "不为空", @@ -997,6 +1006,7 @@ "its dedicated page": "其专用页面", "Japanese": "日语", "JAVA Virtual Machine": "JAVA虚拟机", + "KerberoastableAccount": "Kerberoastable 帐户", "Key": "密钥", "Kill chain name": "杀伤链名称", "Kill Chain Phase": "杀伤链阶段", @@ -1327,6 +1337,7 @@ "Partial": "部分", "Password": "密码", "Password validation": "密码验证", + "PasswordPolicy": "密码策略", "Passwords do no match": "密码不匹配", "Paste text to analyse for selected TTPs.": "为选定的 TTPs 粘贴要分析的文本。", "Paste your Filigran OpenAEV Enterprise Edition license": "粘贴您的 Filigran OpenAEV 企业版许可证", @@ -1402,6 +1413,7 @@ "Polls": "投票", "Port": "港口", "Portscan": "分析港口", + "PortsScan": "端口扫描", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "先决条件检查命令", @@ -1604,6 +1616,7 @@ "SEVERAL_MATCHES": "找到多个匹配项", "Severity": "严重性", "Sh": "Sh", + "Share": "分享", "Shares": "分享", "Should at least select one dimension": "至少应选择一个维度", "Should be a valid email address": "应为有效邮件地址", @@ -1616,6 +1629,7 @@ "Show in findings": "在调查结果中显示", "Show Timeline": "显示时间线", "Show timeline": "显示时间线", + "Sid": "SID", "SIEM": "SIEM", "Sign in": "登录", "Simulate Now": "现在模拟", diff --git a/openaev-model/src/main/java/io/openaev/database/model/ContractOutputType.java b/openaev-model/src/main/java/io/openaev/database/model/ContractOutputType.java index e189f96a8be..543f0c9068f 100644 --- a/openaev-model/src/main/java/io/openaev/database/model/ContractOutputType.java +++ b/openaev-model/src/main/java/io/openaev/database/model/ContractOutputType.java @@ -28,6 +28,42 @@ public enum ContractOutputType { @JsonProperty("cve") CVE("cve"), + @JsonProperty("username") + Username("username"), + + @JsonProperty("share") + Share("share"), + + @JsonProperty("admin_username") + AdminUsername("admin_username"), + + @JsonProperty("group") + Group("group"), + + @JsonProperty("computer") + Computer("computer"), + + @JsonProperty("password_policy") + PasswordPolicy("password_policy"), + + @JsonProperty("delegation") + Delegation("delegation"), + + @JsonProperty("sid") + Sid("sid"), + + @JsonProperty("vulnerability") + Vulnerability("vulnerability"), + + @JsonProperty("account_with_password_not_required") + AccountWithPasswordNotRequired("account_with_password_not_required"), + + @JsonProperty("asreproastable_account") + AsreproastableAccount("asreproastable_account"), + + @JsonProperty("kerberoastable_account") + KerberoastableAccount("kerberoastable_account"), + @Hidden @JsonProperty("asset") Asset("asset"); diff --git a/openaev-model/src/main/java/io/openaev/healthcheck/enums/ExternalServiceDependency.java b/openaev-model/src/main/java/io/openaev/healthcheck/enums/ExternalServiceDependency.java index 7437add8103..cf68e4caafa 100644 --- a/openaev-model/src/main/java/io/openaev/healthcheck/enums/ExternalServiceDependency.java +++ b/openaev-model/src/main/java/io/openaev/healthcheck/enums/ExternalServiceDependency.java @@ -16,6 +16,7 @@ *
  • {@code IMAP} - Email receiving service for response tracking *
  • {@code NUCLEI} - Nuclei vulnerability scanner integration *
  • {@code NMAP} - Nmap network scanner integration + *
  • {@code NETEXEC} - NetExec network execution and credential testing integration * */ public enum ExternalServiceDependency { @@ -35,6 +36,10 @@ public enum ExternalServiceDependency { @JsonProperty("NMAP") NMAP("openaev_nmap"), + /** NetExec network execution and credential testing service. */ + @JsonProperty("NETEXEC") + NETEXEC("openaev_netexec"), + /** OpenAEV Implant service. */ @JsonProperty("OpenAEV Email") OPENAEV_EMAIL("openaev_email"), From 80004ad379b17b79fc2b5c112676652dcd8e3e37 Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 11:46:01 +0100 Subject: [PATCH 02/12] [backend] feat: feedback review --- .../AccountWithPasswordNotRequiredOutputProcessorTest.java | 3 +-- .../output_processor/AdminUsernameOutputProcessorTest.java | 6 +++--- .../AsreproastableAccountOutputProcessorTest.java | 3 +-- .../output_processor/ComputerOutputProcessorTest.java | 3 +-- .../output_processor/DelegationOutputProcessorTest.java | 3 +-- .../openaev/output_processor/GroupOutputProcessorTest.java | 3 +-- .../KerberoastableAccountOutputProcessorTest.java | 3 +-- .../output_processor/PasswordPolicyOutputProcessorTest.java | 6 +++--- .../openaev/output_processor/ShareOutputProcessorTest.java | 3 +-- .../io/openaev/output_processor/SidOutputProcessorTest.java | 3 +-- .../output_processor/UsernameOutputProcessorTest.java | 3 +-- .../output_processor/VulnerabilityOutputProcessorTest.java | 6 +++--- 12 files changed, 18 insertions(+), 27 deletions(-) diff --git a/openaev-api/src/test/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessorTest.java index 0b01738de53..ae0bcdc1e30 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/AccountWithPasswordNotRequiredOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/AdminUsernameOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/AdminUsernameOutputProcessorTest.java index 2697d99a4b3..3a18cbe5920 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/AdminUsernameOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/AdminUsernameOutputProcessorTest.java @@ -5,16 +5,16 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class AdminUsernameOutputProcessorTest { private final FindingService findingService = mock(FindingService.class); - private final AdminUsernameOutputProcessor processor = new AdminUsernameOutputProcessor(findingService); + private final AdminUsernameOutputProcessor processor = + new AdminUsernameOutputProcessor(findingService); private final ObjectMapper objectMapper = new ObjectMapper(); @Test diff --git a/openaev-api/src/test/java/io/openaev/output_processor/AsreproastableAccountOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/AsreproastableAccountOutputProcessorTest.java index d256c7a54c8..961f6a4e9fe 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/AsreproastableAccountOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/AsreproastableAccountOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/ComputerOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/ComputerOutputProcessorTest.java index 8fa60f48f9d..71dac2fc420 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/ComputerOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/ComputerOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/DelegationOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/DelegationOutputProcessorTest.java index aa823e0a083..0c07fda58c1 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/DelegationOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/DelegationOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/GroupOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/GroupOutputProcessorTest.java index 8409870b64b..fc854ec1a87 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/GroupOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/GroupOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/KerberoastableAccountOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/KerberoastableAccountOutputProcessorTest.java index d5fd14d3a26..6ea3908a66b 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/KerberoastableAccountOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/KerberoastableAccountOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/PasswordPolicyOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/PasswordPolicyOutputProcessorTest.java index 3b169afa084..cf53a2e9473 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/PasswordPolicyOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/PasswordPolicyOutputProcessorTest.java @@ -5,16 +5,16 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class PasswordPolicyOutputProcessorTest { private final FindingService findingService = mock(FindingService.class); - private final PasswordPolicyOutputProcessor processor = new PasswordPolicyOutputProcessor(findingService); + private final PasswordPolicyOutputProcessor processor = + new PasswordPolicyOutputProcessor(findingService); private final ObjectMapper objectMapper = new ObjectMapper(); @Test diff --git a/openaev-api/src/test/java/io/openaev/output_processor/ShareOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/ShareOutputProcessorTest.java index d95eba39eb4..3a98f912389 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/ShareOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/ShareOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/SidOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/SidOutputProcessorTest.java index 65f834d5b2f..93a51fa6721 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/SidOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/SidOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/UsernameOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/UsernameOutputProcessorTest.java index 63897353042..0d57a347bd9 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/UsernameOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/UsernameOutputProcessorTest.java @@ -5,9 +5,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/openaev-api/src/test/java/io/openaev/output_processor/VulnerabilityOutputProcessorTest.java b/openaev-api/src/test/java/io/openaev/output_processor/VulnerabilityOutputProcessorTest.java index d3a8b32d0af..4f529bba3ef 100644 --- a/openaev-api/src/test/java/io/openaev/output_processor/VulnerabilityOutputProcessorTest.java +++ b/openaev-api/src/test/java/io/openaev/output_processor/VulnerabilityOutputProcessorTest.java @@ -5,16 +5,16 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import java.util.List; - import io.openaev.rest.finding.FindingService; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; class VulnerabilityOutputProcessorTest { private final FindingService findingService = mock(FindingService.class); - private final VulnerabilityOutputProcessor processor = new VulnerabilityOutputProcessor(findingService); + private final VulnerabilityOutputProcessor processor = + new VulnerabilityOutputProcessor(findingService); private final ObjectMapper objectMapper = new ObjectMapper(); @Test From 7629b79e216c8fc14f5266a4dcb0a4fdfaba09b1 Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 12:35:27 +0100 Subject: [PATCH 03/12] [backend] feat: feedback review --- openaev-front/src/admin/components/payloads/PayloadForm.tsx | 2 +- .../components/payloads/form/ContractOutputElementCard.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openaev-front/src/admin/components/payloads/PayloadForm.tsx b/openaev-front/src/admin/components/payloads/PayloadForm.tsx index 5b8df56823e..ebe7c211875 100644 --- a/openaev-front/src/admin/components/payloads/PayloadForm.tsx +++ b/openaev-front/src/admin/components/payloads/PayloadForm.tsx @@ -8,11 +8,11 @@ import { z, type ZodTypeAny } from 'zod'; import Tabs, { type TabsEntry } from '../../../components/common/tabs/Tabs'; import useTabs from '../../../components/common/tabs/useTabs'; import { useFormatter } from '../../../components/i18n'; -import { CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS } from '../findings/ContractOutputElementType'; import { type DetectionRemediationInput } from '../../../utils/api-types'; import { type PayloadCreateInput } from '../../../utils/api-types-custom'; import useEnterpriseEdition from '../../../utils/hooks/useEnterpriseEdition'; import EEChip from '../common/entreprise_edition/EEChip'; +import { CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS } from '../findings/ContractOutputElementType'; import CommandsFormTab from './form/CommandsFormTab'; import GeneralFormTab from './form/GeneralFormTab'; import OutputFormTab from './form/OutputFormTab'; diff --git a/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx b/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx index 9ed3d2227d1..e627f08c8be 100644 --- a/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx +++ b/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx @@ -9,8 +9,8 @@ import SelectFieldController from '../../../../components/fields/SelectFieldCont import TagFieldController from '../../../../components/fields/TagFieldController'; import TextFieldController from '../../../../components/fields/TextFieldController'; import { useFormatter } from '../../../../components/i18n'; -import ContractOutputElementType, { CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS } from '../../findings/ContractOutputElementType'; import { type RegexGroup } from '../../../../utils/api-types'; +import ContractOutputElementType, { CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS } from '../../findings/ContractOutputElementType'; interface Props { prefixName: string; From f60ce1924bcb64102cd9d0c2fff9ff4bb719e36e Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 15:17:56 +0100 Subject: [PATCH 04/12] [backend] feat: feedback review --- .../payload_info/OutputParserInfoCard.tsx | 3 ++- .../components/findings/ContractOutputElementType.ts | 2 -- .../src/admin/components/findings/FindingList.tsx | 6 +++++- .../payloads/form/ContractOutputElementCard.tsx | 2 +- .../common/queryable/filter/useRetrieveOptions.tsx | 7 +++++++ .../common/queryable/filter/useSearchOptions.tsx | 11 +++++++++++ openaev-front/src/utils/lang/en.json | 9 +++++++++ 7 files changed, 35 insertions(+), 5 deletions(-) diff --git a/openaev-front/src/admin/components/atomic_testings/atomic_testing/payload_info/OutputParserInfoCard.tsx b/openaev-front/src/admin/components/atomic_testings/atomic_testing/payload_info/OutputParserInfoCard.tsx index 26195fbdcf4..bc38d65e144 100644 --- a/openaev-front/src/admin/components/atomic_testings/atomic_testing/payload_info/OutputParserInfoCard.tsx +++ b/openaev-front/src/admin/components/atomic_testings/atomic_testing/payload_info/OutputParserInfoCard.tsx @@ -4,6 +4,7 @@ import { makeStyles } from 'tss-react/mui'; import { useFormatter } from '../../../../../components/i18n'; import ItemCopy from '../../../../../components/ItemCopy'; import ItemTags from '../../../../../components/ItemTags'; +import ContractOutputElementType from '../../../findings/ContractOutputElementType'; import { type OutputParserSimple } from '../../../../../utils/api-types'; const useStyles = makeStyles()(theme => ({ @@ -63,7 +64,7 @@ const OutputParserInfoCard = ({ outputParsers }: Props) => { {contractOutput.contract_output_element_key} {contractOutput.contract_output_element_type - ? t(contractOutput.contract_output_element_type.charAt(0).toUpperCase() + contractOutput.contract_output_element_type.slice(1)) : ''} + ? t(ContractOutputElementType[contractOutput.contract_output_element_type as keyof typeof ContractOutputElementType] ?? contractOutput.contract_output_element_type) : ''} diff --git a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts index 15db2de9ecb..f0332551707 100644 --- a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts +++ b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts @@ -28,6 +28,4 @@ export const CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS = [ 'asreproastable_account', 'kerberoastable_account', ] as const; -export type ContractOutputElementTypeKey = typeof CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS[number]; - export default ContractOutputElementType; diff --git a/openaev-front/src/admin/components/findings/FindingList.tsx b/openaev-front/src/admin/components/findings/FindingList.tsx index 9231a03817f..997e531e342 100644 --- a/openaev-front/src/admin/components/findings/FindingList.tsx +++ b/openaev-front/src/admin/components/findings/FindingList.tsx @@ -13,9 +13,11 @@ import useBodyItemsStyles from '../../../components/common/queryable/style/style import { useQueryableWithLocalStorage } from '../../../components/common/queryable/useQueryableWithLocalStorage'; import { type Header } from '../../../components/common/SortHeadersList'; import FindingIcon from '../../../components/FindingIcon'; +import { useFormatter } from '../../../components/i18n'; import ItemTargets from '../../../components/ItemTargets'; import PaginatedListLoader from '../../../components/PaginatedListLoader'; import { type AggregatedFindingOutput, type RelatedFindingOutput, type SearchPaginationInput, type TargetSimple } from '../../../utils/api-types'; +import ContractOutputElementType from './ContractOutputElementType'; import FindingDetail from './FindingDetail'; const useStyles = makeStyles()(() => ({ @@ -35,6 +37,7 @@ interface Props { const FindingList = ({ searchFindings, searchDistinctFindings, filterLocalStorageKey, contextId, additionalHeaders = [], additionalFilterNames = [] }: Props) => { const { classes } = useStyles(); const bodyItemsStyles = useBodyItemsStyles(); + const { t } = useFormatter(); const [loading, setLoading] = useState(true); const availableFilterNames = [ @@ -70,7 +73,8 @@ const FindingList = ({ searchFindings, searchDistinctFindings, filterLocalStorag field: 'finding_type', label: 'Type', isSortable: true, - value: (finding: AggregatedFindingOutput) => finding.finding_type, + value: (finding: AggregatedFindingOutput) => + t(ContractOutputElementType[finding.finding_type] ?? finding.finding_type), }, { field: 'finding_value', diff --git a/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx b/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx index e627f08c8be..0972fc0e1ad 100644 --- a/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx +++ b/openaev-front/src/admin/components/payloads/form/ContractOutputElementCard.tsx @@ -66,7 +66,7 @@ const ContractOutputElementCard = ({ prefixName, index, remove }: Props) => { const outputParserTypeList = CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS.map(type => ({ value: type, - label: ContractOutputElementType[type], + label: t(ContractOutputElementType[type]), })); useEffect(() => { diff --git a/openaev-front/src/components/common/queryable/filter/useRetrieveOptions.tsx b/openaev-front/src/components/common/queryable/filter/useRetrieveOptions.tsx index 37a94fd6b3e..01722cbfb6c 100644 --- a/openaev-front/src/components/common/queryable/filter/useRetrieveOptions.tsx +++ b/openaev-front/src/components/common/queryable/filter/useRetrieveOptions.tsx @@ -16,6 +16,7 @@ import { searchScenarioByIdAsOption } from '../../../../actions/scenarios/scenar import { searchSimulationByIdAsOptions } from '../../../../actions/simulations/simulation-action'; import { searchTagByIdAsOption } from '../../../../actions/tags/tag-action'; import { searchTeamByIdAsOption } from '../../../../actions/teams/team-actions'; +import ContractOutputElementType from '../../../../admin/components/findings/ContractOutputElementType'; import { type GroupOption, type Option } from '../../../../utils/Option'; import { AbilityContext } from '../../../../utils/permissions/permissionsContext'; import { ACTIONS, SUBJECTS } from '../../../../utils/permissions/types'; @@ -154,6 +155,12 @@ const useRetrieveOptions = () => { setOptions(response.data); }); break; + case 'finding_type': + setOptions(ids.map(id => ({ + id, + label: ContractOutputElementType[id as keyof typeof ContractOutputElementType] ?? id, + }))); + break; case 'finding_simulation': searchExerciseByIdAsOption(ids).then((response) => { setOptions(response.data); diff --git a/openaev-front/src/components/common/queryable/filter/useSearchOptions.tsx b/openaev-front/src/components/common/queryable/filter/useSearchOptions.tsx index 6d4997c3311..c29f3062634 100644 --- a/openaev-front/src/components/common/queryable/filter/useSearchOptions.tsx +++ b/openaev-front/src/components/common/queryable/filter/useSearchOptions.tsx @@ -17,6 +17,7 @@ import { searchScenarioSimulationsAsOption } from '../../../../actions/scenarios import { searchSimulationAsOptions } from '../../../../actions/simulations/simulation-action'; import { searchTagAsOption } from '../../../../actions/tags/tag-action'; import { searchTeamsAsOption } from '../../../../actions/teams/team-actions'; +import ContractOutputElementType, { CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS } from '../../../../admin/components/findings/ContractOutputElementType'; import { type GroupOption, type Option } from '../../../../utils/Option'; import { useFormatter } from '../../../i18n'; import { CUSTOM_DASHBOARD, SCENARIO_SIMULATIONS, SCENARIOS, SIMULATIONS } from './constants'; @@ -158,6 +159,16 @@ const useSearchOptions = () => { setOptions(response.data); }); break; + case 'finding_type': { + const typeOptions = CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS + .filter(type => !search || ContractOutputElementType[type].toLowerCase().includes(search.toLowerCase())) + .map(type => ({ + id: type, + label: ContractOutputElementType[type], + })); + setOptions(typeOptions); + break; + } case 'finding_simulation': searchExerciseLinkedToFindingsAsOption(search, contextId).then((response) => { setOptions(response.data); diff --git a/openaev-front/src/utils/lang/en.json b/openaev-front/src/utils/lang/en.json index 5685d01a20f..768bfaab009 100644 --- a/openaev-front/src/utils/lang/en.json +++ b/openaev-front/src/utils/lang/en.json @@ -28,6 +28,7 @@ "Access reports": "Access reports", "Access these injects": "Access these injects", "AccountWithPasswordNotRequired": "Account with password not required", + "Accountwithpasswordnotrequired": "Account with password not required", "Action type": "Action type", "Active": "Active", "Add": "Add", @@ -64,6 +65,7 @@ "Admin": "Admin", "Administrator": "Administrator", "AdminUsername": "Admin username", + "Adminusername": "Admin username", "Advanced Installation": "Advanced Installation", "Advanced options": "Advanced options", "Advanced search": "Advanced search", @@ -122,6 +124,7 @@ "ARTICLE": "Automatic - Triggered when target reads the articles", "Ask AI": "Ask AI", "AsreproastableAccount": "AS-REP roastable account", + "Asreproastableaccount": "AS-REP roastable account", "Assertive": "Assertive", "Asset": "Asset", "asset": "asset", @@ -405,6 +408,7 @@ "Customization": "Customization", "Customize columns": "Customize columns", "CVE": "CVE", + "Cve": "CVE", "CVSS must be at least 0.0": "CVSS must be at least 0.0", "CVSS must be at most 10.0": "CVSS must be at most 10.0", "CVSS score is required": "CVSS score is required", @@ -995,7 +999,9 @@ "IP Addresses": "IP Addresses", "Ip Addresses": "Ip Addresses", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "is", "is empty": "is empty", "is not empty": "is not empty", @@ -1007,6 +1013,7 @@ "Japanese": "Japanese", "JAVA Virtual Machine": "JAVA Virtual Machine", "KerberoastableAccount": "Kerberoastable account", + "Kerberoastableaccount": "Kerberoastable account", "Key": "Key", "Kill chain name": "Kill chain name", "Kill Chain Phase": "Kill Chain Phase", @@ -1338,6 +1345,7 @@ "Password": "Password", "Password validation": "Password validation", "PasswordPolicy": "Password policy", + "Passwordpolicy": "Password policy", "Passwords do no match": "Passwords do no match", "Paste text to analyse for selected TTPs.": "Paste text to analyse for selected TTPs.", "Paste your Filigran OpenAEV Enterprise Edition license": "Paste your Filigran OpenAEV Enterprise Edition license", @@ -1414,6 +1422,7 @@ "Port": "Port", "Portscan": "Port scan", "PortsScan": "Ports scan", + "Portsscan": "Ports scan", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Prerequisite check command", From b8fbeb602f514ae6ba379f463d29974ba73393a9 Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 15:32:13 +0100 Subject: [PATCH 05/12] [backend] feat: feedback review --- .../io/openaev/rest/inject/InjectApiTest.java | 900 ++++++++++++++++++ 1 file changed, 900 insertions(+) diff --git a/openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java b/openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java index ff8fe6d363b..8349e52a6be 100644 --- a/openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java @@ -2189,6 +2189,906 @@ void shouldNotCreateIPv6FindingsWhenRawOutputContainsNoIPv6Addresses() throws Ex .until(() -> injectTestHelper.hasInjectStatusTrace(ipv6Inject.getId())); assertTrue(injectTestHelper.findFindingsByInjectId(ipv6Inject.getId()).isEmpty()); } + + // Username + + @Test + @DisplayName("Should create a finding for each username extracted from raw output") + void shouldCreateFindingForEachUsernameExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); + ContractOutputElement usernameElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Username, "USER:\\s*(\\S+)", Set.of(usernameGroup), true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(usernameElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject usernameInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "USER: jdoe\\nUSER: asmith\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, usernameInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until( + () -> injectTestHelper.findFindingsByInjectId(usernameInject.getId()).size() >= 2); + + List usernameFindings = + injectTestHelper.findFindingsByInjectId(usernameInject.getId()); + assertEquals(2, usernameFindings.size()); + assertTrue( + usernameFindings.stream().anyMatch(f -> f.getValue().equals("jdoe")), + "Expected finding for jdoe"); + assertTrue( + usernameFindings.stream().anyMatch(f -> f.getValue().equals("asmith")), + "Expected finding for asmith"); + usernameFindings.forEach(f -> assertEquals(ContractOutputType.Username, f.getType())); + } + + @Test + @DisplayName("Should not create Username findings when raw output contains no username matches") + void shouldNotCreateUsernameFindingsWhenRawOutputContainsNoUsernameMatches() + throws Exception { + // -- PREPARE -- + RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); + ContractOutputElement usernameElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Username, "USER:\\s*(\\S+)", Set.of(usernameGroup), true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(usernameElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject usernameInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no users found"); + + // -- EXECUTE -- + performCallbackRequest(agentId, usernameInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(usernameInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(usernameInject.getId()).isEmpty()); + } + + // Share + + @Test + @DisplayName("Should create a finding for each network share extracted from raw output") + void shouldCreateFindingForEachShareExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup shareNameGroup = OutputParserFixture.getRegexGroup("share_name", "$1"); + RegexGroup permissionsGroup = OutputParserFixture.getRegexGroup("permissions", "$2"); + ContractOutputElement shareElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Share, + "SHARE:\\s*(\\S+)\\s+(\\w+)", + Set.of(shareNameGroup, permissionsGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(shareElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject shareInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "SHARE: SYSVOL READ\\nSHARE: NETLOGON WRITE\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, shareInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(shareInject.getId()).size() >= 2); + + List shareFindings = injectTestHelper.findFindingsByInjectId(shareInject.getId()); + assertEquals(2, shareFindings.size()); + assertTrue( + shareFindings.stream().anyMatch(f -> f.getValue().contains("SYSVOL")), + "Expected finding for SYSVOL share"); + assertTrue( + shareFindings.stream().anyMatch(f -> f.getValue().contains("NETLOGON")), + "Expected finding for NETLOGON share"); + shareFindings.forEach(f -> assertEquals(ContractOutputType.Share, f.getType())); + } + + @Test + @DisplayName("Should not create Share findings when raw output contains no share matches") + void shouldNotCreateShareFindingsWhenRawOutputContainsNoShareMatches() throws Exception { + // -- PREPARE -- + RegexGroup shareNameGroup = OutputParserFixture.getRegexGroup("share_name", "$1"); + RegexGroup permissionsGroup = OutputParserFixture.getRegexGroup("permissions", "$2"); + ContractOutputElement shareElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Share, + "SHARE:\\s*(\\S+)\\s+(\\w+)", + Set.of(shareNameGroup, permissionsGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(shareElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject shareInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no shares found"); + + // -- EXECUTE -- + performCallbackRequest(agentId, shareInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(shareInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(shareInject.getId()).isEmpty()); + } + + // AdminUsername + + @Test + @DisplayName("Should create a finding for each admin username extracted from raw output") + void shouldCreateFindingForEachAdminUsernameExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); + ContractOutputElement adminUsernameElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.AdminUsername, + "ADMIN:\\s*(\\S+)", + Set.of(usernameGroup), + true); + OutputParser outputParser = + OutputParserFixture.getOutputParser(Set.of(adminUsernameElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject adminInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "ADMIN: administrator\\nADMIN: sysadmin\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, adminInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(adminInject.getId()).size() >= 2); + + List adminFindings = injectTestHelper.findFindingsByInjectId(adminInject.getId()); + assertEquals(2, adminFindings.size()); + assertTrue( + adminFindings.stream().anyMatch(f -> f.getValue().equals("administrator")), + "Expected finding for administrator"); + assertTrue( + adminFindings.stream().anyMatch(f -> f.getValue().equals("sysadmin")), + "Expected finding for sysadmin"); + adminFindings.forEach(f -> assertEquals(ContractOutputType.AdminUsername, f.getType())); + } + + @Test + @DisplayName( + "Should not create AdminUsername findings when raw output contains no admin matches") + void shouldNotCreateAdminUsernameFindingsWhenRawOutputContainsNoAdminMatches() + throws Exception { + // -- PREPARE -- + RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); + ContractOutputElement adminUsernameElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.AdminUsername, + "ADMIN:\\s*(\\S+)", + Set.of(usernameGroup), + true); + OutputParser outputParser = + OutputParserFixture.getOutputParser(Set.of(adminUsernameElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject adminInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no admin accounts here"); + + // -- EXECUTE -- + performCallbackRequest(agentId, adminInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(adminInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(adminInject.getId()).isEmpty()); + } + + // Group + + @Test + @DisplayName("Should create a finding for each group extracted from raw output") + void shouldCreateFindingForEachGroupExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup groupNameGroup = OutputParserFixture.getRegexGroup("group_name", "$1"); + ContractOutputElement groupElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Group, "GROUP:\\s*(\\S+)", Set.of(groupNameGroup), true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(groupElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject groupInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "GROUP: Administrators\\nGROUP: DomainAdmins\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, groupInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(groupInject.getId()).size() >= 2); + + List groupFindings = injectTestHelper.findFindingsByInjectId(groupInject.getId()); + assertEquals(2, groupFindings.size()); + assertTrue( + groupFindings.stream().anyMatch(f -> f.getValue().equals("Administrators")), + "Expected finding for Administrators"); + assertTrue( + groupFindings.stream().anyMatch(f -> f.getValue().equals("DomainAdmins")), + "Expected finding for DomainAdmins"); + groupFindings.forEach(f -> assertEquals(ContractOutputType.Group, f.getType())); + } + + @Test + @DisplayName("Should not create Group findings when raw output contains no group matches") + void shouldNotCreateGroupFindingsWhenRawOutputContainsNoGroupMatches() throws Exception { + // -- PREPARE -- + RegexGroup groupNameGroup = OutputParserFixture.getRegexGroup("group_name", "$1"); + ContractOutputElement groupElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Group, "GROUP:\\s*(\\S+)", Set.of(groupNameGroup), true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(groupElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject groupInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no groups found"); + + // -- EXECUTE -- + performCallbackRequest(agentId, groupInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(groupInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(groupInject.getId()).isEmpty()); + } + + // Computer + + @Test + @DisplayName("Should create a finding for each computer name extracted from raw output") + void shouldCreateFindingForEachComputerExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup computerNameGroup = OutputParserFixture.getRegexGroup("computer_name", "$1"); + ContractOutputElement computerElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Computer, + "COMPUTER:\\s*(\\S+)", + Set.of(computerNameGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(computerElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject computerInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "COMPUTER: DESKTOP-ABC\\nCOMPUTER: SERVER01\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, computerInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until( + () -> injectTestHelper.findFindingsByInjectId(computerInject.getId()).size() >= 2); + + List computerFindings = + injectTestHelper.findFindingsByInjectId(computerInject.getId()); + assertEquals(2, computerFindings.size()); + assertTrue( + computerFindings.stream().anyMatch(f -> f.getValue().equals("DESKTOP-ABC")), + "Expected finding for DESKTOP-ABC"); + assertTrue( + computerFindings.stream().anyMatch(f -> f.getValue().equals("SERVER01")), + "Expected finding for SERVER01"); + computerFindings.forEach(f -> assertEquals(ContractOutputType.Computer, f.getType())); + } + + @Test + @DisplayName("Should not create Computer findings when raw output contains no computer matches") + void shouldNotCreateComputerFindingsWhenRawOutputContainsNoComputerMatches() + throws Exception { + // -- PREPARE -- + RegexGroup computerNameGroup = OutputParserFixture.getRegexGroup("computer_name", "$1"); + ContractOutputElement computerElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Computer, + "COMPUTER:\\s*(\\S+)", + Set.of(computerNameGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(computerElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject computerInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no computers found"); + + // -- EXECUTE -- + performCallbackRequest(agentId, computerInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(computerInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(computerInject.getId()).isEmpty()); + } + + // PasswordPolicy + + @Test + @DisplayName("Should create a finding for each password policy entry extracted from raw output") + void shouldCreateFindingForEachPasswordPolicyEntryExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup keyGroup = OutputParserFixture.getRegexGroup("key", "$1"); + RegexGroup valueGroup = OutputParserFixture.getRegexGroup("value", "$2"); + ContractOutputElement passwordPolicyElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.PasswordPolicy, + "POLICY:\\s*(\\S+)\\s+=\\s+(\\S+)", + Set.of(keyGroup, valueGroup), + true); + OutputParser outputParser = + OutputParserFixture.getOutputParser(Set.of(passwordPolicyElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject policyInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = + "POLICY: MinPasswordLength = 8\\nPOLICY: MaxPasswordAge = 90\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, policyInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(policyInject.getId()).size() >= 2); + + List policyFindings = + injectTestHelper.findFindingsByInjectId(policyInject.getId()); + assertEquals(2, policyFindings.size()); + assertTrue( + policyFindings.stream().anyMatch(f -> f.getValue().contains("MinPasswordLength")), + "Expected finding for MinPasswordLength"); + assertTrue( + policyFindings.stream().anyMatch(f -> f.getValue().contains("MaxPasswordAge")), + "Expected finding for MaxPasswordAge"); + policyFindings.forEach(f -> assertEquals(ContractOutputType.PasswordPolicy, f.getType())); + } + + @Test + @DisplayName( + "Should not create PasswordPolicy findings when raw output contains no policy matches") + void shouldNotCreatePasswordPolicyFindingsWhenRawOutputContainsNoPolicyMatches() + throws Exception { + // -- PREPARE -- + RegexGroup keyGroup = OutputParserFixture.getRegexGroup("key", "$1"); + RegexGroup valueGroup = OutputParserFixture.getRegexGroup("value", "$2"); + ContractOutputElement passwordPolicyElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.PasswordPolicy, + "POLICY:\\s*(\\S+)\\s+=\\s+(\\S+)", + Set.of(keyGroup, valueGroup), + true); + OutputParser outputParser = + OutputParserFixture.getOutputParser(Set.of(passwordPolicyElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject policyInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no policy settings here"); + + // -- EXECUTE -- + performCallbackRequest(agentId, policyInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(policyInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(policyInject.getId()).isEmpty()); + } + + // Delegation + + @Test + @DisplayName("Should create a finding for each delegation account extracted from raw output") + void shouldCreateFindingForEachDelegationExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup accountGroup = OutputParserFixture.getRegexGroup("account", "$1"); + ContractOutputElement delegationElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Delegation, + "DELEGATION:\\s*(\\S+)", + Set.of(accountGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(delegationElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject delegationInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "DELEGATION: svc_account\\nDELEGATION: http_service\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, delegationInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until( + () -> + injectTestHelper.findFindingsByInjectId(delegationInject.getId()).size() >= 2); + + List delegationFindings = + injectTestHelper.findFindingsByInjectId(delegationInject.getId()); + assertEquals(2, delegationFindings.size()); + assertTrue( + delegationFindings.stream().anyMatch(f -> f.getValue().equals("svc_account")), + "Expected finding for svc_account"); + assertTrue( + delegationFindings.stream().anyMatch(f -> f.getValue().equals("http_service")), + "Expected finding for http_service"); + delegationFindings.forEach(f -> assertEquals(ContractOutputType.Delegation, f.getType())); + } + + @Test + @DisplayName( + "Should not create Delegation findings when raw output contains no delegation matches") + void shouldNotCreateDelegationFindingsWhenRawOutputContainsNoDelegationMatches() + throws Exception { + // -- PREPARE -- + RegexGroup accountGroup = OutputParserFixture.getRegexGroup("account", "$1"); + ContractOutputElement delegationElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Delegation, + "DELEGATION:\\s*(\\S+)", + Set.of(accountGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(delegationElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject delegationInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no delegation configured"); + + // -- EXECUTE -- + performCallbackRequest(agentId, delegationInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(delegationInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(delegationInject.getId()).isEmpty()); + } + + // Sid + + @Test + @DisplayName("Should create a finding for each SID extracted from raw output") + void shouldCreateFindingForEachSidExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup sidGroup = OutputParserFixture.getRegexGroup("sid", "$1"); + ContractOutputElement sidElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Sid, + "SID:\\s*(S-\\d-[\\d-]+)", + Set.of(sidGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(sidElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject sidInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "SID: S-1-5-21-12345\\nSID: S-1-5-21-67890\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, sidInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(sidInject.getId()).size() >= 2); + + List sidFindings = injectTestHelper.findFindingsByInjectId(sidInject.getId()); + assertEquals(2, sidFindings.size()); + assertTrue( + sidFindings.stream().anyMatch(f -> f.getValue().equals("S-1-5-21-12345")), + "Expected finding for S-1-5-21-12345"); + assertTrue( + sidFindings.stream().anyMatch(f -> f.getValue().equals("S-1-5-21-67890")), + "Expected finding for S-1-5-21-67890"); + sidFindings.forEach(f -> assertEquals(ContractOutputType.Sid, f.getType())); + } + + @Test + @DisplayName("Should not create Sid findings when raw output contains no SID matches") + void shouldNotCreateSidFindingsWhenRawOutputContainsNoSidMatches() throws Exception { + // -- PREPARE -- + RegexGroup sidGroup = OutputParserFixture.getRegexGroup("sid", "$1"); + ContractOutputElement sidElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Sid, + "SID:\\s*(S-\\d-[\\d-]+)", + Set.of(sidGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(sidElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject sidInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no SIDs found here"); + + // -- EXECUTE -- + performCallbackRequest(agentId, sidInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(sidInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(sidInject.getId()).isEmpty()); + } + + // Vulnerability + + @Test + @DisplayName("Should create a finding for each vulnerability extracted from raw output") + void shouldCreateFindingForEachVulnerabilityExtractedFromRawOutput() throws Exception { + // -- PREPARE -- + RegexGroup nameGroup = OutputParserFixture.getRegexGroup("name", "$1"); + RegexGroup statusGroup = OutputParserFixture.getRegexGroup("status", "$2"); + ContractOutputElement vulnerabilityElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Vulnerability, + "VULN:\\s*(\\S+)\\s+(\\S+)", + Set.of(nameGroup, statusGroup), + true); + OutputParser outputParser = + OutputParserFixture.getOutputParser(Set.of(vulnerabilityElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject vulnInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "VULN: EternalBlue EXPLOITABLE\\nVULN: BlueKeep PATCHED\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, vulnInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(vulnInject.getId()).size() >= 2); + + List vulnFindings = injectTestHelper.findFindingsByInjectId(vulnInject.getId()); + assertEquals(2, vulnFindings.size()); + assertTrue( + vulnFindings.stream().anyMatch(f -> f.getValue().contains("EternalBlue")), + "Expected finding for EternalBlue"); + assertTrue( + vulnFindings.stream().anyMatch(f -> f.getValue().contains("BlueKeep")), + "Expected finding for BlueKeep"); + vulnFindings.forEach(f -> assertEquals(ContractOutputType.Vulnerability, f.getType())); + } + + @Test + @DisplayName( + "Should not create Vulnerability findings when raw output contains no vulnerability matches") + void shouldNotCreateVulnerabilityFindingsWhenRawOutputContainsNoVulnerabilityMatches() + throws Exception { + // -- PREPARE -- + RegexGroup nameGroup = OutputParserFixture.getRegexGroup("name", "$1"); + RegexGroup statusGroup = OutputParserFixture.getRegexGroup("status", "$2"); + ContractOutputElement vulnerabilityElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.Vulnerability, + "VULN:\\s*(\\S+)\\s+(\\S+)", + Set.of(nameGroup, statusGroup), + true); + OutputParser outputParser = + OutputParserFixture.getOutputParser(Set.of(vulnerabilityElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject vulnInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no vulnerabilities found"); + + // -- EXECUTE -- + performCallbackRequest(agentId, vulnInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(vulnInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(vulnInject.getId()).isEmpty()); + } + + // AccountWithPasswordNotRequired + + @Test + @DisplayName( + "Should create a finding for each account with password not required extracted from raw output") + void shouldCreateFindingForEachAccountWithPasswordNotRequiredExtractedFromRawOutput() + throws Exception { + // -- PREPARE -- + RegexGroup accountGroup = OutputParserFixture.getRegexGroup("account", "$1"); + ContractOutputElement accountElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.AccountWithPasswordNotRequired, + "NOPASS:\\s*(\\S+)", + Set.of(accountGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(accountElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject nopassInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "NOPASS: guest_account\\nNOPASS: test_user\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, nopassInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(nopassInject.getId()).size() >= 2); + + List nopassFindings = + injectTestHelper.findFindingsByInjectId(nopassInject.getId()); + assertEquals(2, nopassFindings.size()); + assertTrue( + nopassFindings.stream().anyMatch(f -> f.getValue().equals("guest_account")), + "Expected finding for guest_account"); + assertTrue( + nopassFindings.stream().anyMatch(f -> f.getValue().equals("test_user")), + "Expected finding for test_user"); + nopassFindings.forEach( + f -> assertEquals(ContractOutputType.AccountWithPasswordNotRequired, f.getType())); + } + + @Test + @DisplayName( + "Should not create AccountWithPasswordNotRequired findings when raw output contains no matches") + void + shouldNotCreateAccountWithPasswordNotRequiredFindingsWhenRawOutputContainsNoMatches() + throws Exception { + // -- PREPARE -- + RegexGroup accountGroup = OutputParserFixture.getRegexGroup("account", "$1"); + ContractOutputElement accountElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.AccountWithPasswordNotRequired, + "NOPASS:\\s*(\\S+)", + Set.of(accountGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(accountElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject nopassInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("all accounts require passwords"); + + // -- EXECUTE -- + performCallbackRequest(agentId, nopassInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(nopassInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(nopassInject.getId()).isEmpty()); + } + + // AsreproastableAccount + + @Test + @DisplayName( + "Should create a finding for each AS-REP roastable account extracted from raw output") + void shouldCreateFindingForEachAsreproastableAccountExtractedFromRawOutput() + throws Exception { + // -- PREPARE -- + RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); + ContractOutputElement asrepElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.AsreproastableAccount, + "ASREP:\\s*(\\S+)", + Set.of(usernameGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(asrepElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject asrepInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "ASREP: victim_user\\nASREP: service_account\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, asrepInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(asrepInject.getId()).size() >= 2); + + List asrepFindings = injectTestHelper.findFindingsByInjectId(asrepInject.getId()); + assertEquals(2, asrepFindings.size()); + assertTrue( + asrepFindings.stream().anyMatch(f -> f.getValue().equals("victim_user")), + "Expected finding for victim_user"); + assertTrue( + asrepFindings.stream().anyMatch(f -> f.getValue().equals("service_account")), + "Expected finding for service_account"); + asrepFindings.forEach( + f -> assertEquals(ContractOutputType.AsreproastableAccount, f.getType())); + } + + @Test + @DisplayName( + "Should not create AsreproastableAccount findings when raw output contains no AS-REP matches") + void shouldNotCreateAsreproastableAccountFindingsWhenRawOutputContainsNoAsrepMatches() + throws Exception { + // -- PREPARE -- + RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); + ContractOutputElement asrepElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.AsreproastableAccount, + "ASREP:\\s*(\\S+)", + Set.of(usernameGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(asrepElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject asrepInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no AS-REP roastable accounts found"); + + // -- EXECUTE -- + performCallbackRequest(agentId, asrepInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(asrepInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(asrepInject.getId()).isEmpty()); + } + + // KerberoastableAccount + + @Test + @DisplayName( + "Should create a finding for each Kerberoastable account extracted from raw output") + void shouldCreateFindingForEachKerberoastableAccountExtractedFromRawOutput() + throws Exception { + // -- PREPARE -- + RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); + ContractOutputElement kerbElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.KerberoastableAccount, + "KERB:\\s*(\\S+)", + Set.of(usernameGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(kerbElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject kerbInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + String rawOutput = "KERB: krbtgt\\nKERB: sql_service\\n"; + InjectExecutionInput input = buildStdoutInput(rawOutput); + + // -- EXECUTE -- + performCallbackRequest(agentId, kerbInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.findFindingsByInjectId(kerbInject.getId()).size() >= 2); + + List kerbFindings = injectTestHelper.findFindingsByInjectId(kerbInject.getId()); + assertEquals(2, kerbFindings.size()); + assertTrue( + kerbFindings.stream().anyMatch(f -> f.getValue().equals("krbtgt")), + "Expected finding for krbtgt"); + assertTrue( + kerbFindings.stream().anyMatch(f -> f.getValue().equals("sql_service")), + "Expected finding for sql_service"); + kerbFindings.forEach( + f -> assertEquals(ContractOutputType.KerberoastableAccount, f.getType())); + } + + @Test + @DisplayName( + "Should not create KerberoastableAccount findings when raw output contains no Kerberoastable matches") + void shouldNotCreateKerberoastableAccountFindingsWhenRawOutputContainsNoKerbMatches() + throws Exception { + // -- PREPARE -- + RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); + ContractOutputElement kerbElement = + OutputParserFixture.getContractOutputElement( + ContractOutputType.KerberoastableAccount, + "KERB:\\s*(\\S+)", + Set.of(usernameGroup), + true); + OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(kerbElement)); + Object[] setup = buildInjectWithOutputParser(outputParser); + Inject kerbInject = (Inject) setup[0]; + String agentId = (String) setup[1]; + + InjectExecutionInput input = buildStdoutInput("no Kerberoastable accounts found"); + + // -- EXECUTE -- + performCallbackRequest(agentId, kerbInject.getId(), input); + + // -- ASSERT -- + Awaitility.await() + .atMost(15, TimeUnit.SECONDS) + .with() + .pollInterval(1, TimeUnit.SECONDS) + .until(() -> injectTestHelper.hasInjectStatusTrace(kerbInject.getId())); + assertTrue(injectTestHelper.findFindingsByInjectId(kerbInject.getId()).isEmpty()); + } } @Nested From f738046a0841889e4b097266256b5e62b527a6b3 Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 15:36:56 +0100 Subject: [PATCH 06/12] [backend] feat: feedback review --- .../io/openaev/rest/inject/InjectApiTest.java | 47 ++++++------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java b/openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java index 8349e52a6be..5359fdccca1 100644 --- a/openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/inject/InjectApiTest.java @@ -2232,7 +2232,8 @@ void shouldCreateFindingForEachUsernameExtractedFromRawOutput() throws Exception } @Test - @DisplayName("Should not create Username findings when raw output contains no username matches") + @DisplayName( + "Should not create Username findings when raw output contains no username matches") void shouldNotCreateUsernameFindingsWhenRawOutputContainsNoUsernameMatches() throws Exception { // -- PREPARE -- @@ -2342,10 +2343,7 @@ void shouldCreateFindingForEachAdminUsernameExtractedFromRawOutput() throws Exce RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); ContractOutputElement adminUsernameElement = OutputParserFixture.getContractOutputElement( - ContractOutputType.AdminUsername, - "ADMIN:\\s*(\\S+)", - Set.of(usernameGroup), - true); + ContractOutputType.AdminUsername, "ADMIN:\\s*(\\S+)", Set.of(usernameGroup), true); OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(adminUsernameElement)); Object[] setup = buildInjectWithOutputParser(outputParser); @@ -2385,10 +2383,7 @@ void shouldNotCreateAdminUsernameFindingsWhenRawOutputContainsNoAdminMatches() RegexGroup usernameGroup = OutputParserFixture.getRegexGroup("username", "$1"); ContractOutputElement adminUsernameElement = OutputParserFixture.getContractOutputElement( - ContractOutputType.AdminUsername, - "ADMIN:\\s*(\\S+)", - Set.of(usernameGroup), - true); + ContractOutputType.AdminUsername, "ADMIN:\\s*(\\S+)", Set.of(usernameGroup), true); OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(adminUsernameElement)); Object[] setup = buildInjectWithOutputParser(outputParser); @@ -2520,7 +2515,8 @@ void shouldCreateFindingForEachComputerExtractedFromRawOutput() throws Exception } @Test - @DisplayName("Should not create Computer findings when raw output contains no computer matches") + @DisplayName( + "Should not create Computer findings when raw output contains no computer matches") void shouldNotCreateComputerFindingsWhenRawOutputContainsNoComputerMatches() throws Exception { // -- PREPARE -- @@ -2553,7 +2549,8 @@ void shouldNotCreateComputerFindingsWhenRawOutputContainsNoComputerMatches() // PasswordPolicy @Test - @DisplayName("Should create a finding for each password policy entry extracted from raw output") + @DisplayName( + "Should create a finding for each password policy entry extracted from raw output") void shouldCreateFindingForEachPasswordPolicyEntryExtractedFromRawOutput() throws Exception { // -- PREPARE -- RegexGroup keyGroup = OutputParserFixture.getRegexGroup("key", "$1"); @@ -2570,8 +2567,7 @@ void shouldCreateFindingForEachPasswordPolicyEntryExtractedFromRawOutput() throw Inject policyInject = (Inject) setup[0]; String agentId = (String) setup[1]; - String rawOutput = - "POLICY: MinPasswordLength = 8\\nPOLICY: MaxPasswordAge = 90\\n"; + String rawOutput = "POLICY: MinPasswordLength = 8\\nPOLICY: MaxPasswordAge = 90\\n"; InjectExecutionInput input = buildStdoutInput(rawOutput); // -- EXECUTE -- @@ -2639,10 +2635,7 @@ void shouldCreateFindingForEachDelegationExtractedFromRawOutput() throws Excepti RegexGroup accountGroup = OutputParserFixture.getRegexGroup("account", "$1"); ContractOutputElement delegationElement = OutputParserFixture.getContractOutputElement( - ContractOutputType.Delegation, - "DELEGATION:\\s*(\\S+)", - Set.of(accountGroup), - true); + ContractOutputType.Delegation, "DELEGATION:\\s*(\\S+)", Set.of(accountGroup), true); OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(delegationElement)); Object[] setup = buildInjectWithOutputParser(outputParser); Inject delegationInject = (Inject) setup[0]; @@ -2684,10 +2677,7 @@ void shouldNotCreateDelegationFindingsWhenRawOutputContainsNoDelegationMatches() RegexGroup accountGroup = OutputParserFixture.getRegexGroup("account", "$1"); ContractOutputElement delegationElement = OutputParserFixture.getContractOutputElement( - ContractOutputType.Delegation, - "DELEGATION:\\s*(\\S+)", - Set.of(accountGroup), - true); + ContractOutputType.Delegation, "DELEGATION:\\s*(\\S+)", Set.of(accountGroup), true); OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(delegationElement)); Object[] setup = buildInjectWithOutputParser(outputParser); Inject delegationInject = (Inject) setup[0]; @@ -2716,10 +2706,7 @@ void shouldCreateFindingForEachSidExtractedFromRawOutput() throws Exception { RegexGroup sidGroup = OutputParserFixture.getRegexGroup("sid", "$1"); ContractOutputElement sidElement = OutputParserFixture.getContractOutputElement( - ContractOutputType.Sid, - "SID:\\s*(S-\\d-[\\d-]+)", - Set.of(sidGroup), - true); + ContractOutputType.Sid, "SID:\\s*(S-\\d-[\\d-]+)", Set.of(sidGroup), true); OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(sidElement)); Object[] setup = buildInjectWithOutputParser(outputParser); Inject sidInject = (Inject) setup[0]; @@ -2756,10 +2743,7 @@ void shouldNotCreateSidFindingsWhenRawOutputContainsNoSidMatches() throws Except RegexGroup sidGroup = OutputParserFixture.getRegexGroup("sid", "$1"); ContractOutputElement sidElement = OutputParserFixture.getContractOutputElement( - ContractOutputType.Sid, - "SID:\\s*(S-\\d-[\\d-]+)", - Set.of(sidGroup), - true); + ContractOutputType.Sid, "SID:\\s*(S-\\d-[\\d-]+)", Set.of(sidGroup), true); OutputParser outputParser = OutputParserFixture.getOutputParser(Set.of(sidElement)); Object[] setup = buildInjectWithOutputParser(outputParser); Inject sidInject = (Inject) setup[0]; @@ -2906,9 +2890,8 @@ void shouldCreateFindingForEachAccountWithPasswordNotRequiredExtractedFromRawOut @Test @DisplayName( "Should not create AccountWithPasswordNotRequired findings when raw output contains no matches") - void - shouldNotCreateAccountWithPasswordNotRequiredFindingsWhenRawOutputContainsNoMatches() - throws Exception { + void shouldNotCreateAccountWithPasswordNotRequiredFindingsWhenRawOutputContainsNoMatches() + throws Exception { // -- PREPARE -- RegexGroup accountGroup = OutputParserFixture.getRegexGroup("account", "$1"); ContractOutputElement accountElement = From 5ee12fd3e49ead0a0f5fb358c0ff593149ff81cc Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 15:40:48 +0100 Subject: [PATCH 07/12] [backend] feat: feedback review --- .../atomic_testing/payload_info/OutputParserInfoCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openaev-front/src/admin/components/atomic_testings/atomic_testing/payload_info/OutputParserInfoCard.tsx b/openaev-front/src/admin/components/atomic_testings/atomic_testing/payload_info/OutputParserInfoCard.tsx index bc38d65e144..be74cd76d03 100644 --- a/openaev-front/src/admin/components/atomic_testings/atomic_testing/payload_info/OutputParserInfoCard.tsx +++ b/openaev-front/src/admin/components/atomic_testings/atomic_testing/payload_info/OutputParserInfoCard.tsx @@ -4,8 +4,8 @@ import { makeStyles } from 'tss-react/mui'; import { useFormatter } from '../../../../../components/i18n'; import ItemCopy from '../../../../../components/ItemCopy'; import ItemTags from '../../../../../components/ItemTags'; -import ContractOutputElementType from '../../../findings/ContractOutputElementType'; import { type OutputParserSimple } from '../../../../../utils/api-types'; +import ContractOutputElementType from '../../../findings/ContractOutputElementType'; const useStyles = makeStyles()(theme => ({ paperContainer: { From 3309f294d26c7d501a4589b8b87a36900d9d4ce5 Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 15:51:10 +0100 Subject: [PATCH 08/12] [backend] feat: feedback review --- .../findings/ContractOutputElementType.ts | 52 ++++++++++++------- .../queryable/filter/useSearchOptions.tsx | 5 +- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts index f0332551707..5fe3abdf1af 100644 --- a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts +++ b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts @@ -1,31 +1,47 @@ enum ContractOutputElementType { - text = 'Text', - number = 'Number', - port = 'Port', - portscan = 'PortsScan', - ipv4 = 'IPv4', - ipv6 = 'IPv6', + account_with_password_not_required = 'AccountWithPasswordNotRequired', + admin_username = 'AdminUsername', + asreproastable_account = 'AsreproastableAccount', + computer = 'Computer', credentials = 'Credentials', cve = 'CVE', - username = 'Username', - share = 'Share', - admin_username = 'AdminUsername', + delegation = 'Delegation', group = 'Group', - computer = 'Computer', + ipv4 = 'IPv4', + ipv6 = 'IPv6', + kerberoastable_account = 'KerberoastableAccount', + number = 'Number', password_policy = 'PasswordPolicy', - delegation = 'Delegation', + port = 'Port', + portscan = 'PortsScan', + share = 'Share', sid = 'Sid', - account_with_password_not_required = 'AccountWithPasswordNotRequired', - asreproastable_account = 'AsreproastableAccount', - kerberoastable_account = 'KerberoastableAccount', + text = 'Text', + username = 'Username', vulnerability = 'Vulnerability', } export const CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS = [ - 'text', 'number', 'port', 'portscan', 'ipv4', 'ipv6', 'credentials', 'cve', - 'vulnerability', 'username', 'share', 'admin_username', 'group', 'computer', - 'password_policy', 'delegation', 'sid', 'account_with_password_not_required', - 'asreproastable_account', 'kerberoastable_account', + 'account_with_password_not_required', + 'admin_username', + 'asreproastable_account', + 'computer', + 'credentials', + 'cve', + 'delegation', + 'group', + 'ipv4', + 'ipv6', + 'kerberoastable_account', + 'number', + 'password_policy', + 'port', + 'portscan', + 'share', + 'sid', + 'text', + 'username', + 'vulnerability', ] as const; export default ContractOutputElementType; diff --git a/openaev-front/src/components/common/queryable/filter/useSearchOptions.tsx b/openaev-front/src/components/common/queryable/filter/useSearchOptions.tsx index c29f3062634..295af764695 100644 --- a/openaev-front/src/components/common/queryable/filter/useSearchOptions.tsx +++ b/openaev-front/src/components/common/queryable/filter/useSearchOptions.tsx @@ -161,11 +161,12 @@ const useSearchOptions = () => { break; case 'finding_type': { const typeOptions = CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS - .filter(type => !search || ContractOutputElementType[type].toLowerCase().includes(search.toLowerCase())) + .filter(type => !search || t(ContractOutputElementType[type]).toLowerCase().includes(search.toLowerCase())) .map(type => ({ id: type, label: ContractOutputElementType[type], - })); + })) + .sort((a, b) => t(a.label).localeCompare(t(b.label))); setOptions(typeOptions); break; } From 064371df15238cff9675f82d522ad90281962e23 Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 16:11:07 +0100 Subject: [PATCH 09/12] [backend] feat: feedback review --- .../filter/FilterChipPopoverInput.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/openaev-front/src/components/common/queryable/filter/FilterChipPopoverInput.tsx b/openaev-front/src/components/common/queryable/filter/FilterChipPopoverInput.tsx index a3525b2ba09..4b6c72723b7 100644 --- a/openaev-front/src/components/common/queryable/filter/FilterChipPopoverInput.tsx +++ b/openaev-front/src/components/common/queryable/filter/FilterChipPopoverInput.tsx @@ -69,13 +69,17 @@ export const BasicSelectInput: FunctionComponent { if (propertySchema.schema_property_values && propertySchema.schema_property_values?.length > 0) { - setOptions(propertySchema.schema_property_values.map((value) => { - const label = wordsToExcludeFromTranslation.includes(value) ? value : t(value.charAt(0).toUpperCase() + value.slice(1).toLowerCase()); - return ({ - id: value, - label, - }); - })); + setOptions( + propertySchema.schema_property_values + .map((value) => { + const label = wordsToExcludeFromTranslation.includes(value) ? value : t(value.charAt(0).toUpperCase() + value.slice(1).toLowerCase()); + return ({ + id: value, + label, + }); + }) + .sort((a, b) => a.label.localeCompare(b.label)), + ); } else { handleSearchOptions(''); } From d40191c49ee8f003bc9b9678092e6bf8946a72bd Mon Sep 17 00:00:00 2001 From: savacano28 Date: Tue, 24 Mar 2026 16:37:53 +0100 Subject: [PATCH 10/12] [backend] feat: feedback review --- openaev-front/src/utils/lang/de.json | 9 +++++++++ openaev-front/src/utils/lang/es.json | 9 +++++++++ openaev-front/src/utils/lang/fr.json | 9 +++++++++ openaev-front/src/utils/lang/it.json | 9 +++++++++ openaev-front/src/utils/lang/ja.json | 9 +++++++++ openaev-front/src/utils/lang/ko.json | 9 +++++++++ openaev-front/src/utils/lang/ru.json | 9 +++++++++ openaev-front/src/utils/lang/zh.json | 9 +++++++++ 8 files changed, 72 insertions(+) diff --git a/openaev-front/src/utils/lang/de.json b/openaev-front/src/utils/lang/de.json index dd689a2b799..ad9ffba46e4 100644 --- a/openaev-front/src/utils/lang/de.json +++ b/openaev-front/src/utils/lang/de.json @@ -28,6 +28,7 @@ "Access reports": "Zugriff auf Berichte", "Access these injects": "Zugriff auf diese Injektionen", "AccountWithPasswordNotRequired": "Konto mit Passwort nicht erforderlich", + "Accountwithpasswordnotrequired": "Konto mit Passwort nicht erforderlich", "Action type": "Aktionstyp", "Active": "Aktiv", "Add": "Hinzufügen", @@ -64,6 +65,7 @@ "Admin": "Admin", "Administrator": "Administrator", "AdminUsername": "Admin-Benutzername", + "Adminusername": "Admin-Benutzername", "Advanced Installation": "Erweiterte Installation", "Advanced options": "Erweiterte Optionen", "Advanced search": "Erweiterte Suche", @@ -122,6 +124,7 @@ "ARTICLE": "Automatisch - Ausgelöst, wenn das Ziel die Artikel liest", "Ask AI": "KI fragen", "AsreproastableAccount": "AS-REP röstbares Konto", + "Asreproastableaccount": "AS-REP röstbares Konto", "Assertive": "Assuré", "Asset": "Asset", "asset": "vermögenswert", @@ -405,6 +408,7 @@ "Customization": "Anpassung", "Customize columns": "Anpassen von Spalten", "CVE": "CVE", + "Cve": "CVE", "CVSS must be at least 0.0": "CVSS muss mindestens 0.0 sein", "CVSS must be at most 10.0": "CVSS darf höchstens 10.0 sein", "CVSS score is required": "CVSS-Score ist erforderlich", @@ -995,7 +999,9 @@ "IP Addresses": "IP-Adressen", "Ip Addresses": "Ip-Adressen", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "ist", "is empty": "ist leer", "is not empty": "ist nicht leer", @@ -1007,6 +1013,7 @@ "Japanese": "Japanisch", "JAVA Virtual Machine": "Virtuelle JAVA-Maschine", "KerberoastableAccount": "Kerberoastbares Konto", + "Kerberoastableaccount": "Kerberoastable-Konto", "Key": "Schlüssel", "Kill chain name": "Name der Abbruchkette", "Kill Chain Phase": "Phase der Tötungskette", @@ -1338,6 +1345,7 @@ "Password": "Passwort", "Password validation": "Passwort-Überprüfung", "PasswordPolicy": "Passwort-Richtlinie", + "Passwordpolicy": "Passwort-Richtlinie", "Passwords do no match": "Passwörter stimmen nicht überein", "Paste text to analyse for selected TTPs.": "Text zur Analyse für ausgewählte TTPs einfügen.", "Paste your Filigran OpenAEV Enterprise Edition license": "Fügen Sie Ihre Filigran OpenAEV Enterprise Edition Lizenz ein", @@ -1414,6 +1422,7 @@ "Port": "Hafen", "Portscan": "Hafen-Scan", "PortsScan": "Ports scannen", + "Portsscan": "Ports scannen", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Befehl zur Überprüfung der Voraussetzungen", diff --git a/openaev-front/src/utils/lang/es.json b/openaev-front/src/utils/lang/es.json index 857d5b9d39f..f4083eb1ee7 100644 --- a/openaev-front/src/utils/lang/es.json +++ b/openaev-front/src/utils/lang/es.json @@ -28,6 +28,7 @@ "Access reports": "Acceder a informes", "Access these injects": "Acceder a estas inyecciones", "AccountWithPasswordNotRequired": "Cuenta con contraseña no necesaria", + "Accountwithpasswordnotrequired": "Cuenta con contraseña no requerida", "Action type": "Tipo de acción", "Active": "Activo", "Add": "Añadir", @@ -64,6 +65,7 @@ "Admin": "Admin", "Administrator": "Administrador", "AdminUsername": "Admin nombre de usuario", + "Adminusername": "Nombre de usuario Admin", "Advanced Installation": "Instalación avanzada", "Advanced options": "Opciones avanzadas", "Advanced search": "Búsqueda avanzada", @@ -122,6 +124,7 @@ "ARTICLE": "Automático - Se activa cuando el objetivo lee los artículos", "Ask AI": "Preguntar AI", "AsreproastableAccount": "Cuenta AS-REP roastable", + "Asreproastableaccount": "Cuenta AS-REP roastable", "Assertive": "Assuré", "Asset": "Activo", "asset": "activo", @@ -405,6 +408,7 @@ "Customization": "Personalización", "Customize columns": "Personalizar columnas", "CVE": "CVE", + "Cve": "CVE", "CVSS must be at least 0.0": "CVSS debe ser al menos 0.0", "CVSS must be at most 10.0": "CVSS debe ser como máximo 10.0", "CVSS score is required": "Se requiere la puntuación CVSS", @@ -995,7 +999,9 @@ "IP Addresses": "Direcciones IP", "Ip Addresses": "Direcciones IP", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "es", "is empty": "está vacío", "is not empty": "no está vacío", @@ -1007,6 +1013,7 @@ "Japanese": "Japonés", "JAVA Virtual Machine": "Máquina Virtual JAVA", "KerberoastableAccount": "Cuenta Kerberoastable", + "Kerberoastableaccount": "Cuenta Kerberoastable", "Key": "Clave", "Kill chain name": "Nombre de la cadena de matanza", "Kill Chain Phase": "Fase de la cadena de ataque", @@ -1338,6 +1345,7 @@ "Password": "Contraseña", "Password validation": "Validación de contraseña", "PasswordPolicy": "Política de contraseña", + "Passwordpolicy": "Política de contraseñas", "Passwords do no match": "Las contraseñas no coinciden", "Paste text to analyse for selected TTPs.": "Pegar texto para analizar los Patrones de Attaques seleccionados.", "Paste your Filigran OpenAEV Enterprise Edition license": "Pegue su licencia de Filigran OpenAEV Enterprise Edition", @@ -1414,6 +1422,7 @@ "Port": "Puerto", "Portscan": "Puerto scan", "PortsScan": "Escaneo de puertos", + "Portsscan": "Escaneo de puertos", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Comando de comprobación de requisitos previos", diff --git a/openaev-front/src/utils/lang/fr.json b/openaev-front/src/utils/lang/fr.json index 841e3656a94..5e53fd1a7ed 100644 --- a/openaev-front/src/utils/lang/fr.json +++ b/openaev-front/src/utils/lang/fr.json @@ -28,6 +28,7 @@ "Access reports": "Accéder aux rapports", "Access these injects": "Accéder à ces injections", "AccountWithPasswordNotRequired": "Compte avec mot de passe non requis", + "Accountwithpasswordnotrequired": "Compte avec mot de passe non requis", "Action type": "Type d'action", "Active": "Actif", "Add": "Ajouter", @@ -64,6 +65,7 @@ "Admin": "Administrateur", "Administrator": "Administrateur", "AdminUsername": "Nom d'utilisateur de l'administrateur", + "Adminusername": "Nom d'utilisateur de l'administrateur", "Advanced Installation": "Installation avancée", "Advanced options": "Options avancées", "Advanced search": "Recherche avancée", @@ -122,6 +124,7 @@ "ARTICLE": "Automatique - Déclenché lorsque l'équipe a lu l'article", "Ask AI": "Demander à l'IA", "AsreproastableAccount": "Compte roastable AS-REP", + "Asreproastableaccount": "Compte AS-REP roastable", "Assertive": "Assertif", "Asset": "Actif", "asset": "actif", @@ -405,6 +408,7 @@ "Customization": "Personnalisation", "Customize columns": "Personnaliser les colonnes", "CVE": "CVE", + "Cve": "CVE", "CVSS must be at least 0.0": "CVSS doit être au moins égal à 0.0", "CVSS must be at most 10.0": "CVSS doit être au plus égal à 10.0", "CVSS score is required": "Le score CVSS est requis", @@ -995,7 +999,9 @@ "IP Addresses": "Adresses IP", "Ip Addresses": "Addresse Ips", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "est", "is empty": "est vide", "is not empty": "n'est pas vide", @@ -1007,6 +1013,7 @@ "Japanese": "Japonais", "JAVA Virtual Machine": "Machine virtuelle JAVA", "KerberoastableAccount": "Compte kerberoastable", + "Kerberoastableaccount": "Compte Kerberoastable", "Key": "Clé", "Kill chain name": "Nom de la kill chain", "Kill Chain Phase": "Kill chain d'intrusion", @@ -1338,6 +1345,7 @@ "Password": "Mot de passe", "Password validation": "Validation du mot de passe", "PasswordPolicy": "Mot de passe", + "Passwordpolicy": "Politique de mot de passe", "Passwords do no match": "Les mots de passe ne correspondent pas", "Paste text to analyse for selected TTPs.": "Coller le texte à analyser pour les TTPs sélectionnés.", "Paste your Filigran OpenAEV Enterprise Edition license": "Collez votre licence Filigran OpenAEV Enterprise Edition", @@ -1414,6 +1422,7 @@ "Port": "Port", "Portscan": "Analyse des ports", "PortsScan": "Balayage des ports", + "Portsscan": "Analyse des ports", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Commande de vérification des prérequis", diff --git a/openaev-front/src/utils/lang/it.json b/openaev-front/src/utils/lang/it.json index 029f7cb56d2..5cdef1f2265 100644 --- a/openaev-front/src/utils/lang/it.json +++ b/openaev-front/src/utils/lang/it.json @@ -28,6 +28,7 @@ "Access reports": "Rapporti di accesso", "Access these injects": "Accedere a questi injects", "AccountWithPasswordNotRequired": "Account con password non richiesta", + "Accountwithpasswordnotrequired": "Account con password non richiesta", "Action type": "Tipo di azione", "Active": "Attivo", "Add": "Aggiungi", @@ -64,6 +65,7 @@ "Admin": "Admin", "Administrator": "Amministratore", "AdminUsername": "Nome utente amministratore", + "Adminusername": "Nome utente amministratore", "Advanced Installation": "Installazione avanzata", "Advanced options": "Opzioni avanzate", "Advanced search": "Ricerca avanzata", @@ -122,6 +124,7 @@ "ARTICLE": "Automatico - Si attiva quando il bersaglio legge gli articoli", "Ask AI": "Chiedi ad AI", "AsreproastableAccount": "Conto AS-REP roastable", + "Asreproastableaccount": "Account AS-REP roastable", "Assertive": "Assicurato", "Asset": "Asset", "asset": "patrimonio", @@ -405,6 +408,7 @@ "Customization": "Personalizzazione", "Customize columns": "Personalizzazione delle colonne", "CVE": "CVE", + "Cve": "CVE", "CVSS must be at least 0.0": "CVSS deve essere almeno 0.0", "CVSS must be at most 10.0": "CVSS deve essere al massimo 10.0", "CVSS score is required": "Il punteggio CVSS è richiesto", @@ -995,7 +999,9 @@ "IP Addresses": "Indirizzi IP", "Ip Addresses": "Indirizzi Ip", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "è", "is empty": "è vuoto", "is not empty": "non è vuoto", @@ -1007,6 +1013,7 @@ "Japanese": "Giapponese", "JAVA Virtual Machine": "Macchina virtuale JAVA", "KerberoastableAccount": "Conto Kerberoastable", + "Kerberoastableaccount": "Account Kerberoastable", "Key": "Chiave", "Kill chain name": "Nome della catena di uccisione", "Kill Chain Phase": "Fase della catena di uccisione", @@ -1338,6 +1345,7 @@ "Password": "Password", "Password validation": "Convalida della password", "PasswordPolicy": "Politica di password", + "Passwordpolicy": "Politica di password", "Passwords do no match": "Le password non corrispondono", "Paste text to analyse for selected TTPs.": "Incolla il testo da analizzare per i TTP selezionati.", "Paste your Filigran OpenAEV Enterprise Edition license": "Incolla la tua licenza Filigran OpenAEV Enterprise Edition", @@ -1414,6 +1422,7 @@ "Port": "Porto", "Portscan": "Scansione della porta", "PortsScan": "Scansione delle porte", + "Portsscan": "Scansione delle porte", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Comando di controllo dei prerequisiti", diff --git a/openaev-front/src/utils/lang/ja.json b/openaev-front/src/utils/lang/ja.json index aaf022ef436..c7362e6b58b 100644 --- a/openaev-front/src/utils/lang/ja.json +++ b/openaev-front/src/utils/lang/ja.json @@ -28,6 +28,7 @@ "Access reports": "アクセスレポート", "Access these injects": "これらのインジェクトにアクセスする", "AccountWithPasswordNotRequired": "パスワード不要のアカウント", + "Accountwithpasswordnotrequired": "パスワード不要のアカウント", "Action type": "アクションタイプ", "Active": "アクティブ", "Add": "追加", @@ -64,6 +65,7 @@ "Admin": "管理者", "Administrator": "管理者", "AdminUsername": "管理者ユーザー名", + "Adminusername": "管理者ユーザー名", "Advanced Installation": "高度なインストール", "Advanced options": "高度なオプション", "Advanced search": "高度な検索", @@ -122,6 +124,7 @@ "ARTICLE": "自動 - ターゲットが記事を読むと発動", "Ask AI": "AIに聞く", "AsreproastableAccount": "AS-REPロースタブルアカウント", + "Asreproastableaccount": "AS-REP ロースト可能アカウント", "Assertive": "アシュレ", "Asset": "アセット", "asset": "アセット", @@ -405,6 +408,7 @@ "Customization": "カスタマイズ", "Customize columns": "カラムのカスタマイズ", "CVE": "CVE", + "Cve": "CVE", "CVSS must be at least 0.0": "CVSSが0.0以上であること", "CVSS must be at most 10.0": "CVSSは10.0以上であること", "CVSS score is required": "CVSSスコアが必要", @@ -995,7 +999,9 @@ "IP Addresses": "IPアドレス", "Ip Addresses": "IPアドレス", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "は", "is empty": "は空", "is not empty": "は空でない", @@ -1007,6 +1013,7 @@ "Japanese": "日本語", "JAVA Virtual Machine": "JAVA仮想マシン", "KerberoastableAccount": "ケベロースター可能アカウント", + "Kerberoastableaccount": "Kerberoastable アカウント", "Key": "キー", "Kill chain name": "キルチェーン名", "Kill Chain Phase": "キルチェーンフェーズ", @@ -1338,6 +1345,7 @@ "Password": "パスワード", "Password validation": "パスワード認証", "PasswordPolicy": "パスワードポリシー", + "Passwordpolicy": "パスワードポリシー", "Passwords do no match": "パスワードが一致しない", "Paste text to analyse for selected TTPs.": "選択されたTTPを分析するためにテキストを貼り付ける。", "Paste your Filigran OpenAEV Enterprise Edition license": "Filigran OpenAEV Enterprise Edition のライセンスを貼り付ける。", @@ -1414,6 +1422,7 @@ "Port": "ポート", "Portscan": "ポートスキャン", "PortsScan": "ポートスキャン", + "Portsscan": "ポートスキャン", "PostgreSQL": "PostgreSQL", "PowerShell": "パワーシェル", "PREREQUISITE_CHECK": "前提条件チェックコマンド", diff --git a/openaev-front/src/utils/lang/ko.json b/openaev-front/src/utils/lang/ko.json index 3e96b08167e..32c62eae65a 100644 --- a/openaev-front/src/utils/lang/ko.json +++ b/openaev-front/src/utils/lang/ko.json @@ -28,6 +28,7 @@ "Access reports": "보고서 액세스", "Access these injects": "다음 인젝트에 액세스", "AccountWithPasswordNotRequired": "비밀번호가 필요하지 않은 계정", + "Accountwithpasswordnotrequired": "비밀번호가 필요하지 않은 계정", "Action type": "작업 유형", "Active": "활성", "Add": "추가", @@ -64,6 +65,7 @@ "Admin": "관리자", "Administrator": "관리자", "AdminUsername": "관리자 사용자 아이디", + "Adminusername": "관리자 사용자 아이디", "Advanced Installation": "고급 설치", "Advanced options": "고급 옵션", "Advanced search": "고급 검색", @@ -122,6 +124,7 @@ "ARTICLE": "자동 - 타겟이 기사를 읽으면 트리거됩니다", "Ask AI": "AI에게 질문", "AsreproastableAccount": "AS-REP 로스팅 가능한 계정", + "Asreproastableaccount": "AS-REP 로스팅 가능 계정", "Assertive": "단호한", "Asset": "유산", "asset": "유산", @@ -405,6 +408,7 @@ "Customization": "사용자 지정", "Customize columns": "열 사용자 지정", "CVE": "CVE", + "Cve": "CVE", "CVSS must be at least 0.0": "CVSS는 0.0 이상이어야 합니다", "CVSS must be at most 10.0": "CVSS는 최대 10.0이어야 합니다", "CVSS score is required": "CVSS 점수가 필요합니다", @@ -995,7 +999,9 @@ "IP Addresses": "IP 주소", "Ip Addresses": "IP 주소", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "is", "is empty": "비어 있습니다", "is not empty": "비어 있지 않습니다", @@ -1007,6 +1013,7 @@ "Japanese": "일본어", "JAVA Virtual Machine": "자바 가상 머신", "KerberoastableAccount": "로스터블 계정", + "Kerberoastableaccount": "케르베로스테이블 계정", "Key": "키", "Kill chain name": "킬 체인 이름", "Kill Chain Phase": "킬 체인 단계", @@ -1338,6 +1345,7 @@ "Password": "비밀번호", "Password validation": "비밀번호 유효성 검사", "PasswordPolicy": "비밀번호 정책", + "Passwordpolicy": "비밀번호 정책", "Passwords do no match": "비밀번호가 일치하지 않음", "Paste text to analyse for selected TTPs.": "선택한 TTP에 대해 분석할 텍스트를 붙여넣습니다.", "Paste your Filigran OpenAEV Enterprise Edition license": "필리그란 OpenAEV 엔터프라이즈 에디션 라이선스 붙여넣기", @@ -1414,6 +1422,7 @@ "Port": "포트", "Portscan": "포트 스캔", "PortsScan": "포트 스캔", + "Portsscan": "포트 스캔", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "전제 조건 확인 명령", diff --git a/openaev-front/src/utils/lang/ru.json b/openaev-front/src/utils/lang/ru.json index d41d9b253e3..3bb34e66a3a 100644 --- a/openaev-front/src/utils/lang/ru.json +++ b/openaev-front/src/utils/lang/ru.json @@ -28,6 +28,7 @@ "Access reports": "Отчеты о доступе", "Access these injects": "Доступ к этим инъекциям", "AccountWithPasswordNotRequired": "Учетная запись с необязательным паролем", + "Accountwithpasswordnotrequired": "Учетная запись с необязательным паролем", "Action type": "Тип действия", "Active": "Активный", "Add": "Добавить", @@ -64,6 +65,7 @@ "Admin": "Admin", "Administrator": "Администратор", "AdminUsername": "Имя пользователя", + "Adminusername": "Имя пользователя администратора", "Advanced Installation": "Расширенная установка", "Advanced options": "Дополнительные опции", "Advanced search": "Расширенный поиск", @@ -122,6 +124,7 @@ "ARTICLE": "Автоматически - срабатывает, когда цель читает статьи", "Ask AI": "Спросить АИ", "AsreproastableAccount": "AS-REP roastable account", + "Asreproastableaccount": "AS-REP roastable account", "Assertive": "Assuré", "Asset": "Asset", "asset": "актив", @@ -405,6 +408,7 @@ "Customization": "Персонализация", "Customize columns": "Настроить колонки", "CVE": "CVE", + "Cve": "CVE", "CVSS must be at least 0.0": "CVSS должен быть не менее 0.0", "CVSS must be at most 10.0": "CVSS должен быть не менее 10.0", "CVSS score is required": "Требуется оценка CVSS", @@ -995,7 +999,9 @@ "IP Addresses": "IP-адреса", "Ip Addresses": "Ip-адреса", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "это", "is empty": "пустой", "is not empty": "не пустой", @@ -1007,6 +1013,7 @@ "Japanese": "Японский", "JAVA Virtual Machine": "Виртуальная машина JAVA", "KerberoastableAccount": "Керберостабильная учетная запись", + "Kerberoastableaccount": "Kerberoastable account", "Key": "Ключ", "Kill chain name": "Имя цепочки убийств", "Kill Chain Phase": "Фаза цепи убийств", @@ -1338,6 +1345,7 @@ "Password": "Пароль", "Password validation": "Проверка пароля", "PasswordPolicy": "Политика паролей", + "Passwordpolicy": "Политика паролей", "Passwords do no match": "Пароли не совпадают", "Paste text to analyse for selected TTPs.": "Вставьте текст для анализа выбранных TTP.", "Paste your Filigran OpenAEV Enterprise Edition license": "Вставьте лицензию Filigran OpenAEV Enterprise Edition", @@ -1414,6 +1422,7 @@ "Port": "Порт", "Portscan": "Сканирование порта", "PortsScan": "Сканирование портов", + "Portsscan": "Сканирование портов", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "Команда проверки предпосылок", diff --git a/openaev-front/src/utils/lang/zh.json b/openaev-front/src/utils/lang/zh.json index 6e4b5480704..bea5522a7b2 100644 --- a/openaev-front/src/utils/lang/zh.json +++ b/openaev-front/src/utils/lang/zh.json @@ -28,6 +28,7 @@ "Access reports": "访问报告", "Access these injects": "访问这些注入", "AccountWithPasswordNotRequired": "无需密码的账户", + "Accountwithpasswordnotrequired": "无需密码的帐户", "Action type": "行动类型", "Active": "激活", "Add": "添加", @@ -64,6 +65,7 @@ "Admin": "管理", "Administrator": "管理员", "AdminUsername": "管理员用户名", + "Adminusername": "管理员用户名", "Advanced Installation": "高级安装", "Advanced options": "高级选项", "Advanced search": "高级搜索", @@ -122,6 +124,7 @@ "ARTICLE": "自动 - 团队阅读文章后触发", "Ask AI": "问AI", "AsreproastableAccount": "AS-REP 可焙烧账户", + "Asreproastableaccount": "AS-REP 可焙烧账户", "Assertive": "自信", "Asset": "资产", "asset": "資產", @@ -405,6 +408,7 @@ "Customization": "自定义", "Customize columns": "专栏标准化", "CVE": "CVE", + "Cve": "内容", "CVSS must be at least 0.0": "CVSS 必须至少为 0.0", "CVSS must be at most 10.0": "CVSS 必须至少为 10.0", "CVSS score is required": "CVSS 分数是必需的", @@ -995,7 +999,9 @@ "IP Addresses": "IP地址", "Ip Addresses": "IP地址组", "IPv4": "IPv4", + "Ipv4": "IPv4", "IPv6": "IPv6", + "Ipv6": "IPv6", "is": "是", "is empty": "为空", "is not empty": "不为空", @@ -1007,6 +1013,7 @@ "Japanese": "日语", "JAVA Virtual Machine": "JAVA虚拟机", "KerberoastableAccount": "Kerberoastable 帐户", + "Kerberoastableaccount": "Kerberoastable 账户", "Key": "密钥", "Kill chain name": "杀伤链名称", "Kill Chain Phase": "杀伤链阶段", @@ -1338,6 +1345,7 @@ "Password": "密码", "Password validation": "密码验证", "PasswordPolicy": "密码策略", + "Passwordpolicy": "密码策略", "Passwords do no match": "密码不匹配", "Paste text to analyse for selected TTPs.": "为选定的 TTPs 粘贴要分析的文本。", "Paste your Filigran OpenAEV Enterprise Edition license": "粘贴您的 Filigran OpenAEV 企业版许可证", @@ -1414,6 +1422,7 @@ "Port": "港口", "Portscan": "分析港口", "PortsScan": "端口扫描", + "Portsscan": "端口扫描", "PostgreSQL": "PostgreSQL", "PowerShell": "PowerShell", "PREREQUISITE_CHECK": "先决条件检查命令", From 2ba600f97494bfa8bba539473f57bfa464d39dea Mon Sep 17 00:00:00 2001 From: savacano28 Date: Mon, 30 Mar 2026 16:08:16 +0200 Subject: [PATCH 11/12] [backend] feat: feedback review --- .../findings/ContractOutputElementType.ts | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts index 5fe3abdf1af..a7d1ec37ad2 100644 --- a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts +++ b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts @@ -21,27 +21,8 @@ enum ContractOutputElementType { vulnerability = 'Vulnerability', } -export const CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS = [ - 'account_with_password_not_required', - 'admin_username', - 'asreproastable_account', - 'computer', - 'credentials', - 'cve', - 'delegation', - 'group', - 'ipv4', - 'ipv6', - 'kerberoastable_account', - 'number', - 'password_policy', - 'port', - 'portscan', - 'share', - 'sid', - 'text', - 'username', - 'vulnerability', -] as const; +export const CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS = Object.keys( + ContractOutputElementType, +) as Array; export default ContractOutputElementType; From 1d170df45e01419f1ce941bb1e10e9cf30c191c9 Mon Sep 17 00:00:00 2001 From: savacano28 Date: Mon, 30 Mar 2026 16:32:57 +0200 Subject: [PATCH 12/12] [backend] feat: feedback review --- .../src/admin/components/findings/ContractOutputElementType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts index a7d1ec37ad2..e025117c967 100644 --- a/openaev-front/src/admin/components/findings/ContractOutputElementType.ts +++ b/openaev-front/src/admin/components/findings/ContractOutputElementType.ts @@ -23,6 +23,6 @@ enum ContractOutputElementType { export const CONTRACT_OUTPUT_ELEMENT_TYPE_KEYS = Object.keys( ContractOutputElementType, -) as Array; +) as [keyof typeof ContractOutputElementType, ...Array]; export default ContractOutputElementType;