diff --git a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java index 7694cbe273..0c36378b0e 100644 --- a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java +++ b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java @@ -19,6 +19,7 @@ @Log public class ExpectationsExpirationManagerService { + public static final String COLLECTOR = "collector"; private final InjectExpectationService injectExpectationService; private final ExpectationsExpirationManagerConfig config; @@ -26,6 +27,7 @@ public class ExpectationsExpirationManagerService { public void computeExpectations() { List expectations = this.injectExpectationService.expectationsNotFill(); if (!expectations.isEmpty()) { + this.computeExpectationsForAgents(expectations); this.computeExpectationsForAssets(expectations); this.computeExpectationsForAssetGroups(expectations); this.computeExpectations(expectations); @@ -33,32 +35,48 @@ public void computeExpectations() { } // -- PRIVATE -- - private void computeExpectations(@NotNull final List expectations) { - List expectationAssets = expectations.stream().toList(); - expectationAssets.forEach( - (expectation) -> { + expectations.forEach( + expectation -> { if (isExpired(expectation)) { String result = computeFailedMessage(expectation.getType()); this.injectExpectationService.computeExpectation( - expectation, this.config.getId(), "collector", PRODUCT_NAME, result, false, null); + expectation, this.config.getId(), COLLECTOR, PRODUCT_NAME, result, false, null); } }); } - private void computeExpectationsForAssets(@NotNull final List expectations) { - List expectationAssets = - expectations.stream().filter(e -> e.getAsset() != null).toList(); - expectationAssets.forEach( - (expectation) -> { + private void computeExpectationsForAgents(@NotNull final List expectations) { + List expectationAgents = + expectations.stream().filter(e -> e.getAgent() != null).toList(); + expectationAgents.forEach( + expectation -> { if (isExpired(expectation)) { String result = computeFailedMessage(expectation.getType()); this.injectExpectationService.computeExpectation( - expectation, this.config.getId(), "collector", PRODUCT_NAME, result, false, null); + expectation, this.config.getId(), COLLECTOR, PRODUCT_NAME, result, false, null); } }); } + private void computeExpectationsForAssets(@NotNull final List expectations) { + List expectationAssets = + expectations.stream().filter(e -> e.getAsset() != null && e.getAgent() == null).toList(); + expectationAssets.forEach( + (expectationAsset -> { + List expectationAgents = + this.injectExpectationService.expectationsForAgents( + expectationAsset.getInject(), + expectationAsset.getAsset(), + expectationAsset.getType()); + // Every agent expectation is filled + if (expectationAgents.stream().noneMatch(e -> e.getResults().isEmpty())) { + this.injectExpectationService.computeExpectationAsset( + expectationAsset, expectationAgents, this.config.getId(), COLLECTOR, PRODUCT_NAME); + } + })); + } + private void computeExpectationsForAssetGroups( @NotNull final List expectations) { List expectationAssetGroups = @@ -70,13 +88,13 @@ private void computeExpectationsForAssetGroups( expectationAssetGroup.getInject(), expectationAssetGroup.getAssetGroup(), expectationAssetGroup.getType()); - // Every expectation assets are filled + // Every asset expectation is filled if (expectationAssets.stream().noneMatch(e -> e.getResults().isEmpty())) { this.injectExpectationService.computeExpectationGroup( expectationAssetGroup, expectationAssets, this.config.getId(), - "collector", + COLLECTOR, PRODUCT_NAME); } })); diff --git a/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASImplantExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASImplantExecutor.java index 18ea6559eb..0cf85bba7f 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASImplantExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASImplantExecutor.java @@ -21,6 +21,7 @@ import io.openbas.model.expectation.PreventionExpectation; import io.openbas.service.AssetGroupService; import io.openbas.service.InjectExpectationService; +import jakarta.persistence.EntityNotFoundException; import jakarta.validation.constraints.NotNull; import java.util.*; import java.util.stream.Stream; @@ -226,7 +227,9 @@ private void computeExpectationsForAssetGroup( public ExecutionProcess process(Execution execution, ExecutableInject injection) throws Exception { Inject inject = - this.injectRepository.findById(injection.getInjection().getInject().getId()).orElseThrow(); + this.injectRepository + .findById(injection.getInjection().getInject().getId()) + .orElseThrow(() -> new EntityNotFoundException("Inject not found")); Map assets = this.resolveAllAssets(injection); // Check assets target diff --git a/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASInjector.java b/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASInjector.java index fd94007697..392852ce53 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASInjector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASInjector.java @@ -15,8 +15,8 @@ @Log public class OpenBASInjector { - private static final String OPENBAS_INJECTOR_NAME = "OpenBAS Implant"; - private static final String OPENBAS_INJECTOR_ID = "49229430-b5b5-431f-ba5b-f36f599b0144"; + public static final String OPENBAS_INJECTOR_NAME = "OpenBAS Implant"; + public static final String OPENBAS_INJECTOR_ID = "49229430-b5b5-431f-ba5b-f36f599b0144"; private String dlUri(OpenBASConfig openBASConfig, String platform, String arch) { return openBASConfig.getBaseUrlForAgent() + "/api/implant/openbas/" + platform + "/" + arch; @@ -46,7 +46,7 @@ public OpenBASInjector( Map executorCommands = new HashMap<>(); executorCommands.put( Endpoint.PLATFORM_TYPE.Windows.name() + "." + Endpoint.PLATFORM_ARCH.x86_64, - "$x=\"#{location}\";$location=$x.Replace(\"\\obas-agent-caldera.exe\", \"\");[Environment]::CurrentDirectory = $location;$filename=\"obas-implant-#{inject}.exe\";$" + "$x=\"#{location}\";$location=$x.Replace(\"\\obas-agent-caldera.exe\", \"\");[Environment]::CurrentDirectory = $location;$filename=\"obas-implant-#{inject}-agent-#{agent}.exe\";$" + tokenVar + ";$" + serverVar @@ -59,7 +59,7 @@ public OpenBASInjector( + ";$wc=New-Object System.Net.WebClient;$data=$wc.DownloadData($url);[io.file]::WriteAllBytes($filename,$data) | Out-Null;Remove-NetFirewallRule -DisplayName \"Allow OpenBAS Inbound\";New-NetFirewallRule -DisplayName \"Allow OpenBAS Inbound\" -Direction Inbound -Program \"$location\\$filename\" -Action Allow | Out-Null;Remove-NetFirewallRule -DisplayName \"Allow OpenBAS Outbound\";New-NetFirewallRule -DisplayName \"Allow OpenBAS Outbound\" -Direction Outbound -Program \"$location\\$filename\" -Action Allow | Out-Null;Start-Process -FilePath \"$location\\$filename\" -ArgumentList \"--uri $server --token $token --unsecured-certificate $unsecured_certificate --with-proxy $with_proxy --agent-id #{agent} --inject-id #{inject}\" -WindowStyle hidden;"); executorCommands.put( Endpoint.PLATFORM_TYPE.Windows.name() + "." + Endpoint.PLATFORM_ARCH.arm64, - "$x=\"#{location}\";$location=$x.Replace(\"\\obas-agent-caldera.exe\", \"\");[Environment]::CurrentDirectory = $location;$filename=\"obas-implant-#{inject}.exe\";$" + "$x=\"#{location}\";$location=$x.Replace(\"\\obas-agent-caldera.exe\", \"\");[Environment]::CurrentDirectory = $location;$filename=\"obas-implant-#{inject}-agent-#{agent}.exe\";$" + tokenVar + ";$" + serverVar @@ -72,7 +72,7 @@ public OpenBASInjector( + ";$wc=New-Object System.Net.WebClient;$data=$wc.DownloadData($url);[io.file]::WriteAllBytes($filename,$data) | Out-Null;Remove-NetFirewallRule -DisplayName \"Allow OpenBAS Inbound\";New-NetFirewallRule -DisplayName \"Allow OpenBAS Inbound\" -Direction Inbound -Program \"$location\\$filename\" -Action Allow | Out-Null;Remove-NetFirewallRule -DisplayName \"Allow OpenBAS Outbound\";New-NetFirewallRule -DisplayName \"Allow OpenBAS Outbound\" -Direction Outbound -Program \"$location\\$filename\" -Action Allow | Out-Null;Start-Process -FilePath \"$location\\$filename\" -ArgumentList \"--uri $server --token $token --unsecured-certificate $unsecured_certificate --with-proxy $with_proxy --agent-id #{agent} --inject-id #{inject}\" -WindowStyle hidden;"); executorCommands.put( Endpoint.PLATFORM_TYPE.Linux.name() + "." + Endpoint.PLATFORM_ARCH.x86_64, - "x=\"#{location}\";location=$(echo \"$x\" | sed \"s#/openbas-caldera-agent##\");filename=obas-implant-#{inject};" + "x=\"#{location}\";location=$(echo \"$x\" | sed \"s#/openbas-caldera-agent##\");filename=obas-implant-#{inject}-agent-#{agent};" + serverVar + ";" + tokenVar @@ -85,7 +85,7 @@ public OpenBASInjector( + " > $location/$filename;chmod +x $location/$filename;$location/$filename --uri $server --token $token --unsecured-certificate $unsecured_certificate --with-proxy $with_proxy --agent-id #{agent} --inject-id #{inject} &"); executorCommands.put( Endpoint.PLATFORM_TYPE.Linux.name() + "." + Endpoint.PLATFORM_ARCH.arm64, - "x=\"#{location}\";location=$(echo \"$x\" | sed \"s#/openbas-caldera-agent##\");filename=obas-implant-#{inject};" + "x=\"#{location}\";location=$(echo \"$x\" | sed \"s#/openbas-caldera-agent##\");filename=obas-implant-#{inject}-agent-#{agent};" + serverVar + ";" + tokenVar @@ -98,7 +98,7 @@ public OpenBASInjector( + " > $location/$filename;chmod +x $location/$filename;$location/$filename --uri $server --token $token --unsecured-certificate $unsecured_certificate --with-proxy $with_proxy --agent-id #{agent} --inject-id #{inject} &"); executorCommands.put( Endpoint.PLATFORM_TYPE.MacOS.name() + "." + Endpoint.PLATFORM_ARCH.x86_64, - "x=\"#{location}\";location=$(echo \"$x\" | sed \"s#/openbas-caldera-agent##\");filename=obas-implant-#{inject};" + "x=\"#{location}\";location=$(echo \"$x\" | sed \"s#/openbas-caldera-agent##\");filename=obas-implant-#{inject}-agent-#{agent};" + serverVar + ";" + tokenVar @@ -111,7 +111,7 @@ public OpenBASInjector( + " > $location/$filename;chmod +x $location/$filename;$location/$filename --uri $server --token $token --unsecured-certificate $unsecured_certificate --with-proxy $with_proxy --agent-id #{agent} --inject-id #{inject} &"); executorCommands.put( Endpoint.PLATFORM_TYPE.MacOS.name() + "." + Endpoint.PLATFORM_ARCH.arm64, - "x=\"#{location}\";location=$(echo \"$x\" | sed \"s#/openbas-caldera-agent##\");filename=obas-implant-#{inject};" + "x=\"#{location}\";location=$(echo \"$x\" | sed \"s#/openbas-caldera-agent##\");filename=obas-implant-#{inject}-agent-#{agent};" + serverVar + ";" + tokenVar diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_62__Add_agent_to_inject_expectation.java b/openbas-api/src/main/java/io/openbas/migration/V3_62__Add_agent_to_inject_expectation.java new file mode 100644 index 0000000000..e9becca1de --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_62__Add_agent_to_inject_expectation.java @@ -0,0 +1,24 @@ +package io.openbas.migration; + +import java.sql.Connection; +import java.sql.Statement; +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +@Component +public class V3_62__Add_agent_to_inject_expectation extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Connection connection = context.getConnection(); + Statement select = connection.createStatement(); + // Add agent to inject expectation + select.execute( + """ + ALTER TABLE injects_expectations ADD COLUMN agent_id varchar(256) constraint fk_agent references agents on delete cascade; + """); + select.execute( + "CREATE INDEX IF NOT EXISTS idx_inject_expectation_agent_id ON injects_expectations(agent_id);"); + } +} diff --git a/openbas-api/src/main/java/io/openbas/rest/asset/endpoint/form/AgentOutput.java b/openbas-api/src/main/java/io/openbas/rest/asset/endpoint/form/AgentOutput.java index 15be57d247..6042482036 100644 --- a/openbas-api/src/main/java/io/openbas/rest/asset/endpoint/form/AgentOutput.java +++ b/openbas-api/src/main/java/io/openbas/rest/asset/endpoint/form/AgentOutput.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.openbas.database.model.*; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.persistence.*; import jakarta.validation.constraints.NotBlank; import java.time.Instant; import lombok.Builder; diff --git a/openbas-api/src/main/java/io/openbas/rest/atomic_testing/form/InjectTargetWithResult.java b/openbas-api/src/main/java/io/openbas/rest/atomic_testing/form/InjectTargetWithResult.java index 26813dcf63..ca2f719605 100644 --- a/openbas-api/src/main/java/io/openbas/rest/atomic_testing/form/InjectTargetWithResult.java +++ b/openbas-api/src/main/java/io/openbas/rest/atomic_testing/form/InjectTargetWithResult.java @@ -1,8 +1,8 @@ package io.openbas.rest.atomic_testing.form; -import io.openbas.atomic_testing.TargetType; import io.openbas.database.model.Endpoint.PLATFORM_TYPE; import io.openbas.utils.AtomicTestingUtils.ExpectationResultsByType; +import io.openbas.utils.TargetType; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.util.ArrayList; diff --git a/openbas-api/src/main/java/io/openbas/rest/atomic_testing/form/TargetSimple.java b/openbas-api/src/main/java/io/openbas/rest/atomic_testing/form/TargetSimple.java index 0703b7a6a2..33ca32b942 100644 --- a/openbas-api/src/main/java/io/openbas/rest/atomic_testing/form/TargetSimple.java +++ b/openbas-api/src/main/java/io/openbas/rest/atomic_testing/form/TargetSimple.java @@ -1,7 +1,7 @@ package io.openbas.rest.atomic_testing.form; import com.fasterxml.jackson.annotation.JsonProperty; -import io.openbas.atomic_testing.TargetType; +import io.openbas.utils.TargetType; import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Getter; diff --git a/openbas-api/src/main/java/io/openbas/rest/attack_pattern/form/AttackPatternCreateInput.java b/openbas-api/src/main/java/io/openbas/rest/attack_pattern/form/AttackPatternCreateInput.java index 885a2357f0..745d06717f 100644 --- a/openbas-api/src/main/java/io/openbas/rest/attack_pattern/form/AttackPatternCreateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/attack_pattern/form/AttackPatternCreateInput.java @@ -17,8 +17,6 @@ public class AttackPatternCreateInput { @JsonProperty("attack_pattern_stix_id") private String stixId = "attack-pattern--" + UUID.randomUUID(); - ; - @NotBlank(message = MANDATORY_MESSAGE) @JsonProperty("attack_pattern_name") private String name; diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/form/ExpectationUpdateInput.java b/openbas-api/src/main/java/io/openbas/rest/exercise/form/ExpectationUpdateInput.java index 1bd6817459..4287090034 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/form/ExpectationUpdateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/form/ExpectationUpdateInput.java @@ -2,11 +2,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; +import lombok.*; -@Getter -@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +@Data public class ExpectationUpdateInput { @JsonProperty("source_id") @NotNull diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/service/ExerciseService.java b/openbas-api/src/main/java/io/openbas/rest/exercise/service/ExerciseService.java index c81df88997..7f698cf66f 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/service/ExerciseService.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/service/ExerciseService.java @@ -14,7 +14,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; -import io.openbas.atomic_testing.TargetType; import io.openbas.config.OpenBASConfig; import io.openbas.database.model.*; import io.openbas.database.raw.RawExerciseSimple; @@ -37,6 +36,7 @@ import io.openbas.utils.ExerciseMapper; import io.openbas.utils.InjectMapper; import io.openbas.utils.ResultUtils; +import io.openbas.utils.TargetType; import jakarta.annotation.Resource; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; diff --git a/openbas-api/src/main/java/io/openbas/rest/expectation/ExpectationApi.java b/openbas-api/src/main/java/io/openbas/rest/expectation/ExpectationApi.java index 8ef58e2708..fabb6598af 100644 --- a/openbas-api/src/main/java/io/openbas/rest/expectation/ExpectationApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/expectation/ExpectationApi.java @@ -1,14 +1,11 @@ package io.openbas.rest.expectation; -import io.openbas.database.model.Collector; -import io.openbas.database.model.Inject; import io.openbas.database.model.InjectExpectation; -import io.openbas.database.repository.CollectorRepository; import io.openbas.rest.exercise.form.ExpectationUpdateInput; import io.openbas.rest.helper.RestBehavior; import io.openbas.rest.inject.form.InjectExpectationUpdateInput; -import io.openbas.service.ExerciseExpectationService; import io.openbas.service.InjectExpectationService; +import io.swagger.v3.oas.annotations.Operation; import jakarta.transaction.Transactional; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -16,50 +13,34 @@ import java.util.List; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RequiredArgsConstructor @RestController public class ExpectationApi extends RestBehavior { - private ExerciseExpectationService exerciseExpectationService; - private InjectExpectationService injectExpectationService; - private CollectorRepository collectorRepository; + public static final String EXPECTATIONS_URI = "/api/expectations"; + public static final String INJECTS_EXPECTATIONS_URI = "/api/injects/expectations"; - @Autowired - public void setExerciseExpectationService( - final ExerciseExpectationService exerciseExpectationService) { - this.exerciseExpectationService = exerciseExpectationService; - } - - @Autowired - public void setInjectExpectationService(final InjectExpectationService injectExpectationService) { - this.injectExpectationService = injectExpectationService; - } - - @Autowired - public void setCollectorRepository(CollectorRepository collectorRepository) { - this.collectorRepository = collectorRepository; - } + private final InjectExpectationService injectExpectationService; @Transactional(rollbackOn = Exception.class) - @PutMapping("/api/expectations/{expectationId}") + @PutMapping(EXPECTATIONS_URI + "/{expectationId}") public InjectExpectation updateInjectExpectation( @PathVariable @NotBlank final String expectationId, @Valid @RequestBody final ExpectationUpdateInput input) { - return this.exerciseExpectationService.updateInjectExpectation(expectationId, input); + return injectExpectationService.updateInjectExpectation(expectationId, input); } @Transactional(rollbackOn = Exception.class) - @PutMapping("/api/expectations/{expectationId}/{sourceId}/delete") + @PutMapping(EXPECTATIONS_URI + "/{expectationId}/{sourceId}/delete") public InjectExpectation deleteInjectExpectationResult( @PathVariable @NotBlank final String expectationId, @PathVariable @NotBlank final String sourceId) { - return this.exerciseExpectationService.deleteInjectExpectationResult(expectationId, sourceId); + return injectExpectationService.deleteInjectExpectationResult(expectationId, sourceId); } - @GetMapping("/api/injects/expectations") + @GetMapping(INJECTS_EXPECTATIONS_URI) public List getInjectExpectationsNotFilled() { return Stream.concat( injectExpectationService.manualExpectationsNotFill().stream(), @@ -69,7 +50,7 @@ public List getInjectExpectationsNotFilled() { .toList(); } - @GetMapping("/api/injects/expectations/{sourceId}") + @GetMapping(INJECTS_EXPECTATIONS_URI + "/{sourceId}") public List getInjectExpectationsNotFilledForSource( @PathVariable String sourceId) { return Stream.concat( @@ -80,7 +61,11 @@ public List getInjectExpectationsNotFilledForSource( .toList(); } - @GetMapping("/api/injects/expectations/assets/{sourceId}") + @Operation( + summary = "Get Inject Expectations for a Specific Source", + description = + "Retrieves inject expectations of agents installed on an asset for a given source ID.") + @GetMapping(INJECTS_EXPECTATIONS_URI + "/assets/{sourceId}") public List getInjectExpectationsAssetsNotFilledForSource( @PathVariable String sourceId) { return Stream.concat( @@ -89,69 +74,44 @@ public List getInjectExpectationsAssetsNotFilledForSource( .toList(); } - @GetMapping("/api/injects/expectations/prevention") + @GetMapping(INJECTS_EXPECTATIONS_URI + "/prevention") public List getInjectPreventionExpectationsNotFilled() { return injectExpectationService.preventionExpectationsNotFill().stream().toList(); } - @GetMapping("/api/injects/expectations/prevention/{sourceId}") + @Operation( + summary = "Get Inject Expectations for a Specific Source and type Prevention", + description = + "Retrieves inject expectations of agents installed on an asset for a given source ID and type Prevention.") + @GetMapping(INJECTS_EXPECTATIONS_URI + "/prevention/{sourceId}") public List getInjectPreventionExpectationsNotFilledForSource( @PathVariable String sourceId) { return injectExpectationService.preventionExpectationsNotFill(sourceId).stream().toList(); } - @GetMapping("/api/injects/expectations/detection") + @GetMapping(INJECTS_EXPECTATIONS_URI + "/detection") public List getInjectDetectionExpectationsNotFilled() { return injectExpectationService.detectionExpectationsNotFill().stream().toList(); } - @GetMapping("/api/injects/expectations/detection/{sourceId}") + @Operation( + summary = "Get Inject Expectations for a Specific Source and type Detection", + description = + "Retrieves inject expectations of agents installed on an asset for a given source ID and type detection.") + @GetMapping(INJECTS_EXPECTATIONS_URI + "/detection/{sourceId}") public List getInjectDetectionExpectationsNotFilledForSource( @PathVariable String sourceId) { return injectExpectationService.detectionExpectationsNotFill(sourceId).stream().toList(); } - @PutMapping("/api/injects/expectations/{expectationId}") + @Operation( + summary = "Update Inject Expectation", + description = "Update Inject expectation from an external source, e.g., EDR collector.") + @PutMapping(INJECTS_EXPECTATIONS_URI + "/{expectationId}") @Transactional(rollbackOn = Exception.class) public InjectExpectation updateInjectExpectation( @PathVariable @NotBlank final String expectationId, @Valid @RequestBody @NotNull InjectExpectationUpdateInput input) { - InjectExpectation injectExpectation = - this.injectExpectationService.findInjectExpectation(expectationId).orElseThrow(); - Collector collector = this.collectorRepository.findById(input.getCollectorId()).orElseThrow(); - injectExpectation = - this.injectExpectationService.computeExpectation( - injectExpectation, - collector.getId(), - "collector", - collector.getName(), - input.getResult(), - input.getIsSuccess(), - input.getMetadata()); - - // Compute potential expectations for asset groups - Inject inject = injectExpectation.getInject(); - List expectationAssetGroups = - inject.getExpectations().stream().filter(e -> e.getAssetGroup() != null).toList(); - expectationAssetGroups.forEach( - (expectationAssetGroup -> { - List expectationAssets = - this.injectExpectationService.expectationsForAssets( - expectationAssetGroup.getInject(), - expectationAssetGroup.getAssetGroup(), - expectationAssetGroup.getType()); - // Every expectation assets are filled - if (expectationAssets.stream().noneMatch(e -> e.getResults().isEmpty())) { - this.injectExpectationService.computeExpectationGroup( - expectationAssetGroup, - expectationAssets, - collector.getId(), - "collector", - collector.getName()); - } - })); - // end of computing - - return injectExpectation; + return injectExpectationService.updateInjectExpectation(expectationId, input); } } diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/form/InjectExpectationUpdateInput.java b/openbas-api/src/main/java/io/openbas/rest/inject/form/InjectExpectationUpdateInput.java index 4a02759c8a..4be6a6e16a 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/form/InjectExpectationUpdateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/form/InjectExpectationUpdateInput.java @@ -3,8 +3,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotNull; import java.util.Map; -import lombok.Data; +import lombok.*; +@AllArgsConstructor +@NoArgsConstructor +@Builder @Data public class InjectExpectationUpdateInput { @NotNull diff --git a/openbas-api/src/main/java/io/openbas/service/AgentService.java b/openbas-api/src/main/java/io/openbas/service/AgentService.java index b7b781f78f..9fe44d7692 100644 --- a/openbas-api/src/main/java/io/openbas/service/AgentService.java +++ b/openbas-api/src/main/java/io/openbas/service/AgentService.java @@ -1,6 +1,10 @@ package io.openbas.service; +import io.openbas.database.model.Agent; import io.openbas.database.repository.AgentRepository; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -9,4 +13,12 @@ public class AgentService { private final AgentRepository agentRepository; + + public Map> getAgentsGroupedByAsset(List assetIds) { + List agents = agentRepository.findAgentsByAssetIds(assetIds); + + return agents.stream() + .filter(Agent::isActive) + .collect(Collectors.groupingBy(agent -> agent.getAsset().getId())); + } } diff --git a/openbas-api/src/main/java/io/openbas/service/AssetGroupService.java b/openbas-api/src/main/java/io/openbas/service/AssetGroupService.java index 67b44a195d..7de2d00250 100644 --- a/openbas-api/src/main/java/io/openbas/service/AssetGroupService.java +++ b/openbas-api/src/main/java/io/openbas/service/AssetGroupService.java @@ -14,9 +14,7 @@ import io.openbas.database.specification.EndpointSpecification; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -125,7 +123,7 @@ public AssetGroup computeDynamicAssets(@NotNull final AssetGroup assetGroup) { Specification specification2 = EndpointSpecification.findEndpointsForInjection(); List assets = this.endpointService.endpoints(specification.and(specification2)).stream() - .map(endpoint -> (Asset) endpoint) + .map(Asset.class::cast) .distinct() .toList(); assetGroup.setDynamicAssets(assets); @@ -142,14 +140,20 @@ public Map> computeDynamicAssetFromRaw( .collect( Collectors.toMap( RawAssetGroup::getAsset_group_id, - assetGroup -> { - Specification specification = - computeFilterGroupJpa(assetGroup.getAssetGroupDynamicFilter()); - Specification specification2 = - EndpointSpecification.findEndpointsForInjection(); - return this.endpointService.endpoints(specification.and(specification2)).stream() - .distinct() - .toList(); - })); + assetGroup -> + Optional.of(assetGroup.getAssetGroupDynamicFilter()) + .filter(filterGroup -> !isEmptyFilterGroup(filterGroup)) + .map( + filter -> { + Specification specification = computeFilterGroupJpa(filter); + Specification specification2 = + EndpointSpecification.findEndpointsForInjection(); + return this.endpointService + .endpoints(specification.and(specification2)) + .stream() + .distinct() + .toList(); + }) + .orElse(Collections.emptyList()))); } } diff --git a/openbas-api/src/main/java/io/openbas/service/AssetService.java b/openbas-api/src/main/java/io/openbas/service/AssetService.java index cb76a04485..2cb8bef89b 100644 --- a/openbas-api/src/main/java/io/openbas/service/AssetService.java +++ b/openbas-api/src/main/java/io/openbas/service/AssetService.java @@ -28,10 +28,6 @@ public List assets() { return fromIterable(this.assetRepository.findAll()); } - public List assetsFromTypes(@NotNull final List types) { - return this.assetRepository.findByType(types); - } - public Iterable assetFromIds(@NotNull final List assetIds) { return this.assetRepository.findAllById(assetIds); } diff --git a/openbas-api/src/main/java/io/openbas/service/ExerciseExpectationService.java b/openbas-api/src/main/java/io/openbas/service/ExerciseExpectationService.java index baf10c781b..3befe4f602 100644 --- a/openbas-api/src/main/java/io/openbas/service/ExerciseExpectationService.java +++ b/openbas-api/src/main/java/io/openbas/service/ExerciseExpectationService.java @@ -1,18 +1,10 @@ package io.openbas.service; -import static java.time.Instant.now; - import io.openbas.database.model.Exercise; import io.openbas.database.model.InjectExpectation; -import io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE; -import io.openbas.database.model.InjectExpectationResult; import io.openbas.database.repository.ExerciseRepository; import io.openbas.database.repository.InjectExpectationRepository; -import io.openbas.rest.exception.ElementNotFoundException; -import io.openbas.rest.exercise.form.ExpectationUpdateInput; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.time.Instant; import java.util.*; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -28,225 +20,4 @@ public List injectExpectations(@NotBlank final String exercis Exercise exercise = this.exerciseRepository.findById(exerciseId).orElseThrow(); return this.injectExpectationRepository.findAllForExercise(exercise.getId()); } - - public InjectExpectation updateInjectExpectation( - @NotBlank final String expectationId, @NotNull final ExpectationUpdateInput input) { - InjectExpectation injectExpectation = - this.injectExpectationRepository.findById(expectationId).orElseThrow(); - Optional exists = - injectExpectation.getResults().stream() - .filter(r -> input.getSourceId().equals(r.getSourceId())) - .findAny(); - - String result = ""; - if (injectExpectation.getType() == EXPECTATION_TYPE.MANUAL) { - result = input.getScore() >= injectExpectation.getExpectedScore() ? "Success" : "Failed"; - injectExpectation.getResults().clear(); - exists = Optional.empty(); - } else if (injectExpectation.getType() == EXPECTATION_TYPE.DETECTION) { - if (input.getScore() >= injectExpectation.getExpectedScore()) { - result = "Detected"; - } else if (input.getScore() > 0) { - result = "Partially Detected"; - } else { - result = "Not Detected"; - } - } else if (injectExpectation.getType() == EXPECTATION_TYPE.PREVENTION) { - if (input.getScore() >= injectExpectation.getExpectedScore()) { - result = "Prevented"; - } else if (input.getScore() > 0) { - result = "Partially Prevented"; - } else { - result = "Not Prevented"; - } - } - if (exists.isPresent()) { - exists.get().setResult(result); - exists.get().setScore(input.getScore()); - exists.get().setDate(now().toString()); - } else { - injectExpectation - .getResults() - .add( - buildInjectExpectationResult( - input.getSourceId(), - input.getSourceType(), - input.getSourceName(), - result, - input.getScore())); - } - if (injectExpectation.getScore() == null) { - injectExpectation.setScore(input.getScore()); - } else { - if (input.getScore() > injectExpectation.getScore() - || injectExpectation.getType() == EXPECTATION_TYPE.MANUAL) { - injectExpectation.setScore(input.getScore()); - } else { - injectExpectation.setScore( - Collections.max( - injectExpectation.getResults().stream() - .map(InjectExpectationResult::getScore) - .filter(Objects::nonNull) - .toList())); - } - } - injectExpectation.setUpdatedAt(now()); - InjectExpectation updated = this.injectExpectationRepository.save(injectExpectation); - - // If The expectation is type manual, We should update expectations for teams and players - if (updated.getType() == EXPECTATION_TYPE.MANUAL && updated.getTeam() != null) { - computeExpectationsForTeamsAndPlayer(updated, result); - } - return updated; - } - - public InjectExpectation deleteInjectExpectationResult( - @NotBlank final String expectationId, @NotBlank final String sourceId) { - InjectExpectation injectExpectation = - this.injectExpectationRepository.findById(expectationId).orElseThrow(); - Optional exists = - injectExpectation.getResults().stream() - .filter(r -> sourceId.equals(r.getSourceId())) - .findAny(); - if (exists.isPresent()) { - injectExpectation.setResults( - injectExpectation.getResults().stream() - .filter(r -> !sourceId.equals(r.getSourceId())) - .toList()); - if (injectExpectation.getType() == EXPECTATION_TYPE.MANUAL) { - injectExpectation.setScore(null); - } else { - List scores = - injectExpectation.getResults().stream() - .map(InjectExpectationResult::getScore) - .filter(Objects::nonNull) - .toList(); - injectExpectation.setScore(!scores.isEmpty() ? Collections.max(scores) : 0.0); - } - } - injectExpectation.setUpdatedAt(now()); - InjectExpectation updated = this.injectExpectationRepository.save(injectExpectation); - - // If The expectation is type manual, We should update expectations for teams and players - if (updated.getType() == EXPECTATION_TYPE.MANUAL && updated.getTeam() != null) { - computeExpectationsForTeamsAndPlayer(updated, null); - } - - return updated; - } - - // -- VALIDATION TYPE -- - private void computeExpectationsForTeamsAndPlayer(InjectExpectation updated, String result) { - // If the updated expectation was a player expectation, We have to update the team expectation - // using player expectations (based on validation type) - if (updated.getUser() != null) { - List toProcess = - injectExpectationRepository.findAllByInjectAndTeamAndExpectationName( - updated.getInject().getId(), updated.getTeam().getId(), updated.getName()); - InjectExpectation parentExpectation = - toProcess.stream() - .filter(exp -> exp.getUser() == null) - .findFirst() - .orElseThrow(ElementNotFoundException::new); - List playersExpectations = - toProcess.stream().filter(exp -> exp.getUser() != null).toList(); - List playersAnsweredExpectations = - playersExpectations.stream().filter(exp -> exp.getScore() != null).toList(); - - if (updated.isExpectationGroup()) { // If At least one player - List successPlayerExpectations = - playersExpectations.stream() - .filter(exp -> exp.getScore() != null) - .filter(exp -> exp.getScore() >= updated.getExpectedScore()) - .toList(); - - if (!successPlayerExpectations.isEmpty()) { // At least one player success - result = "Success"; - OptionalDouble avgSuccessPlayer = - successPlayerExpectations.stream().mapToDouble(InjectExpectation::getScore).average(); - parentExpectation.setScore(avgSuccessPlayer.getAsDouble()); - } else if (playersAnsweredExpectations.size() - == playersExpectations.size()) { // All players had answers and no one success - result = "Failed"; - parentExpectation.setScore(0.0); - } else { - result = "Pending"; - parentExpectation.setScore(null); - } - - } else { // All Player - boolean hasFailedPlayer = - playersExpectations.stream() - .filter(exp -> exp.getScore() != null) - .anyMatch(exp -> exp.getScore() < updated.getExpectedScore()); - - if (hasFailedPlayer) { - result = "Failed"; - } else if (playersAnsweredExpectations.size() - == playersExpectations.size()) { // All players answered and no failures - result = "Success"; - } else { // Some players haven't answered yet - result = "Pending"; - parentExpectation.setScore(null); - } - - if (!result.equals("Pending")) { - OptionalDouble avgAllPlayer = - playersAnsweredExpectations.stream() - .mapToDouble(InjectExpectation::getScore) - .average(); - parentExpectation.setScore(avgAllPlayer.orElse(0.0)); - } - } - - parentExpectation.setUpdatedAt(Instant.now()); - parentExpectation.getResults().clear(); - parentExpectation - .getResults() - .add( - buildInjectExpectationResult( - "player-manual-validation", - "player-manual-validation", - "Player Manual Validation", - result, - parentExpectation.getScore())); - injectExpectationRepository.save(parentExpectation); - - } else { - // If I update the expectation team: What happens with children? -> update expectation score - // for all children -> set score from InjectExpectation - List toProcess = - injectExpectationRepository.findAllByInjectAndTeamAndExpectationNameAndUserIsNotNull( - updated.getInject().getId(), updated.getTeam().getId(), updated.getName()); - for (InjectExpectation expectation : toProcess) { - expectation.setScore(updated.getScore()); - expectation.setUpdatedAt(Instant.now()); - expectation.getResults().clear(); - if (result != null) { - expectation - .getResults() - .add( - buildInjectExpectationResult( - "team-manual-validation", - "team-manual-validation", - "Team Manual Validation", - result, - updated.getScore())); - } - injectExpectationRepository.save(expectation); - } - } - } - - private InjectExpectationResult buildInjectExpectationResult( - String id, String type, String name, String resultStatus, Double score) { - return InjectExpectationResult.builder() - .sourceId(id) - .sourceType(type) - .sourceName(name) - .result(resultStatus) - .date(now().toString()) - .score(score) - .build(); - } } diff --git a/openbas-api/src/main/java/io/openbas/service/InjectExpectationService.java b/openbas-api/src/main/java/io/openbas/service/InjectExpectationService.java index aafb3054b3..de4b55beb4 100644 --- a/openbas-api/src/main/java/io/openbas/service/InjectExpectationService.java +++ b/openbas-api/src/main/java/io/openbas/service/InjectExpectationService.java @@ -6,20 +6,28 @@ import static java.time.Instant.now; import com.fasterxml.jackson.databind.ObjectMapper; -import io.openbas.atomic_testing.TargetType; import io.openbas.database.model.*; +import io.openbas.database.repository.CollectorRepository; import io.openbas.database.repository.InjectExpectationRepository; import io.openbas.database.repository.InjectRepository; import io.openbas.database.specification.InjectExpectationSpecification; import io.openbas.execution.ExecutableInject; import io.openbas.model.Expectation; +import io.openbas.rest.exception.ElementNotFoundException; +import io.openbas.rest.exercise.form.ExpectationUpdateInput; +import io.openbas.rest.inject.form.InjectExpectationUpdateInput; +import io.openbas.utils.TargetType; import jakarta.annotation.Resource; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import java.lang.reflect.InvocationTargetException; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import org.apache.commons.beanutils.BeanUtils; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,9 +36,16 @@ @Service public class InjectExpectationService { + public static final String SUCCESS = "Success"; + public static final String FAILED = "Failed"; + public static final String PENDING = "Pending"; + public static final String COLLECTOR = "collector"; private final InjectExpectationRepository injectExpectationRepository; private final InjectRepository injectRepository; private final AssetGroupService assetGroupService; + private final EndpointService endpointService; + private final CollectorRepository collectorRepository; + private final AgentService agentService; @Resource protected ObjectMapper mapper; @@ -41,6 +56,415 @@ public Optional findInjectExpectation( return this.injectExpectationRepository.findById(injectExpectationId); } + // -- UPDATE FROM UI -- + + public InjectExpectation updateInjectExpectation( + @NotBlank final String expectationId, @NotNull final ExpectationUpdateInput input) { + InjectExpectation injectExpectation = + this.injectExpectationRepository.findById(expectationId).orElseThrow(); + Optional exists = + injectExpectation.getResults().stream() + .filter(r -> input.getSourceId().equals(r.getSourceId())) + .findAny(); + + String result; + if (MANUAL.equals(injectExpectation.getType())) { + result = input.getScore() >= injectExpectation.getExpectedScore() ? SUCCESS : FAILED; + injectExpectation.getResults().clear(); + exists = Optional.empty(); + } else if (DETECTION.equals(injectExpectation.getType())) { + if (input.getScore() >= injectExpectation.getExpectedScore()) { + result = "Detected"; + } else if (input.getScore() > 0) { + result = "Partially Detected"; + } else { + result = "Not Detected"; + } + } else if (PREVENTION.equals(injectExpectation.getType())) { + if (input.getScore() >= injectExpectation.getExpectedScore()) { + result = "Prevented"; + } else if (input.getScore() > 0) { + result = "Partially Prevented"; + } else { + result = "Not Prevented"; + } + } else { + result = ""; + } + if (exists.isPresent()) { + exists.get().setResult(result); + exists.get().setScore(input.getScore()); + exists.get().setDate(now().toString()); + } else { + injectExpectation + .getResults() + .add( + buildInjectExpectationResult( + input.getSourceId(), + input.getSourceType(), + input.getSourceName(), + result, + input.getScore())); + } + if (injectExpectation.getScore() == null) { + injectExpectation.setScore(input.getScore()); + } else { + if (input.getScore() > injectExpectation.getScore() + || MANUAL.equals(injectExpectation.getType())) { + injectExpectation.setScore(input.getScore()); + } else { + injectExpectation.setScore( + Collections.max( + injectExpectation.getResults().stream() + .map(InjectExpectationResult::getScore) + .filter(Objects::nonNull) + .toList())); + } + } + injectExpectation.setUpdatedAt(now()); + InjectExpectation updated = this.injectExpectationRepository.save(injectExpectation); + + boolean isAssetGroupExpectation = updated.getAssetGroup() != null && updated.getAsset() == null; + boolean isAssetExpectation = updated.getAsset() != null && updated.getAgent() == null; + + if (isAssetGroupExpectation) { + // Update InjectExpectations for Assets linked to this asset group + updateInjectExpectationAsset(input, updated, result); + } else if (isAssetExpectation) { + // Update InjectExpectations for Agents linked to this asset + updateInjectExpectationAgent(input, updated, result); + } + + // If the expectation is type manual, We should update expectations for teams and players + if (MANUAL.equals(updated.getType()) && updated.getTeam() != null) { + computeExpectationsForTeamsAndPlayer(updated, result); + } + return updated; + } + + private void updateInjectExpectation( + ExpectationUpdateInput input, InjectExpectation updated, String result, boolean isAsset) { + List expectations = + isAsset + ? this.expectationsForAssets( + updated.getInject(), updated.getAssetGroup(), updated.getType()) + : this.expectationsForAgents( + updated.getInject(), updated.getAsset(), updated.getType()); + + expectations.forEach( + expectation -> { + expectation + .getResults() + .add( + buildInjectExpectationResult( + input.getSourceId(), + input.getSourceType(), + input.getSourceName(), + result, + input.getScore())); + expectation.setScore(updated.getScore()); + expectation.setUpdatedAt(updated.getUpdatedAt()); + if (isAsset) { + updateInjectExpectationAgent(input, expectation, result); + } + }); + + injectExpectationRepository.saveAll(expectations); + } + + private void updateInjectExpectationAsset( + ExpectationUpdateInput input, InjectExpectation updated, String result) { + updateInjectExpectation(input, updated, result, true); + } + + private void updateInjectExpectationAgent( + ExpectationUpdateInput input, InjectExpectation updated, String result) { + updateInjectExpectation(input, updated, result, false); + } + + // -- DELETE RESULT FROM UI -- + + public InjectExpectation deleteInjectExpectationResult( + @NotBlank final String expectationId, @NotBlank final String sourceId) { + InjectExpectation injectExpectation = + this.injectExpectationRepository.findById(expectationId).orElseThrow(); + Optional exists = + injectExpectation.getResults().stream() + .filter(r -> sourceId.equals(r.getSourceId())) + .findAny(); + if (exists.isPresent()) { + injectExpectation.setResults( + injectExpectation.getResults().stream() + .filter(r -> !sourceId.equals(r.getSourceId())) + .toList()); + if (MANUAL.equals(injectExpectation.getType())) { + injectExpectation.setScore(null); + } else { + List scores = + injectExpectation.getResults().stream() + .map(InjectExpectationResult::getScore) + .filter(Objects::nonNull) + .toList(); + injectExpectation.setScore(!scores.isEmpty() ? Collections.max(scores) : 0.0); + } + } + injectExpectation.setUpdatedAt(now()); + InjectExpectation updated = this.injectExpectationRepository.save(injectExpectation); + + boolean isAssetGroupExpectation = updated.getAssetGroup() != null && updated.getAsset() == null; + boolean isAssetExpectation = updated.getAsset() != null && updated.getAgent() == null; + + if (isAssetGroupExpectation) { + // Delete result InjectExpectations for Assets linked to this asset group + deleteInjectExpectationResultAsset(sourceId, updated); + } else if (isAssetExpectation) { + // Delete InjectExpectations results for Agents installed on this asset + deleteInjectExpectationResultAgent(sourceId, updated); + } + + // If The expectation is type manual, We should update expectations for teams and players + if (MANUAL.equals(updated.getType()) && updated.getTeam() != null) { + computeExpectationsForTeamsAndPlayer(updated, null); + } + + return updated; + } + + private void deleteInjectExpectationResult( + String sourceId, InjectExpectation updated, boolean isAsset) { + List expectations = + isAsset + ? this.expectationsForAssets( + updated.getInject(), updated.getAssetGroup(), updated.getType()) + : this.expectationsForAgents( + updated.getInject(), updated.getAsset(), updated.getType()); + + expectations.forEach( + expectation -> { + expectation.setResults( + expectation.getResults().stream() + .filter(r -> !sourceId.equals(r.getSourceId())) + .toList()); + expectation.setScore(updated.getScore()); + expectation.setUpdatedAt(updated.getUpdatedAt()); + if (isAsset) { + deleteInjectExpectationResultAgent(sourceId, expectation); + } + }); + + injectExpectationRepository.saveAll(expectations); + } + + private void deleteInjectExpectationResultAsset(String sourceId, InjectExpectation updated) { + deleteInjectExpectationResult(sourceId, updated, true); + } + + private void deleteInjectExpectationResultAgent(String sourceId, InjectExpectation updated) { + deleteInjectExpectationResult(sourceId, updated, false); + } + + // -- COMMUN -- + + private void computeExpectationsForTeamsAndPlayer(InjectExpectation updated, String result) { + // If the updated expectation was a player expectation, We have to update the team expectation + // using player expectations (based on validation type) + if (updated.getUser() != null) { + List toProcess = + injectExpectationRepository.findAllByInjectAndTeamAndExpectationName( + updated.getInject().getId(), updated.getTeam().getId(), updated.getName()); + InjectExpectation parentExpectation = + toProcess.stream() + .filter(exp -> exp.getUser() == null) + .findFirst() + .orElseThrow(ElementNotFoundException::new); + List playersExpectations = + toProcess.stream().filter(exp -> exp.getUser() != null).toList(); + List playersAnsweredExpectations = + playersExpectations.stream().filter(exp -> exp.getScore() != null).toList(); + + if (updated.isExpectationGroup()) { // If At least one player + List successPlayerExpectations = + playersExpectations.stream() + .filter(exp -> exp.getScore() != null) + .filter(exp -> exp.getScore() >= updated.getExpectedScore()) + .toList(); + + if (!successPlayerExpectations.isEmpty()) { // At least one player success + result = SUCCESS; + OptionalDouble avgSuccessPlayer = + successPlayerExpectations.stream().mapToDouble(InjectExpectation::getScore).average(); + parentExpectation.setScore(avgSuccessPlayer.getAsDouble()); + } else if (playersAnsweredExpectations.size() + == playersExpectations.size()) { // All players had answers and no one success + result = FAILED; + parentExpectation.setScore(0.0); + } else { + result = PENDING; + parentExpectation.setScore(null); + } + + } else { // All Player + boolean hasFailedPlayer = + playersExpectations.stream() + .filter(exp -> exp.getScore() != null) + .anyMatch(exp -> exp.getScore() < updated.getExpectedScore()); + + if (hasFailedPlayer) { + result = FAILED; + } else if (playersAnsweredExpectations.size() + == playersExpectations.size()) { // All players answered and no failures + result = SUCCESS; + } else { // Some players haven't answered yet + result = PENDING; + parentExpectation.setScore(null); + } + + if (!result.equals(PENDING)) { + OptionalDouble avgAllPlayer = + playersAnsweredExpectations.stream() + .mapToDouble(InjectExpectation::getScore) + .average(); + parentExpectation.setScore(avgAllPlayer.orElse(0.0)); + } + } + + parentExpectation.setUpdatedAt(Instant.now()); + parentExpectation.getResults().clear(); + parentExpectation + .getResults() + .add( + buildInjectExpectationResult( + "player-manual-validation", + "player-manual-validation", + "Player Manual Validation", + result, + parentExpectation.getScore())); + injectExpectationRepository.save(parentExpectation); + + } else { + // If I update the expectation team: What happens with children? -> update expectation score + // for all children -> set score from InjectExpectation + List toProcess = + injectExpectationRepository.findAllByInjectAndTeamAndExpectationNameAndUserIsNotNull( + updated.getInject().getId(), updated.getTeam().getId(), updated.getName()); + for (InjectExpectation expectation : toProcess) { + expectation.setScore(updated.getScore()); + expectation.setUpdatedAt(Instant.now()); + expectation.getResults().clear(); + if (result != null) { + expectation + .getResults() + .add( + buildInjectExpectationResult( + "team-manual-validation", + "team-manual-validation", + "Team Manual Validation", + result, + updated.getScore())); + } + injectExpectationRepository.save(expectation); + } + } + } + + private InjectExpectationResult buildInjectExpectationResult( + String id, String type, String name, String resultStatus, Double score) { + return InjectExpectationResult.builder() + .sourceId(id) + .sourceType(type) + .sourceName(name) + .result(resultStatus) + .date(now().toString()) + .score(score) + .build(); + } + + // -- UPDATE FROM EXTERNAL SOURCE : COLLECTORS -- + + public InjectExpectation updateInjectExpectation( + @NotBlank String expectationId, @Valid @NotNull InjectExpectationUpdateInput input) { + InjectExpectation injectExpectation = + this.findInjectExpectation(expectationId).orElseThrow(ElementNotFoundException::new); + Collector collector = + this.collectorRepository + .findById(input.getCollectorId()) + .orElseThrow(ElementNotFoundException::new); + injectExpectation = + this.computeExpectation( + injectExpectation, + collector.getId(), + COLLECTOR, + collector.getName(), + input.getResult(), + input.getIsSuccess(), + input.getMetadata()); + + Inject inject = injectExpectation.getInject(); + // Compute potential expectations for asset + propagateUpdateToAssets(injectExpectation, inject, collector); + // Compute potential expectations for asset groups + propagateUpdateToAssetGroups(inject, collector); + + // end of computing + + return injectExpectation; + } + + private void propagateUpdateToAssets( + InjectExpectation injectExpectation, Inject inject, Collector collector) { + InjectExpectation finalInjectExpectation = injectExpectation; + List expectationAssets = + inject.getExpectations().stream() + .filter( + e -> + e.getAsset() != null + && e.getAgent() == null + && e.getAsset().getId().equals(finalInjectExpectation.getAsset().getId())) + .toList(); + + expectationAssets.forEach( + (expectationAsset -> { + List expectationAgents = + this.expectationsForAgents( + expectationAsset.getInject(), + expectationAsset.getAsset(), + expectationAsset.getType()); + // Every agent expectation is filled + if (expectationAgents.stream().noneMatch(e -> e.getResults().isEmpty())) { + this.computeExpectationAsset( + expectationAsset, + expectationAgents, + collector.getId(), + COLLECTOR, + collector.getName()); + } + })); + } + + private void propagateUpdateToAssetGroups(Inject inject, Collector collector) { + List expectationAssetGroups = + inject.getExpectations().stream().filter(e -> e.getAssetGroup() != null).toList(); + expectationAssetGroups.forEach( + (expectationAssetGroup -> { + List expectationAssetsByAssetGroup = + this.expectationsForAssets( + expectationAssetGroup.getInject(), + expectationAssetGroup.getAssetGroup(), + expectationAssetGroup.getType()); + // Every asset expectation is filled + if (expectationAssetsByAssetGroup.stream().noneMatch(e -> e.getResults().isEmpty())) { + this.computeExpectationGroup( + expectationAssetGroup, + expectationAssetsByAssetGroup, + collector.getId(), + COLLECTOR, + collector.getName()); + } + })); + } + + // -- COMPUTE RESULTS FROM INJECT EXPECTATIONS -- + public InjectExpectation computeExpectation( @NotNull final InjectExpectation expectation, @NotBlank final String sourceId, @@ -58,6 +482,27 @@ public InjectExpectation computeExpectation( return this.update(expectation); } + public void computeExpectationAsset( + InjectExpectation expectationAsset, + List expectationAgents, + String sourceId, + String sourceType, + String sourceName) { + boolean success = + !expectationAgents.isEmpty() + && expectationAgents.stream().allMatch(e -> e.getExpectedScore().equals(e.getScore())); + computeResult( + expectationAsset, + sourceId, + sourceType, + sourceName, + success ? "SUCCESS" : "FAILED", + success ? expectationAsset.getExpectedScore() : 0.0, + null); + expectationAsset.setScore(success ? expectationAsset.getExpectedScore() : 0.0); + this.update(expectationAsset); + } + public void computeExpectationGroup( @NotNull final InjectExpectation expectationAssetGroup, @NotNull final List expectationAssets, @@ -66,11 +511,9 @@ public void computeExpectationGroup( @NotBlank final String sourceName) { boolean success; if (expectationAssetGroup.isExpectationGroup()) { - success = - expectationAssets.stream().anyMatch((e) -> e.getExpectedScore().equals(e.getScore())); + success = expectationAssets.stream().anyMatch(e -> e.getExpectedScore().equals(e.getScore())); } else { - success = - expectationAssets.stream().allMatch((e) -> e.getExpectedScore().equals(e.getScore())); + success = expectationAssets.stream().allMatch(e -> e.getExpectedScore().equals(e.getScore())); } computeResult( expectationAssetGroup, @@ -78,12 +521,14 @@ public void computeExpectationGroup( sourceType, sourceName, success ? "SUCCESS" : "FAILED", - success ? expectationAssetGroup.getExpectedScore() : 0, + success ? expectationAssetGroup.getExpectedScore() : 0.0, null); expectationAssetGroup.setScore(success ? expectationAssetGroup.getExpectedScore() : 0.0); this.update(expectationAssetGroup); } + // -- FINAL UPDATE -- + public InjectExpectation update(@NotNull InjectExpectation injectExpectation) { injectExpectation.setUpdatedAt(now()); Inject inject = injectExpectation.getInject(); @@ -92,7 +537,7 @@ public InjectExpectation update(@NotNull InjectExpectation injectExpectation) { return this.injectExpectationRepository.save(injectExpectation); } - // -- ALL -- + // -- FETCH INJECT EXPECTATIONS -- public List expectationsNotFill() { return fromIterable(this.injectExpectationRepository.findAll()).stream() @@ -100,13 +545,42 @@ public List expectationsNotFill() { .toList(); } + public List expectationsForAgents( + @NotNull final Inject inject, + @NotNull final Asset asset, + @NotNull final InjectExpectation.EXPECTATION_TYPE expectationType) { + Endpoint resolvedEndpoint = endpointService.endpoint(asset.getId()); + List agentIds = + resolvedEndpoint.getAgents().stream().map(Agent::getId).distinct().toList(); + return this.injectExpectationRepository.findAll( + Specification.where(InjectExpectationSpecification.type(expectationType)) + .and(InjectExpectationSpecification.fromAgents(inject.getId(), agentIds))); + } + + public List expectationsForAssets( + @NotNull final Inject inject, + @NotNull final AssetGroup assetGroup, + @NotNull final InjectExpectation.EXPECTATION_TYPE expectationType) { + AssetGroup resolvedAssetGroup = assetGroupService.assetGroup(assetGroup.getId()); + List assetIds = + Stream.concat( + resolvedAssetGroup.getAssets().stream(), + resolvedAssetGroup.getDynamicAssets().stream()) + .map(Asset::getId) + .distinct() + .toList(); + return this.injectExpectationRepository.findAll( + Specification.where(InjectExpectationSpecification.type(expectationType)) + .and(InjectExpectationSpecification.fromAssets(inject.getId(), assetIds))); + } + // -- PREVENTION -- public List preventionExpectationsNotFill(@NotBlank final String source) { return this.injectExpectationRepository .findAll(Specification.where(InjectExpectationSpecification.type(PREVENTION))) .stream() - .filter(e -> e.getAsset() != null) + .filter(e -> e.getAsset() != null && e.getAgent() != null) .filter(e -> e.getResults().stream().noneMatch(r -> source.equals(r.getSourceId()))) .toList(); } @@ -115,35 +589,18 @@ public List preventionExpectationsNotFill() { return this.injectExpectationRepository .findAll(Specification.where(InjectExpectationSpecification.type(PREVENTION))) .stream() - .filter(e -> e.getAsset() != null) + .filter(e -> e.getAsset() != null && e.getAgent() != null) .filter(e -> e.getResults().stream().toList().isEmpty()) .toList(); } // -- DETECTION -- - public List expectationsForAssets( - @NotNull final Inject inject, - @NotNull final AssetGroup assetGroup, - @NotNull final InjectExpectation.EXPECTATION_TYPE expectationType) { - AssetGroup resolvedAssetGroup = assetGroupService.assetGroup(assetGroup.getId()); - List assetIds = - Stream.concat( - resolvedAssetGroup.getAssets().stream(), - resolvedAssetGroup.getDynamicAssets().stream()) - .map(Asset::getId) - .distinct() - .toList(); - return this.injectExpectationRepository.findAll( - Specification.where(InjectExpectationSpecification.type(expectationType)) - .and(InjectExpectationSpecification.fromAssets(inject.getId(), assetIds))); - } - public List detectionExpectationsNotFill(@NotBlank final String source) { return this.injectExpectationRepository .findAll(Specification.where(InjectExpectationSpecification.type(DETECTION))) .stream() - .filter(e -> e.getAsset() != null) + .filter(e -> e.getAsset() != null && e.getAgent() != null) .filter(e -> e.getResults().stream().noneMatch(r -> source.equals(r.getSourceId()))) .toList(); } @@ -152,7 +609,7 @@ public List detectionExpectationsNotFill() { return this.injectExpectationRepository .findAll(Specification.where(InjectExpectationSpecification.type(DETECTION))) .stream() - .filter(e -> e.getAsset() != null) + .filter(e -> e.getAsset() != null && e.getAgent() != null) .filter(e -> e.getResults().stream().toList().isEmpty()) .toList(); } @@ -189,6 +646,7 @@ public List findExpectationsByInjectAndTargetAndTargetType( case PLAYER -> injectExpectationRepository.findAllByInjectAndTeamAndPlayer( injectId, parentTargetId, targetId); + case AGENT -> injectExpectationRepository.findAllByInjectAndAgent(injectId, targetId); case ASSETS -> injectExpectationRepository.findAllByInjectAndAsset(injectId, targetId); case ASSETS_GROUPS -> injectExpectationRepository.findAllByInjectAndAssetGroup(injectId, targetId); @@ -198,7 +656,7 @@ public List findExpectationsByInjectAndTargetAndTargetType( } } - // -- BUILD AND SAVE EXPECTATION AFTER SUCCESSFUL INJECT EXECUTION -- + // -- BUILD AND SAVE INJECT EXPECTATION -- @Transactional public void buildAndSaveInjectExpectations( @@ -209,6 +667,7 @@ public void buildAndSaveInjectExpectations( List teams = executableInject.getTeams(); List assets = executableInject.getAssets(); List assetGroups = executableInject.getAssetGroups(); + if ((isScheduledInject || isAtomicTesting) && !expectations.isEmpty()) { if (!teams.isEmpty()) { List injectExpectationsByTeam; @@ -285,16 +744,51 @@ public void buildAndSaveInjectExpectations( expectationConverter(team, executableInject, expectation))) .collect(Collectors.toList()); } - injectExpectationsByTeam.addAll(injectExpectationsByUserAndTeam); injectExpectationRepository.saveAll(injectExpectationsByTeam); } else if (!assets.isEmpty() || !assetGroups.isEmpty()) { List injectExpectations = expectations.stream() .map(expectation -> expectationConverter(executableInject, expectation)) + .collect(Collectors.toList()); + + List injectExpectationsByAsset = + injectExpectations.stream() + .filter(injectExpectation -> injectExpectation.getAsset() != null) .toList(); + + List assetIds = + injectExpectationsByAsset.stream().map(i -> i.getAsset().getId()).toList(); + + Map> mapAssetAgents = agentService.getAgentsGroupedByAsset(assetIds); + List injectExpectationsAgent = new ArrayList<>(); + + for (InjectExpectation injectExpectation : injectExpectationsByAsset) { + Asset asset = injectExpectation.getAsset(); + List agents = mapAssetAgents.getOrDefault(asset.getId(), Collections.emptyList()); + + for (Agent agent : agents) { + injectExpectationsAgent.add(cloneInjectExpectationForAgent(agent, injectExpectation)); + } + injectExpectation.setSignatures(Collections.emptyList()); + } + injectExpectations.addAll(injectExpectationsAgent); injectExpectationRepository.saveAll(injectExpectations); } } } + + private InjectExpectation cloneInjectExpectationForAgent( + Agent agent, InjectExpectation injectExpectation) { + InjectExpectation clone = new InjectExpectation(); + try { + BeanUtils.copyProperties(clone, injectExpectation); + clone.setType(injectExpectation.getType()); + clone.setAgent(agent); + clone.setSignatures(injectExpectation.getSignatures()); + return clone; + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to copy object", e); + } + } } diff --git a/openbas-api/src/main/java/io/openbas/service/InjectSearchService.java b/openbas-api/src/main/java/io/openbas/service/InjectSearchService.java index 513c9c2a43..1f7f0386e4 100644 --- a/openbas-api/src/main/java/io/openbas/service/InjectSearchService.java +++ b/openbas-api/src/main/java/io/openbas/service/InjectSearchService.java @@ -8,7 +8,6 @@ import static java.util.Optional.ofNullable; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.openbas.atomic_testing.TargetType; import io.openbas.database.model.*; import io.openbas.database.raw.RawInjectExpectation; import io.openbas.database.repository.AssetGroupRepository; @@ -20,6 +19,7 @@ import io.openbas.telemetry.Tracing; import io.openbas.utils.AtomicTestingUtils; import io.openbas.utils.InjectMapper; +import io.openbas.utils.TargetType; import io.openbas.utils.pagination.SearchPaginationInput; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -313,8 +313,7 @@ private void setComputedAttribute(List injects) { } } - private Map> fetchRelatedTargets( - Set injectIds, String targetType) { + public Map> fetchRelatedTargets(Set injectIds, String targetType) { if (injectIds == null || injectIds.isEmpty()) { return new HashMap<>(); } diff --git a/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java b/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java index b0ef6f00ca..4e48cb3d42 100644 --- a/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java +++ b/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java @@ -1,6 +1,5 @@ package io.openbas.utils; -import io.openbas.atomic_testing.TargetType; import io.openbas.database.model.*; import io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE; import io.openbas.database.raw.*; @@ -17,6 +16,7 @@ public class AtomicTestingUtils { // -- TARGETS WITH RESULTS -- public static List getTargetsWithResultsFromRaw( List expectations, + List injectAssets, Map rawTeamMap, Map rawUserMap, Map rawAssetMap, @@ -31,22 +31,23 @@ public static List getTargetsWithResultsFromRaw( List playerExpectations = new ArrayList<>(); List assetExpectations = new ArrayList<>(); List assetGroupExpectations = new ArrayList<>(); - List injectAssetIds = new ArrayList<>(); // Loop through the expectations to separate them by target expectations.forEach( expectation -> { - if (expectation.getTeam_id() != null && expectation.getTeam_id() != null) { + if (expectation.getTeam_id() != null) { if (expectation.getUser_id() != null) { playerExpectations.add(expectation); } else { teamExpectations.add(expectation); } } - if (expectation.getAsset_id() != null && expectation.getAsset_id() != null) { + if (expectation.getAsset_id() != null && expectation.getAgent_id() == null) { assetExpectations.add(expectation); } - if (expectation.getAsset_group_id() != null && expectation.getAsset_group_id() != null) { + if (expectation.getAsset_group_id() != null + && expectation.getAsset_id() == null + && expectation.getAgent_id() == null) { assetGroupExpectations.add(expectation); } }); @@ -106,29 +107,31 @@ public static List getTargetsWithResultsFromRaw( // Check if each asset defined in an inject has an expectation. If not, create a result with // default expectations - if (rawAssetMap != null) { - rawAssetMap.forEach( - (assetId, asset) -> { - // Check if there are no expectations matching the current asset - boolean noMatchingExpectations = - assetExpectations.stream() - .noneMatch(exp -> exp.getAsset_id().equals(asset.getAsset_id())); - - if (noMatchingExpectations) { - InjectTargetWithResult target = - new InjectTargetWithResult( - TargetType.ASSETS, - asset.getAsset_id(), - asset.getAsset_name(), - defaultExpectationResultsByTypes, - Objects.equals(asset.getAsset_type(), ENDPOINT) - ? Endpoint.PLATFORM_TYPE.valueOf(asset.getEndpoint_platform()) - : null); - - targets.add(target); - injectAssetIds.add(assetId); - } - }); + if (!injectAssets.isEmpty() && rawAssetMap != null) { + rawAssetMap.entrySet().stream() + .filter(entry -> injectAssets.contains(entry.getKey())) + .forEach( + entry -> { + RawAsset asset = entry.getValue(); + // Check if there are no expectations matching the current asset + boolean noMatchingExpectations = + assetExpectations.stream() + .noneMatch(exp -> exp.getAsset_id().equals(asset.getAsset_id())); + + if (noMatchingExpectations) { + InjectTargetWithResult target = + new InjectTargetWithResult( + TargetType.ASSETS, + asset.getAsset_id(), + asset.getAsset_name(), + defaultExpectationResultsByTypes, + Objects.equals(asset.getAsset_type(), ENDPOINT) + ? Endpoint.PLATFORM_TYPE.valueOf(asset.getEndpoint_platform()) + : null); + + targets.add(target); + } + }); } // Check if each asset group defined in an inject has an expectation. If not, create a result @@ -358,10 +361,10 @@ public static List getTargetsWithResultsFromRaw( .toList()); } - // Compare the assets directly linked to the inject {injectAssetIds} to retain only the results + // Compare the assets directly linked to inject {injectAssetIds} to retain only the results // from these assets assetsToRefine.removeAll( - assetsToRemove.stream().filter(asset -> !injectAssetIds.contains(asset.getId())).toList()); + assetsToRemove.stream().filter(asset -> !injectAssets.contains(asset.getId())).toList()); targets.addAll(assetsToRefine); return sortResults(targets); diff --git a/openbas-api/src/main/java/io/openbas/utils/ExerciseMapper.java b/openbas-api/src/main/java/io/openbas/utils/ExerciseMapper.java index edc98783d6..10abda793f 100644 --- a/openbas-api/src/main/java/io/openbas/utils/ExerciseMapper.java +++ b/openbas-api/src/main/java/io/openbas/utils/ExerciseMapper.java @@ -2,7 +2,6 @@ import static java.util.Collections.emptyList; -import io.openbas.atomic_testing.TargetType; import io.openbas.database.model.ExerciseStatus; import io.openbas.database.raw.RawExerciseSimple; import io.openbas.database.raw.RawInjectExpectation; diff --git a/openbas-api/src/main/java/io/openbas/utils/InjectMapper.java b/openbas-api/src/main/java/io/openbas/utils/InjectMapper.java index 9161f1b888..1d1ea40fa1 100644 --- a/openbas-api/src/main/java/io/openbas/utils/InjectMapper.java +++ b/openbas-api/src/main/java/io/openbas/utils/InjectMapper.java @@ -1,6 +1,5 @@ package io.openbas.utils; -import io.openbas.atomic_testing.TargetType; import io.openbas.database.model.*; import io.openbas.rest.atomic_testing.form.*; import java.util.List; diff --git a/openbas-api/src/main/java/io/openbas/utils/ResultUtils.java b/openbas-api/src/main/java/io/openbas/utils/ResultUtils.java index a9887ac9bb..04af3d9e9b 100644 --- a/openbas-api/src/main/java/io/openbas/utils/ResultUtils.java +++ b/openbas-api/src/main/java/io/openbas/utils/ResultUtils.java @@ -86,7 +86,6 @@ public List computeTargetResults(@NotNull Set in .collect(Collectors.groupingBy(RawInjectExpectation::getInject_id)); // -- TEAMS INJECT -- - Set teamIds = rawInjectExpectations.stream() .map(RawInjectExpectation::getTeam_id) @@ -98,7 +97,6 @@ public List computeTargetResults(@NotNull Set in rawTeams.stream().collect(Collectors.toMap(RawTeam::getTeam_id, rawTeam -> rawTeam)); // -- USER MAP FROM TEAMS -- - Set userIds = rawInjectExpectations.stream() .map(RawInjectExpectation::getUser_id) @@ -144,11 +142,20 @@ public List computeTargetResults(@NotNull Set in Map> dynamicForAssetGroupMap = assetGroupService.computeDynamicAssetFromRaw(rawAssetGroups); + Map> injectAssetMap = + assetRepository.assetsByInjectIds(injectIds).stream() + .collect( + Collectors.groupingBy( + injectAsset -> (String) injectAsset[0], + Collectors.mapping( + injectAsset -> (String) injectAsset[1], Collectors.toList()))); + return injectIds.stream() .flatMap( injectId -> { return AtomicTestingUtils.getTargetsWithResultsFromRaw( expectationMap.getOrDefault(injectId, emptyList()), + injectAssetMap.getOrDefault(injectId, emptyList()), teamMap, userMap, assetMap, diff --git a/openbas-framework/src/main/java/io/openbas/atomic_testing/TargetType.java b/openbas-api/src/main/java/io/openbas/utils/TargetType.java similarity index 67% rename from openbas-framework/src/main/java/io/openbas/atomic_testing/TargetType.java rename to openbas-api/src/main/java/io/openbas/utils/TargetType.java index b6a958b42c..31a17cf86c 100644 --- a/openbas-framework/src/main/java/io/openbas/atomic_testing/TargetType.java +++ b/openbas-api/src/main/java/io/openbas/utils/TargetType.java @@ -1,6 +1,7 @@ -package io.openbas.atomic_testing; +package io.openbas.utils; public enum TargetType { + AGENT, ASSETS, ASSETS_GROUPS, PLAYER, diff --git a/openbas-api/src/test/java/io/openbas/rest/ExpectationApiTest.java b/openbas-api/src/test/java/io/openbas/rest/ExpectationApiTest.java new file mode 100644 index 0000000000..4ddae90f66 --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/rest/ExpectationApiTest.java @@ -0,0 +1,868 @@ +package io.openbas.rest; + +import static io.openbas.injectors.openbas.OpenBASInjector.OPENBAS_INJECTOR_ID; +import static io.openbas.injectors.openbas.OpenBASInjector.OPENBAS_INJECTOR_NAME; +import static io.openbas.utils.JsonUtils.asJsonString; +import static io.openbas.utils.fixtures.ExpectationFixture.getExpectationUpdateInput; +import static io.openbas.utils.fixtures.InjectExpectationFixture.getInjectExpectationUpdateInput; +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.jayway.jsonpath.JsonPath; +import io.openbas.IntegrationTest; +import io.openbas.database.model.*; +import io.openbas.database.repository.*; +import io.openbas.execution.ExecutableInject; +import io.openbas.model.expectation.DetectionExpectation; +import io.openbas.model.expectation.PreventionExpectation; +import io.openbas.rest.exercise.form.ExpectationUpdateInput; +import io.openbas.rest.inject.form.InjectExpectationUpdateInput; +import io.openbas.service.InjectExpectationService; +import io.openbas.utils.fixtures.*; +import io.openbas.utils.mockUser.WithMockAdminUser; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +@TestInstance(PER_CLASS) +public class ExpectationApiTest extends IntegrationTest { + + // API Endpoints + private static final String EXPECTATIONS_URI = "/api/expectations/"; + private static final String INJECTS_EXPECTATIONS_URI = "/api/injects/expectations"; + + private static final String INJECTION_NAME = "AMSI Bypass - AMSI InitFailed"; + private static final String INJECTOR_TYPE = "openbas_implant"; + static Long EXPIRATION_TIME_SIX_HOURS = 21600L; + + @Autowired private MockMvc mvc; + @Autowired private AssetGroupRepository assetGroupRepository; + @Autowired private EndpointRepository endpointRepository; + @Autowired private AgentRepository agentRepository; + @Autowired private InjectRepository injectRepository; + @Autowired private InjectorRepository injectorRepository; + @Autowired private CollectorRepository collectorRepository; + @Autowired private InjectorContractRepository injectorContractRepository; + @Autowired private InjectExpectationRepository injectExpectationRepository; + @Autowired private InjectExpectationService injectExpectationService; + + // Saved entities for test setup + private static Injector savedInjector; + private static InjectorContract savedInjectorContract; + private static AssetGroup savedAssetGroup; + private static Endpoint savedEndpoint; + private static Agent savedAgent; + private static Agent savedAgent1; + private static Inject savedInject; + private static Collector savedCollector; + + @BeforeAll + void beforeAll() { + InjectorContract injectorContract = + InjectorContractFixture.createInjectorContract(Map.of("en", INJECTION_NAME), "{}"); + savedInjector = + injectorRepository.save( + InjectorFixture.createInjector( + OPENBAS_INJECTOR_ID, OPENBAS_INJECTOR_NAME, INJECTOR_TYPE)); + injectorContract.setInjector(savedInjector); + savedInjectorContract = injectorContractRepository.save(injectorContract); + + // -- Targets -- + savedEndpoint = endpointRepository.save(EndpointFixture.createEndpoint()); + savedAgent = agentRepository.save(AgentFixture.createAgent(savedEndpoint, "external01")); + savedAgent1 = agentRepository.save(AgentFixture.createAgent(savedEndpoint, "external02")); + savedAssetGroup = + assetGroupRepository.save( + AssetGroupFixture.createAssetGroupWithAssets( + "asset group name", List.of(savedEndpoint))); + + // -- Inject -- + savedInject = + injectRepository.save( + InjectFixture.createTechnicalInjectWithAssetGroup( + savedInjectorContract, INJECTION_NAME, savedAssetGroup)); + + // -- Collector -- + Collector collector = new Collector(); + collector.setId(UUID.randomUUID().toString()); + collector.setName("collector-name"); + collector.setType(UUID.randomUUID().toString()); + collector.setExternal(true); + savedCollector = collectorRepository.save(collector); + } + + @AfterAll + void afterAll() { + agentRepository.deleteAll(); + injectRepository.deleteAll(); + endpointRepository.deleteAll(); + collectorRepository.deleteById(savedCollector.getId()); + } + + @AfterEach + void afterEach() { + injectExpectationRepository.deleteAll(); + } + + @Nested + @WithMockAdminUser + @DisplayName("Update and delete inject expectation results from UI") + class ResultInjectExpectation { + + @Test + @DisplayName("Update and delete Asset group inject expectation result from UI") + @WithMockAdminUser + void updateAssetGroupInjectExpectationResults() throws Exception { + // -- PREPARE -- + // Build and save expectations + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + List.of(savedAssetGroup), + emptyList()); + DetectionExpectation detectionExpectation = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_SIX_HOURS); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_SIX_HOURS); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(detectionExpectation, detectionExpectationForAsset)); + + // Fetch injectExpectation created for asset group + List injectExpectations = + injectExpectationRepository.findAllByInjectAndAssetGroup( + savedInject.getId(), savedAssetGroup.getId()); + ExpectationUpdateInput expectationUpdateInput = + getExpectationUpdateInput("security-platform-1", 40.0); + + // -- EXECUTE -- + String response = + mvc.perform( + put(EXPECTATIONS_URI + "/" + injectExpectations.get(0).getId()) + .content(asJsonString(expectationUpdateInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(40.0, JsonPath.read(response, "$.inject_expectation_results[0].score")); + assertEquals( + 40.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getScore()); + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + expectationUpdateInput.getSourceId(), + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getResults() + .getFirst() + .getSourceId()); + assertEquals( + 40.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getScore()); + assertEquals( + 40.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .getFirst() + .getScore()); + + // Delete results from Asset group: Add new result and delete last result + + // -- PREPARE -- + expectationUpdateInput = getExpectationUpdateInput("security-platform-2", 250.0); + + // -- EXECUTE -- + response = + mvc.perform( + put(EXPECTATIONS_URI + "/" + injectExpectations.get(0).getId()) + .content(asJsonString(expectationUpdateInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(250.0, JsonPath.read(response, "$.inject_expectation_results[1].score")); + assertEquals( + 250.0, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .getFirst() + .getScore()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getResults() + .size()); + + // -- EXECUTE -- + mvc.perform( + put( + EXPECTATIONS_URI + + "/" + + injectExpectations.get(0).getId() + + "/" + + expectationUpdateInput.getSourceId() + + "/delete")) + .andExpect(status().is2xxSuccessful()); + + assertEquals( + 40.0, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .getFirst() + .getScore()); + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 40.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getScore()); + } + + @Test + @DisplayName("Update and delete asset inject expectation result from UI") + @WithMockAdminUser + void updateAssetInjectExpectationResults() throws Exception { + // -- PREPARE -- + // Build and save expectations + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + emptyList(), + emptyList()); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_SIX_HOURS); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(detectionExpectationForAsset)); + + // Fetch injectExpectation created for asset group + List injectExpectations = + injectExpectationRepository.findAllByInjectAndAsset( + savedInject.getId(), savedEndpoint.getId()); + ExpectationUpdateInput expectationUpdateInput = + getExpectationUpdateInput("security-platform-1", 50.0); + + // -- EXECUTE -- + String response = + mvc.perform( + put(EXPECTATIONS_URI + "/" + injectExpectations.get(0).getId()) + .content(asJsonString(expectationUpdateInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(50.0, JsonPath.read(response, "$.inject_expectation_results[0].score")); + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 50.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getScore()); + assertEquals( + 50.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .getFirst() + .getScore()); + + // Delete results from Asset: Add new result and delete the last one + + // -- PREPARE -- + expectationUpdateInput = getExpectationUpdateInput("security-platform-2", 180.0); + + // -- EXECUTE -- + response = + mvc.perform( + put(EXPECTATIONS_URI + "/" + injectExpectations.get(0).getId()) + .content(asJsonString(expectationUpdateInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(180.0, JsonPath.read(response, "$.inject_expectation_results[1].score")); + assertEquals( + 180.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getScore()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .getFirst() + .getResults() + .size()); + + // -- EXECUTE -- + mvc.perform( + put( + EXPECTATIONS_URI + + "/" + + injectExpectations.get(0).getId() + + "/" + + expectationUpdateInput.getSourceId() + + "/delete")) + .andExpect(status().is2xxSuccessful()); + + assertEquals( + 50.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getScore()); + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getResults() + .size()); + assertEquals( + 50.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .getFirst() + .getScore()); + } + } + + @Nested + @WithMockAdminUser + @DisplayName("Fetch and update InjectExpectations from collectors") + class ProcessInjectExpectationsForCollectors { + + @Test + @DisplayName("Get Inject Expectations for a Specific Source") + void getInjectExpectationsForSource() throws Exception { + // -- PREPARE -- + // Build and save expectations + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + emptyList(), + emptyList()); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_SIX_HOURS); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(detectionExpectationForAsset)); + + // Update one expectation from one agent with source collector-id + List injectExpectations = + injectExpectationRepository.findAllByInjectAndAgent( + savedInject.getId(), savedAgent.getId()); + + injectExpectations + .get(0) + .setResults( + List.of( + InjectExpectationResult.builder() + .sourceId(savedCollector.getId()) + .sourceName(savedCollector.getName()) + .sourceType(savedCollector.getType()) + .score(50.0) + .build())); + + injectExpectationRepository.save(injectExpectations.get(0)); + + // -- EXECUTE -- + String response = + mvc.perform( + get(INJECTS_EXPECTATIONS_URI + "/assets/" + savedCollector.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(1, ((List) JsonPath.read(response, "$")).size()); + assertEquals( + savedEndpoint.getId(), JsonPath.read(response, "$.[0].inject_expectation_asset")); + assertEquals(savedAgent1.getId(), JsonPath.read(response, "$.[0].inject_expectation_agent")); + } + + @Test + @DisplayName("Get Prevention Inject Expectations for a Specific Source") + void getInjectPreventionExpectationsForSource() throws Exception { + // -- PREPARE -- + // Build and save expectations for an asset with 2 agents + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + emptyList(), + emptyList()); + PreventionExpectation preventionExpectationForAsset = + ExpectationFixture.createTechnicalPreventionExpectation( + savedEndpoint, EXPIRATION_TIME_SIX_HOURS); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(preventionExpectationForAsset)); + + // -- EXECUTE -- + String response = + mvc.perform( + get(INJECTS_EXPECTATIONS_URI + "/prevention/" + savedCollector.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(2, ((List) JsonPath.read(response, "$")).size()); + assertEquals( + savedEndpoint.getId(), JsonPath.read(response, "$.[0].inject_expectation_asset")); + assertEquals("PREVENTION", JsonPath.read(response, "$.[0].inject_expectation_type")); + + // -- PREPARE -- + // Update one expectation from one agent with source collector-id then this expectation is + // filled and it should return just one + List injectExpectations = + injectExpectationRepository.findAllByInjectAndAgent( + savedInject.getId(), savedAgent1.getId()); + + injectExpectations + .get(0) + .setResults( + List.of( + InjectExpectationResult.builder() + .sourceId(savedCollector.getId()) + .sourceName(savedCollector.getName()) + .sourceType(savedCollector.getType()) + .score(80.0) + .build())); + + injectExpectationRepository.save(injectExpectations.get(0)); + + // -- EXECUTE -- + response = + mvc.perform( + get(INJECTS_EXPECTATIONS_URI + "/prevention/" + savedCollector.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(1, ((List) JsonPath.read(response, "$")).size()); + assertEquals( + savedEndpoint.getId(), JsonPath.read(response, "$.[0].inject_expectation_asset")); + assertEquals(savedAgent.getId(), JsonPath.read(response, "$.[0].inject_expectation_agent")); + } + + @Test + @DisplayName("Get Detection Inject Expectations for a Specific Source") + void getInjectDetectionExpectationsForSource() throws Exception { + // -- PREPARE -- + // Build and save expectations for an asset with 2 agents + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + emptyList(), + emptyList()); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_SIX_HOURS); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(detectionExpectationForAsset)); + + // -- EXECUTE -- + String response = + mvc.perform( + get(INJECTS_EXPECTATIONS_URI + "/detection/" + savedCollector.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(2, ((List) JsonPath.read(response, "$")).size()); + assertEquals( + savedEndpoint.getId(), JsonPath.read(response, "$.[0].inject_expectation_asset")); + assertEquals("DETECTION", JsonPath.read(response, "$.[0].inject_expectation_type")); + + // -- PREPARE -- + // Update one expectation from one agent with source collector-id then it should return one + // expectation + List injectExpectations = + injectExpectationRepository.findAllByInjectAndAgent( + savedInject.getId(), savedAgent1.getId()); + + injectExpectations + .get(0) + .setResults( + List.of( + InjectExpectationResult.builder() + .sourceId(savedCollector.getId()) + .sourceName(savedCollector.getName()) + .sourceType(savedCollector.getType()) + .score(90.0) + .build())); + + injectExpectationRepository.save(injectExpectations.get(0)); + + // -- EXECUTE -- + response = + mvc.perform( + get(INJECTS_EXPECTATIONS_URI + "/detection/" + savedCollector.getId()) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals(1, ((List) JsonPath.read(response, "$")).size()); + assertEquals( + savedEndpoint.getId(), JsonPath.read(response, "$.[0].inject_expectation_asset")); + assertEquals(savedAgent.getId(), JsonPath.read(response, "$.[0].inject_expectation_agent")); + } + + @Test + @DisplayName("Update Inject expectation from collector one success and one failed") + void updateInjectExpectationWithOneSuccessAndOneFailed() throws Exception { + // -- PREPARE -- + // Build and save expectations for an asset with 2 agents + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + List.of(savedAssetGroup), + emptyList()); + DetectionExpectation detectionExpectationForAssetGroup = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_SIX_HOURS); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_SIX_HOURS); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, + List.of(detectionExpectationForAssetGroup, detectionExpectationForAsset)); + + // Fetch injectExpectation created for agent 1 + List injectExpectations = + injectExpectationRepository.findAllByInjectAndAgent( + savedInject.getId(), savedAgent1.getId()); + InjectExpectationUpdateInput expectationUpdateInput = + getInjectExpectationUpdateInput(savedCollector.getId(), "Detected", true); + + // -- EXECUTE -- + mvc.perform( + put(INJECTS_EXPECTATIONS_URI + "/" + injectExpectations.get(0).getId()) + .content(asJsonString(expectationUpdateInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .getFirst() + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getScore()); + assertEquals( + injectExpectations.get(0).getExpectedScore(), + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .getFirst() + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getScore()); + + // Fetch injectExpectation created for second agent + injectExpectations = + injectExpectationRepository.findAllByInjectAndAgent( + savedInject.getId(), savedAgent.getId()); + expectationUpdateInput = + getInjectExpectationUpdateInput(savedCollector.getId(), "Not Detected", false); + + // -- EXECUTE -- + mvc.perform( + put(INJECTS_EXPECTATIONS_URI + "/" + injectExpectations.get(0).getId()) + .content(asJsonString(expectationUpdateInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .getFirst() + .getScore()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getScore()); + assertEquals( + injectExpectations.get(0).getExpectedScore(), + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .getFirst() + .getScore()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getScore()); + } + + @Test + @DisplayName("Update Inject expectation from collector with success") + void updateInjectExpectationWithTwoSuccess() throws Exception { + // -- PREPARE -- + // Build and save expectations for an asset with 2 agents + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + List.of(savedAssetGroup), + emptyList()); + DetectionExpectation detectionExpectationForAssetGroup = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_SIX_HOURS); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_SIX_HOURS); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, + List.of(detectionExpectationForAssetGroup, detectionExpectationForAsset)); + + // Fetch injectExpectation created for agent 1 + List injectExpectations = + injectExpectationRepository.findAllByInjectAndAgent( + savedInject.getId(), savedAgent1.getId()); + InjectExpectationUpdateInput expectationUpdateInput = + getInjectExpectationUpdateInput(savedCollector.getId(), "Detected", true); + + // -- EXECUTE -- + mvc.perform( + put(INJECTS_EXPECTATIONS_URI + "/" + injectExpectations.get(0).getId()) + .content(asJsonString(expectationUpdateInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .getFirst() + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getScore()); + assertEquals( + injectExpectations.get(0).getExpectedScore(), + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .getFirst() + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getScore()); + + // Fetch injectExpectation created for second agent + injectExpectations = + injectExpectationRepository.findAllByInjectAndAgent( + savedInject.getId(), savedAgent.getId()); + expectationUpdateInput = + getInjectExpectationUpdateInput(savedCollector.getId(), "Detected", true); + + // -- EXECUTE -- + mvc.perform( + put(INJECTS_EXPECTATIONS_URI + "/" + injectExpectations.get(0).getId()) + .content(asJsonString(expectationUpdateInput)) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andReturn() + .getResponse() + .getContentAsString(); + + // -- ASSERT -- + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .getFirst() + .getScore()); + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .getFirst() + .getScore()); + assertEquals( + injectExpectations.get(0).getExpectedScore(), + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .getFirst() + .getScore()); + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .getFirst() + .getScore()); + } + } +} diff --git a/openbas-api/src/test/java/io/openbas/rest/inject_expectation/ExpectationsExpirationManagerServiceTest.java b/openbas-api/src/test/java/io/openbas/rest/inject_expectation/ExpectationsExpirationManagerServiceTest.java new file mode 100644 index 0000000000..4772ab0e83 --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/rest/inject_expectation/ExpectationsExpirationManagerServiceTest.java @@ -0,0 +1,496 @@ +package io.openbas.rest.inject_expectation; + +import static io.openbas.injectors.openbas.OpenBASInjector.OPENBAS_INJECTOR_ID; +import static io.openbas.injectors.openbas.OpenBASInjector.OPENBAS_INJECTOR_NAME; +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.openbas.IntegrationTest; +import io.openbas.collectors.expectations_expiration_manager.service.ExpectationsExpirationManagerService; +import io.openbas.database.model.*; +import io.openbas.database.repository.*; +import io.openbas.execution.ExecutableInject; +import io.openbas.model.expectation.DetectionExpectation; +import io.openbas.service.InjectExpectationService; +import io.openbas.utils.fixtures.*; +import io.openbas.utils.mockUser.WithMockAdminUser; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExtendWith(MockitoExtension.class) +public class ExpectationsExpirationManagerServiceTest extends IntegrationTest { + + private static final String INJECTION_NAME = "AMSI Bypass - AMSI InitFailed"; + private static final String INJECTOR_TYPE = "openbas_implant"; + + public static final long EXPIRATION_TIME_1_s = 1L; + @Autowired private AssetGroupRepository assetGroupRepository; + @Autowired private EndpointRepository endpointRepository; + @Autowired private AgentRepository agentRepository; + @Autowired private InjectRepository injectRepository; + @Autowired private InjectorRepository injectorRepository; + @Autowired private InjectorContractRepository injectorContractRepository; + @Autowired private InjectExpectationRepository injectExpectationRepository; + @Autowired private InjectExpectationService injectExpectationService; + @Autowired private ExpectationsExpirationManagerService expectationsExpirationManagerService; + + // Saved entities for test setup + private static Injector savedInjector; + private static InjectorContract savedInjectorContract; + private static AssetGroup savedAssetGroup; + private static Endpoint savedEndpoint; + private static Agent savedAgent; + private static Agent savedAgent1; + private static Inject savedInject; + + @BeforeAll + void beforeAll() { + InjectorContract injectorContract = + InjectorContractFixture.createInjectorContract(Map.of("en", INJECTION_NAME), "{}"); + savedInjector = + injectorRepository.save( + InjectorFixture.createInjector( + OPENBAS_INJECTOR_ID, OPENBAS_INJECTOR_NAME, INJECTOR_TYPE)); + injectorContract.setInjector(savedInjector); + savedInjectorContract = injectorContractRepository.save(injectorContract); + + // -- Targets -- + savedEndpoint = endpointRepository.save(EndpointFixture.createEndpoint()); + savedAgent = agentRepository.save(AgentFixture.createAgent(savedEndpoint, "external01")); + savedAgent1 = agentRepository.save(AgentFixture.createAgent(savedEndpoint, "external02")); + savedAssetGroup = + assetGroupRepository.save( + AssetGroupFixture.createAssetGroupWithAssets( + "asset group name", List.of(savedEndpoint))); + + // -- Inject -- + savedInject = + injectRepository.save( + InjectFixture.createTechnicalInjectWithAssetGroup( + savedInjectorContract, INJECTION_NAME, savedAssetGroup)); + } + + @AfterAll + void afterAll() { + agentRepository.deleteAll(); + injectRepository.deleteAll(); + endpointRepository.deleteAll(); + } + + @AfterEach + void afterEach() { + injectExpectationRepository.deleteAll(); + } + + @Nested + @WithMockAdminUser + @DisplayName("Update injectExpectations with expectationsExpirationManagerService") + class ComputeExpectationsWithExpectationExpiredManagerService { + + @Test + @DisplayName("All injectExpectations are expired") + @WithMockAdminUser + void allExpectationAreExpired() { + // -- PREPARE -- + // Build and save expectations for asset group with one asset and two agents + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + List.of(savedAssetGroup), + emptyList()); + DetectionExpectation detectionExpectation = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_1_s); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_1_s); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(detectionExpectation, detectionExpectationForAsset)); + + // Verify inject expectations : existence and score null + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .get(0) + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .get(0) + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .get(0) + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .get(0) + .getScore()); + + // -- EXECUTE -- + expectationsExpirationManagerService.computeExpectations(); + + // -- ASSERT -- + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .get(0) + .getScore()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .get(0) + .getScore()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .get(0) + .getScore()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .get(0) + .getScore()); + } + + @Test + @DisplayName("One injectExpectations is already filled") + @WithMockAdminUser + void OneExpectationIsAlreadyFilled() { + // -- PREPARE -- + // Build and save expectations for asset group with one asset and two agents + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + List.of(savedAssetGroup), + emptyList()); + DetectionExpectation detectionExpectation = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_1_s); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_1_s); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(detectionExpectation, detectionExpectationForAsset)); + + // Update one expectation from one agent with source collector-id + List injectExpectations = + injectExpectationRepository.findAllByInjectAndAgent( + savedInject.getId(), savedAgent.getId()); + + injectExpectations + .get(0) + .setResults( + List.of( + InjectExpectationResult.builder() + .sourceId("collector-id") + .sourceName("collector-name") + .sourceType("collector-type") + .score(50.0) + .build())); + + injectExpectationRepository.save(injectExpectations.get(0)); + + // Verify inject expectations : existence and score null + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .get(0) + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .get(0) + .getScore()); + assertEquals( + 50.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .get(0) + .getResults() + .get(0) + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .get(0) + .getScore()); + + // -- EXECUTE -- + expectationsExpirationManagerService.computeExpectations(); + + // -- ASSERT -- + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .get(0) + .getScore()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .get(0) + .getScore()); + assertEquals( + 50.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .get(0) + .getResults() + .get(0) + .getScore()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .get(0) + .getScore()); + } + + @Test + @DisplayName("The agent expectations are already filled") + @WithMockAdminUser + void agentExpectationsAreAlreadyFilled() { + // -- PREPARE -- + // Build and save expectations for asset group with one asset and two agents + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + List.of(savedAssetGroup), + emptyList()); + DetectionExpectation detectionExpectation = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_1_s); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_1_s); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(detectionExpectation, detectionExpectationForAsset)); + + // Update agent expectations with source collector-id + List injectExpectations = + List.of( + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .get(0), + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .get(0)); + + injectExpectations.forEach( + injectExpectation -> { + injectExpectation.setResults( + List.of( + InjectExpectationResult.builder() + .sourceId("collector-id") + .sourceName("collector-name") + .sourceType("collector-type") + .score(100.0) + .build())); + injectExpectation.setScore(100.0); + }); + + injectExpectationRepository.saveAll(injectExpectations); + + // Verify inject expectations : existence and score null + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .get(0) + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .get(0) + .getScore()); + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .get(0) + .getScore()); + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .get(0) + .getScore()); + + // -- EXECUTE -- + expectationsExpirationManagerService.computeExpectations(); + + // -- ASSERT -- + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .get(0) + .getScore()); + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .get(0) + .getScore()); + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .get(0) + .getScore()); + assertEquals( + 100.0, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .get(0) + .getScore()); + } + + @Test + @DisplayName("Asset expectations without agent expectation linked") + @WithMockAdminUser + void assetExpectationWithoutAgentExpectationsLinked() { + // -- PREPARE -- + // Build and save expectations for asset group with one asset and two agents + ExecutableInject executableInject = + new ExecutableInject( + false, + true, + savedInject, + emptyList(), + List.of(savedEndpoint), + List.of(savedAssetGroup), + emptyList()); + DetectionExpectation detectionExpectation = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_1_s); + DetectionExpectation detectionExpectationForAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedEndpoint, EXPIRATION_TIME_1_s); + + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(detectionExpectation, detectionExpectationForAsset)); + + // Delete agent inject expectations to test behavior of assets without agents + List injectExpectations = + List.of( + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .get(0), + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .get(0)); + + List ids = injectExpectations.stream().map(e -> e.getId()).toList(); + + injectExpectationRepository.deleteAllById(ids); + + // Verify inject expectations : existence and score null + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .get(0) + .getScore()); + assertEquals( + null, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .get(0) + .getScore()); + + // -- EXECUTE -- + expectationsExpirationManagerService.computeExpectations(); + + // -- ASSERT -- + assertEquals( + 1, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .get(0) + .getScore()); + assertEquals( + 0.0, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedEndpoint.getId()) + .get(0) + .getScore()); + } + } +} diff --git a/openbas-api/src/test/java/io/openbas/rest/inject_expectation/InjectExpectationServiceTest.java b/openbas-api/src/test/java/io/openbas/rest/inject_expectation/InjectExpectationServiceTest.java new file mode 100644 index 0000000000..c927a3460c --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/rest/inject_expectation/InjectExpectationServiceTest.java @@ -0,0 +1,271 @@ +package io.openbas.rest.inject_expectation; + +import static io.openbas.injectors.openbas.OpenBASInjector.OPENBAS_INJECTOR_ID; +import static io.openbas.injectors.openbas.OpenBASInjector.OPENBAS_INJECTOR_NAME; +import static java.util.Collections.emptyList; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.openbas.IntegrationTest; +import io.openbas.database.model.*; +import io.openbas.database.repository.*; +import io.openbas.execution.ExecutableInject; +import io.openbas.model.expectation.DetectionExpectation; +import io.openbas.model.expectation.PreventionExpectation; +import io.openbas.service.InjectExpectationService; +import io.openbas.utils.fixtures.*; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@ExtendWith(MockitoExtension.class) +class InjectExpectationServiceTest extends IntegrationTest { + + private static final String INJECTION_NAME = "AMSI Bypass - AMSI InitFailed"; + private static final String INJECTOR_TYPE = "openbas_implant"; + static Long EXPIRATION_TIME_SIX_HOURS = 21600L; + + // Saved entities for test setup + @Autowired private InjectExpectationRepository injectExpectationRepository; + @Autowired private InjectorContractRepository injectorContractRepository; + @Autowired private InjectorRepository injectorRepository; + @Autowired private InjectRepository injectRepository; + @Autowired private AssetRepository assetRepository; + @Autowired private AssetGroupRepository assetGroupRepository; + @Autowired private AgentRepository agentRepository; + @Autowired private InjectExpectationService injectExpectationService; + + private static Injector savedInjector; + private static InjectorContract savedInjectorContract; + private static Asset savedAsset; + + @BeforeAll + void beforeAll() { + InjectorContract injectorContract = + InjectorContractFixture.createInjectorContract(Map.of("en", INJECTION_NAME), "{}"); + savedInjector = + injectorRepository.save( + InjectorFixture.createInjector( + OPENBAS_INJECTOR_ID, OPENBAS_INJECTOR_NAME, INJECTOR_TYPE)); + injectorContract.setInjector(savedInjector); + + savedInjectorContract = injectorContractRepository.save(injectorContract); + savedAsset = assetRepository.save(AssetFixture.createDefaultAsset("asset name")); + } + + @AfterAll + void afterAll() { + assetRepository.deleteAll(); + } + + @AfterEach + void afterEach() { + injectExpectationRepository.deleteAll(); + injectRepository.deleteAll(); + assetGroupRepository.deleteAll(); + agentRepository.deleteAll(); + } + + private Inject saveInject(InjectorContract injectorContract) { + Inject inject = + InjectFixture.createTechnicalInject(injectorContract, INJECTION_NAME, savedAsset); + return injectRepository.save(inject); + } + + private ExecutableInject createExecutableInject( + Inject savedInject, List assetGroups) { + return new ExecutableInject( + false, true, savedInject, emptyList(), List.of(savedAsset), assetGroups, emptyList()); + } + + private Agent createAgent(String external01) { + Agent agent = AgentFixture.createAgent(savedAsset, external01); + return this.agentRepository.save(agent); + } + + private AssetGroup createAssetGroup(String name) { + AssetGroup assetGroup = AssetGroupFixture.createAssetGroupWithAssets(name, List.of(savedAsset)); + return assetGroupRepository.save(assetGroup); + } + + @Test + @DisplayName( + "Expectations type prevention and detection should be created for agent linked to asset") + void expectationsForAssetLinkedToAgent() { + // -- PREPARE -- + Agent savedAgent = createAgent("external01"); + Inject savedInject = saveInject(savedInjectorContract); + ExecutableInject executableInject = createExecutableInject(savedInject, emptyList()); + DetectionExpectation detectionExpectation = + ExpectationFixture.createTechnicalDetectionExpectation( + savedAsset, EXPIRATION_TIME_SIX_HOURS); + PreventionExpectation preventionExpectation = + ExpectationFixture.createTechnicalPreventionExpectation( + savedAsset, EXPIRATION_TIME_SIX_HOURS); + + // -- EXECUTE -- + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(preventionExpectation, detectionExpectation)); + + // -- ASSERT -- + assertEquals(4, injectExpectationRepository.findAll().spliterator().getExactSizeIfKnown()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedAsset.getId()) + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .size()); + } + + @Test + @DisplayName( + "Expectations should be created for agent linked to asset who is part of an asset group") + void expectationsForAssetGroupLinkedToAgent() { + // -- PREPARE -- + Agent savedAgent = createAgent("external01"); + AssetGroup savedAssetGroup = createAssetGroup("asset group name"); + Inject savedInject = saveInject(savedInjectorContract); + ExecutableInject executableInject = + createExecutableInject(savedInject, List.of(savedAssetGroup)); + DetectionExpectation detectionExpectation = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_SIX_HOURS); + PreventionExpectation preventionExpectation = + ExpectationFixture.createPreventionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_SIX_HOURS); + DetectionExpectation detectionExpectationAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedAsset, EXPIRATION_TIME_SIX_HOURS); + PreventionExpectation preventionExpectationAsset = + ExpectationFixture.createTechnicalPreventionExpectation( + savedAsset, EXPIRATION_TIME_SIX_HOURS); + + // -- EXECUTE -- + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, + List.of( + preventionExpectation, + detectionExpectation, + preventionExpectationAsset, + detectionExpectationAsset)); + + // -- ASSERT -- + assertEquals(6, injectExpectationRepository.findAll().spliterator().getExactSizeIfKnown()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedAsset.getId()) + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .size()); + } + + @Test + @DisplayName("Expectations should be created for asset with multiple agents") + void expectationsForAssetWithMultipleAgents() { + // -- PREPARE -- + Agent savedAgent = createAgent("external01"); + Agent savedAgent1 = createAgent("external02"); + Inject savedInject = saveInject(savedInjectorContract); + ExecutableInject executableInject = createExecutableInject(savedInject, emptyList()); + DetectionExpectation detectionExpectation = + ExpectationFixture.createTechnicalDetectionExpectation( + savedAsset, EXPIRATION_TIME_SIX_HOURS); + PreventionExpectation preventionExpectation = + ExpectationFixture.createTechnicalPreventionExpectation( + savedAsset, EXPIRATION_TIME_SIX_HOURS); + + // -- EXECUTE -- + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, List.of(preventionExpectation, detectionExpectation)); + + // -- ASSERT -- + assertEquals(6, injectExpectationRepository.findAll().spliterator().getExactSizeIfKnown()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedAsset.getId()) + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .size()); + } + + @Test + @DisplayName("Expectations should be created for asset group with multiple agents") + void expectationsForAssetGroupWithMultipleAgents() { + // -- PREPARE -- + Agent savedAgent = createAgent("external01"); + Agent savedAgent1 = createAgent("external02"); + AssetGroup savedAssetGroup = createAssetGroup("assetGroup name"); + Inject savedInject = saveInject(savedInjectorContract); + ExecutableInject executableInject = + createExecutableInject(savedInject, List.of(savedAssetGroup)); + DetectionExpectation detectionExpectation = + ExpectationFixture.createDetectionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_SIX_HOURS); + PreventionExpectation preventionExpectation = + ExpectationFixture.createPreventionExpectationForAssetGroup( + savedAssetGroup, EXPIRATION_TIME_SIX_HOURS); + DetectionExpectation detectionExpectationAsset = + ExpectationFixture.createTechnicalDetectionExpectation( + savedAsset, EXPIRATION_TIME_SIX_HOURS); + PreventionExpectation preventionExpectationAsset = + ExpectationFixture.createTechnicalPreventionExpectation( + savedAsset, EXPIRATION_TIME_SIX_HOURS); + + // -- EXECUTE -- + injectExpectationService.buildAndSaveInjectExpectations( + executableInject, + List.of( + preventionExpectation, + detectionExpectation, + preventionExpectationAsset, + detectionExpectationAsset)); + + // -- ASSERT -- + assertEquals(8, injectExpectationRepository.findAll().spliterator().getExactSizeIfKnown()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAsset(savedInject.getId(), savedAsset.getId()) + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAssetGroup(savedInject.getId(), savedAssetGroup.getId()) + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent.getId()) + .size()); + assertEquals( + 2, + injectExpectationRepository + .findAllByInjectAndAgent(savedInject.getId(), savedAgent1.getId()) + .size()); + } +} diff --git a/openbas-api/src/test/java/io/openbas/service/ExerciseExpectationServiceTest.java b/openbas-api/src/test/java/io/openbas/service/ExerciseExpectationServiceTest.java index 88a5fdebe1..06ba12bd8d 100644 --- a/openbas-api/src/test/java/io/openbas/service/ExerciseExpectationServiceTest.java +++ b/openbas-api/src/test/java/io/openbas/service/ExerciseExpectationServiceTest.java @@ -26,6 +26,8 @@ public class ExerciseExpectationServiceTest { "The animation team can validate the audience reaction"; @Autowired private ExerciseExpectationService exerciseExpectationService; + @Autowired private InjectExpectationService injectExpectationService; + @Autowired private ExerciseRepository exerciseRepository; @Autowired private InjectRepository injectRepository; @@ -72,10 +74,9 @@ void updateInjectExpectation() { String id = expectations.getFirst().getId(); // -- EXECUTE -- - ExpectationUpdateInput input = new ExpectationUpdateInput(); - input.setScore(7.0); + ExpectationUpdateInput input = ExpectationUpdateInput.builder().score(7.0).build(); InjectExpectation expectation = - this.exerciseExpectationService.updateInjectExpectation(id, input); + this.injectExpectationService.updateInjectExpectation(id, input); // -- ASSERT -- assertNotNull(expectation); diff --git a/openbas-api/src/test/java/io/openbas/utils/AtomicTestingUtilsTest.java b/openbas-api/src/test/java/io/openbas/utils/AtomicTestingUtilsTest.java new file mode 100644 index 0000000000..dc7ab33ccc --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/utils/AtomicTestingUtilsTest.java @@ -0,0 +1,81 @@ +package io.openbas.utils; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.openbas.database.model.Endpoint; +import io.openbas.database.raw.RawAsset; +import io.openbas.database.raw.RawAssetGroup; +import io.openbas.rest.atomic_testing.form.InjectTargetWithResult; +import io.openbas.utils.fixtures.RawAssetFixture; +import io.openbas.utils.fixtures.RawAssetGroupFixture; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class AtomicTestingUtilsTest { + + @Test + @DisplayName("Fetch target results for every target linked to inject") + void shouldFetchTargetsWithResultsLinkedToInject() { + // -- PREPARE -- + String asset1Id = "7e3fee31-70ac-4bcc-befb-8c37397f1e25"; + String asset2Id = "8f4rgg31-70ac-4bcc-befb-8f4rgg311e25"; + String assetGroupId = "9p4rgg31-70ac-4bcc-befb-8f4rgg311e25"; + + List injectAssets = List.of(asset1Id, asset2Id); + + Map assets = + Map.of( + asset1Id, + RawAssetFixture.createDefaultRawAsset( + asset1Id, "raw asset one", Endpoint.PLATFORM_TYPE.Linux), + asset2Id, + RawAssetFixture.createDefaultRawAsset( + asset2Id, "raw asset two", Endpoint.PLATFORM_TYPE.Windows)); + + Map assetGroups = + Map.of( + assetGroupId, + RawAssetGroupFixture.createDefaultRawAssetGroup( + assetGroupId, "raw asset group one", List.of(asset1Id))); + + // -- EXECUTE -- + List result = + AtomicTestingUtils.getTargetsWithResultsFromRaw( + emptyList(), injectAssets, emptyMap(), emptyMap(), assets, assetGroups, emptyMap()); + + // -- ASSERT -- + assertEquals(3, result.size(), "The result should contain three targets"); + + assertEquals( + injectAssets, + result.stream() + .filter(target -> target.getTargetType() == TargetType.ASSETS) + .map(InjectTargetWithResult::getId) + .toList(), + "Expected assets to be in the result"); + + assertEquals( + List.of(assetGroupId), + result.stream() + .filter(target -> target.getTargetType() == TargetType.ASSETS_GROUPS) + .map(InjectTargetWithResult::getId) + .toList(), + "Expected asset group to be in the result"); + + assertEquals( + 1, + result.stream() + .filter(target -> target.getTargetType() == TargetType.ASSETS_GROUPS) + .map(InjectTargetWithResult::getChildren) + .flatMap(List::stream) + .count(), + "Expected asset group to have 1 child"); + } +} diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/AgentFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/AgentFixture.java index 3e41df56c8..8083207ab0 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/AgentFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/AgentFixture.java @@ -1,16 +1,18 @@ package io.openbas.utils.fixtures; import io.openbas.database.model.Agent; -import io.openbas.database.model.Endpoint; +import io.openbas.database.model.Asset; +import java.time.Instant; public class AgentFixture { - public static Agent createAgent(Endpoint endpoint, String externalReference) { + public static Agent createAgent(Asset asset, String externalReference) { Agent agent = new Agent(); agent.setExecutedByUser(Agent.ADMIN_SYSTEM_WINDOWS); agent.setPrivilege(Agent.PRIVILEGE.admin); agent.setDeploymentMode(Agent.DEPLOYMENT_MODE.service); - agent.setAsset(endpoint); + agent.setAsset(asset); + agent.setLastSeen(Instant.now()); agent.setExternalReference(externalReference); return agent; } diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/AssetFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/AssetFixture.java index 9104b5d3d9..69d8191946 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/AssetFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/AssetFixture.java @@ -5,12 +5,12 @@ import org.jetbrains.annotations.NotNull; public class AssetFixture { - public static Asset createDefaultAsset(@NotNull final String id) { + + public static Asset createDefaultAsset(@NotNull final String name) { Asset asset = new Asset(); - asset.setId(id); asset.setCreatedAt(Instant.now()); asset.setUpdatedAt(Instant.now()); - asset.setName("asset name"); + asset.setName(name); asset.setDescription("asset description"); return asset; } diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/AssetGroupFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/AssetGroupFixture.java index ce0ae46468..d58038417b 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/AssetGroupFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/AssetGroupFixture.java @@ -1,5 +1,6 @@ package io.openbas.utils.fixtures; +import io.openbas.database.model.Asset; import io.openbas.database.model.AssetGroup; import io.openbas.rest.asset_group.form.AssetGroupInput; import java.util.List; @@ -22,4 +23,13 @@ public static AssetGroupInput createAssetGroupWithTags( assetGroupInput.setTagIds(tagIds); return assetGroupInput; } + + public static AssetGroup createAssetGroupWithAssets( + @NotNull final String name, List assets) { + AssetGroup assetGroup = new AssetGroup(); + assetGroup.setName(name); + assetGroup.setDescription("An asset group"); + assetGroup.setAssets(assets); + return assetGroup; + } } diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/EndpointFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/EndpointFixture.java index 989d6cf854..81f9a96a67 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/EndpointFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/EndpointFixture.java @@ -47,4 +47,17 @@ public static EndpointRegisterInput createWindowsEndpointRegisterInput( input.setExternalReference(externalReference); return input; } + + public static Endpoint createEndpoint() { + Endpoint endpoint = new Endpoint(); + endpoint.setCreatedAt(Instant.now()); + endpoint.setUpdatedAt(Instant.now()); + endpoint.setName("Endpoint test"); + endpoint.setDescription("Endpoint description"); + endpoint.setHostname("Windows Hostname"); + endpoint.setIps(IPS); + endpoint.setPlatform(Endpoint.PLATFORM_TYPE.Windows); + endpoint.setArch(Endpoint.PLATFORM_ARCH.x86_64); + return endpoint; + } } diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/ExpectationFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/ExpectationFixture.java new file mode 100644 index 0000000000..42eafa606c --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/ExpectationFixture.java @@ -0,0 +1,70 @@ +package io.openbas.utils.fixtures; + +import io.openbas.database.model.Asset; +import io.openbas.database.model.AssetGroup; +import io.openbas.model.expectation.DetectionExpectation; +import io.openbas.model.expectation.PreventionExpectation; +import io.openbas.rest.exercise.form.ExpectationUpdateInput; +import java.util.Collections; + +public class ExpectationFixture { + + static Double SCORE = 100.0; + + public static PreventionExpectation createTechnicalPreventionExpectation( + Asset asset, Long expirationTime) { + return PreventionExpectation.preventionExpectationForAsset( + SCORE, + "Prevention", + "Prevention Expectation", + asset, + false, + expirationTime, + Collections.emptyList()); + } + + public static DetectionExpectation createTechnicalDetectionExpectation( + Asset asset, Long expirationTime) { + return DetectionExpectation.detectionExpectationForAsset( + SCORE, + "Detection", + "Detection Expectation", + asset, + false, + expirationTime, + Collections.emptyList()); + } + + public static PreventionExpectation createPreventionExpectationForAssetGroup( + AssetGroup assetGroup, Long expirationTime) { + return PreventionExpectation.preventionExpectationForAssetGroup( + SCORE, + "Prevention", + "Prevention Expectation", + assetGroup, + false, + expirationTime, + Collections.emptyList()); + } + + public static DetectionExpectation createDetectionExpectationForAssetGroup( + AssetGroup assetGroup, Long expirationTime) { + return DetectionExpectation.detectionExpectationForAssetGroup( + SCORE, + "Detection", + "Detection Expectation", + assetGroup, + false, + expirationTime, + Collections.emptyList()); + } + + public static ExpectationUpdateInput getExpectationUpdateInput(String sourceId, Double score) { + return ExpectationUpdateInput.builder() + .sourceId(sourceId) + .sourceName("security-platform-name") + .sourceType("security-platform-type") + .score(score) + .build(); + } +} diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java index b5882b268d..bd0cd6f5bf 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java @@ -4,6 +4,8 @@ import io.openbas.database.model.Inject; import io.openbas.database.model.InjectExpectation; import io.openbas.database.model.Team; +import io.openbas.rest.inject.form.InjectExpectationUpdateInput; +import java.util.Map; public class InjectExpectationFixture { @@ -64,4 +66,14 @@ public static InjectExpectation createManualInjectExpectationWithExercise( injectExpectation.setName(expectationName); return injectExpectation; } + + public static InjectExpectationUpdateInput getInjectExpectationUpdateInput( + String collectorId, String result, boolean isSuccess) { + return InjectExpectationUpdateInput.builder() + .collectorId(collectorId) + .result(result) + .isSuccess(isSuccess) + .metadata(Map.of("alertId", "alertId")) + .build(); + } } diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectFixture.java index b1ac75d1b9..15a9d01ec3 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectFixture.java @@ -22,6 +22,28 @@ private static Inject createInject(InjectorContract injectorContract, String tit return inject; } + public static Inject createTechnicalInject( + InjectorContract injectorContract, String title, Asset asset) { + Inject inject = new Inject(); + inject.setTitle(title); + inject.setInjectorContract(injectorContract); + inject.setAssets(List.of(asset)); + inject.setEnabled(true); + inject.setDependsDuration(0L); + return inject; + } + + public static Inject createTechnicalInjectWithAssetGroup( + InjectorContract injectorContract, String title, AssetGroup assetGroup) { + Inject inject = new Inject(); + inject.setTitle(title); + inject.setInjectorContract(injectorContract); + inject.setAssetGroups(List.of(assetGroup)); + inject.setEnabled(true); + inject.setDependsDuration(0L); + return inject; + } + public static Inject getInjectWithoutContract() { Inject inject = createInjectWithTitle(INJECT_EMAIL_NAME); inject.setEnabled(true); diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java index f0993119b9..dff16721ab 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorContractFixture.java @@ -8,6 +8,7 @@ import io.openbas.database.model.Payload; import io.openbas.injector_contract.ContractCardinality; import io.openbas.injector_contract.fields.ContractSelect; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.UUID; @@ -46,4 +47,16 @@ public static InjectorContract createPayloadInjectorContractWithObfuscator( return createPayloadInjectorContractInternal( injector, payloadCommand, List.of(obfuscatorSelect)); } + + public static InjectorContract createInjectorContract( + Map labels, String content) { + InjectorContract injectorContract = new InjectorContract(); + injectorContract.setId(UUID.randomUUID().toString()); + injectorContract.setLabels(labels); + injectorContract.setContent(content); + injectorContract.setAtomicTesting(true); + injectorContract.setCreatedAt(Instant.now()); + injectorContract.setUpdatedAt(Instant.now()); + return injectorContract; + } } diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorFixture.java new file mode 100644 index 0000000000..5033ddd238 --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectorFixture.java @@ -0,0 +1,18 @@ +package io.openbas.utils.fixtures; + +import io.openbas.database.model.Injector; +import java.time.Instant; + +public class InjectorFixture { + + public static Injector createInjector(String id, String name, String type) { + Injector injector = new Injector(); + injector.setId(id); + injector.setName(name); + injector.setType(type); + injector.setExternal(false); + injector.setCreatedAt(Instant.now()); + injector.setUpdatedAt(Instant.now()); + return injector; + } +} diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/RawAssetFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/RawAssetFixture.java new file mode 100644 index 0000000000..a27bf50adf --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/RawAssetFixture.java @@ -0,0 +1,32 @@ +package io.openbas.utils.fixtures; + +import io.openbas.database.model.Endpoint; +import io.openbas.database.raw.RawAsset; + +public class RawAssetFixture { + + public static RawAsset createDefaultRawAsset( + String id, String name, Endpoint.PLATFORM_TYPE platformType) { + return new RawAsset() { + @Override + public String getAsset_id() { + return id; + } + + @Override + public String getAsset_type() { + return "Endpoint"; + } + + @Override + public String getAsset_name() { + return name; + } + + @Override + public String getEndpoint_platform() { + return platformType.name(); + } + }; + } +} diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/RawAssetGroupFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/RawAssetGroupFixture.java new file mode 100644 index 0000000000..477092fa7f --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/RawAssetGroupFixture.java @@ -0,0 +1,32 @@ +package io.openbas.utils.fixtures; + +import io.openbas.database.raw.RawAssetGroup; +import java.util.List; + +public class RawAssetGroupFixture { + + public static RawAssetGroup createDefaultRawAssetGroup( + String id, String name, List assetIds) { + return new RawAssetGroup() { + @Override + public String getAsset_group_id() { + return id; + } + + @Override + public String getAsset_group_name() { + return name; + } + + @Override + public List getAsset_ids() { + return assetIds; + } + + @Override + public String getAsset_group_dynamic_filter() { + return ""; + } + }; + } +} diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/RawInjectExpectationFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/RawInjectExpectationFixture.java index 0887bbabe3..fe4ff3c38b 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/RawInjectExpectationFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/RawInjectExpectationFixture.java @@ -43,6 +43,11 @@ public String getUser_id() { return userId; } + @Override + public String getAgent_id() { + return ""; + } + @Override public String getAsset_id() { return assetId; diff --git a/openbas-framework/src/main/java/io/openbas/model/expectation/DetectionExpectation.java b/openbas-framework/src/main/java/io/openbas/model/expectation/DetectionExpectation.java index 76335cc30c..b68aa5ca10 100644 --- a/openbas-framework/src/main/java/io/openbas/model/expectation/DetectionExpectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/expectation/DetectionExpectation.java @@ -2,10 +2,7 @@ import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.DETECTION; -import io.openbas.database.model.Asset; -import io.openbas.database.model.AssetGroup; -import io.openbas.database.model.InjectExpectation; -import io.openbas.database.model.InjectExpectationSignature; +import io.openbas.database.model.*; import io.openbas.model.Expectation; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTesting.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTesting.tsx index a0475c15ff..3c3178f998 100644 --- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTesting.tsx +++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTesting.tsx @@ -71,9 +71,7 @@ const AtomicTesting = () => { // Handles const handleTargetClick = (target: InjectTargetWithResult, currentParent?: InjectTargetWithResult) => { setSelectedTarget(target); - if (currentParent) { - setCurrentParentTarget(currentParent); - } + setCurrentParentTarget(currentParent); }; if (!injectResultOverviewOutput) { @@ -266,7 +264,11 @@ const AtomicTesting = () => { {sortedTargets.map(target => (
- handleTargetClick(target)} target={target} selected={selectedTarget?.id === target.id} /> + handleTargetClick(target, undefined)} + target={target} + selected={selectedTarget?.id === target.id && currentParentTarget?.id === undefined} + /> {target?.children?.map(child => ( = ({ ); })} - {(['DETECTION', 'PREVENTION'].includes(injectExpectation.inject_expectation_type) || (injectExpectation.inject_expectation_type === 'MANUAL' && injectExpectation.inject_expectation_results && injectExpectation.inject_expectation_results.length === 0)) - && ( - - - setSelectedExpectationForCreation({ - injectExpectation, - sourceIds: computeExistingSourceIds(injectExpectation.inject_expectation_results ?? []), - })} - > - - - - - )} + {(['DETECTION', 'PREVENTION'].includes(injectExpectation.inject_expectation_type) + || (injectExpectation.inject_expectation_type === 'MANUAL' + && injectExpectation.inject_expectation_results + && injectExpectation.inject_expectation_results.length === 0)) + && ( + + + setSelectedExpectationForCreation({ + injectExpectation, + sourceIds: computeExistingSourceIds(injectExpectation.inject_expectation_results ?? []), + })} + > + + + + + )}
diff --git a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java index d9c53ce11f..cd627682a3 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java +++ b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java @@ -1,5 +1,6 @@ package io.openbas.database.model; +import static io.openbas.database.model.InjectExpectationSignature.EXPECTATION_SIGNATURE_TYPE_PARENT_PROCESS_NAME; import static java.time.Instant.now; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -72,7 +73,6 @@ public enum EXPECTATION_STATUS { @JsonProperty("inject_expectation_description") private String description; - @Setter @Type(JsonType.class) @Column(name = "inject_expectation_signatures") @JsonProperty("inject_expectation_signatures") @@ -102,7 +102,7 @@ public EXPECTATION_STATUS getResponse() { if (this.getScore() >= this.getExpectedScore()) { return EXPECTATION_STATUS.SUCCESS; } - if (this.getScore() == 0) { + if (0.0 == this.getScore()) { return EXPECTATION_STATUS.FAILED; } return EXPECTATION_STATUS.PARTIAL; @@ -183,6 +183,14 @@ public EXPECTATION_STATUS getExpectationStatus() { @Schema(type = "string") private Team team; + @Setter + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "agent_id") + @JsonSerialize(using = MonoIdDeserializer.class) + @JsonProperty("inject_expectation_agent") + @Schema(type = "string") + private Agent agent; + @Setter @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "asset_id") @@ -243,6 +251,22 @@ public void setDetection(@NotNull final Asset asset, @NotNull final AssetGroup a this.assetGroup = assetGroup; } + public void setSignatures(List injectExpectationSignatures) { + this.signatures = + injectExpectationSignatures.stream() + .map( + signature -> { + if (EXPECTATION_SIGNATURE_TYPE_PARENT_PROCESS_NAME.equals(signature.getType()) + && this.agent != null) { + return new InjectExpectationSignature( + EXPECTATION_SIGNATURE_TYPE_PARENT_PROCESS_NAME, + signature.getValue() + "-agent-" + this.agent.getId()); + } + return signature; + }) + .toList(); + } + public boolean isUserHasAccess(User user) { return getExercise().isUserHasAccess(user); } diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawInjectExpectation.java b/openbas-model/src/main/java/io/openbas/database/raw/RawInjectExpectation.java index bcf81422ff..07df4eb794 100644 --- a/openbas-model/src/main/java/io/openbas/database/raw/RawInjectExpectation.java +++ b/openbas-model/src/main/java/io/openbas/database/raw/RawInjectExpectation.java @@ -12,6 +12,8 @@ public interface RawInjectExpectation { String getUser_id(); + String getAgent_id(); + String getAsset_id(); String getAsset_group_id(); diff --git a/openbas-model/src/main/java/io/openbas/database/raw/impl/SimpleRawInjectExpectation.java b/openbas-model/src/main/java/io/openbas/database/raw/impl/SimpleRawInjectExpectation.java index 45f8db7d89..846ac9fb98 100644 --- a/openbas-model/src/main/java/io/openbas/database/raw/impl/SimpleRawInjectExpectation.java +++ b/openbas-model/src/main/java/io/openbas/database/raw/impl/SimpleRawInjectExpectation.java @@ -18,6 +18,7 @@ public class SimpleRawInjectExpectation implements RawInjectExpectation { private String user_id; private String user_firstname; private String user_lastname; + private String agent_id; private String asset_id; private String asset_name; private String asset_type; diff --git a/openbas-model/src/main/java/io/openbas/database/repository/AgentRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/AgentRepository.java index ed6f8d1f20..60eea48c95 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/AgentRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/AgentRepository.java @@ -1,10 +1,17 @@ package io.openbas.database.repository; import io.openbas.database.model.Agent; +import java.util.List; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface AgentRepository - extends CrudRepository, JpaSpecificationExecutor {} + extends CrudRepository, JpaSpecificationExecutor { + + @Query("SELECT a FROM Agent a WHERE a.asset.id IN :assetIds") + List findAgentsByAssetIds(@Param("assetIds") List assetIds); +} diff --git a/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java index d8248452e5..9d69b06038 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java @@ -101,28 +101,25 @@ List findAllByInjectAndTeam( @Query( value = - "select i from InjectExpectation i where i.inject.id = :injectId and i.asset.id = :assetId") + "select i from InjectExpectation i where i.inject.id = :injectId and i.agent.id = :agentId") + List findAllByInjectAndAgent( + @Param("injectId") @NotBlank final String injectId, + @Param("agentId") @NotBlank final String agentId); + + @Query( + value = + "select i from InjectExpectation i where i.inject.id = :injectId and i.asset.id = :assetId and i.agent is null") List findAllByInjectAndAsset( @Param("injectId") @NotBlank final String injectId, @Param("assetId") @NotBlank final String assetId); @Query( value = - "select i from InjectExpectation i where i.inject.id = :injectId and i.assetGroup.id = :assetGroupId") + "select i from InjectExpectation i where i.inject.id = :injectId and i.assetGroup.id = :assetGroupId and i.asset is null and i.agent is null") List findAllByInjectAndAssetGroup( @Param("injectId") @NotBlank final String injectId, @Param("assetGroupId") @NotBlank final String assetGroupId); - @Query( - value = - "SELECT " - + "team_id, asset_id, asset_group_id, inject_expectation_type, " - + "inject_expectation_score, inject_expectation_group, inject_expectation_expected_score, inject_expectation_id, exercise_id " - + "FROM injects_expectations i " - + "where i.inject_expectation_id IN :ids ;", - nativeQuery = true) - List rawByIds(@Param("ids") final List ids); - @Query( value = "SELECT " @@ -130,6 +127,7 @@ List findAllByInjectAndAssetGroup( + "i.inject_id AS inject_id, " + "i.exercise_id AS exercise_id, " + "i.team_id AS team_id, " + + "i.agent_id AS agent_id, " + "i.asset_id AS asset_id, " + "i.asset_group_id AS asset_group_id, " + "i.inject_expectation_type AS inject_expectation_type, " @@ -149,6 +147,7 @@ List findAllByInjectAndAssetGroup( + "i.inject_id AS inject_id, " + "i.exercise_id AS exercise_id, " + "i.team_id AS team_id, " + + "i.agent_id AS agent_id, " + "i.asset_id AS asset_id, " + "i.asset_group_id AS asset_group_id, " + "i.inject_expectation_type AS inject_expectation_type, " @@ -158,9 +157,10 @@ List findAllByInjectAndAssetGroup( + "i.inject_expectation_group AS inject_expectation_group " + "FROM injects_expectations i " + "WHERE i.inject_id IN (:injectIds) " - + "AND i.user_id is null ;", + + "AND i.user_id is null " + + "AND i.agent_id is null ;", nativeQuery = true) - // We don't include expectations for players, only for the team, if applicable + // We don't include expectations for players, only for the team, neither for agents, if applicable List rawForComputeGlobalByInjectIds( @Param("injectIds") Set injectIds); @@ -171,6 +171,7 @@ List rawForComputeGlobalByInjectIds( + "i.inject_id AS inject_id, " + "i.exercise_id AS exercise_id, " + "i.team_id AS team_id, " + + "i.agent_id AS agent_id, " + "i.asset_id AS asset_id, " + "i.asset_group_id AS asset_group_id, " + "i.inject_expectation_type AS inject_expectation_type, " @@ -180,7 +181,8 @@ List rawForComputeGlobalByInjectIds( + "i.inject_expectation_group AS inject_expectation_group " + "FROM injects_expectations i " + "WHERE i.exercise_id IN (:exerciseIds) " - + "AND i.user_id is null ;", + + "AND i.user_id is null " + + "AND i.agent_id is null ;", nativeQuery = true) // We don't include expectations for players, only for the team, if applicable List rawForComputeGlobalByExerciseIds( diff --git a/openbas-model/src/main/java/io/openbas/database/specification/InjectExpectationSpecification.java b/openbas-model/src/main/java/io/openbas/database/specification/InjectExpectationSpecification.java index d617a33251..d37dfc7274 100644 --- a/openbas-model/src/main/java/io/openbas/database/specification/InjectExpectationSpecification.java +++ b/openbas-model/src/main/java/io/openbas/database/specification/InjectExpectationSpecification.java @@ -18,11 +18,20 @@ public static Specification from(@NotBlank final Instant date return (root, query, cb) -> cb.greaterThanOrEqualTo(root.get("createdAt"), date); } + public static Specification fromAgents( + @NotBlank final String injectId, @NotEmpty final List agentIds) { + return (root, query, cb) -> + cb.and( + cb.equal(root.get("inject").get("id"), injectId), + root.get("agent").get("id").in(agentIds)); + } + public static Specification fromAssets( @NotBlank final String injectId, @NotEmpty final List assetIds) { return (root, query, cb) -> cb.and( cb.equal(root.get("inject").get("id"), injectId), + cb.isNull(root.get("agent")), root.get("asset").get("id").in(assetIds)); } }