From c914c4821ff62e74c2b1ad02ea68d1644686ab06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Fri, 17 Oct 2025 11:04:37 +0200 Subject: [PATCH 1/7] [frontend/backend] Use healthchecks to manage inject readyness --- .../openaev/healthcheck/dto/HealthCheck.java | 46 ++- .../healthcheck/utils/HealthCheckUtils.java | 262 ++++++++++++++- .../io/openaev/rest/inject/InjectApi.java | 35 +- .../rest/inject/output/InjectOutput.java | 212 ++++++++----- .../rest/inject/service/InjectService.java | 1 + .../scenario/response/ImportTestSummary.java | 33 +- .../scheduler/jobs/InjectsExecutionJob.java | 5 +- .../openaev/service/InjectImportService.java | 18 +- .../openaev/service/InjectSearchService.java | 96 ++++-- .../io/openaev/service/ScenarioService.java | 58 +++- .../java/io/openaev/service/TeamService.java | 4 + .../io/openaev/utils/mapper/InjectMapper.java | 184 ++++++----- .../HealthCheckUtilsTest.java} | 300 ++++++++++-------- .../inject/service/InjectServiceTest.java | 3 +- .../components/common/injects/Injects.tsx | 13 +- openaev-front/src/utils/api-types.d.ts | 79 ++++- openaev-front/src/utils/lang/en.json | 7 + .../io/openaev/database/model/Inject.java | 13 +- .../io/openaev/helper/InjectModelHelper.java | 156 +-------- 19 files changed, 964 insertions(+), 561 deletions(-) rename openaev-api/src/test/java/io/openaev/{helper/InjectModelHelperTest.java => healthcheck/HealthCheckUtilsTest.java} (68%) diff --git a/openaev-api/src/main/java/io/openaev/healthcheck/dto/HealthCheck.java b/openaev-api/src/main/java/io/openaev/healthcheck/dto/HealthCheck.java index 12f2708fa0..043381beee 100644 --- a/openaev-api/src/main/java/io/openaev/healthcheck/dto/HealthCheck.java +++ b/openaev-api/src/main/java/io/openaev/healthcheck/dto/HealthCheck.java @@ -7,10 +7,12 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import lombok.extern.slf4j.Slf4j; @Getter @Setter @AllArgsConstructor +@Slf4j public class HealthCheck { public enum Status { @@ -22,17 +24,45 @@ public enum Detail { SERVICE_UNAVAILABLE, NOT_READY, EMPTY, + MANDATORY_CONTENT, } public enum Type { - SMTP, - IMAP, - AGENT_OR_EXECUTOR, - SECURITY_SYSTEM_COLLECTOR, - INJECT, - TEAMS, - NMAP, - NUCLEI, + SMTP("smtp"), + IMAP("imap"), + AGENT_OR_EXECUTOR("agent_or_executor"), + SECURITY_SYSTEM_COLLECTOR("security_system_collector"), + INJECT("inject"), + TEAMS("teams"), + NMAP("nmap"), + NUCLEI("nuclei"), + INJECTOR_CONTRACT("injector_contract"), + ASSETS("assets"), + ASSET_GROUPS("asset_groups"), + SUBJECT("subject"), + BODY("body"), + OPTIONAL_ARGS("optional_args"), + UNKNOWN("unknown"); + + private final String value; + + public String getValue() { + return value; + } + + Type(String value) { + this.value = value; + } + + public static Type fromValue(String value) { + for (Type type : Type.values()) { + if (type.value.equalsIgnoreCase(value)) { + return type; + } + } + log.warn(String.format("Unknown HealthCheck Type: %s", value)); + return UNKNOWN; + } } @Schema(description = "Type of the check, could be a service, an attribute, etc") diff --git a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java index 429f3264f7..c71856de9a 100644 --- a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java +++ b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java @@ -1,7 +1,18 @@ package io.openaev.healthcheck.utils; +import static io.openaev.database.model.InjectorContract.*; +import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_KEY; +import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_FIELDS; +import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_VALUES; +import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_MANDATORY_GROUPS; import static java.time.Instant.now; +import static java.util.stream.StreamSupport.stream; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.openaev.database.model.*; import io.openaev.executors.utils.ExecutorUtils; import io.openaev.healthcheck.dto.HealthCheck; @@ -10,6 +21,7 @@ import io.openaev.rest.inject.output.AgentsAndAssetsAgentless; import jakarta.validation.constraints.NotNull; import java.util.*; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.ArrayUtils; import org.springframework.stereotype.Component; @@ -157,6 +169,25 @@ public List runInjectorCheck( return result; } + public List runInjectsChecksFor( + HealthCheck.Type type, + HealthCheck.Detail detail, + HealthCheck.Status status, + List injectsHealthChecks) { + List result = new ArrayList<>(); + + if (injectsHealthChecks.stream() + .anyMatch( + healthCheck -> + type.equals(healthCheck.getType()) + && detail.equals(healthCheck.getDetail()) + && status.equals(healthCheck.getStatus()))) { + result.add(new HealthCheck(type, detail, status, now())); + } + + return result; + } + /** * Run all missing content checks for one scenario * @@ -166,7 +197,26 @@ public List runInjectorCheck( public List runMissingContentChecks(@NotNull final Scenario scenario) { List result = new ArrayList<>(); boolean atLeastOneInjectIsNotReady = - scenario.getInjects().stream().anyMatch(inject -> !inject.isReady()); + scenario.getInjects().stream() + .anyMatch( + inject -> + !runContentChecks( + inject.getInjectorContract().orElse(null), + inject.getContent(), + inject.isAllTeams(), + inject.getTeams() != null + ? inject.getTeams().stream().map(Team::getId).toList() + : new ArrayList<>(), + inject.getAssets() != null + ? inject.getAssets().stream().map(Asset::getId).toList() + : new ArrayList<>(), + inject.getAssetGroups() != null + ? new ArrayList<>( + inject.getAssetGroups().stream() + .map(AssetGroup::getId) + .toList()) + : new ArrayList<>()) + .isEmpty()); if (atLeastOneInjectIsNotReady) { result.add( @@ -224,4 +274,214 @@ public List runTeamsChecks(@NotNull final Scenario scenario) { return result; } + + public List runContentChecks(Inject inject) { + return runContentChecks( + inject.getInjectorContract().orElse(null), + inject.getContent(), + inject.isAllTeams(), + inject.getTeams() != null + ? inject.getTeams().stream().map(Team::getId).toList() + : new ArrayList<>(), + inject.getAssets() != null + ? inject.getAssets().stream().map(Asset::getId).toList() + : new ArrayList<>(), + inject.getAssetGroups() != null + ? new ArrayList<>(inject.getAssetGroups().stream().map(AssetGroup::getId).toList()) + : new ArrayList<>()); + } + + public List runContentChecks( + InjectorContract injectorContract, + ObjectNode content, + boolean allTeams, + @NotNull final List teams, + @NotNull final List assets, + @NotNull final List assetGroups) { + List result = new ArrayList<>(); + + if (injectorContract == null) { + result.add( + new HealthCheck( + HealthCheck.Type.INJECTOR_CONTRACT, + HealthCheck.Detail.MANDATORY_CONTENT, + HealthCheck.Status.ERROR, + now())); + return result; + } + + ObjectMapper mapper = new ObjectMapper(); + ArrayNode injectContractFields; + + try { + injectContractFields = + (ArrayNode) + mapper + .readValue(injectorContract.getContent(), ObjectNode.class) + .get(CONTRACT_CONTENT_FIELDS); + } catch (JsonProcessingException e) { + throw new RuntimeException("Error parsing injector contract content", e); + } + + ObjectNode contractContent = injectorContract.getConvertedContent(); + List contractFields = + stream(contractContent.get(CONTRACT_CONTENT_FIELDS).spliterator(), false).toList(); + + for (JsonNode jsonField : contractFields) { + + // If field is mandatory + if (jsonField.get(CONTRACT_ELEMENT_CONTENT_MANDATORY).asBoolean() + && !InjectModelHelper.isFieldSet( + allTeams, teams, assets, assetGroups, jsonField, content, injectContractFields)) { + result.add( + new HealthCheck( + HealthCheck.Type.fromValue(jsonField.get(CONTRACT_ELEMENT_CONTENT_KEY).asText()), + HealthCheck.Detail.MANDATORY_CONTENT, + HealthCheck.Status.ERROR, + now())); + } + + // If field is mandatory group + if (jsonField.hasNonNull(CONTRACT_ELEMENT_CONTENT_MANDATORY_GROUPS)) { + ArrayNode mandatoryGroups = + (ArrayNode) jsonField.get(CONTRACT_ELEMENT_CONTENT_MANDATORY_GROUPS); + if (!mandatoryGroups.isEmpty()) { + boolean atLeastOneSet = false; + for (JsonNode mandatoryFieldKey : mandatoryGroups) { + Optional groupField = + contractFields.stream() + .filter( + jsonNode -> + mandatoryFieldKey + .asText() + .equals(jsonNode.get(CONTRACT_ELEMENT_CONTENT_KEY).asText())) + .findFirst(); + if (groupField.isPresent() + && InjectModelHelper.isFieldSet( + allTeams, + teams, + assets, + assetGroups, + groupField.get(), + content, + injectContractFields)) { + atLeastOneSet = true; + break; + } + } + if (!atLeastOneSet) { + for (JsonNode mandatoryFieldKey : mandatoryGroups) { + result.add( + new HealthCheck( + HealthCheck.Type.fromValue(mandatoryFieldKey.asText()), + HealthCheck.Detail.MANDATORY_CONTENT, + HealthCheck.Status.ERROR, + now())); + } + } + } + } + + // If field is mandatory conditional, if the conditional field is set, check if the current + // field is set + if (jsonField.hasNonNull(CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_FIELDS)) { + JsonNode fields = jsonField.get(CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_FIELDS); + + if (fields.isArray()) { + for (JsonNode node : fields) { + if (!node.isNull()) { + String fieldKey = node.asText(); + + Optional conditionalFieldOpt = + contractFields.stream() + .filter( + jsonNode -> + fieldKey.equals(jsonNode.get(CONTRACT_ELEMENT_CONTENT_KEY).asText())) + .findFirst(); + + // If field not exists -> skip + if (conditionalFieldOpt.isEmpty()) { + continue; + } + if (jsonField.hasNonNull(CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_VALUES)) { + JsonNode conditionalValuesNode = + jsonField.get(CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_VALUES); + + if (conditionalValuesNode.has(fieldKey)) { + List specificValuesNode = + conditionalValuesNode.get(fieldKey).isArray() + ? stream(conditionalValuesNode.get(fieldKey).spliterator(), false) + .map(JsonNode::asText) + .toList() + : List.of(conditionalValuesNode.get(fieldKey).asText()); + + List actualValues = + InjectModelHelper.getFieldValue( + teams, assets, assetGroups, conditionalFieldOpt.get(), content); + boolean conditionMet = + actualValues.stream().anyMatch(specificValuesNode::contains); + + if (!conditionMet) { + continue; // condition not met → skip + } + } + } + Optional fieldOpt = + contractFields.stream() + .filter( + jsonNode -> + jsonField + .get(CONTRACT_ELEMENT_CONTENT_KEY) + .asText() + .equals(jsonNode.get(CONTRACT_ELEMENT_CONTENT_KEY).asText())) + .findFirst(); + // If field not exists -> skip + if (fieldOpt.isEmpty()) { + continue; + } + if (!InjectModelHelper.isFieldSet( + allTeams, + teams, + assets, + assetGroups, + fieldOpt.get(), + content, + injectContractFields)) { + result.add( + new HealthCheck( + HealthCheck.Type.fromValue( + fieldOpt.get().get(CONTRACT_ELEMENT_CONTENT_KEY).asText()), + HealthCheck.Detail.MANDATORY_CONTENT, + HealthCheck.Status.ERROR, + now())); + } + } + } + } + } + } + + return result; + } + + /** + * Remove all duplicates healthchecks + * + * @param healthChecks to filter + * @return filtered healthchecks + */ + public List removeDuplicates(List healthChecks) { + return healthChecks.stream() + .collect( + Collectors.toMap( + hc -> hc.getType() + "_" + hc.getDetail(), + hc -> hc, + (a, b) -> + HealthCheck.Status.ERROR.equals(a.getStatus()) + ? a + : HealthCheck.Status.ERROR.equals(b.getStatus()) ? b : a)) + .values() + .stream() + .toList(); + } } diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/InjectApi.java b/openaev-api/src/main/java/io/openaev/rest/inject/InjectApi.java index ac253e0855..6c14dbeaef 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/InjectApi.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/InjectApi.java @@ -10,7 +10,6 @@ import io.openaev.database.model.*; import io.openaev.database.raw.RawDocument; import io.openaev.database.repository.ExerciseRepository; -import io.openaev.database.repository.GrantRepository; import io.openaev.database.repository.InjectRepository; import io.openaev.database.repository.UserRepository; import io.openaev.database.specification.InjectSpecification; @@ -23,16 +22,17 @@ import io.openaev.rest.exercise.exports.ExportOptions; import io.openaev.rest.helper.RestBehavior; import io.openaev.rest.inject.form.*; +import io.openaev.rest.inject.output.InjectOutput; import io.openaev.rest.inject.service.ExecutableInjectService; import io.openaev.rest.inject.service.InjectExecutionService; import io.openaev.rest.inject.service.InjectExportService; import io.openaev.rest.inject.service.InjectService; import io.openaev.rest.payload.form.DetectionRemediationOutput; -import io.openaev.service.InjectImportService; import io.openaev.service.UserService; import io.openaev.service.targets.TargetService; import io.openaev.utils.FilterUtilsJpa; import io.openaev.utils.TargetType; +import io.openaev.utils.mapper.InjectMapper; import io.openaev.utils.mapper.PayloadMapper; import io.openaev.utils.pagination.SearchPaginationInput; import io.swagger.v3.oas.annotations.Operation; @@ -70,7 +70,6 @@ public class InjectApi extends RestBehavior { private final ExerciseRepository exerciseRepository; private final InjectRepository injectRepository; private final InjectService injectService; - private final InjectImportService injectImportService; private final InjectExecutionService injectExecutionService; private final InjectExportService injectExportService; private final TargetService targetService; @@ -78,14 +77,16 @@ public class InjectApi extends RestBehavior { private final PayloadMapper payloadMapper; private final UserService userService; private final DocumentService documentService; - private final GrantRepository grantRepository; + private final InjectMapper injectMapper; // -- INJECTS -- @GetMapping(INJECT_URI + "/{injectId}") @RBAC(resourceId = "#injectId", actionPerformed = Action.READ, resourceType = ResourceType.INJECT) - public Inject inject(@PathVariable @NotBlank final String injectId) { - return this.injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); + public InjectOutput inject(@PathVariable @NotBlank final String injectId) { + Inject inject = + this.injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); + return injectMapper.toInjectOutput(inject, injectService.runChecks(inject)); } @LogExecutionTime @@ -290,12 +291,13 @@ public List targetOptionsById( resourceId = "#injectId", actionPerformed = Action.WRITE, resourceType = ResourceType.INJECT) - public Inject injectExecutionReception( + public InjectOutput injectExecutionReception( @PathVariable String injectId, @Valid @RequestBody InjectReceptionInput input) { Inject inject = injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); InjectStatus injectStatus = inject.getStatus().orElseThrow(ElementNotFoundException::new); injectStatus.setName(ExecutionStatus.PENDING); - return injectRepository.save(inject); + Inject savedInject = injectRepository.save(inject); + return injectMapper.toInjectOutput(savedInject, injectService.runChecks(savedInject)); } @PostMapping(INJECT_URI + "/execution/callback/{injectId}") @@ -357,7 +359,7 @@ public Payload getExecutablePayloadInject( resourceId = "#exerciseId", actionPerformed = Action.WRITE, resourceType = ResourceType.SIMULATION) - public Inject updateInject( + public InjectOutput updateInject( @PathVariable String exerciseId, @PathVariable String injectId, @Valid @RequestBody InjectInput input) { @@ -381,12 +383,13 @@ public Inject updateInject( } }); this.exerciseRepository.save(exercise); - return injectRepository.save(inject); + Inject savedInject = injectRepository.save(inject); + return injectMapper.toInjectOutput(savedInject, injectService.runChecks(savedInject)); } @GetMapping(INJECT_URI + "/next") @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.INJECT) - public List nextInjectsToExecute(@RequestParam Optional size) { + public List nextInjectsToExecute(@RequestParam Optional size) { return injectRepository.findAll(InjectSpecification.next()).stream() // Keep only injects visible by the user .filter(inject -> inject.getDate().isPresent()) @@ -403,6 +406,7 @@ public List nextInjectsToExecute(@RequestParam Optional size) { .sorted(Inject.executionComparator) // Keep only the expected size .limit(size.orElse(MAX_NEXT_INJECTS)) + .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) // Collect the result .toList(); } @@ -414,14 +418,19 @@ public List nextInjectsToExecute(@RequestParam Optional size) { @PutMapping(INJECT_URI) @RBAC(actionPerformed = Action.WRITE, resourceType = ResourceType.INJECT) @LogExecutionTime - public List bulkUpdateInject(@RequestBody @Valid final InjectBulkUpdateInputs input) { + public List bulkUpdateInject( + @RequestBody @Valid final InjectBulkUpdateInputs input) { // Control and format inputs List injectsToUpdate = getInjectsAndCheckInputForBulkProcessing(input, Grant.GRANT_TYPE.PLANNER); // Bulk update - return this.injectService.bulkUpdateInject(injectsToUpdate, input.getUpdateOperations()); + return this.injectService + .bulkUpdateInject(injectsToUpdate, input.getUpdateOperations()) + .stream() + .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) + .toList(); } @Operation( diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java b/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java index 686b0c67d4..152baa7186 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java @@ -1,18 +1,25 @@ package io.openaev.rest.inject.output; +import static java.time.Instant.now; + import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.openaev.database.model.InjectDependency; -import io.openaev.database.model.InjectorContract; +import io.openaev.database.converter.ContentConverter; +import io.openaev.database.model.*; import io.openaev.healthcheck.dto.HealthCheck; -import io.openaev.helper.InjectModelHelper; +import io.openaev.helper.*; import io.openaev.injectors.email.EmailContract; import io.openaev.injectors.ovh.OvhSmsContract; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.Convert; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import java.time.Instant; import java.util.*; import lombok.AllArgsConstructor; import lombok.Data; @@ -33,17 +40,58 @@ public class InjectOutput { @Schema(description = "Title of the inject") private String title; + @JsonProperty("inject_description") + @Schema(description = "Description of the inject") + private String description; + + @JsonProperty("inject_country") + @Schema(description = "Country of the inject") + private String country; + + @JsonProperty("inject_city") + @Schema(description = "City of the inject") + private String city; + @JsonProperty("inject_enabled") @Schema(description = "Enabled state of the inject") private boolean enabled; + @JsonProperty("inject_trigger_now_date") + @Schema(description = "Trigger date of the inject") + private Instant triggerNowDate; + + @JsonProperty("inject_content") + @Convert(converter = ContentConverter.class) + @Schema(description = "Content of the inject") + private ObjectNode content; + + @JsonProperty("inject_created_at") + @NotNull + @Schema(description = "Creation date of the inject") + private Instant createdAt = now(); + + @JsonProperty("inject_updated_at") + @NotNull + @Schema(description = "Update date of the inject") + private Instant updatedAt = now(); + + @JsonProperty("inject_all_teams") + @Schema(description = "All teams value of the inject") + private boolean allTeams; + @JsonProperty("inject_exercise") - @Schema(description = "Simulation ID of the inject") - private String exercise; + @JsonSerialize(using = MonoIdDeserializer.class) + @Schema(type = "string", description = "Simulation ID of the inject") + private Exercise exercise; @JsonProperty("inject_scenario") - @Schema(description = "Scenario ID of the inject") - private String scenario; + @JsonSerialize(using = MonoIdDeserializer.class) + @Schema(type = "string", description = "Scenario ID of the inject") + private Scenario scenario; + + @JsonProperty("inject_depends_on") + @ArraySchema(schema = @Schema(description = "Dependencies of the inject")) + private List dependsOn = new ArrayList<>(); @JsonProperty("inject_depends_duration") @NotNull @@ -51,92 +99,108 @@ public class InjectOutput { @Schema(description = "Depend duration of the inject") private Long dependsDuration; - @JsonProperty("inject_depends_on") - @ArraySchema(schema = @Schema(description = "Inject dependencies of the inject")) - private List dependsOn; - @JsonProperty("inject_injector_contract") @Schema(description = "Injector contract of the inject") private InjectorContract injectorContract; - @JsonProperty("inject_tags") - @ArraySchema(schema = @Schema(description = "Tags of the inject")) - private Set tags; + @JsonProperty("inject_user") + @JsonSerialize(using = MonoIdDeserializer.class) + @Schema(type = "string", description = "User of the inject") + private User user; - @JsonProperty("inject_ready") - @Schema(description = "Ready state of the inject") - public boolean isReady; + @JsonProperty("inject_status") + @Schema(description = "Status of the inject") + private InjectStatus status; - @JsonProperty("inject_type") - @Schema(description = "Type of the inject") - public String injectType; + @JsonProperty("inject_collect_status") + @Enumerated(EnumType.STRING) + @Schema(description = "Collect execution status of the inject") + private CollectExecutionStatus collectExecutionStatus; + + @JsonProperty("inject_tags") + @JsonSerialize(using = MultiIdSetDeserializer.class) + @ArraySchema(schema = @Schema(type = "string", description = "Tags of the inject")) + private Set tags; @JsonProperty("inject_teams") - @ArraySchema(schema = @Schema(description = "Teams of the inject")) - private List teams; + @JsonSerialize(using = MultiIdListDeserializer.class) + @ArraySchema(schema = @Schema(type = "string", description = "Teams of the inject")) + private List teams; @JsonProperty("inject_assets") - @ArraySchema(schema = @Schema(description = "Assets of the inject")) - private List assets; + @JsonSerialize(using = MultiIdListDeserializer.class) + @ArraySchema(schema = @Schema(type = "string", description = "Assets of the inject")) + private List assets; @JsonProperty("inject_asset_groups") - @ArraySchema(schema = @Schema(description = "Asset groups of the inject")) - private List assetGroups; + @JsonSerialize(using = MultiIdListDeserializer.class) + @ArraySchema(schema = @Schema(type = "string", description = "Asset groups of the inject")) + private List assetGroups; - @JsonProperty("inject_content") - @Schema(description = "Content of the inject") - private ObjectNode content; + @JsonProperty("inject_documents") + @JsonSerialize(using = MultiModelDeserializer.class) + @ArraySchema(schema = @Schema(type = "string", description = "Documents of the inject")) + private List documents = new ArrayList<>(); - @JsonProperty("inject_healthchecks") - @ArraySchema(schema = @Schema(description = "Healthchecks of the inject")) - private List healthchecks = new ArrayList<>(); + @JsonProperty("inject_communications") + @JsonSerialize(using = MultiModelDeserializer.class) + @ArraySchema(schema = @Schema(type = "string", description = "Communications of the inject")) + private List communications = new ArrayList<>(); + + @JsonProperty("inject_expectations") + @JsonSerialize(using = MultiModelDeserializer.class) + @ArraySchema(schema = @Schema(type = "string", description = "Expectations of the inject")) + private List expectations = new ArrayList<>(); + + @JsonProperty("inject_users_number") + @Schema(description = "Number of users tageted by the inject") + public Long numberOfTargetUsers; + + @JsonProperty("inject_date") + @Schema(description = "Date of the inject") + private Instant date; + + @JsonProperty("inject_communications_number") + @Schema(description = "Communications size of the inject") + public Long communicationsNumber; + + @JsonProperty("inject_communications_not_ack_number") + @Schema(description = "Communications not ack size of the inject") + private Long communicationsNotAckNumber; + + @JsonProperty("inject_sent_at") + @Schema(description = "Sent date of the inject") + public Instant sentAt; + + @JsonProperty("inject_kill_chain_phases") + @ArraySchema(schema = @Schema(description = "Kill chain phases of the inject")) + public List killChainPhases; + + @JsonProperty("inject_attack_patterns") + @ArraySchema(schema = @Schema(description = "Attack pattern of the inject")) + public List attackPatterns; + + @JsonProperty("inject_type") + @Schema(description = "Type of the inject") + private String type; @JsonProperty("inject_testable") @Schema(description = "Testable state of the inject") public boolean canBeTested() { - return EmailContract.TYPE.equals(this.getInjectType()) - || OvhSmsContract.TYPE.equals(this.getInjectType()); + return EmailContract.TYPE.equals(this.getType()) || OvhSmsContract.TYPE.equals(this.getType()); } - public InjectOutput( - String id, - String title, - boolean enabled, - ObjectNode content, - boolean allTeams, - String exerciseId, - String scenarioId, - Long dependsDuration, - InjectorContract injectorContract, - String[] tags, - String[] teams, - String[] assets, - String[] assetGroups, - String injectType, - InjectDependency injectDependency) { - this.id = id; - this.title = title; - this.enabled = enabled; - this.exercise = exerciseId; - this.scenario = scenarioId; - this.dependsDuration = dependsDuration; - this.injectorContract = injectorContract; - this.tags = tags != null ? new HashSet<>(Arrays.asList(tags)) : new HashSet<>(); - - this.teams = teams != null ? new ArrayList<>(Arrays.asList(teams)) : new ArrayList<>(); - this.assets = assets != null ? new ArrayList<>(Arrays.asList(assets)) : new ArrayList<>(); - this.assetGroups = - assetGroups != null ? new ArrayList<>(Arrays.asList(assetGroups)) : new ArrayList<>(); - - this.isReady = - InjectModelHelper.isReady( - injectorContract, content, allTeams, this.teams, this.assets, this.assetGroups); - this.injectType = injectType; - this.teams = teams != null ? new ArrayList<>(Arrays.asList(teams)) : new ArrayList<>(); - this.content = content; - - if (injectDependency != null) { - this.dependsOn = List.of(injectDependency); - } + @JsonProperty("inject_healthchecks") + @ArraySchema(schema = @Schema(description = "Healthchecks of the inject")) + private List healthchecks = new ArrayList<>(); + + @JsonProperty("inject_ready") + @Schema(description = "Ready state of the inject") + public boolean isReady() { + return healthchecks.isEmpty() + || healthchecks.stream() + .noneMatch( + healthcheck -> + HealthCheck.Detail.MANDATORY_CONTENT.equals(healthcheck.getDetail())); } } diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/service/InjectService.java b/openaev-api/src/main/java/io/openaev/rest/inject/service/InjectService.java index 49bdf1b7da..67a94c4d5a 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/service/InjectService.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/service/InjectService.java @@ -1290,6 +1290,7 @@ public List runChecks(final Inject inject) { inject, this.getAgentsAndAgentlessAssetsByInject(inject))); healthChecks.addAll(healthCheckUtils.runCollectorChecks(inject, collectors)); healthChecks.addAll(healthCheckUtils.runAllInjectorChecks(inject, injectors)); + healthChecks.addAll(healthCheckUtils.runContentChecks(inject)); return healthChecks; } diff --git a/openaev-api/src/main/java/io/openaev/rest/scenario/response/ImportTestSummary.java b/openaev-api/src/main/java/io/openaev/rest/scenario/response/ImportTestSummary.java index 8297802d11..7a49b6eb8c 100644 --- a/openaev-api/src/main/java/io/openaev/rest/scenario/response/ImportTestSummary.java +++ b/openaev-api/src/main/java/io/openaev/rest/scenario/response/ImportTestSummary.java @@ -6,8 +6,6 @@ import io.openaev.rest.inject.output.InjectOutput; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; import lombok.Data; @Data @@ -22,34 +20,5 @@ public class ImportTestSummary { @JsonIgnore private List injects = new ArrayList<>(); @JsonProperty("injects") - @Deprecated - public List getInjectResults() { - return injects.stream() - .map( - inject -> - new InjectOutput( - inject.getId(), - inject.getTitle(), - inject.isEnabled(), - inject.getContent(), - inject.isAllTeams(), - Optional.ofNullable(inject.getExercise()).map(Exercise::getId).orElse(null), - Optional.ofNullable(inject.getScenario()).map(Scenario::getId).orElse(null), - inject.getDependsDuration(), - inject.getInjectorContract().orElse(null), - inject.getTags().stream().map(Tag::getId).toArray(String[]::new), - inject.getTeams().stream().map(Team::getId).toArray(String[]::new), - inject.getAssets().stream().map(Asset::getId).toArray(String[]::new), - inject.getAssetGroups().stream().map(AssetGroup::getId).toArray(String[]::new), - inject - .getInjectorContract() - .map(InjectorContract::getInjector) - .map(Injector::getType) - .orElse(null), - Optional.ofNullable(inject.getDependsOn()) - .map(List::stream) - .flatMap(Stream::findAny) - .orElse(null))) - .toList(); - } + public List injectOutputs; } diff --git a/openaev-api/src/main/java/io/openaev/scheduler/jobs/InjectsExecutionJob.java b/openaev-api/src/main/java/io/openaev/scheduler/jobs/InjectsExecutionJob.java index 8c5ec3eb34..0e6e445163 100644 --- a/openaev-api/src/main/java/io/openaev/scheduler/jobs/InjectsExecutionJob.java +++ b/openaev-api/src/main/java/io/openaev/scheduler/jobs/InjectsExecutionJob.java @@ -13,6 +13,7 @@ import io.openaev.database.repository.InjectDependenciesRepository; import io.openaev.database.repository.InjectExpectationRepository; import io.openaev.execution.ExecutableInject; +import io.openaev.healthcheck.utils.HealthCheckUtils; import io.openaev.helper.InjectHelper; import io.openaev.notification.model.NotificationEvent; import io.openaev.notification.model.NotificationEventType; @@ -40,6 +41,7 @@ import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; @@ -80,6 +82,7 @@ public class InjectsExecutionJob implements Job { List.of(InjectExpectation.EXPECTATION_STATUS.SUCCESS); @Resource protected ObjectMapper mapper; + @Autowired private HealthCheckUtils healthCheckUtils; @PostConstruct private void init() { @@ -177,7 +180,7 @@ private void executeInject(ExecutableInject executableInject) if (ofNullable(executableInject.getExerciseId()).isPresent()) { checkErrorMessagesPreExecution(executableInject.getExerciseId(), inject); } - if (!inject.isReady()) { + if (!healthCheckUtils.runContentChecks(inject).isEmpty()) { throw new UnsupportedOperationException( "The inject is not ready to be executed (missing mandatory fields)"); } diff --git a/openaev-api/src/main/java/io/openaev/service/InjectImportService.java b/openaev-api/src/main/java/io/openaev/service/InjectImportService.java index 1bcc59d141..2654643c96 100644 --- a/openaev-api/src/main/java/io/openaev/service/InjectImportService.java +++ b/openaev-api/src/main/java/io/openaev/service/InjectImportService.java @@ -11,11 +11,13 @@ import io.openaev.database.repository.*; import io.openaev.rest.exception.BadRequestException; import io.openaev.rest.exception.ElementNotFoundException; +import io.openaev.rest.inject.service.InjectService; import io.openaev.rest.scenario.response.ImportMessage; import io.openaev.rest.scenario.response.ImportPostSummary; import io.openaev.rest.scenario.response.ImportTestSummary; import io.openaev.service.utils.InjectImportUtils; import io.openaev.utils.InjectUtils; +import io.openaev.utils.mapper.InjectMapper; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileSystems; @@ -70,6 +72,8 @@ public class InjectImportService { final String pathSeparator = FileSystems.getDefault().getSeparator(); final int FILE_STORAGE_DURATION = 60; + private final InjectMapper injectMapper; + private final InjectService injectService; /** * Store an xls file for ulterior import. The file will be deleted on exit. @@ -163,11 +167,7 @@ public ImportTestSummary importInjectIntoFromXLS( } else if (saveAll) { importTestSummary.setInjects( importTestSummary.getInjects().stream() - .map( - inject -> { - inject.setListened(false); - return inject; - }) + .peek(inject -> inject.setListened(false)) .toList()); Iterable newInjects = injectRepository.saveAll(importTestSummary.getInjects()); if (exercise != null) { @@ -178,9 +178,13 @@ public ImportTestSummary importInjectIntoFromXLS( throw new IllegalArgumentException( "At least one of exercise or scenario should be present"); } - importTestSummary.setInjects(new ArrayList<>()); + importTestSummary.setInjectOutputs(new ArrayList<>()); } else { - importTestSummary.setInjects(importTestSummary.getInjects().stream().limit(5).toList()); + importTestSummary.setInjectOutputs( + importTestSummary.getInjects().stream() + .limit(5) + .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) + .toList()); } return importTestSummary; diff --git a/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java b/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java index b70a50fd22..77b9ca0046 100644 --- a/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java +++ b/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java @@ -14,11 +14,14 @@ import io.openaev.database.repository.AssetRepository; import io.openaev.database.repository.InjectExpectationRepository; import io.openaev.database.repository.TeamRepository; +import io.openaev.healthcheck.dto.HealthCheck; +import io.openaev.healthcheck.utils.HealthCheckUtils; import io.openaev.rest.atomic_testing.form.InjectResultOutput; import io.openaev.rest.atomic_testing.form.InjectStatusSimple; import io.openaev.rest.atomic_testing.form.InjectorContractSimple; import io.openaev.rest.atomic_testing.form.TargetSimple; import io.openaev.rest.inject.output.InjectOutput; +import io.openaev.rest.inject.service.InjectService; import io.openaev.rest.payload.output.PayloadSimple; import io.openaev.utils.InjectExpectationResultUtils; import io.openaev.utils.TargetType; @@ -51,9 +54,15 @@ public class InjectSearchService { private final AssetRepository assetRepository; private final AssetGroupRepository assetGroupRepository; + private final InjectService injectService; + private final AssetService assetService; + private final AssetGroupService assetGroupService; + private final InjectMapper injectMapper; private final InjectExpectationMapper injectExpectationMapper; + private final HealthCheckUtils healthCheckUtils; + @PersistenceContext private EntityManager entityManager; // -- LIST INJECTOUTPUT -- @@ -120,6 +129,9 @@ public Page injects( // -- Count Query -- Long total = countQuery(cb, this.entityManager, Inject.class, specificationCount); + // -- Feed injects with healthchecks + // TO DO + return new PageImpl<>(injects, pageable, total); } @@ -166,7 +178,6 @@ private void selectForInject( teamIdsExpression.alias("inject_teams"), assetIdsExpression.alias("inject_assets"), assetGroupIdsExpression.alias("inject_asset_groups"), - injectorJoin.get("type").alias("inject_type"), injectDependency.alias("inject_depends_on")) .distinct(true); @@ -184,23 +195,72 @@ private void selectForInject( private List execInject(TypedQuery query) { return query.getResultList().stream() .map( - tuple -> - injectMapper.toInjectOutput( - tuple.get("inject_id", String.class), - tuple.get("inject_title", String.class), - tuple.get("inject_enabled", Boolean.class), - tuple.get("inject_content", ObjectNode.class), - tuple.get("inject_all_teams", Boolean.class), - tuple.get("inject_exercise", String.class), - tuple.get("inject_scenario", String.class), - tuple.get("inject_depends_duration", Long.class), - tuple.get("inject_injector_contract", InjectorContract.class), - tuple.get("inject_tags", String[].class), - tuple.get("inject_teams", String[].class), - tuple.get("inject_assets", String[].class), - tuple.get("inject_asset_groups", String[].class), - tuple.get("inject_type", String.class), - tuple.get("inject_depends_on", InjectDependency.class))) + tuple -> { + Inject inject = new Inject(); + inject.setId(tuple.get("inject_id", String.class)); + inject.setTitle(tuple.get("inject_title", String.class)); + inject.setEnabled(tuple.get("inject_enabled", Boolean.class)); + inject.setContent(tuple.get("inject_content", ObjectNode.class)); + inject.setAllTeams(tuple.get("inject_all_teams", Boolean.class)); + inject.setExercise( + ofNullable(tuple.get("inject_exercise", String.class)) + .map( + id -> { + Exercise exercise = new Exercise(); + exercise.setId(id); + return exercise; + }) + .orElse(null)); + inject.setScenario( + ofNullable(tuple.get("inject_scenario", String.class)) + .map( + id -> { + Scenario scenario = new Scenario(); + scenario.setId(id); + return scenario; + }) + .orElse(null)); + inject.setDependsDuration(tuple.get("inject_depends_duration", Long.class)); + inject.setInjectorContract( + tuple.get("inject_injector_contract", InjectorContract.class)); + inject.setTags( + ofNullable(tuple.get("inject_tags", String[].class)) + .map( + ids -> + Arrays.stream(ids) + .map( + id -> { + Tag tag = new Tag(); + tag.setId(id); + return tag; + }) + .collect(Collectors.toSet())) + .orElse(new HashSet<>())); + inject.setTeams( + ofNullable(tuple.get("inject_teams", String[].class)) + .map( + ids -> + Arrays.stream(ids) + .map( + id -> { + Team team = new Team(); + team.setId(id); + return team; + }) + .collect(Collectors.toList())) + .orElse(new ArrayList<>())); + inject.setAssets( + ofNullable(tuple.get("inject_assets", String[].class)) + .map(ids -> assetService.assets(Arrays.asList(ids))) + .orElse(new ArrayList<>())); + inject.setAssetGroups( + ofNullable(tuple.get("inject_asset_groups", String[].class)) + .map(ids -> assetGroupService.assetGroups(Arrays.asList(ids))) + .orElse(new ArrayList<>())); + List healthChecks = + healthCheckUtils.removeDuplicates(injectService.runChecks(inject)); + return injectMapper.toInjectOutput(inject, healthChecks); + }) .toList(); } diff --git a/openaev-api/src/main/java/io/openaev/service/ScenarioService.java b/openaev-api/src/main/java/io/openaev/service/ScenarioService.java index 357ebc6538..26fb442c6b 100644 --- a/openaev-api/src/main/java/io/openaev/service/ScenarioService.java +++ b/openaev-api/src/main/java/io/openaev/service/ScenarioService.java @@ -925,22 +925,48 @@ public List runChecks(String scenarioId) { // get the healthcheck for each injects, remove duplicate from injects HealthCheck results and // add them to the result - List injectsHealthChecksNoDuplicate = - scenario.getInjects().stream() - .flatMap(inject -> injectService.runChecks(inject).stream()) - .collect( - Collectors.toMap( - hc -> hc.getType() + "_" + hc.getDetail(), - hc -> hc, - (a, b) -> - HealthCheck.Status.ERROR.equals(a.getStatus()) - ? a - : HealthCheck.Status.ERROR.equals(b.getStatus()) ? b : a)) - .values() - .stream() - .toList(); - healthChecks.addAll(injectsHealthChecksNoDuplicate); - + List injectsHealthChecks = + healthCheckUtils.removeDuplicates( + scenario.getInjects().stream() + .flatMap(inject -> injectService.runChecks(inject).stream()) + .toList()); + + healthChecks.addAll( + healthCheckUtils.runInjectsChecksFor( + HealthCheck.Type.SMTP, + HealthCheck.Detail.SERVICE_UNAVAILABLE, + HealthCheck.Status.ERROR, + injectsHealthChecks)); + healthChecks.addAll( + healthCheckUtils.runInjectsChecksFor( + HealthCheck.Type.IMAP, + HealthCheck.Detail.SERVICE_UNAVAILABLE, + HealthCheck.Status.WARNING, + injectsHealthChecks)); + healthChecks.addAll( + healthCheckUtils.runInjectsChecksFor( + HealthCheck.Type.SECURITY_SYSTEM_COLLECTOR, + HealthCheck.Detail.EMPTY, + HealthCheck.Status.ERROR, + injectsHealthChecks)); + healthChecks.addAll( + healthCheckUtils.runInjectsChecksFor( + HealthCheck.Type.NMAP, + HealthCheck.Detail.SERVICE_UNAVAILABLE, + HealthCheck.Status.ERROR, + injectsHealthChecks)); + healthChecks.addAll( + healthCheckUtils.runInjectsChecksFor( + HealthCheck.Type.NUCLEI, + HealthCheck.Detail.SERVICE_UNAVAILABLE, + HealthCheck.Status.ERROR, + injectsHealthChecks)); + healthChecks.addAll( + healthCheckUtils.runInjectsChecksFor( + HealthCheck.Type.AGENT_OR_EXECUTOR, + HealthCheck.Detail.EMPTY, + HealthCheck.Status.ERROR, + injectsHealthChecks)); healthChecks.addAll(healthCheckUtils.runMissingContentChecks(scenario)); healthChecks.addAll(healthCheckUtils.runTeamsChecks(scenario)); diff --git a/openaev-api/src/main/java/io/openaev/service/TeamService.java b/openaev-api/src/main/java/io/openaev/service/TeamService.java index 19258e418f..160b3846d2 100644 --- a/openaev-api/src/main/java/io/openaev/service/TeamService.java +++ b/openaev-api/src/main/java/io/openaev/service/TeamService.java @@ -42,6 +42,10 @@ public class TeamService { private final UserService userService; + public List teams(@NotNull List teamIds) { + return teamRepository.findAllById(teamIds); + } + public List getTeams(@NotNull List teamIds) { List rawTeams = teamRepository.rawTeamByIds(teamIds).stream() diff --git a/openaev-api/src/main/java/io/openaev/utils/mapper/InjectMapper.java b/openaev-api/src/main/java/io/openaev/utils/mapper/InjectMapper.java index f17a38141c..4f50f0eab0 100644 --- a/openaev-api/src/main/java/io/openaev/utils/mapper/InjectMapper.java +++ b/openaev-api/src/main/java/io/openaev/utils/mapper/InjectMapper.java @@ -2,7 +2,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.openaev.database.model.*; -import io.openaev.helper.InjectModelHelper; +import io.openaev.healthcheck.dto.HealthCheck; +import io.openaev.healthcheck.utils.HealthCheckUtils; import io.openaev.rest.atomic_testing.form.*; import io.openaev.rest.document.form.RelatedEntityOutput; import io.openaev.rest.inject.output.InjectOutput; @@ -11,9 +12,9 @@ import io.openaev.utils.InjectExpectationResultUtils; import io.openaev.utils.InjectUtils; import io.openaev.utils.TargetType; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -24,6 +25,7 @@ public class InjectMapper { private final InjectStatusMapper injectStatusMapper; private final InjectExpectationMapper injectExpectationMapper; private final InjectUtils injectUtils; + private final HealthCheckUtils healthCheckUtils; public InjectResultOverviewOutput toInjectResultOverviewOutput(Inject inject) { // -- @@ -53,7 +55,7 @@ public InjectResultOverviewOutput toInjectResultOverviewOutput(Inject inject) { inject.getContent(), injectUtils.getPrimaryExpectations(inject), InjectExpectationResultUtils::getScores)) - .isReady(inject.isReady()) + .isReady(healthCheckUtils.runContentChecks(inject).isEmpty()) .updatedAt(inject.getUpdatedAt()) .build(); } @@ -148,100 +150,114 @@ private static RelatedEntityOutput toRelatedEntityOutput(Inject inject) { public InjectOutput toInjectOutput( String id, String title, + String description, + String country, + String city, boolean enabled, + Instant triggerNowDate, ObjectNode content, + Instant createdAt, + Instant updatedAt, boolean allTeams, - String exerciseId, - String scenarioId, + Exercise exercise, + Scenario scenario, + List dependsOn, Long dependsDuration, InjectorContract injectorContract, - String[] tags, - String[] teams, - String[] assets, - String[] assetGroups, - String injectType, - InjectDependency injectDependency) { + User user, + InjectStatus status, + CollectExecutionStatus collectExecutionStatus, + Set tags, + List teams, + List assets, + List assetGroups, + List documents, + List communications, + List expectations, + Long numberOfTargetUsers, + Instant date, + Long communicationsNumber, + Long communicationsNotAckNumber, + Instant sentAt, + List killChainPhases, + List attackPatterns, + String type, + List healthchecks) { InjectOutput injectOutput = new InjectOutput(); injectOutput.setId(id); injectOutput.setTitle(title); + injectOutput.setDescription(description); + injectOutput.setCountry(country); + injectOutput.setCity(city); injectOutput.setEnabled(enabled); - injectOutput.setExercise(exerciseId); - injectOutput.setScenario(scenarioId); + injectOutput.setTriggerNowDate(triggerNowDate); + injectOutput.setContent(content); + injectOutput.setCreatedAt(createdAt); + injectOutput.setUpdatedAt(updatedAt); + injectOutput.setAllTeams(allTeams); + injectOutput.setExercise(exercise); + injectOutput.setScenario(scenario); + injectOutput.setDependsOn(dependsOn); injectOutput.setDependsDuration(dependsDuration); injectOutput.setInjectorContract(injectorContract); - injectOutput.setTags(tags != null ? new HashSet<>(Arrays.asList(tags)) : new HashSet<>()); - injectOutput.setTeams( - teams != null ? new ArrayList<>(Arrays.asList(teams)) : new ArrayList<>()); - injectOutput.setAssets( - assets != null ? new ArrayList<>(Arrays.asList(assets)) : new ArrayList<>()); - injectOutput.setAssetGroups( - assetGroups != null ? new ArrayList<>(Arrays.asList(assetGroups)) : new ArrayList<>()); - injectOutput.setReady( - InjectModelHelper.isReady( - injectorContract, - content, - allTeams, - injectOutput.getTeams(), - injectOutput.getAssets(), - injectOutput.getAssetGroups())); - injectOutput.setInjectType(injectType); - injectOutput.setTeams( - teams != null ? new ArrayList<>(Arrays.asList(teams)) : new ArrayList<>()); - injectOutput.setContent(content); - if (injectDependency != null) { - injectOutput.setDependsOn(List.of(injectDependency)); - } + injectOutput.setUser(user); + injectOutput.setStatus(status); + injectOutput.setCollectExecutionStatus(collectExecutionStatus); + injectOutput.setTags(tags); + injectOutput.setTeams(teams); + injectOutput.setAssets(assets); + injectOutput.setAssetGroups(assetGroups); + injectOutput.setDocuments(documents); + injectOutput.setCommunications(communications); + injectOutput.setExpectations(expectations); + injectOutput.setNumberOfTargetUsers(numberOfTargetUsers); + injectOutput.setDate(date); + injectOutput.setCommunicationsNumber(communicationsNumber); + injectOutput.setCommunicationsNotAckNumber(communicationsNotAckNumber); + injectOutput.setSentAt(sentAt); + injectOutput.setKillChainPhases(killChainPhases); + injectOutput.setAttackPatterns(attackPatterns); + injectOutput.setType(type); + injectOutput.setHealthchecks(healthchecks); return injectOutput; } - public InjectOutput toInjectOuput(Inject inject) { - InjectOutput injectOutput = new InjectOutput(); - injectOutput.setId(inject.getId()); - injectOutput.setTitle(inject.getTitle()); - injectOutput.setEnabled(inject.isEnabled()); - injectOutput.setExercise( - Optional.ofNullable(inject.getExercise()).map(Exercise::getId).orElse(null)); - injectOutput.setScenario( - Optional.ofNullable(inject.getScenario()).map(Scenario::getId).orElse(null)); - injectOutput.setDependsDuration(inject.getDependsDuration()); - injectOutput.setInjectorContract(inject.getInjectorContract().orElse(null)); - injectOutput.setTags( - inject.getTags() != null - ? new HashSet<>(inject.getTags().stream().map(Tag::getId).toList()) - : new HashSet<>()); - injectOutput.setTeams( - inject.getTeams() != null - ? inject.getTeams().stream().map(Team::getId).toList() - : new ArrayList<>()); - injectOutput.setAssets( - inject.getAssets() != null - ? inject.getAssets().stream().map(Asset::getId).toList() - : new ArrayList<>()); - injectOutput.setAssetGroups( - inject.getAssetGroups() != null - ? new ArrayList<>(inject.getAssetGroups().stream().map(AssetGroup::getId).toList()) - : new ArrayList<>()); - injectOutput.setContent(inject.getContent()); - injectOutput.setReady( - InjectModelHelper.isReady( - injectOutput.getInjectorContract(), - injectOutput.getContent(), - inject.isAllTeams(), - injectOutput.getTeams(), - injectOutput.getAssets(), - injectOutput.getAssetGroups())); - injectOutput.setInjectType( - inject - .getInjectorContract() - .map(InjectorContract::getInjector) - .map(Injector::getType) - .orElse(null)); - injectOutput.setDependsOn( - Optional.ofNullable(inject.getDependsOn()) - .map(List::stream) - .flatMap(Stream::findAny) - .stream() - .toList()); - return injectOutput; + public InjectOutput toInjectOutput(Inject inject, List healthchecks) { + return toInjectOutput( + inject.getId(), + inject.getTitle(), + inject.getDescription(), + inject.getCountry(), + inject.getCity(), + inject.isEnabled(), + inject.getTriggerNowDate(), + inject.getContent(), + inject.getCreatedAt(), + inject.getUpdatedAt(), + inject.isAllTeams(), + inject.getExercise(), + inject.getScenario(), + inject.getDependsOn(), + inject.getDependsDuration(), + inject.getInjectorContract().orElse(null), + inject.getUser(), + inject.getStatus().orElse(null), + inject.getCollectExecutionStatus(), + inject.getTags(), + inject.getTeams(), + inject.getAssets(), + inject.getAssetGroups(), + inject.getDocuments(), + inject.getCommunications(), + inject.getExpectations(), + inject.getNumberOfTargetUsers(), + inject.getDate().orElse(null), + inject.getCommunicationsNumber(), + inject.getCommunicationsNotAckNumber(), + inject.getSentAt(), + inject.getKillChainPhases(), + inject.getAttackPatterns(), + inject.getType(), + healthchecks); } } diff --git a/openaev-api/src/test/java/io/openaev/helper/InjectModelHelperTest.java b/openaev-api/src/test/java/io/openaev/healthcheck/HealthCheckUtilsTest.java similarity index 68% rename from openaev-api/src/test/java/io/openaev/helper/InjectModelHelperTest.java rename to openaev-api/src/test/java/io/openaev/healthcheck/HealthCheckUtilsTest.java index 7d4f2e6bf0..04180dff5b 100644 --- a/openaev-api/src/test/java/io/openaev/helper/InjectModelHelperTest.java +++ b/openaev-api/src/test/java/io/openaev/healthcheck/HealthCheckUtilsTest.java @@ -1,9 +1,9 @@ -package io.openaev.helper; +package io.openaev.healthcheck; -import static io.openaev.helper.InjectModelHelper.isReady; import static io.openaev.helper.ObjectMapperHelper.openAEVJsonMapper; import static io.openaev.injector_contract.fields.ContractText.textField; import static io.openaev.utils.fixtures.InjectorContractFixture.*; +import static io.openaev.utils.fixtures.InjectorContractFixture.addField; import static io.openaev.utils.fixtures.InjectorFixture.createDefaultPayloadInjector; import static io.openaev.utils.fixtures.PayloadFixture.createCommand; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -14,15 +14,21 @@ import io.openaev.database.model.Command; import io.openaev.database.model.Injector; import io.openaev.database.model.InjectorContract; +import io.openaev.healthcheck.utils.HealthCheckUtils; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; -class InjectModelHelperTest { +@SpringBootTest +public class HealthCheckUtilsTest { private final ObjectMapper mapper = openAEVJsonMapper(); + @Autowired private HealthCheckUtils healthCheckUtils; + private InjectorContract prepareInjectorContract() throws JsonProcessingException { Injector injector = createDefaultPayloadInjector(); Command payloadCommand = createCommand("cmd", "whoami", List.of(), "whoami"); @@ -45,13 +51,15 @@ void given_an_injector_contract_with_asset_mandatory_and_an_asset_should_be_read // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -70,13 +78,15 @@ void given_an_injector_contract_with_asset_mandatory_and_no_asset_should_not_be_ // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertFalse(isReady); @@ -95,13 +105,15 @@ void given_an_injector_contract_with_asset_optional_and_an_asset_should_be_ready // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -120,13 +132,15 @@ void given_an_injector_contract_with_asset_optional_and_not_asset_should_be_read // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -149,13 +163,15 @@ void given_an_injector_contract_with_mandatory_groups_and_an_element_should_be_r // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -174,13 +190,15 @@ void given_an_injector_contract_with_mandatory_groups_and_full_elements_should_b // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -199,13 +217,15 @@ void given_an_injector_contract_with_mandatory_groups_and_no_element_should_not_ // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertFalse(isReady); @@ -228,13 +248,15 @@ class MandatoryOnConditionTests { // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertFalse(isReady); @@ -253,13 +275,15 @@ void given_an_injector_contract_with_mandatory_on_condition_and_element_should_b // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -279,13 +303,15 @@ void given_an_injector_contract_with_mandatory_on_condition_and_element_should_b // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertFalse(isReady); @@ -304,13 +330,15 @@ void given_an_injector_contract_with_mandatory_on_condition_and_all_elements_sho // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -334,13 +362,15 @@ void given_mandatory_on_condition_with_specific_value_when_condition_matches_sho // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -362,13 +392,15 @@ void given_mandatory_on_condition_with_specific_values_when_condition_matches_sh // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -388,13 +420,15 @@ void given_mandatory_on_condition_with_specific_values_when_condition_matches_sh // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertFalse(isReady); @@ -414,13 +448,15 @@ void given_mandatory_on_condition_with_specific_values_when_condition_matches_sh // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -443,13 +479,15 @@ void given_mandatory_on_condition_with_specific_values_when_condition_matches_sh // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -473,13 +511,15 @@ void given_text_field_with_default_value_and_no_content_should_be_ready() // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertTrue(isReady); @@ -499,13 +539,15 @@ void given_text_field_without_default_value_and_no_content_should_not_be_ready() // -- EXECUTE -- boolean isReady = - isReady( - injectorContract, - injectorContract.getConvertedContent(), - allTeams, - teams, - assets, - assetGroups); + healthCheckUtils + .runContentChecks( + injectorContract, + injectorContract.getConvertedContent(), + allTeams, + teams, + assets, + assetGroups) + .isEmpty(); // -- ASSERT -- assertFalse(isReady); diff --git a/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java b/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java index c5e5b82c99..4dedb5813f 100644 --- a/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java @@ -51,6 +51,7 @@ import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.Specification; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.test.util.ReflectionTestUtils; @@ -111,7 +112,7 @@ void setUp() { ReflectionTestUtils.setField( injectService, "injectMapper", - new InjectMapper(injectStatusMapper, injectExpectationMapper, injectUtils)); + new InjectMapper(injectStatusMapper, injectExpectationMapper, injectUtils, new HealthCheckUtils(new ExecutorUtils()))); ReflectionTestUtils.setField( injectService, "injectorContractContentUtils", injectorContractContentUtils); } diff --git a/openaev-front/src/admin/components/common/injects/Injects.tsx b/openaev-front/src/admin/components/common/injects/Injects.tsx index fd72387881..7fe5962357 100644 --- a/openaev-front/src/admin/components/common/injects/Injects.tsx +++ b/openaev-front/src/admin/components/common/injects/Injects.tsx @@ -172,19 +172,24 @@ const Injects: FunctionComponent = ({ label: 'Status', isSortable: false, value: (inject: InjectOutputType, _: InjectorContractConverted['convertedContent']) => { - let injectStatus = inject.inject_enabled + let injectLabel = inject.inject_enabled ? t('Enabled') : t('Disabled'); + let injectTooltip = ''; if (!inject.inject_ready) { - injectStatus = t('Missing content'); + injectLabel = t('Missing content'); + injectTooltip = inject.inject_healthchecks ? t('Missing content') + ' : ' + inject.inject_healthchecks + .filter(healthcheck => "MANDATORY_CONTENT" === healthcheck.detail) + .map(healthcheck => t(`healthcheck.description.${healthcheck.type}.${healthcheck.detail}`)) + .join(', ') : ''; } return ( ); }, diff --git a/openaev-front/src/utils/api-types.d.ts b/openaev-front/src/utils/api-types.d.ts index 9920cbc355..72e157875d 100644 --- a/openaev-front/src/utils/api-types.d.ts +++ b/openaev-front/src/utils/api-types.d.ts @@ -347,6 +347,7 @@ export interface AtomicTestingUpdateTagsInput { atomic_tags?: string[]; } +/** Attack pattern of the inject */ export interface AttackPattern { /** @format date-time */ attack_pattern_created_at?: string; @@ -2566,7 +2567,7 @@ export interface HealthCheck { */ creation_date: string; /** Detail of the check failure */ - detail: "SERVICE_UNAVAILABLE" | "NOT_READY" | "EMPTY"; + detail: "SERVICE_UNAVAILABLE" | "NOT_READY" | "EMPTY" | "MANDATORY_CONTENT"; /** Define if it's an error or a warning */ status: "ERROR" | "WARNING"; /** Type of the check, could be a service, an attribute, etc */ @@ -2578,7 +2579,14 @@ export interface HealthCheck { | "INJECT" | "TEAMS" | "NMAP" - | "NUCLEI"; + | "NUCLEI" + | "INJECTOR_CONTRACT" + | "ASSETS" + | "ASSET_GROUPS" + | "SUBJECT" + | "BODY" + | "OPTIONAL_ARGS" + | "UNKNOWN"; } export interface HistogramWidget { @@ -2653,7 +2661,6 @@ export interface ImportPostSummary { export interface ImportTestSummary { import_message?: ImportMessage[]; - /** @deprecated */ injects?: InjectOutput[]; /** @format int32 */ total_injects?: number; @@ -2694,10 +2701,10 @@ export interface Inject { /** Injector contract of the inject */ inject_injector_contract?: InjectorContract; inject_kill_chain_phases?: KillChainPhase[]; - inject_ready?: boolean; inject_scenario?: string; /** @format date-time */ inject_sent_at?: string; + /** Status of the inject */ inject_status?: InjectStatus; inject_tags?: string[]; inject_teams?: string[]; @@ -2750,7 +2757,7 @@ export interface InjectBulkUpdateOperation { values?: string[]; } -/** Inject dependencies of the inject */ +/** Dependencies of the inject */ export interface InjectDependency { dependency_condition?: InjectDependencyCondition; /** @format date-time */ @@ -2990,9 +2997,39 @@ export interface InjectInput { } export interface InjectOutput { + /** All teams value of the inject */ + inject_all_teams?: boolean; inject_asset_groups?: string[]; inject_assets?: string[]; + inject_attack_patterns?: AttackPattern[]; + /** City of the inject */ + inject_city?: string; + /** Collect execution status of the inject */ + inject_collect_status?: "COLLECTING" | "COMPLETED"; + inject_communications?: string[]; + /** + * Communications not ack size of the inject + * @format int64 + */ + inject_communications_not_ack_number?: number; + /** + * Communications size of the inject + * @format int64 + */ + inject_communications_number?: number; inject_content?: object; + /** Country of the inject */ + inject_country?: string; + /** + * Creation date of the inject + * @format date-time + */ + inject_created_at: string; + /** + * Date of the inject + * @format date-time + */ + inject_date?: string; /** * Depend duration of the inject * @format int64 @@ -3000,28 +3037,56 @@ export interface InjectOutput { */ inject_depends_duration: number; inject_depends_on?: InjectDependency[]; + /** Description of the inject */ + inject_description?: string; + inject_documents?: string[]; /** Enabled state of the inject */ inject_enabled?: boolean; /** Simulation ID of the inject */ inject_exercise?: string; + inject_expectations?: string[]; inject_healthchecks?: HealthCheck[]; /** ID of the inject */ inject_id: string; /** Injector contract of the inject */ inject_injector_contract?: InjectorContract; + inject_kill_chain_phases?: KillChainPhase[]; /** Ready state of the inject */ inject_ready?: boolean; /** Scenario ID of the inject */ inject_scenario?: string; - /** @uniqueItems true */ + /** + * Sent date of the inject + * @format date-time + */ + inject_sent_at?: string; + /** Status of the inject */ + inject_status?: InjectStatus; inject_tags?: string[]; inject_teams?: string[]; /** Testable state of the inject */ inject_testable?: boolean; /** Title of the inject */ inject_title: string; + /** + * Trigger date of the inject + * @format date-time + */ + inject_trigger_now_date?: string; /** Type of the inject */ inject_type?: string; + /** + * Update date of the inject + * @format date-time + */ + inject_updated_at: string; + /** User of the inject */ + inject_user?: string; + /** + * Number of users tageted by the inject + * @format int64 + */ + inject_users_number?: number; } export interface InjectReceptionInput { @@ -3096,6 +3161,7 @@ export interface InjectSimple { inject_title: string; } +/** Status of the inject */ export interface InjectStatus { listened?: boolean; status_id?: string; @@ -3426,6 +3492,7 @@ export interface JsonApiDocumentResourceObject { export type JsonNode = object; +/** Kill chain phases of the inject */ export interface KillChainPhase { listened?: boolean; /** @format date-time */ diff --git a/openaev-front/src/utils/lang/en.json b/openaev-front/src/utils/lang/en.json index e4ea8f4e23..0915227a94 100644 --- a/openaev-front/src/utils/lang/en.json +++ b/openaev-front/src/utils/lang/en.json @@ -803,6 +803,13 @@ "healthcheck.description.SECURITY_SYSTEM_COLLECTOR.EMPTY": "To fetch alert raised through OpenAEV, please setup a collector", "healthcheck.description.SMTP.SERVICE_UNAVAILABLE": "missing SMTP services, please check your configuration or credentials", "healthcheck.description.TEAMS.EMPTY": "You have to set a team into the definition tab, and associate the correct team into the inject", + "healthcheck.description.INJECTOR_CONTRACT.MANDATORY_CONTENT": "Injector contract", + "healthcheck.description.ASSETS.MANDATORY_CONTENT": "Asset", + "healthcheck.description.ASSET_GROUPS.MANDATORY_CONTENT": "Asset group", + "healthcheck.description.BODY.MANDATORY_CONTENT": "Body", + "healthcheck.description.SUBJECT.MANDATORY_CONTENT": "Subject", + "healthcheck.description.OPTIONAL_ARGS.MANDATORY_CONTENT": "Optional Args", + "healthcheck.description.UNKNOWN.MANDATORY_CONTENT": "Unknown", "healthcheck.type.AGENT_OR_EXECUTOR": "Agent or executor", "healthcheck.type.IMAP": "IMAP", "healthcheck.type.INJECT": "Inject", diff --git a/openaev-model/src/main/java/io/openaev/database/model/Inject.java b/openaev-model/src/main/java/io/openaev/database/model/Inject.java index 5227a2d48c..24280c9710 100644 --- a/openaev-model/src/main/java/io/openaev/database/model/Inject.java +++ b/openaev-model/src/main/java/io/openaev/database/model/Inject.java @@ -362,17 +362,6 @@ public long getNumberOfTargetUsers() { .orElse(0L); } - @JsonProperty("inject_ready") - public boolean isReady() { - return InjectModelHelper.isReady( - getInjectorContract().orElse(null), - getContent(), - isAllTeams(), - getTeams().stream().map(Team::getId).collect(Collectors.toList()), - getAssets().stream().map(Asset::getId).collect(Collectors.toList()), - getAssetGroups().stream().map(AssetGroup::getId).collect(Collectors.toList())); - } - @JsonIgnore public Instant computeInjectDate(Instant source, int speed) { return InjectModelHelper.computeInjectDate(source, speed, getDependsDuration(), getExercise()); @@ -477,7 +466,7 @@ public List getAttackPatterns() { @JsonProperty("inject_type") @Queryable(filterable = true, path = "injectorContract.labels", clazz = Map.class) - private String getType() { + public String getType() { return getInjectorContract() .map(InjectorContract::getInjector) .map(Injector::getType) diff --git a/openaev-model/src/main/java/io/openaev/helper/InjectModelHelper.java b/openaev-model/src/main/java/io/openaev/helper/InjectModelHelper.java index 7812b1d2a8..db5a179451 100644 --- a/openaev-model/src/main/java/io/openaev/helper/InjectModelHelper.java +++ b/openaev-model/src/main/java/io/openaev/helper/InjectModelHelper.java @@ -5,11 +5,8 @@ import static java.time.Duration.between; import static java.time.Instant.now; import static java.util.Optional.ofNullable; -import static java.util.stream.StreamSupport.stream; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.openaev.database.model.*; @@ -25,156 +22,6 @@ public class InjectModelHelper { private InjectModelHelper() {} - public static boolean isReady( - InjectorContract injectorContract, - ObjectNode content, - boolean allTeams, - @NotNull final List teams, - @NotNull final List assets, - @NotNull final List assetGroups) { - if (injectorContract == null) { - return false; - } - - ObjectMapper mapper = new ObjectMapper(); - ArrayNode injectContractFields; - - try { - injectContractFields = - (ArrayNode) - mapper - .readValue(injectorContract.getContent(), ObjectNode.class) - .get(CONTRACT_CONTENT_FIELDS); - } catch (JsonProcessingException e) { - throw new RuntimeException("Error parsing injector contract content", e); - } - - ObjectNode contractContent = injectorContract.getConvertedContent(); - List contractFields = - stream(contractContent.get(CONTRACT_CONTENT_FIELDS).spliterator(), false).toList(); - - boolean isReady = true; - for (JsonNode jsonField : contractFields) { - - // If field is mandatory - if (jsonField.get(CONTRACT_ELEMENT_CONTENT_MANDATORY).asBoolean()) { - isReady = - isFieldSet( - allTeams, teams, assets, assetGroups, jsonField, content, injectContractFields); - } - - // If field is mandatory group - if (jsonField.hasNonNull(CONTRACT_ELEMENT_CONTENT_MANDATORY_GROUPS)) { - ArrayNode mandatoryGroups = - (ArrayNode) jsonField.get(CONTRACT_ELEMENT_CONTENT_MANDATORY_GROUPS); - if (!mandatoryGroups.isEmpty()) { - boolean atLeastOneSet = false; - for (JsonNode mandatoryFieldKey : mandatoryGroups) { - Optional groupField = - contractFields.stream() - .filter( - jsonNode -> - mandatoryFieldKey - .asText() - .equals(jsonNode.get(CONTRACT_ELEMENT_CONTENT_KEY).asText())) - .findFirst(); - if (groupField.isPresent() - && isFieldSet( - allTeams, - teams, - assets, - assetGroups, - groupField.get(), - content, - injectContractFields)) { - atLeastOneSet = true; - break; - } - } - if (!atLeastOneSet) { - isReady = false; - } - } - } - - // If field is mandatory conditional, if the conditional field is set, check if the current - // field is set - if (jsonField.hasNonNull(CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_FIELDS)) { - JsonNode fields = jsonField.get(CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_FIELDS); - - if (fields.isArray()) { - for (JsonNode node : fields) { - if (!node.isNull()) { - String fieldKey = node.asText(); - - Optional conditionalFieldOpt = - contractFields.stream() - .filter( - jsonNode -> - fieldKey.equals(jsonNode.get(CONTRACT_ELEMENT_CONTENT_KEY).asText())) - .findFirst(); - - // If field not exists -> skip - if (conditionalFieldOpt.isEmpty()) { - continue; - } - if (jsonField.hasNonNull(CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_VALUES)) { - JsonNode conditionalValuesNode = - jsonField.get(CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_VALUES); - - if (conditionalValuesNode.has(fieldKey)) { - List specificValuesNode = - conditionalValuesNode.get(fieldKey).isArray() - ? stream(conditionalValuesNode.get(fieldKey).spliterator(), false) - .map(JsonNode::asText) - .toList() - : List.of(conditionalValuesNode.get(fieldKey).asText()); - - List actualValues = - getFieldValue(teams, assets, assetGroups, conditionalFieldOpt.get(), content); - boolean conditionMet = - actualValues.stream().anyMatch(specificValuesNode::contains); - - if (!conditionMet) { - continue; // condition not met → skip - } - } - } - Optional fieldOpt = - contractFields.stream() - .filter( - jsonNode -> - jsonField - .get(CONTRACT_ELEMENT_CONTENT_KEY) - .asText() - .equals(jsonNode.get(CONTRACT_ELEMENT_CONTENT_KEY).asText())) - .findFirst(); - // If field not exists -> skip - if (fieldOpt.isEmpty()) { - continue; - } - if (!isFieldSet( - allTeams, - teams, - assets, - assetGroups, - fieldOpt.get(), - content, - injectContractFields)) { - isReady = false; - } - } - } - } - } - if (!isReady) { - break; - } - } - - return isReady; - } - private static boolean isTextOrTextarea(JsonNode jsonField) { String type = jsonField.get("type").asText(); return "text".equals(type) || "textarea".equals(type); @@ -270,7 +117,7 @@ public static Instant getSentAt(Optional status) { return null; } - private static boolean isFieldSet( + public static boolean isFieldSet( final boolean allTeams, @NotNull final List teams, @NotNull final List assets, @@ -292,7 +139,6 @@ private static boolean isFieldSet( isSet = false; } } - case CONTRACT_ELEMENT_CONTENT_TYPE_ASSET_GROUP -> { if (assetGroups.isEmpty()) { isSet = false; From 27d828a9c304ea60ba7e1148e1bdb1a55143e252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Fri, 17 Oct 2025 11:20:04 +0200 Subject: [PATCH 2/7] [frontend/backend] Run spotless/lit/i18n --- .../rest/inject/service/InjectServiceTest.java | 7 +++++-- .../src/admin/components/common/injects/Injects.tsx | 9 +++++---- openaev-front/src/utils/lang/en.json | 12 ++++++------ openaev-front/src/utils/lang/fr.json | 7 +++++++ openaev-front/src/utils/lang/zh.json | 7 +++++++ 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java b/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java index 4dedb5813f..c242d70af9 100644 --- a/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java @@ -51,7 +51,6 @@ import org.mockito.Mock; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.Specification; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.test.util.ReflectionTestUtils; @@ -112,7 +111,11 @@ void setUp() { ReflectionTestUtils.setField( injectService, "injectMapper", - new InjectMapper(injectStatusMapper, injectExpectationMapper, injectUtils, new HealthCheckUtils(new ExecutorUtils()))); + new InjectMapper( + injectStatusMapper, + injectExpectationMapper, + injectUtils, + new HealthCheckUtils(new ExecutorUtils()))); ReflectionTestUtils.setField( injectService, "injectorContractContentUtils", injectorContractContentUtils); } diff --git a/openaev-front/src/admin/components/common/injects/Injects.tsx b/openaev-front/src/admin/components/common/injects/Injects.tsx index 7fe5962357..a81d07f2b0 100644 --- a/openaev-front/src/admin/components/common/injects/Injects.tsx +++ b/openaev-front/src/admin/components/common/injects/Injects.tsx @@ -177,11 +177,12 @@ const Injects: FunctionComponent = ({ : t('Disabled'); let injectTooltip = ''; if (!inject.inject_ready) { - injectLabel = t('Missing content'); - injectTooltip = inject.inject_healthchecks ? t('Missing content') + ' : ' + inject.inject_healthchecks - .filter(healthcheck => "MANDATORY_CONTENT" === healthcheck.detail) + injectLabel = t('Missing content'); + injectTooltip = inject.inject_healthchecks + ? inject.inject_healthchecks.filter(healthcheck => 'MANDATORY_CONTENT' === healthcheck.detail) .map(healthcheck => t(`healthcheck.description.${healthcheck.type}.${healthcheck.detail}`)) - .join(', ') : ''; + .join(', ') + : ''; } return ( Date: Fri, 17 Oct 2025 17:26:19 +0200 Subject: [PATCH 3/7] [frontend/backend] Refactor code --- .../healthcheck/utils/HealthCheckUtils.java | 106 ++++++++++++------ .../rest/inject/ScenarioInjectApi.java | 40 ++++--- .../rest/inject/SimulationInjectApi.java | 42 ++++--- .../rest/inject/output/InjectOutput.java | 26 ++--- .../openaev/service/InjectSearchService.java | 32 +++--- .../components/common/injects/Injects.tsx | 4 +- 6 files changed, 158 insertions(+), 92 deletions(-) diff --git a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java index c71856de9a..68a7594b9f 100644 --- a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java +++ b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java @@ -6,6 +6,7 @@ import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_VALUES; import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_MANDATORY_GROUPS; import static java.time.Instant.now; +import static java.util.Optional.ofNullable; import static java.util.stream.StreamSupport.stream; import com.fasterxml.jackson.core.JsonProcessingException; @@ -19,8 +20,10 @@ import io.openaev.healthcheck.enums.ExternalServiceDependency; import io.openaev.helper.InjectModelHelper; import io.openaev.rest.inject.output.AgentsAndAssetsAgentless; +import io.openaev.rest.inject.output.InjectOutput; import jakarta.validation.constraints.NotNull; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.ArrayUtils; @@ -169,20 +172,29 @@ public List runInjectorCheck( return result; } + /** + * Verify if into the provided list of healthchecks, at least one specific exist + * + * @param type to search + * @param detail to search + * @param status to search + * @param injectsHealthChecks to filter + * @return list of found healthcheck + */ public List runInjectsChecksFor( HealthCheck.Type type, HealthCheck.Detail detail, HealthCheck.Status status, List injectsHealthChecks) { - List result = new ArrayList<>(); + List result = new ArrayList<>(); if (injectsHealthChecks.stream() .anyMatch( healthCheck -> - type.equals(healthCheck.getType()) - && detail.equals(healthCheck.getDetail()) - && status.equals(healthCheck.getStatus()))) { - result.add(new HealthCheck(type, detail, status, now())); + Objects.equals(type, healthCheck.getType()) + && Objects.equals(detail, healthCheck.getDetail()) + && Objects.equals(status, healthCheck.getStatus()))) { + result.add(new HealthCheck(type, detail, status, now())); } return result; @@ -204,18 +216,17 @@ public List runMissingContentChecks(@NotNull final Scenario scenari inject.getInjectorContract().orElse(null), inject.getContent(), inject.isAllTeams(), - inject.getTeams() != null - ? inject.getTeams().stream().map(Team::getId).toList() - : new ArrayList<>(), - inject.getAssets() != null - ? inject.getAssets().stream().map(Asset::getId).toList() - : new ArrayList<>(), - inject.getAssetGroups() != null - ? new ArrayList<>( - inject.getAssetGroups().stream() - .map(AssetGroup::getId) - .toList()) - : new ArrayList<>()) + ofNullable(inject.getTeams()) + .map(teams -> teams.stream().map(Team::getId).toList()) + .orElse(new ArrayList<>()), + ofNullable(inject.getAssets()) + .map(assets -> assets.stream().map(Asset::getId).toList()) + .orElse(new ArrayList<>()), + ofNullable(inject.getAssetGroups()) + .map( + assetGroups -> + assetGroups.stream().map(AssetGroup::getId).toList()) + .orElse(new ArrayList<>())) .isEmpty()); if (atLeastOneInjectIsNotReady) { @@ -275,22 +286,39 @@ public List runTeamsChecks(@NotNull final Scenario scenario) { return result; } + /** + * Run content checks by injects + * + * @param inject to verify + * @return found healthchecks + */ public List runContentChecks(Inject inject) { return runContentChecks( inject.getInjectorContract().orElse(null), inject.getContent(), inject.isAllTeams(), - inject.getTeams() != null - ? inject.getTeams().stream().map(Team::getId).toList() - : new ArrayList<>(), - inject.getAssets() != null - ? inject.getAssets().stream().map(Asset::getId).toList() - : new ArrayList<>(), - inject.getAssetGroups() != null - ? new ArrayList<>(inject.getAssetGroups().stream().map(AssetGroup::getId).toList()) - : new ArrayList<>()); + ofNullable(inject.getTeams()) + .map(teams -> teams.stream().map(Team::getId).toList()) + .orElse(new ArrayList<>()), + ofNullable(inject.getAssets()) + .map(assets -> assets.stream().map(Asset::getId).toList()) + .orElse(new ArrayList<>()), + ofNullable(inject.getAssetGroups()) + .map(assetGroups -> assetGroups.stream().map(AssetGroup::getId).toList()) + .orElse(new ArrayList<>())); } + /** + * Run content check by injector contract + * + * @param injectorContract to validate + * @param content to validate + * @param allTeams to control + * @param teams to control + * @param assets to control + * @param assetGroups to control + * @return found list of healthchecks + */ public List runContentChecks( InjectorContract injectorContract, ObjectNode content, @@ -461,7 +489,7 @@ public List runContentChecks( } } - return result; + return removeDuplicates(result); } /** @@ -471,17 +499,29 @@ public List runContentChecks( * @return filtered healthchecks */ public List removeDuplicates(List healthChecks) { + if (healthChecks == null || healthChecks.isEmpty()) { + return Collections.emptyList(); + } + return healthChecks.stream() .collect( Collectors.toMap( - hc -> hc.getType() + "_" + hc.getDetail(), - hc -> hc, - (a, b) -> - HealthCheck.Status.ERROR.equals(a.getStatus()) - ? a - : HealthCheck.Status.ERROR.equals(b.getStatus()) ? b : a)) + this::createHealthCheckKey, Function.identity(), this::keepErrorStatusPriority)) .values() .stream() .toList(); } + + private String createHealthCheckKey(HealthCheck healthCheck) { + return healthCheck.getType() + "_" + healthCheck.getDetail(); + } + + private HealthCheck keepErrorStatusPriority(HealthCheck first, HealthCheck second) { + if (HealthCheck.Status.ERROR.equals(first.getStatus())) { + return first; + } else if (HealthCheck.Status.ERROR.equals(second.getStatus())) { + return second; + } + return first; + } } diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/ScenarioInjectApi.java b/openaev-api/src/main/java/io/openaev/rest/inject/ScenarioInjectApi.java index 11aae3b04d..3717545782 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/ScenarioInjectApi.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/ScenarioInjectApi.java @@ -7,6 +7,7 @@ import io.openaev.aop.RBAC; import io.openaev.database.model.*; import io.openaev.database.repository.*; +import io.openaev.healthcheck.utils.HealthCheckUtils; import io.openaev.rest.exception.ElementNotFoundException; import io.openaev.rest.helper.RestBehavior; import io.openaev.rest.inject.form.InjectAssistantInput; @@ -18,6 +19,7 @@ import io.openaev.rest.inject.service.InjectService; import io.openaev.rest.inject.service.ScenarioInjectService; import io.openaev.service.*; +import io.openaev.utils.mapper.InjectMapper; import io.openaev.utils.pagination.SearchPaginationInput; import io.swagger.v3.oas.annotations.Operation; import jakarta.persistence.criteria.Join; @@ -42,8 +44,10 @@ public class ScenarioInjectApi extends RestBehavior { private final InjectService injectService; private final InjectDuplicateService injectDuplicateService; private final ScenarioInjectService scenarioInjectService; + private final InjectMapper injectMapper; + private final HealthCheckUtils healthCheckUtils; - @GetMapping(SCENARIO_URI + "/{scenarioId}/injects/simple") + @GetMapping(SCENARIO_URI + "/{scenarioId}/injects/simple") @RBAC( resourceId = "#scenarioId", actionPerformed = Action.READ, @@ -84,10 +88,11 @@ public Iterable scenarioInjectsSimple( actionPerformed = Action.WRITE, resourceType = ResourceType.SCENARIO) @Transactional(rollbackFor = Exception.class) - public Inject createInjectForScenario( + public InjectOutput createInjectForScenario( @PathVariable @NotBlank final String scenarioId, @Valid @RequestBody InjectInput input) { Scenario scenario = this.scenarioService.scenario(scenarioId); - return this.injectService.createInject(null, scenario, input); + Inject createdInject = this.injectService.createInject(null, scenario, input); + return injectMapper.toInjectOutput(createdInject, injectService.runChecks(createdInject)); } @PostMapping(SCENARIO_URI + "/{scenarioId}/injects/assistant") @@ -99,12 +104,14 @@ public Inject createInjectForScenario( @Operation( summary = "Assistant to generate injects for scenario", description = "Generates injects based on the provided attack pattern and targets.") - public List generateInjectsForScenario( + public List generateInjectsForScenario( @PathVariable @NotBlank final String scenarioId, @Valid @RequestBody InjectAssistantInput input) { Scenario scenario = this.scenarioService.scenario(scenarioId); return injectService.saveAll( - this.injectAssistantService.generateInjectsForScenario(scenario, input)); + this.injectAssistantService.generateInjectsForScenario(scenario, input)).stream() + .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) + .toList(); } @PostMapping(SCENARIO_URI + "/{scenarioId}/injects/{injectId}") @@ -112,11 +119,12 @@ public List generateInjectsForScenario( resourceId = "#scenarioId", actionPerformed = Action.WRITE, resourceType = ResourceType.SCENARIO) - public Inject duplicateInjectForScenario( + public InjectOutput duplicateInjectForScenario( @PathVariable @NotBlank final String scenarioId, @PathVariable @NotBlank final String injectId) { - return injectDuplicateService.duplicateInjectForScenarioWithDuplicateWordInTitle( + Inject duplicatedInject = injectDuplicateService.duplicateInjectForScenarioWithDuplicateWordInTitle( scenarioId, injectId); + return injectMapper.toInjectOutput(duplicatedInject, injectService.runChecks(duplicatedInject)); } @GetMapping(SCENARIO_URI + "/{scenarioId}/injects") @@ -124,9 +132,10 @@ public Inject duplicateInjectForScenario( resourceId = "#scenarioId", actionPerformed = Action.READ, resourceType = ResourceType.SCENARIO) - public Iterable scenarioInjects(@PathVariable @NotBlank final String scenarioId) { + public Iterable scenarioInjects(@PathVariable @NotBlank final String scenarioId) { return this.injectRepository.findByScenarioId(scenarioId).stream() .sorted(Inject.executionComparator) + .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) .toList(); } @@ -135,12 +144,13 @@ public Iterable scenarioInjects(@PathVariable @NotBlank final String sce resourceId = "#scenarioId", actionPerformed = Action.READ, resourceType = ResourceType.SCENARIO) - public Inject scenarioInject( + public InjectOutput scenarioInject( @PathVariable @NotBlank final String scenarioId, @PathVariable @NotBlank final String injectId) { Scenario scenario = this.scenarioService.scenario(scenarioId); assert scenarioId.equals(scenario.getId()); - return injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); + Inject inject = injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); + return injectMapper.toInjectOutput(inject, injectService.runChecks(inject)); } @Transactional(rollbackFor = Exception.class) @@ -149,7 +159,7 @@ public Inject scenarioInject( resourceId = "#scenarioId", actionPerformed = Action.WRITE, resourceType = ResourceType.SCENARIO) - public Inject updateInjectForScenario( + public InjectOutput updateInjectForScenario( @PathVariable @NotBlank final String scenarioId, @PathVariable @NotBlank final String injectId, @Valid @RequestBody @NotNull InjectInput input) { @@ -172,7 +182,8 @@ public Inject updateInjectForScenario( } }); this.scenarioService.updateScenario(scenario); - return injectRepository.save(inject); + Inject savedInject = injectRepository.save(inject); + return injectMapper.toInjectOutput(savedInject, injectService.runChecks(savedInject)); } @PutMapping(SCENARIO_URI + "/{scenarioId}/injects/{injectId}/activation") @@ -180,11 +191,12 @@ public Inject updateInjectForScenario( resourceId = "#scenarioId", actionPerformed = Action.WRITE, resourceType = ResourceType.SCENARIO) - public Inject updateInjectActivationForScenario( + public InjectOutput updateInjectActivationForScenario( @PathVariable @NotBlank final String scenarioId, @PathVariable @NotBlank final String injectId, @Valid @RequestBody InjectUpdateActivationInput input) { - return injectService.updateInjectActivation(injectId, input); + Inject updatedInject = injectService.updateInjectActivation(injectId, input); + return injectMapper.toInjectOutput(updatedInject, injectService.runChecks(updatedInject)); } @Transactional(rollbackFor = Exception.class) diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/SimulationInjectApi.java b/openaev-api/src/main/java/io/openaev/rest/inject/SimulationInjectApi.java index b35f61175a..95e2bfd528 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/SimulationInjectApi.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/SimulationInjectApi.java @@ -26,6 +26,7 @@ import io.openaev.rest.inject.service.InjectStatusService; import io.openaev.rest.inject.service.SimulationInjectService; import io.openaev.service.InjectSearchService; +import io.openaev.utils.mapper.InjectMapper; import io.openaev.utils.pagination.SearchPaginationInput; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -68,8 +69,9 @@ public class SimulationInjectApi extends RestBehavior { private final InjectDuplicateService injectDuplicateService; private final InjectStatusService injectStatusService; private final SimulationInjectService simulationInjectService; + private final InjectMapper injectMapper; - @Operation(summary = "Retrieved injects for an exercise") + @Operation(summary = "Retrieved injects for an exercise") @ApiResponses( value = { @ApiResponse( @@ -122,9 +124,10 @@ public Iterable exerciseInjectsSimple( resourceId = "#exerciseId", actionPerformed = Action.READ, resourceType = ResourceType.SIMULATION) - public Iterable exerciseInjects(@PathVariable @NotBlank final String exerciseId) { + public Iterable exerciseInjects(@PathVariable @NotBlank final String exerciseId) { return injectRepository.findByExerciseId(exerciseId).stream() .sorted(Inject.executionComparator) + .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) .toList(); } @@ -157,8 +160,9 @@ public List exerciseInjectsResults(@PathVariable final Strin resourceId = "#exerciseId", actionPerformed = Action.READ, resourceType = ResourceType.SIMULATION) - public Inject exerciseInject(@PathVariable String exerciseId, @PathVariable String injectId) { - return injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); + public InjectOutput exerciseInject(@PathVariable String exerciseId, @PathVariable String injectId) { + Inject inject = injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); + return injectMapper.toInjectOutput(inject, injectService.runChecks(inject)); } @GetMapping(EXERCISE_URI + "/{exerciseId}/injects/{injectId}/teams") @@ -194,11 +198,12 @@ public Iterable exerciseInjectCommunications( actionPerformed = Action.WRITE, resourceType = ResourceType.SIMULATION) @Transactional(rollbackFor = Exception.class) - public Inject createInjectForExercise( + public InjectOutput createInjectForExercise( @PathVariable String exerciseId, @Valid @RequestBody InjectInput input) { Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); - return this.injectService.createInject(exercise, null, input); + Inject createdInject = this.injectService.createInject(exercise, null, input); + return injectMapper.toInjectOutput(createdInject, injectService.runChecks(createdInject)); } @PostMapping(EXERCISE_URI + "/{exerciseId}/injects/{injectId}") @@ -206,11 +211,12 @@ public Inject createInjectForExercise( resourceId = "#exerciseId", actionPerformed = Action.WRITE, resourceType = ResourceType.SIMULATION) - public Inject duplicateInjectForExercise( + public InjectOutput duplicateInjectForExercise( @PathVariable @NotBlank final String exerciseId, @PathVariable @NotBlank final String injectId) { - return injectDuplicateService.duplicateInjectForExerciseWithDuplicateWordInTitle( + Inject duplicatedInject = injectDuplicateService.duplicateInjectForExerciseWithDuplicateWordInTitle( exerciseId, injectId); + return injectMapper.toInjectOutput(duplicatedInject, injectService.runChecks(duplicatedInject)); } @Transactional(rollbackFor = Exception.class) @@ -279,11 +285,12 @@ public void deleteInject(@PathVariable String exerciseId, @PathVariable String i resourceId = "#exerciseId", actionPerformed = Action.WRITE, resourceType = ResourceType.SIMULATION) - public Inject updateInjectActivationForExercise( + public InjectOutput updateInjectActivationForExercise( @PathVariable String exerciseId, @PathVariable String injectId, @Valid @RequestBody InjectUpdateActivationInput input) { - return injectService.updateInjectActivation(injectId, input); + Inject updatedInject = injectService.updateInjectActivation(injectId, input); + return injectMapper.toInjectOutput(updatedInject, injectService.runChecks(updatedInject)); } @PutMapping(EXERCISE_URI + "/{exerciseId}/injects/{injectId}/trigger") @@ -291,12 +298,13 @@ public Inject updateInjectActivationForExercise( resourceId = "#exerciseId", actionPerformed = Action.WRITE, resourceType = ResourceType.SIMULATION) - public Inject updateInjectTrigger( + public InjectOutput updateInjectTrigger( @PathVariable String exerciseId, @PathVariable String injectId) { Inject inject = injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); inject.setTriggerNowDate(now()); inject.setUpdatedAt(now()); - return injectRepository.save(inject); + Inject savedInject = injectRepository.save(inject); + return injectMapper.toInjectOutput(savedInject, injectService.runChecks(savedInject)); } @Transactional(rollbackFor = Exception.class) @@ -305,11 +313,12 @@ public Inject updateInjectTrigger( resourceId = "#exerciseId", actionPerformed = Action.WRITE, resourceType = ResourceType.SIMULATION) - public Inject setInjectStatus( + public InjectOutput setInjectStatus( @PathVariable String exerciseId, @PathVariable String injectId, @Valid @RequestBody InjectUpdateStatusInput input) { - return injectStatusService.updateInjectStatus(injectId, input); + Inject updatedInject = injectStatusService.updateInjectStatus(injectId, input); + return injectMapper.toInjectOutput(updatedInject, injectService.runChecks(updatedInject)); } @PutMapping(EXERCISE_URI + "/{exerciseId}/injects/{injectId}/teams") @@ -317,13 +326,14 @@ public Inject setInjectStatus( resourceId = "#exerciseId", actionPerformed = Action.WRITE, resourceType = ResourceType.SIMULATION) - public Inject updateInjectTeams( + public InjectOutput updateInjectTeams( @PathVariable String exerciseId, @PathVariable String injectId, @Valid @RequestBody InjectTeamsInput input) { Inject inject = injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); Iterable injectTeams = teamRepository.findAllById(input.getTeamIds()); inject.setTeams(fromIterable(injectTeams)); - return injectRepository.save(inject); + Inject savedInject = injectRepository.save(inject); + return injectMapper.toInjectOutput(savedInject, injectService.runChecks(savedInject)); } } diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java b/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java index 152baa7186..aabbe17b7e 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java @@ -90,7 +90,7 @@ public class InjectOutput { private Scenario scenario; @JsonProperty("inject_depends_on") - @ArraySchema(schema = @Schema(description = "Dependencies of the inject")) + @ArraySchema(schema = @Schema(description = "Dependency of the inject")) private List dependsOn = new ArrayList<>(); @JsonProperty("inject_depends_duration") @@ -119,41 +119,41 @@ public class InjectOutput { @JsonProperty("inject_tags") @JsonSerialize(using = MultiIdSetDeserializer.class) - @ArraySchema(schema = @Schema(type = "string", description = "Tags of the inject")) + @ArraySchema(schema = @Schema(type = "string", description = "Tag of the inject")) private Set tags; @JsonProperty("inject_teams") @JsonSerialize(using = MultiIdListDeserializer.class) - @ArraySchema(schema = @Schema(type = "string", description = "Teams of the inject")) + @ArraySchema(schema = @Schema(type = "string", description = "Team of the inject")) private List teams; @JsonProperty("inject_assets") @JsonSerialize(using = MultiIdListDeserializer.class) - @ArraySchema(schema = @Schema(type = "string", description = "Assets of the inject")) + @ArraySchema(schema = @Schema(type = "string", description = "Asset of the inject")) private List assets; @JsonProperty("inject_asset_groups") @JsonSerialize(using = MultiIdListDeserializer.class) - @ArraySchema(schema = @Schema(type = "string", description = "Asset groups of the inject")) + @ArraySchema(schema = @Schema(type = "string", description = "Asset group of the inject")) private List assetGroups; @JsonProperty("inject_documents") @JsonSerialize(using = MultiModelDeserializer.class) - @ArraySchema(schema = @Schema(type = "string", description = "Documents of the inject")) + @ArraySchema(schema = @Schema(type = "string", description = "Document of the inject")) private List documents = new ArrayList<>(); @JsonProperty("inject_communications") @JsonSerialize(using = MultiModelDeserializer.class) - @ArraySchema(schema = @Schema(type = "string", description = "Communications of the inject")) + @ArraySchema(schema = @Schema(type = "string", description = "Communication of the inject")) private List communications = new ArrayList<>(); @JsonProperty("inject_expectations") @JsonSerialize(using = MultiModelDeserializer.class) - @ArraySchema(schema = @Schema(type = "string", description = "Expectations of the inject")) + @ArraySchema(schema = @Schema(type = "string", description = "Expectation of the inject")) private List expectations = new ArrayList<>(); @JsonProperty("inject_users_number") - @Schema(description = "Number of users tageted by the inject") + @Schema(description = "Count of users targeted by the inject") public Long numberOfTargetUsers; @JsonProperty("inject_date") @@ -161,11 +161,11 @@ public class InjectOutput { private Instant date; @JsonProperty("inject_communications_number") - @Schema(description = "Communications size of the inject") + @Schema(description = "Communications count of the inject") public Long communicationsNumber; @JsonProperty("inject_communications_not_ack_number") - @Schema(description = "Communications not ack size of the inject") + @Schema(description = "Communications not ack count of the inject") private Long communicationsNotAckNumber; @JsonProperty("inject_sent_at") @@ -173,7 +173,7 @@ public class InjectOutput { public Instant sentAt; @JsonProperty("inject_kill_chain_phases") - @ArraySchema(schema = @Schema(description = "Kill chain phases of the inject")) + @ArraySchema(schema = @Schema(description = "Kill chain phase of the inject")) public List killChainPhases; @JsonProperty("inject_attack_patterns") @@ -191,7 +191,7 @@ public boolean canBeTested() { } @JsonProperty("inject_healthchecks") - @ArraySchema(schema = @Schema(description = "Healthchecks of the inject")) + @ArraySchema(schema = @Schema(description = "Healthcheck of the inject")) private List healthchecks = new ArrayList<>(); @JsonProperty("inject_ready") diff --git a/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java b/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java index 77b9ca0046..2768351995 100644 --- a/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java +++ b/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java @@ -14,14 +14,12 @@ import io.openaev.database.repository.AssetRepository; import io.openaev.database.repository.InjectExpectationRepository; import io.openaev.database.repository.TeamRepository; -import io.openaev.healthcheck.dto.HealthCheck; import io.openaev.healthcheck.utils.HealthCheckUtils; import io.openaev.rest.atomic_testing.form.InjectResultOutput; import io.openaev.rest.atomic_testing.form.InjectStatusSimple; import io.openaev.rest.atomic_testing.form.InjectorContractSimple; import io.openaev.rest.atomic_testing.form.TargetSimple; import io.openaev.rest.inject.output.InjectOutput; -import io.openaev.rest.inject.service.InjectService; import io.openaev.rest.payload.output.PayloadSimple; import io.openaev.utils.InjectExpectationResultUtils; import io.openaev.utils.TargetType; @@ -54,10 +52,6 @@ public class InjectSearchService { private final AssetRepository assetRepository; private final AssetGroupRepository assetGroupRepository; - private final InjectService injectService; - private final AssetService assetService; - private final AssetGroupService assetGroupService; - private final InjectMapper injectMapper; private final InjectExpectationMapper injectExpectationMapper; @@ -129,9 +123,6 @@ public Page injects( // -- Count Query -- Long total = countQuery(cb, this.entityManager, Inject.class, specificationCount); - // -- Feed injects with healthchecks - // TO DO - return new PageImpl<>(injects, pageable, total); } @@ -251,15 +242,28 @@ private List execInject(TypedQuery query) { .orElse(new ArrayList<>())); inject.setAssets( ofNullable(tuple.get("inject_assets", String[].class)) - .map(ids -> assetService.assets(Arrays.asList(ids))) + .map(ids -> Arrays.stream(ids) + .map( + id -> { + Asset asset = new Asset(); + asset.setId(id); + return asset; + }) + .collect(Collectors.toList())) .orElse(new ArrayList<>())); inject.setAssetGroups( ofNullable(tuple.get("inject_asset_groups", String[].class)) - .map(ids -> assetGroupService.assetGroups(Arrays.asList(ids))) + .map(ids -> Arrays.stream(ids) + .map( + id -> { + AssetGroup assetGroup = new AssetGroup(); + assetGroup.setId(id); + return assetGroup; + }) + .collect(Collectors.toList())) .orElse(new ArrayList<>())); - List healthChecks = - healthCheckUtils.removeDuplicates(injectService.runChecks(inject)); - return injectMapper.toInjectOutput(inject, healthChecks); + // Check only for content checks because this result is only used to display the inject list on scenario + return injectMapper.toInjectOutput(inject, healthCheckUtils.runContentChecks(inject)); }) .toList(); } diff --git a/openaev-front/src/admin/components/common/injects/Injects.tsx b/openaev-front/src/admin/components/common/injects/Injects.tsx index a81d07f2b0..00c4abd3a6 100644 --- a/openaev-front/src/admin/components/common/injects/Injects.tsx +++ b/openaev-front/src/admin/components/common/injects/Injects.tsx @@ -179,9 +179,9 @@ const Injects: FunctionComponent = ({ if (!inject.inject_ready) { injectLabel = t('Missing content'); injectTooltip = inject.inject_healthchecks - ? inject.inject_healthchecks.filter(healthcheck => 'MANDATORY_CONTENT' === healthcheck.detail) + ? `${t('Missing content')} : ${inject.inject_healthchecks.filter(healthcheck => 'MANDATORY_CONTENT' === healthcheck.detail) .map(healthcheck => t(`healthcheck.description.${healthcheck.type}.${healthcheck.detail}`)) - .join(', ') + .join(', ')}` : ''; } return ( From 11e7b2372bcc113cfd594681eb1a4fdab23ce758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Fri, 17 Oct 2025 17:46:16 +0200 Subject: [PATCH 4/7] [frontend/backend] Spotless/lint/api-types --- .../healthcheck/utils/HealthCheckUtils.java | 5 ++- .../rest/inject/ScenarioInjectApi.java | 26 +++++++++------- .../rest/inject/SimulationInjectApi.java | 18 ++++++----- .../openaev/service/InjectSearchService.java | 31 +++++++++++-------- .../components/common/injects/Injects.tsx | 4 +-- openaev-front/src/utils/api-types.d.ts | 12 +++---- 6 files changed, 52 insertions(+), 44 deletions(-) diff --git a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java index 68a7594b9f..ed1e35bcaf 100644 --- a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java +++ b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java @@ -20,7 +20,6 @@ import io.openaev.healthcheck.enums.ExternalServiceDependency; import io.openaev.helper.InjectModelHelper; import io.openaev.rest.inject.output.AgentsAndAssetsAgentless; -import io.openaev.rest.inject.output.InjectOutput; import jakarta.validation.constraints.NotNull; import java.util.*; import java.util.function.Function; @@ -186,7 +185,7 @@ public List runInjectsChecksFor( HealthCheck.Detail detail, HealthCheck.Status status, List injectsHealthChecks) { - List result = new ArrayList<>(); + List result = new ArrayList<>(); if (injectsHealthChecks.stream() .anyMatch( @@ -194,7 +193,7 @@ public List runInjectsChecksFor( Objects.equals(type, healthCheck.getType()) && Objects.equals(detail, healthCheck.getDetail()) && Objects.equals(status, healthCheck.getStatus()))) { - result.add(new HealthCheck(type, detail, status, now())); + result.add(new HealthCheck(type, detail, status, now())); } return result; diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/ScenarioInjectApi.java b/openaev-api/src/main/java/io/openaev/rest/inject/ScenarioInjectApi.java index 3717545782..e7f52b52e2 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/ScenarioInjectApi.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/ScenarioInjectApi.java @@ -44,10 +44,10 @@ public class ScenarioInjectApi extends RestBehavior { private final InjectService injectService; private final InjectDuplicateService injectDuplicateService; private final ScenarioInjectService scenarioInjectService; - private final InjectMapper injectMapper; - private final HealthCheckUtils healthCheckUtils; + private final InjectMapper injectMapper; + private final HealthCheckUtils healthCheckUtils; - @GetMapping(SCENARIO_URI + "/{scenarioId}/injects/simple") + @GetMapping(SCENARIO_URI + "/{scenarioId}/injects/simple") @RBAC( resourceId = "#scenarioId", actionPerformed = Action.READ, @@ -91,7 +91,7 @@ public Iterable scenarioInjectsSimple( public InjectOutput createInjectForScenario( @PathVariable @NotBlank final String scenarioId, @Valid @RequestBody InjectInput input) { Scenario scenario = this.scenarioService.scenario(scenarioId); - Inject createdInject = this.injectService.createInject(null, scenario, input); + Inject createdInject = this.injectService.createInject(null, scenario, input); return injectMapper.toInjectOutput(createdInject, injectService.runChecks(createdInject)); } @@ -108,10 +108,11 @@ public List generateInjectsForScenario( @PathVariable @NotBlank final String scenarioId, @Valid @RequestBody InjectAssistantInput input) { Scenario scenario = this.scenarioService.scenario(scenarioId); - return injectService.saveAll( - this.injectAssistantService.generateInjectsForScenario(scenario, input)).stream() - .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) - .toList(); + return injectService + .saveAll(this.injectAssistantService.generateInjectsForScenario(scenario, input)) + .stream() + .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) + .toList(); } @PostMapping(SCENARIO_URI + "/{scenarioId}/injects/{injectId}") @@ -122,9 +123,10 @@ public List generateInjectsForScenario( public InjectOutput duplicateInjectForScenario( @PathVariable @NotBlank final String scenarioId, @PathVariable @NotBlank final String injectId) { - Inject duplicatedInject = injectDuplicateService.duplicateInjectForScenarioWithDuplicateWordInTitle( - scenarioId, injectId); - return injectMapper.toInjectOutput(duplicatedInject, injectService.runChecks(duplicatedInject)); + Inject duplicatedInject = + injectDuplicateService.duplicateInjectForScenarioWithDuplicateWordInTitle( + scenarioId, injectId); + return injectMapper.toInjectOutput(duplicatedInject, injectService.runChecks(duplicatedInject)); } @GetMapping(SCENARIO_URI + "/{scenarioId}/injects") @@ -196,7 +198,7 @@ public InjectOutput updateInjectActivationForScenario( @PathVariable @NotBlank final String injectId, @Valid @RequestBody InjectUpdateActivationInput input) { Inject updatedInject = injectService.updateInjectActivation(injectId, input); - return injectMapper.toInjectOutput(updatedInject, injectService.runChecks(updatedInject)); + return injectMapper.toInjectOutput(updatedInject, injectService.runChecks(updatedInject)); } @Transactional(rollbackFor = Exception.class) diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/SimulationInjectApi.java b/openaev-api/src/main/java/io/openaev/rest/inject/SimulationInjectApi.java index 95e2bfd528..fa0421b6f8 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/SimulationInjectApi.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/SimulationInjectApi.java @@ -69,9 +69,9 @@ public class SimulationInjectApi extends RestBehavior { private final InjectDuplicateService injectDuplicateService; private final InjectStatusService injectStatusService; private final SimulationInjectService simulationInjectService; - private final InjectMapper injectMapper; + private final InjectMapper injectMapper; - @Operation(summary = "Retrieved injects for an exercise") + @Operation(summary = "Retrieved injects for an exercise") @ApiResponses( value = { @ApiResponse( @@ -127,7 +127,7 @@ public Iterable exerciseInjectsSimple( public Iterable exerciseInjects(@PathVariable @NotBlank final String exerciseId) { return injectRepository.findByExerciseId(exerciseId).stream() .sorted(Inject.executionComparator) - .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) + .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) .toList(); } @@ -160,7 +160,8 @@ public List exerciseInjectsResults(@PathVariable final Strin resourceId = "#exerciseId", actionPerformed = Action.READ, resourceType = ResourceType.SIMULATION) - public InjectOutput exerciseInject(@PathVariable String exerciseId, @PathVariable String injectId) { + public InjectOutput exerciseInject( + @PathVariable String exerciseId, @PathVariable String injectId) { Inject inject = injectRepository.findById(injectId).orElseThrow(ElementNotFoundException::new); return injectMapper.toInjectOutput(inject, injectService.runChecks(inject)); } @@ -203,7 +204,7 @@ public InjectOutput createInjectForExercise( Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); Inject createdInject = this.injectService.createInject(exercise, null, input); - return injectMapper.toInjectOutput(createdInject, injectService.runChecks(createdInject)); + return injectMapper.toInjectOutput(createdInject, injectService.runChecks(createdInject)); } @PostMapping(EXERCISE_URI + "/{exerciseId}/injects/{injectId}") @@ -214,8 +215,9 @@ public InjectOutput createInjectForExercise( public InjectOutput duplicateInjectForExercise( @PathVariable @NotBlank final String exerciseId, @PathVariable @NotBlank final String injectId) { - Inject duplicatedInject = injectDuplicateService.duplicateInjectForExerciseWithDuplicateWordInTitle( - exerciseId, injectId); + Inject duplicatedInject = + injectDuplicateService.duplicateInjectForExerciseWithDuplicateWordInTitle( + exerciseId, injectId); return injectMapper.toInjectOutput(duplicatedInject, injectService.runChecks(duplicatedInject)); } @@ -318,7 +320,7 @@ public InjectOutput setInjectStatus( @PathVariable String injectId, @Valid @RequestBody InjectUpdateStatusInput input) { Inject updatedInject = injectStatusService.updateInjectStatus(injectId, input); - return injectMapper.toInjectOutput(updatedInject, injectService.runChecks(updatedInject)); + return injectMapper.toInjectOutput(updatedInject, injectService.runChecks(updatedInject)); } @PutMapping(EXERCISE_URI + "/{exerciseId}/injects/{injectId}/teams") diff --git a/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java b/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java index 2768351995..7c4f75c965 100644 --- a/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java +++ b/openaev-api/src/main/java/io/openaev/service/InjectSearchService.java @@ -242,27 +242,32 @@ private List execInject(TypedQuery query) { .orElse(new ArrayList<>())); inject.setAssets( ofNullable(tuple.get("inject_assets", String[].class)) - .map(ids -> Arrays.stream(ids) - .map( + .map( + ids -> + Arrays.stream(ids) + .map( id -> { - Asset asset = new Asset(); - asset.setId(id); - return asset; + Asset asset = new Asset(); + asset.setId(id); + return asset; }) - .collect(Collectors.toList())) + .collect(Collectors.toList())) .orElse(new ArrayList<>())); inject.setAssetGroups( ofNullable(tuple.get("inject_asset_groups", String[].class)) - .map(ids -> Arrays.stream(ids) - .map( + .map( + ids -> + Arrays.stream(ids) + .map( id -> { - AssetGroup assetGroup = new AssetGroup(); - assetGroup.setId(id); - return assetGroup; + AssetGroup assetGroup = new AssetGroup(); + assetGroup.setId(id); + return assetGroup; }) - .collect(Collectors.toList())) + .collect(Collectors.toList())) .orElse(new ArrayList<>())); - // Check only for content checks because this result is only used to display the inject list on scenario + // Check only for content checks because this result is only used to display the + // inject list on scenario return injectMapper.toInjectOutput(inject, healthCheckUtils.runContentChecks(inject)); }) .toList(); diff --git a/openaev-front/src/admin/components/common/injects/Injects.tsx b/openaev-front/src/admin/components/common/injects/Injects.tsx index 00c4abd3a6..5e4e466a00 100644 --- a/openaev-front/src/admin/components/common/injects/Injects.tsx +++ b/openaev-front/src/admin/components/common/injects/Injects.tsx @@ -180,8 +180,8 @@ const Injects: FunctionComponent = ({ injectLabel = t('Missing content'); injectTooltip = inject.inject_healthchecks ? `${t('Missing content')} : ${inject.inject_healthchecks.filter(healthcheck => 'MANDATORY_CONTENT' === healthcheck.detail) - .map(healthcheck => t(`healthcheck.description.${healthcheck.type}.${healthcheck.detail}`)) - .join(', ')}` + .map(healthcheck => t(`healthcheck.description.${healthcheck.type}.${healthcheck.detail}`)) + .join(', ')}` : ''; } return ( diff --git a/openaev-front/src/utils/api-types.d.ts b/openaev-front/src/utils/api-types.d.ts index 72e157875d..b10372847b 100644 --- a/openaev-front/src/utils/api-types.d.ts +++ b/openaev-front/src/utils/api-types.d.ts @@ -2559,7 +2559,7 @@ export interface GroupUpdateUsersInput { group_users?: string[]; } -/** Healthchecks of the inject */ +/** Healthcheck of the inject */ export interface HealthCheck { /** * Date when the failure have been found @@ -2757,7 +2757,7 @@ export interface InjectBulkUpdateOperation { values?: string[]; } -/** Dependencies of the inject */ +/** Dependency of the inject */ export interface InjectDependency { dependency_condition?: InjectDependencyCondition; /** @format date-time */ @@ -3008,12 +3008,12 @@ export interface InjectOutput { inject_collect_status?: "COLLECTING" | "COMPLETED"; inject_communications?: string[]; /** - * Communications not ack size of the inject + * Communications not ack count of the inject * @format int64 */ inject_communications_not_ack_number?: number; /** - * Communications size of the inject + * Communications count of the inject * @format int64 */ inject_communications_number?: number; @@ -3083,7 +3083,7 @@ export interface InjectOutput { /** User of the inject */ inject_user?: string; /** - * Number of users tageted by the inject + * Count of users targeted by the inject * @format int64 */ inject_users_number?: number; @@ -3492,7 +3492,7 @@ export interface JsonApiDocumentResourceObject { export type JsonNode = object; -/** Kill chain phases of the inject */ +/** Kill chain phase of the inject */ export interface KillChainPhase { listened?: boolean; /** @format date-time */ From fd603c2bc58600eececd68775a687c20c9df8fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Mon, 20 Oct 2025 14:16:53 +0200 Subject: [PATCH 5/7] [frontend/backend] Add missing attributes and tests --- .../rest/inject/output/InjectOutput.java | 12 +++ .../io/openaev/utils/mapper/InjectMapper.java | 9 ++ .../rest/scenario/ScenarioInjectApiTest.java | 92 +++++++++++++++++-- .../scenario/ScenarioSimulationApiTest.java | 1 + .../fixtures/composers/InjectComposer.java | 9 ++ openaev-front/src/utils/api-types.d.ts | 6 ++ 6 files changed, 122 insertions(+), 7 deletions(-) diff --git a/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java b/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java index aabbe17b7e..32ebc1b5f0 100644 --- a/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java +++ b/openaev-api/src/main/java/io/openaev/rest/inject/output/InjectOutput.java @@ -152,6 +152,18 @@ public class InjectOutput { @ArraySchema(schema = @Schema(type = "string", description = "Expectation of the inject")) private List expectations = new ArrayList<>(); + @JsonProperty("listened") + @Schema(description = "Stream listener value of the inject") + private boolean isListened; + + @JsonProperty("header") + @Schema(description = "Header of the inject") + private String header; + + @JsonProperty("footer") + @Schema(description = "Footer of the inject") + private String footer; + @JsonProperty("inject_users_number") @Schema(description = "Count of users targeted by the inject") public Long numberOfTargetUsers; diff --git a/openaev-api/src/main/java/io/openaev/utils/mapper/InjectMapper.java b/openaev-api/src/main/java/io/openaev/utils/mapper/InjectMapper.java index 4f50f0eab0..44833cdbb6 100644 --- a/openaev-api/src/main/java/io/openaev/utils/mapper/InjectMapper.java +++ b/openaev-api/src/main/java/io/openaev/utils/mapper/InjectMapper.java @@ -174,6 +174,9 @@ public InjectOutput toInjectOutput( List documents, List communications, List expectations, + boolean isListened, + String header, + String footer, Long numberOfTargetUsers, Instant date, Long communicationsNumber, @@ -210,6 +213,9 @@ public InjectOutput toInjectOutput( injectOutput.setDocuments(documents); injectOutput.setCommunications(communications); injectOutput.setExpectations(expectations); + injectOutput.setListened(isListened); + injectOutput.setHeader(header); + injectOutput.setFooter(footer); injectOutput.setNumberOfTargetUsers(numberOfTargetUsers); injectOutput.setDate(date); injectOutput.setCommunicationsNumber(communicationsNumber); @@ -250,6 +256,9 @@ public InjectOutput toInjectOutput(Inject inject, List healthchecks inject.getDocuments(), inject.getCommunications(), inject.getExpectations(), + inject.isListened(), + inject.getHeader(), + inject.getFooter(), inject.getNumberOfTargetUsers(), inject.getDate().orElse(null), inject.getCommunicationsNumber(), diff --git a/openaev-api/src/test/java/io/openaev/rest/scenario/ScenarioInjectApiTest.java b/openaev-api/src/test/java/io/openaev/rest/scenario/ScenarioInjectApiTest.java index f21867b12b..638d6e343c 100644 --- a/openaev-api/src/test/java/io/openaev/rest/scenario/ScenarioInjectApiTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/scenario/ScenarioInjectApiTest.java @@ -20,17 +20,12 @@ import io.openaev.service.EndpointService; import io.openaev.service.ScenarioService; import io.openaev.utils.fixtures.*; -import io.openaev.utils.fixtures.composers.AttackPatternComposer; -import io.openaev.utils.fixtures.composers.InjectorContractComposer; -import io.openaev.utils.fixtures.composers.PayloadComposer; +import io.openaev.utils.fixtures.composers.*; import io.openaev.utils.fixtures.files.AttackPatternFixture; import io.openaev.utils.mockUser.WithMockUser; import jakarta.servlet.ServletException; import jakarta.transaction.Transactional; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; +import java.util.*; import org.json.JSONArray; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; @@ -64,6 +59,13 @@ class ScenarioInjectApiTest extends IntegrationTest { @Autowired private ScenarioService scenarioService; List injectorContractWrapperComposers = new ArrayList<>(); + @Autowired private InjectComposer injectComposer; + @Autowired private ScenarioComposer scenarioComposer; + @Autowired private TeamComposer teamComposer; + @Autowired private TagComposer tagComposer; + @Autowired private EndpointComposer endpointComposer; + @Autowired private AssetGroupComposer assetGroupComposer; + @Autowired private ExerciseComposer exerciseComposer; @BeforeAll void beforeAll() { @@ -478,5 +480,81 @@ void given_TwoInjectByTTPNumber_should_createTwoInjects() throws Exception { assertEquals(2, jsonArray.length()); assertEquals(2, injects.size()); } + + @DisplayName("Retrieve injects simple list for scenario with asset") + @Test + @Transactional + @WithMockUser(isAdmin = true) + void retrieveInjectSimpleForScenarioTestWithAsset() throws Exception { + // -- PREPARE -- + ScenarioComposer.Composer composer = + scenarioComposer + .forScenario(ScenarioFixture.createDefaultCrisisScenario()) + .withInject( + injectComposer + .forInject(InjectFixture.getDefaultInject()) + .withTeam(teamComposer.forTeam(TeamFixture.getDefaultTeam())) + .withExercise( + exerciseComposer.forExercise(ExerciseFixture.createDefaultExercise())) + .withTag(tagComposer.forTag(TagFixture.getTagWithText("test"))) + .withEndpoint(endpointComposer.forEndpoint(EndpointFixture.createEndpoint()))) + .persist(); + + // -- EXECUTE -- + String response = + mvc.perform( + get(SCENARIO_URI + "/" + composer.get().getId() + "/injects/simple") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertNotNull(response); + assertEquals(composer.get().getId(), JsonPath.read(response, "$[0].inject_scenario")); + + // -- CLEAN -- + composer.delete(); + } + + @DisplayName("Retrieve injects simple list for scenario with asset group") + @Test + @Transactional + @WithMockUser(isAdmin = true) + void retrieveInjectSimpleForScenarioTestWithAssetGroup() throws Exception { + // -- PREPARE -- + ScenarioComposer.Composer composer = + scenarioComposer + .forScenario(ScenarioFixture.createDefaultCrisisScenario()) + .withInject( + injectComposer + .forInject(InjectFixture.getDefaultInject()) + .withTeam(teamComposer.forTeam(TeamFixture.getDefaultTeam())) + .withExercise( + exerciseComposer.forExercise(ExerciseFixture.createDefaultExercise())) + .withTag(tagComposer.forTag(TagFixture.getTagWithText("test"))) + .withAssetGroup( + assetGroupComposer.forAssetGroup( + AssetGroupFixture.createDefaultAssetGroup("test")))) + .persist(); + + // -- EXECUTE -- + String response = + mvc.perform( + get(SCENARIO_URI + "/" + composer.get().getId() + "/injects/simple") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertNotNull(response); + assertEquals(composer.get().getId(), JsonPath.read(response, "$[0].inject_scenario")); + + // -- CLEAN -- + composer.delete(); + } } } diff --git a/openaev-api/src/test/java/io/openaev/rest/scenario/ScenarioSimulationApiTest.java b/openaev-api/src/test/java/io/openaev/rest/scenario/ScenarioSimulationApiTest.java index c3c141ca46..5d1398d886 100644 --- a/openaev-api/src/test/java/io/openaev/rest/scenario/ScenarioSimulationApiTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/scenario/ScenarioSimulationApiTest.java @@ -57,6 +57,7 @@ void beforeEach() { scenario = this.scenarioRepository.save(defaultScenario); // Create exercises linked to the scenario + exerciseRepository.deleteAll(); Exercise exercise1 = ExerciseFixture.createDefaultCrisisExercise(); exercise1.setScenario(scenario); exercise1FromScenario = this.exerciseRepository.save(exercise1); diff --git a/openaev-api/src/test/java/io/openaev/utils/fixtures/composers/InjectComposer.java b/openaev-api/src/test/java/io/openaev/utils/fixtures/composers/InjectComposer.java index f1a3b9f059..942bbeac84 100644 --- a/openaev-api/src/test/java/io/openaev/utils/fixtures/composers/InjectComposer.java +++ b/openaev-api/src/test/java/io/openaev/utils/fixtures/composers/InjectComposer.java @@ -25,6 +25,7 @@ public class Composer extends InnerComposerBase { Optional.empty(); private final List documentComposers = new ArrayList<>(); private final List teamComposers = new ArrayList<>(); + private final List exerciseComposers = new ArrayList<>(); private final List assetGroupComposers = new ArrayList<>(); private final List expectationComposers = new ArrayList<>(); private final List findingComposers = new ArrayList<>(); @@ -67,6 +68,12 @@ public Composer withTeam(TeamComposer.Composer teamComposer) { return this; } + public Composer withExercise(ExerciseComposer.Composer exerciseComposer) { + this.exerciseComposers.add(exerciseComposer); + this.inject.setExercise(exerciseComposer.get()); + return this; + } + public Composer withId(String id) { this.inject.setId(id); return this; @@ -142,6 +149,7 @@ public Composer persist() { endpointComposers.forEach(EndpointComposer.Composer::persist); tagComposers.forEach(TagComposer.Composer::persist); teamComposers.forEach(TeamComposer.Composer::persist); + exerciseComposers.forEach(ExerciseComposer.Composer::persist); documentComposers.forEach(DocumentComposer.Composer::persist); injectRepository.save(inject); injectStatusComposers.ifPresent(InjectStatusComposer.Composer::persist); @@ -160,6 +168,7 @@ public Composer delete() { assetGroupComposers.forEach(AssetGroupComposer.Composer::delete); injectStatusComposers.ifPresent(InjectStatusComposer.Composer::delete); teamComposers.forEach(TeamComposer.Composer::delete); + exerciseComposers.forEach(ExerciseComposer.Composer::delete); injectorContractComposer.ifPresent(InjectorContractComposer.Composer::delete); findingComposers.forEach(FindingComposer.Composer::delete); expectationComposers.forEach(InjectExpectationComposer.Composer::delete); diff --git a/openaev-front/src/utils/api-types.d.ts b/openaev-front/src/utils/api-types.d.ts index b10372847b..ee326b3dae 100644 --- a/openaev-front/src/utils/api-types.d.ts +++ b/openaev-front/src/utils/api-types.d.ts @@ -2997,6 +2997,10 @@ export interface InjectInput { } export interface InjectOutput { + /** Footer of the inject */ + footer?: string; + /** Header of the inject */ + header?: string; /** All teams value of the inject */ inject_all_teams?: boolean; inject_asset_groups?: string[]; @@ -3087,6 +3091,8 @@ export interface InjectOutput { * @format int64 */ inject_users_number?: number; + /** Stream listener value of the inject */ + listened?: boolean; } export interface InjectReceptionInput { From 3122c84b3efb12ea7abc2f1d1bac069f0166ca3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Mon, 20 Oct 2025 14:28:32 +0200 Subject: [PATCH 6/7] [backend] refactor code --- .../java/io/openaev/healthcheck/utils/HealthCheckUtils.java | 2 +- openaev-api/src/main/java/io/openaev/service/TeamService.java | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java index ed1e35bcaf..32bc89cc42 100644 --- a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java +++ b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java @@ -286,7 +286,7 @@ public List runTeamsChecks(@NotNull final Scenario scenario) { } /** - * Run content checks by injects + * Run content checks by inject * * @param inject to verify * @return found healthchecks diff --git a/openaev-api/src/main/java/io/openaev/service/TeamService.java b/openaev-api/src/main/java/io/openaev/service/TeamService.java index 160b3846d2..19258e418f 100644 --- a/openaev-api/src/main/java/io/openaev/service/TeamService.java +++ b/openaev-api/src/main/java/io/openaev/service/TeamService.java @@ -42,10 +42,6 @@ public class TeamService { private final UserService userService; - public List teams(@NotNull List teamIds) { - return teamRepository.findAllById(teamIds); - } - public List getTeams(@NotNull List teamIds) { List rawTeams = teamRepository.rawTeamByIds(teamIds).stream() From 5457f3541431bf2d07f820eff61e4f1598048821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20PEZ=C3=89?= Date: Fri, 31 Oct 2025 11:10:06 +0100 Subject: [PATCH 7/7] [backend] Fix PR feedbacks --- .../openaev/healthcheck/dto/HealthCheck.java | 11 +++--- .../healthcheck/utils/HealthCheckUtils.java | 35 ++++++------------- .../openaev/service/InjectImportService.java | 2 +- .../inject/service/InjectServiceTest.java | 8 ++--- .../openaev/service/ScenarioServiceTest.java | 32 ++++++----------- 5 files changed, 32 insertions(+), 56 deletions(-) diff --git a/openaev-api/src/main/java/io/openaev/healthcheck/dto/HealthCheck.java b/openaev-api/src/main/java/io/openaev/healthcheck/dto/HealthCheck.java index 043381beee..31ca1187dc 100644 --- a/openaev-api/src/main/java/io/openaev/healthcheck/dto/HealthCheck.java +++ b/openaev-api/src/main/java/io/openaev/healthcheck/dto/HealthCheck.java @@ -1,16 +1,15 @@ package io.openaev.healthcheck.dto; +import static java.time.Instant.now; + import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import java.time.Instant; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import lombok.extern.slf4j.Slf4j; -@Getter -@Setter +@Data @AllArgsConstructor @Slf4j public class HealthCheck { @@ -83,5 +82,5 @@ public static Type fromValue(String value) { @Schema(description = "Date when the failure have been found") @JsonProperty("creation_date") @NotNull - private Instant creationDate; + private final Instant creationDate = now(); } diff --git a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java index 32bc89cc42..9f4191ce73 100644 --- a/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java +++ b/openaev-api/src/main/java/io/openaev/healthcheck/utils/HealthCheckUtils.java @@ -5,7 +5,6 @@ import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_FIELDS; import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_MANDATORY_CONDITIONAL_VALUES; import static io.openaev.database.model.InjectorContract.CONTRACT_ELEMENT_CONTENT_MANDATORY_GROUPS; -import static java.time.Instant.now; import static java.util.Optional.ofNullable; import static java.util.stream.StreamSupport.stream; @@ -57,7 +56,7 @@ public List runMailServiceChecks( if (injector != null && ArrayUtils.contains(injector.getDependencies(), service) && !isServiceAvailable) { - result.add(new HealthCheck(type, HealthCheck.Detail.SERVICE_UNAVAILABLE, status, now())); + result.add(new HealthCheck(type, HealthCheck.Detail.SERVICE_UNAVAILABLE, status)); } return result; @@ -83,8 +82,7 @@ public List runExecutorChecks( new HealthCheck( HealthCheck.Type.AGENT_OR_EXECUTOR, HealthCheck.Detail.EMPTY, - HealthCheck.Status.ERROR, - now())); + HealthCheck.Status.ERROR)); } return result; @@ -107,8 +105,7 @@ public List runCollectorChecks(Inject inject, List colle new HealthCheck( HealthCheck.Type.SECURITY_SYSTEM_COLLECTOR, HealthCheck.Detail.EMPTY, - HealthCheck.Status.ERROR, - now())); + HealthCheck.Status.ERROR)); } return result; @@ -165,7 +162,7 @@ public List runInjectorCheck( if (!isInjectorRegistered) { result.add( new HealthCheck( - type, HealthCheck.Detail.SERVICE_UNAVAILABLE, HealthCheck.Status.ERROR, now())); + type, HealthCheck.Detail.SERVICE_UNAVAILABLE, HealthCheck.Status.ERROR)); } } return result; @@ -193,7 +190,7 @@ public List runInjectsChecksFor( Objects.equals(type, healthCheck.getType()) && Objects.equals(detail, healthCheck.getDetail()) && Objects.equals(status, healthCheck.getStatus()))) { - result.add(new HealthCheck(type, detail, status, now())); + result.add(new HealthCheck(type, detail, status)); } return result; @@ -231,10 +228,7 @@ public List runMissingContentChecks(@NotNull final Scenario scenari if (atLeastOneInjectIsNotReady) { result.add( new HealthCheck( - HealthCheck.Type.INJECT, - HealthCheck.Detail.NOT_READY, - HealthCheck.Status.WARNING, - now())); + HealthCheck.Type.INJECT, HealthCheck.Detail.NOT_READY, HealthCheck.Status.WARNING)); } return result; @@ -275,10 +269,7 @@ public List runTeamsChecks(@NotNull final Scenario scenario) { if (isMissingTeamsOrEnabledPlayers) { result.add( new HealthCheck( - HealthCheck.Type.TEAMS, - HealthCheck.Detail.EMPTY, - HealthCheck.Status.WARNING, - now())); + HealthCheck.Type.TEAMS, HealthCheck.Detail.EMPTY, HealthCheck.Status.WARNING)); } } @@ -332,8 +323,7 @@ public List runContentChecks( new HealthCheck( HealthCheck.Type.INJECTOR_CONTRACT, HealthCheck.Detail.MANDATORY_CONTENT, - HealthCheck.Status.ERROR, - now())); + HealthCheck.Status.ERROR)); return result; } @@ -364,8 +354,7 @@ public List runContentChecks( new HealthCheck( HealthCheck.Type.fromValue(jsonField.get(CONTRACT_ELEMENT_CONTENT_KEY).asText()), HealthCheck.Detail.MANDATORY_CONTENT, - HealthCheck.Status.ERROR, - now())); + HealthCheck.Status.ERROR)); } // If field is mandatory group @@ -402,8 +391,7 @@ public List runContentChecks( new HealthCheck( HealthCheck.Type.fromValue(mandatoryFieldKey.asText()), HealthCheck.Detail.MANDATORY_CONTENT, - HealthCheck.Status.ERROR, - now())); + HealthCheck.Status.ERROR)); } } } @@ -479,8 +467,7 @@ public List runContentChecks( HealthCheck.Type.fromValue( fieldOpt.get().get(CONTRACT_ELEMENT_CONTENT_KEY).asText()), HealthCheck.Detail.MANDATORY_CONTENT, - HealthCheck.Status.ERROR, - now())); + HealthCheck.Status.ERROR)); } } } diff --git a/openaev-api/src/main/java/io/openaev/service/InjectImportService.java b/openaev-api/src/main/java/io/openaev/service/InjectImportService.java index 2654643c96..669d72dcf9 100644 --- a/openaev-api/src/main/java/io/openaev/service/InjectImportService.java +++ b/openaev-api/src/main/java/io/openaev/service/InjectImportService.java @@ -183,7 +183,7 @@ public ImportTestSummary importInjectIntoFromXLS( importTestSummary.setInjectOutputs( importTestSummary.getInjects().stream() .limit(5) - .map(inject -> injectMapper.toInjectOutput(inject, injectService.runChecks(inject))) + .map(inject -> injectMapper.toInjectOutput(inject, Collections.emptyList())) .toList()); } diff --git a/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java b/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java index c242d70af9..c3e8db2252 100644 --- a/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java +++ b/openaev-api/src/test/java/io/openaev/rest/inject/service/InjectServiceTest.java @@ -646,7 +646,7 @@ public void testRunChecksForSmtpIssue() throws JsonProcessingException { healtchChecks.stream() .filter(hc -> HealthCheck.Type.SMTP.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.SMTP, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.SERVICE_UNAVAILABLE, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.ERROR, healthCheckToVerify.getStatus()); @@ -680,7 +680,7 @@ public void testRunChecksForImapIssue() throws JsonProcessingException { healtchChecks.stream() .filter(hc -> HealthCheck.Type.IMAP.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.IMAP, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.SERVICE_UNAVAILABLE, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.WARNING, healthCheckToVerify.getStatus()); @@ -710,7 +710,7 @@ public void testRunChecksForExecutorIssue() throws JsonProcessingException { healtchChecks.stream() .filter(hc -> HealthCheck.Type.AGENT_OR_EXECUTOR.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.AGENT_OR_EXECUTOR, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.EMPTY, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.ERROR, healthCheckToVerify.getStatus()); @@ -755,7 +755,7 @@ public void testRunChecksForCollectorIssue() throws JsonProcessingException { healtchChecks.stream() .filter(hc -> HealthCheck.Type.SECURITY_SYSTEM_COLLECTOR.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.SECURITY_SYSTEM_COLLECTOR, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.EMPTY, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.ERROR, healthCheckToVerify.getStatus()); diff --git a/openaev-api/src/test/java/io/openaev/service/ScenarioServiceTest.java b/openaev-api/src/test/java/io/openaev/service/ScenarioServiceTest.java index e727a7a156..70e13a99ae 100644 --- a/openaev-api/src/test/java/io/openaev/service/ScenarioServiceTest.java +++ b/openaev-api/src/test/java/io/openaev/service/ScenarioServiceTest.java @@ -5,7 +5,6 @@ import static io.openaev.utils.fixtures.InjectFixture.getInjectForEmailContract; import static io.openaev.utils.fixtures.TeamFixture.getTeam; import static io.openaev.utils.fixtures.UserFixture.getUser; -import static java.time.Instant.now; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -333,8 +332,7 @@ public void testRunChecksForSmtpIssue() { new HealthCheck( HealthCheck.Type.SMTP, HealthCheck.Detail.SERVICE_UNAVAILABLE, - HealthCheck.Status.ERROR, - now()); + HealthCheck.Status.ERROR); // MOCK when(this.injectService.runChecks(any())).thenReturn(List.of(healthCheck)); @@ -349,7 +347,7 @@ public void testRunChecksForSmtpIssue() { healthchecks.stream() .filter(hc -> HealthCheck.Type.SMTP.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.SMTP, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.SERVICE_UNAVAILABLE, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.ERROR, healthCheckToVerify.getStatus()); @@ -368,8 +366,7 @@ public void testRunChecksForImapIssue() { new HealthCheck( HealthCheck.Type.IMAP, HealthCheck.Detail.SERVICE_UNAVAILABLE, - HealthCheck.Status.WARNING, - now()); + HealthCheck.Status.WARNING); // MOCK when(this.injectService.runChecks(any())).thenReturn(List.of(healthCheck)); @@ -384,7 +381,7 @@ public void testRunChecksForImapIssue() { healthchecks.stream() .filter(hc -> HealthCheck.Type.IMAP.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.IMAP, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.SERVICE_UNAVAILABLE, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.WARNING, healthCheckToVerify.getStatus()); @@ -401,10 +398,7 @@ public void testRunChecksForExecutorIssue() { HealthCheck healthCheck = new HealthCheck( - HealthCheck.Type.AGENT_OR_EXECUTOR, - HealthCheck.Detail.EMPTY, - HealthCheck.Status.ERROR, - now()); + HealthCheck.Type.AGENT_OR_EXECUTOR, HealthCheck.Detail.EMPTY, HealthCheck.Status.ERROR); // MOCK when(this.injectService.runChecks(any())).thenReturn(List.of(healthCheck)); @@ -418,7 +412,7 @@ public void testRunChecksForExecutorIssue() { healthchecks.stream() .filter(hc -> HealthCheck.Type.AGENT_OR_EXECUTOR.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.AGENT_OR_EXECUTOR, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.EMPTY, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.ERROR, healthCheckToVerify.getStatus()); @@ -437,8 +431,7 @@ public void testRunChecksForCollectorIssue() { new HealthCheck( HealthCheck.Type.SECURITY_SYSTEM_COLLECTOR, HealthCheck.Detail.EMPTY, - HealthCheck.Status.ERROR, - now()); + HealthCheck.Status.ERROR); // MOCK when(this.injectService.runChecks(any())).thenReturn(List.of(healthCheck)); @@ -452,7 +445,7 @@ public void testRunChecksForCollectorIssue() { healthchecks.stream() .filter(hc -> HealthCheck.Type.SECURITY_SYSTEM_COLLECTOR.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.SECURITY_SYSTEM_COLLECTOR, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.EMPTY, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.ERROR, healthCheckToVerify.getStatus()); @@ -469,10 +462,7 @@ public void testRunChecksForMissingContentIssue() { HealthCheck healthCheck = new HealthCheck( - HealthCheck.Type.INJECT, - HealthCheck.Detail.NOT_READY, - HealthCheck.Status.WARNING, - now()); + HealthCheck.Type.INJECT, HealthCheck.Detail.NOT_READY, HealthCheck.Status.WARNING); // MOCK when(this.injectService.runChecks(any())).thenReturn(List.of(healthCheck)); @@ -486,7 +476,7 @@ public void testRunChecksForMissingContentIssue() { healthchecks.stream() .filter(hc -> HealthCheck.Type.INJECT.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.INJECT, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.NOT_READY, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.WARNING, healthCheckToVerify.getStatus()); @@ -523,7 +513,7 @@ public void testRunChecksForTeamsIssue() { healthchecks.stream() .filter(hc -> HealthCheck.Type.TEAMS.equals(hc.getType())) .findFirst() - .orElse(new HealthCheck(null, null, null, null)); + .orElse(new HealthCheck(null, null, null)); assertEquals(HealthCheck.Type.TEAMS, healthCheckToVerify.getType()); assertEquals(HealthCheck.Detail.EMPTY, healthCheckToVerify.getDetail()); assertEquals(HealthCheck.Status.WARNING, healthCheckToVerify.getStatus());