Repository: OpenAEV-Platform/openaev (java)

Issue:
Add a breakdown of expectations by security platform
## Context

I want to benchmark the performance of my security platform 



Discussion:

Patch:
diff --git a/openbas-api/src/main/java/io/openbas/migration/V4_21__Force_es_reindex.java b/openbas-api/src/main/java/io/openbas/migration/V4_21__Force_es_reindex.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/main/java/io/openbas/migration/V4_21__Force_es_reindex.java
@@ -0,0 +1,40 @@
+package io.openbas.migration;
+
+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 V4_21__Force_es_reindex extends BaseJavaMigration {
+
+  @Override
+  public void migrate(Context context) throws Exception {
+    try (Statement statement = context.getConnection().createStatement()) {
+      // FIXME replace this trigger by another method, update the updated at attribute of the asset
+      // when we delete an inject from an exercise or a scenario is not the best way to re-index the
+      // endpoints in ElasticSearch
+      // Add trigger after deleting an injects_assets to update the asset updated at
+      statement.executeUpdate(
+          """
+            CREATE OR REPLACE FUNCTION update_asset_updated_at_after_delete_inject()
+                RETURNS TRIGGER AS $$
+            BEGIN
+                UPDATE public.assets
+                SET asset_updated_at = now()
+                WHERE asset_id = OLD.asset_id;
+                RETURN OLD;
+            END;
+            $$ LANGUAGE plpgsql;
+
+            -- Trigger for AFTER DELETE
+            CREATE TRIGGER after_delete_update_asset_updated_at
+                AFTER DELETE ON public.injects_assets
+                FOR EACH ROW EXECUTE FUNCTION update_asset_updated_at_after_delete_inject();
+        """);
+      // re-index in ES
+      statement.executeUpdate(
+          "DELETE FROM indexing_status WHERE indexing_status_type in ('expectation-inject', 'simulation', 'scenario', 'endpoint', 'inject', 'vulnerable-endpoint', 'finding', 'security-platform', 'tag', 'attack-pattern');");
+    }
+  }
+}
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Asset.java b/openbas-model/src/main/java/io/openbas/database/model/Asset.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Asset.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Asset.java
@@ -75,6 +75,12 @@ public class Asset implements Base {
   @JsonProperty("asset_tags")
   private Set<Tag> tags = new HashSet<>();
 
+  // UpdatedAt now used to sync with linked object
+  public void setTags(Set<Tag> tags) {
+    this.updatedAt = now();
+    this.tags = tags;
+  }
+
   @JsonIgnore
   @ManyToMany(mappedBy = "assets")
   private Set<AssetGroup> assetGroups = new HashSet<>();
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Finding.java b/openbas-model/src/main/java/io/openbas/database/model/Finding.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Finding.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Finding.java
@@ -119,6 +119,12 @@ public class Finding implements Base {
   @Queryable(filterable = true, dynamicValues = true, path = "assets.id")
   private List<Asset> assets = new ArrayList<>();
 
+  // UpdatedAt now used to sync with linked object
+  public void setAssets(List<Asset> assets) {
+    this.updateDate = now();
+    this.assets = assets;
+  }
+
   @ArraySchema(schema = @Schema(type = "string"))
   @ManyToMany(fetch = FetchType.LAZY)
   @JoinTable(
diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawEndpoint.java b/openbas-model/src/main/java/io/openbas/database/raw/RawEndpoint.java
--- a/openbas-model/src/main/java/io/openbas/database/raw/RawEndpoint.java
+++ b/openbas-model/src/main/java/io/openbas/database/raw/RawEndpoint.java
@@ -1,5 +1,6 @@
 package io.openbas.database.raw;
 
+import java.time.Instant;
 import java.util.Set;
 
 public interface RawEndpoint extends RawAsset {
@@ -16,4 +17,10 @@ public interface RawEndpoint extends RawAsset {
   String getEndpoint_seen_ip();
 
   boolean getEndpoint_is_eol();
+
+  Set<String> getEndpoint_exercises();
+
+  Set<String> getEndpoint_scenarios();
+
+  Instant getEndpoint_updated_at();
 }
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
--- a/openbas-model/src/main/java/io/openbas/database/raw/RawInjectExpectation.java
+++ b/openbas-model/src/main/java/io/openbas/database/raw/RawInjectExpectation.java
@@ -42,4 +42,8 @@ public interface RawInjectExpectation {
   String getAsset_group_id();
 
   Set<String> getAttack_pattern_ids();
+
+  Set<String> getSecurity_platform_ids();
+
+  String getScenario_id();
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawVulnerableEndpoint.java b/openbas-model/src/main/java/io/openbas/database/raw/RawVulnerableEndpoint.java
--- a/openbas-model/src/main/java/io/openbas/database/raw/RawVulnerableEndpoint.java
+++ b/openbas-model/src/main/java/io/openbas/database/raw/RawVulnerableEndpoint.java
@@ -23,6 +23,8 @@ public interface RawVulnerableEndpoint {
 
   String getVulnerable_endpoint_simulation();
 
+  String getVulnerable_endpoint_scenario();
+
   Set<String> getVulnerable_endpoint_agents();
 
   List<String> getVulnerable_endpoint_agents_privileges();
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
--- 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
@@ -29,6 +29,7 @@ public class SimpleRawInjectExpectation implements RawInjectExpectation {
   private String asset_group_id;
   private String asset_group_name;
   private List<String> asset_ids;
+  private String scenario_id;
   private String exercise_id;
   private String inject_id;
   private Boolean inject_expectation_group;
@@ -39,4 +40,5 @@ public class SimpleRawInjectExpectation implements RawInjectExpectation {
   public Long inject_expiration_time;
   public Instant inject_expectation_updated_at;
   public Set<String> attack_pattern_ids;
+  public Set<String> security_platform_ids;
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/EndpointRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/EndpointRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/EndpointRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/EndpointRepository.java
@@ -127,20 +127,31 @@ List<Object[]> findAllByNameLinkedToFindingsWithContext(
 
   @Query(
       value =
-          "SELECT a.asset_id, a.asset_type, a.asset_name, a.asset_external_reference, "
+          "WITH endpoint_data AS ("
+              + "SELECT a.asset_id, a.asset_type, a.asset_name, a.asset_external_reference, "
               + "a.endpoint_ips, a.endpoint_hostname, a.endpoint_platform, a.endpoint_arch, "
-              + "a.endpoint_mac_addresses, a.endpoint_seen_ip, a.asset_created_at, a.asset_updated_at, "
-              + "a.endpoint_is_eol, a.asset_description, "
-              + "array_agg(fa.finding_id) FILTER ( WHERE fa.finding_id IS NOT NULL ) as asset_findings, "
-              + "array_agg(at.tag_id) FILTER ( WHERE at.tag_id IS NOT NULL ) as asset_tags "
+              + "a.endpoint_mac_addresses, a.endpoint_seen_ip, a.asset_created_at, a.endpoint_is_eol, a.asset_description, "
+              + "GREATEST(a.asset_updated_at, max(i.inject_updated_at), max(e.exercise_updated_at), max(s.scenario_updated_at), max(f.finding_updated_at)) as endpoint_updated_at, "
+              + "array_agg(DISTINCT fa.finding_id) FILTER ( WHERE fa.finding_id IS NOT NULL ) as asset_findings, "
+              + "array_agg(DISTINCT at.tag_id) FILTER ( WHERE at.tag_id IS NOT NULL ) as asset_tags, "
+              + "array_agg(DISTINCT i.inject_exercise) FILTER ( WHERE i.inject_exercise IS NOT NULL ) as endpoint_exercises, "
+              + "array_agg(DISTINCT i.inject_scenario) FILTER ( WHERE i.inject_scenario IS NOT NULL ) as endpoint_scenarios "
               + "FROM assets a "
               + "LEFT JOIN findings_assets fa ON a.asset_id = fa.asset_id "
+              + "LEFT JOIN findings f ON fa.finding_id = f.finding_id "
               + "LEFT JOIN assets_tags at ON a.asset_id = at.asset_id "
-              + "WHERE a.asset_updated_at > :from AND a.asset_type = '"
+              + "LEFT JOIN injects_assets ia ON a.asset_id = ia.asset_id "
+              + "LEFT JOIN injects i ON ia.inject_id = i.inject_id "
+              + "LEFT JOIN exercises e ON i.inject_exercise = e.exercise_id "
+              + "LEFT JOIN scenarios s ON i.inject_scenario = s.scenario_id "
+              + "WHERE a.asset_type = '"
               + AssetType.Values.ENDPOINT_TYPE
               + "' "
-              + "GROUP BY a.asset_id, a.asset_updated_at "
-              + "ORDER BY a.asset_updated_at LIMIT "
+              + "GROUP BY a.asset_id"
+              + ") "
+              + "SELECT * FROM endpoint_data ed "
+              + "WHERE ed.endpoint_updated_at > :from "
+              + "ORDER BY ed.endpoint_updated_at ASC LIMIT "
               + Constants.INDEXING_RECORD_SET_SIZE
               + ";",
       nativeQuery = true)
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java
@@ -354,7 +354,7 @@ List<Object[]> findAllOptionByNameLinkedToFindingsWithContext(
   @Query(
       value =
           "WITH exercise_data AS ("
-              + "SELECT ex.exercise_id, ex.exercise_name, ex.exercise_status, ex.exercise_created_at, MAX(se.scenario_id) AS scenario_id, "
+              + "SELECT ex.exercise_id, ex.exercise_name, ex.exercise_status, ex.exercise_created_at, MAX(se.scenario_id) AS scenario_id, " // MAX here is used to get 1 element and not a list because we know that 1 exercise is linked to only 1 scenario
               + "GREATEST(ex.exercise_updated_at, max(inj.inject_updated_at), max(ic.injector_contract_updated_at)) as exercise_injects_updated_at, "
               + "array_agg(DISTINCT et.tag_id) FILTER ( WHERE et.tag_id IS NOT NULL ) as exercise_tags, "
               + "array_agg(DISTINCT ete.team_id) FILTER ( WHERE ete.team_id IS NOT NULL ) as exercise_teams, "
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
--- a/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java
@@ -238,7 +238,8 @@ List<RawInjectExpectation> rawForComputeGlobalByExerciseIds(
   @Query(
       value =
           """
-    SELECT
+    WITH inject_expectation_data AS (
+      SELECT
       ie.inject_expectation_id,
       ie.inject_expectation_name,
       ie.inject_expectation_description,
@@ -249,7 +250,7 @@ List<RawInjectExpectation> rawForComputeGlobalByExerciseIds(
       ie.inject_expiration_time,
       ie.inject_expectation_group,
       ie.inject_expectation_created_at,
-      ie.inject_expectation_updated_at,
+      GREATEST(ie.inject_expectation_updated_at, max(i.inject_updated_at), max(ic.injector_contract_updated_at)) as inject_expectation_updated_at,
       ie.exercise_id,
       ie.inject_id,
       ie.user_id,
@@ -257,7 +258,9 @@ List<RawInjectExpectation> rawForComputeGlobalByExerciseIds(
       ie.agent_id,
       ie.asset_id,
       ie.asset_group_id,
-      array_agg(ap.attack_pattern_id) AS attack_pattern_ids
+      array_agg(ap.attack_pattern_id) AS attack_pattern_ids,
+      MAX(se.scenario_id) AS scenario_id,
+      array_agg(DISTINCT c.collector_security_platform) FILTER ( WHERE c.collector_security_platform IS NOT NULL ) AS security_platform_ids
     FROM injects_expectations ie
     LEFT JOIN exercises ex ON ex.exercise_id = ie.exercise_id
     LEFT JOIN injects i ON i.inject_id = ie.inject_id
@@ -269,11 +272,16 @@ List<RawInjectExpectation> rawForComputeGlobalByExerciseIds(
     LEFT JOIN agents agent ON agent.agent_id = ie.agent_id
     LEFT JOIN assets asset ON asset.asset_id = ie.asset_id
     LEFT JOIN asset_groups ag ON ag.asset_group_id = ie.asset_group_id
-    WHERE ie.inject_expectation_updated_at > :from
+    LEFT JOIN scenarios_exercises se ON se.exercise_id = ie.exercise_id
+    LEFT JOIN LATERAL jsonb_array_elements(ie.inject_expectation_results::jsonb) AS r(elem) ON true
+    LEFT JOIN collectors c ON r.elem->>'sourceId' = c.collector_id::text OR r.elem->>'sourceType' = 'security-platform'
     GROUP BY
       ie.inject_expectation_id,
       ic.injector_contract_id
-    ORDER BY ie.inject_expectation_updated_at
+    )
+    SELECT * FROM inject_expectation_data ied
+    WHERE ied.inject_expectation_updated_at > :from
+    ORDER BY ied.inject_expectation_updated_at ASC
     LIMIT 500
     """,
       nativeQuery = true)
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/VulnerableEndpointRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/VulnerableEndpointRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/VulnerableEndpointRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/VulnerableEndpointRepository.java
@@ -30,6 +30,11 @@ public interface VulnerableEndpointRepository extends JpaRepository<Endpoint, St
               + "SELECT CONCAT(a.asset_id, '_', i.inject_exercise) as base_id, "
               + "a.asset_id as vulnerable_endpoint_id, "
               + "i.inject_exercise as vulnerable_endpoint_simulation, "
+              + "MAX(se.scenario_id) as vulnerable_endpoint_scenario, " // MAX here is used to get 1
+              // element and not a list
+              // because we know that 1
+              // exercise is linked to
+              // only 1 scenario
               + "a.endpoint_hostname as vulnerable_endpoint_hostname, "
               + "a.endpoint_platform as vulnerable_endpoint_platform, "
               + "a.endpoint_is_eol as vulnerable_endpoint_eol, "
@@ -52,6 +57,7 @@ public interface VulnerableEndpointRepository extends JpaRepository<Endpoint, St
               + "LEFT JOIN assets_tags at ON a.asset_id = at.asset_id "
               + "JOIN injects i ON i.inject_id = f.finding_inject_id "
               + "JOIN exercises e ON i.inject_exercise = e.exercise_id "
+              + "LEFT JOIN scenarios_exercises se ON se.exercise_id = e.exercise_id "
               + "WHERE (e.exercise_updated_at > :from OR a.asset_updated_at > :from) "
               + "AND f.finding_type = 'CVE' "
               + "AND a.asset_type = '"
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/endpoint/EndpointHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/endpoint/EndpointHandler.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/endpoint/EndpointHandler.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/endpoint/EndpointHandler.java
@@ -33,7 +33,7 @@ public List<EsEndpoint> fetch(Instant from) {
               esEndpoint.setBase_id(endpoint.getAsset_id());
               esEndpoint.setBase_representative(endpoint.getAsset_name());
               esEndpoint.setBase_created_at(endpoint.getAsset_created_at());
-              esEndpoint.setBase_updated_at(endpoint.getAsset_updated_at());
+              esEndpoint.setBase_updated_at(endpoint.getEndpoint_updated_at());
               // not sure what to put here, if anything
               esEndpoint.setBase_restrictions(buildRestrictions(endpoint.getAsset_id()));
 
@@ -49,15 +49,24 @@ public List<EsEndpoint> fetch(Instant from) {
               esEndpoint.setEndpoint_is_eol(endpoint.getEndpoint_is_eol());
               // Dependencies
               List<String> dependencies = new ArrayList<>();
-              if (!(endpoint.getAsset_findings() == null)
-                  && !endpoint.getAsset_findings().isEmpty()) {
+              if (endpoint.getAsset_findings() != null && !endpoint.getAsset_findings().isEmpty()) {
                 dependencies.addAll(endpoint.getAsset_findings());
                 esEndpoint.setBase_findings_side(endpoint.getAsset_findings());
               }
-              if (!(endpoint.getAsset_tags() == null) && !endpoint.getAsset_tags().isEmpty()) {
+              if (endpoint.getAsset_tags() != null && !endpoint.getAsset_tags().isEmpty()) {
                 dependencies.addAll(endpoint.getAsset_tags());
                 esEndpoint.setBase_tags_side(endpoint.getAsset_tags());
               }
+              if (endpoint.getEndpoint_exercises() != null
+                  && !endpoint.getEndpoint_exercises().isEmpty()) {
+                dependencies.addAll(endpoint.getEndpoint_exercises());
+                esEndpoint.setBase_simulation_side(endpoint.getEndpoint_exercises());
+              }
+              if (endpoint.getEndpoint_scenarios() != null
+                  && !endpoint.getEndpoint_scenarios().isEmpty()) {
+                dependencies.addAll(endpoint.getEndpoint_scenarios());
+                esEndpoint.setBase_scenario_side(endpoint.getEndpoint_scenarios());
+              }
               esEndpoint.setBase_dependencies(dependencies);
               return esEndpoint;
             })
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/endpoint/EsEndpoint.java b/openbas-model/src/main/java/io/openbas/engine/model/endpoint/EsEndpoint.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/endpoint/EsEndpoint.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/endpoint/EsEndpoint.java
@@ -76,4 +76,14 @@ public class EsEndpoint extends EsBase {
   @Queryable(label = "tags", filterable = true, dynamicValues = true)
   @EsQueryable(keyword = true)
   private Set<String> base_tags_side; // Must finish by _side
+
+  @Queryable(label = "simulation", filterable = true, dynamicValues = true)
+  @EsQueryable(keyword = true)
+  private Set<String>
+      base_simulation_side; // Must finish by _side, no plural (Set) to work as the other generic
+
+  @Queryable(label = "scenario", filterable = true, dynamicValues = true)
+  @EsQueryable(keyword = true)
+  private Set<String>
+      base_scenario_side; // Must finish by _side, no plural (Set) to work as the other generic
 }
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/finding/EsFinding.java b/openbas-model/src/main/java/io/openbas/engine/model/finding/EsFinding.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/finding/EsFinding.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/finding/EsFinding.java
@@ -36,7 +36,7 @@ public class EsFinding extends EsBase {
   @EsQueryable(keyword = true)
   private String base_simulation_side; // Must finish by _side
 
-  @Queryable(label = "scenario", filterable = true)
+  @Queryable(label = "scenario", filterable = true, dynamicValues = true)
   @EsQueryable(keyword = true)
   private String base_scenario_side; // Must finish by _side
 
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/inject/EsInject.java b/openbas-model/src/main/java/io/openbas/engine/model/inject/EsInject.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/inject/EsInject.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/inject/EsInject.java
@@ -30,11 +30,11 @@ public class EsInject extends EsBase {
 
   // -- SIDE --
 
-  @Queryable(label = "scenario")
+  @Queryable(label = "scenario", filterable = true, dynamicValues = true)
   @EsQueryable(keyword = true)
   private String base_scenario_side; // Must finish by _side
 
-  @Queryable(label = "simulation", filterable = true)
+  @Queryable(label = "simulation", filterable = true, dynamicValues = true)
   @EsQueryable(keyword = true)
   private String base_simulation_side; // Must finish by _side
 
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/EsInjectExpectation.java b/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/EsInjectExpectation.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/EsInjectExpectation.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/EsInjectExpectation.java
@@ -59,6 +59,10 @@ public class EsInjectExpectation extends EsBase {
   @EsQueryable(keyword = true)
   private String base_simulation_side; // Must finish by _side
 
+  @Queryable(label = "scenario", filterable = true, dynamicValues = true)
+  @EsQueryable(keyword = true)
+  private String base_scenario_side; // Must finish by _side
+
   @Queryable(label = "inject")
   @EsQueryable(keyword = true)
   private String base_inject_side; // Must finish by _side
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/InjectExpectationHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/InjectExpectationHandler.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/InjectExpectationHandler.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/injectexpectation/InjectExpectationHandler.java
@@ -6,18 +6,12 @@
 import static org.springframework.util.CollectionUtils.isEmpty;
 import static org.springframework.util.StringUtils.hasText;
 
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import io.openbas.database.model.InjectExpectationResult;
 import io.openbas.database.raw.RawInjectExpectation;
 import io.openbas.database.repository.InjectExpectationRepository;
 import io.openbas.engine.Handler;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 
@@ -25,8 +19,6 @@
 @RequiredArgsConstructor
 public class InjectExpectationHandler implements Handler<EsInjectExpectation> {
 
-  private static final String SECURITY_PLATFORM = "security-platform";
-
   private final InjectExpectationRepository injectExpectationRepository;
 
   @Override
@@ -73,6 +65,10 @@ public List<EsInjectExpectation> fetch(Instant from) {
                 dependencies.add(injectExpectation.getExercise_id());
                 esInjectExpectation.setBase_simulation_side(injectExpectation.getExercise_id());
               }
+              if (hasText(injectExpectation.getScenario_id())) {
+                dependencies.add(injectExpectation.getScenario_id());
+                esInjectExpectation.setBase_scenario_side(injectExpectation.getScenario_id());
+              }
               if (hasText(injectExpectation.getInject_id())) {
                 dependencies.add(injectExpectation.getInject_id());
                 esInjectExpectation.setBase_inject_side(injectExpectation.getInject_id());
@@ -102,26 +98,10 @@ public List<EsInjectExpectation> fetch(Instant from) {
                 esInjectExpectation.setBase_attack_patterns_side(
                     injectExpectation.getAttack_pattern_ids());
               }
-              if (injectExpectation.getInject_expectation_results() != null
-                  && !injectExpectation.getInject_expectation_results().isBlank()) {
-                ObjectMapper mapper = new ObjectMapper();
-                try {
-                  List<InjectExpectationResult> results =
-                      mapper.readValue(
-                          injectExpectation.getInject_expectation_results(),
-                          new TypeReference<>() {});
-                  Set<String> securityPlatformIds =
-                      results.stream()
-                          .filter(
-                              injectExpectationResult ->
-                                  SECURITY_PLATFORM.equals(injectExpectationResult.getSourceType()))
-                          .map(InjectExpectationResult::getSourceId)
-                          .collect(Collectors.toSet());
-                  dependencies.addAll(securityPlatformIds);
-                  esInjectExpectation.setBase_security_platforms_side(securityPlatformIds);
-                } catch (Exception e) {
-                  esInjectExpectation.setBase_security_platforms_side(new HashSet<>());
-                }
+              if (!isEmpty(injectExpectation.getSecurity_platform_ids())) {
+                dependencies.addAll(injectExpectation.getSecurity_platform_ids());
+                esInjectExpectation.setBase_security_platforms_side(
+                    injectExpectation.getSecurity_platform_ids());
               }
               esInjectExpectation.setInject_expectation_status(
                   valueOf(computeStatusForIndexing(injectExpectation)));
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/vulnerableendpoint/EsVulnerableEndpoint.java b/openbas-model/src/main/java/io/openbas/engine/model/vulnerableendpoint/EsVulnerableEndpoint.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/vulnerableendpoint/EsVulnerableEndpoint.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/vulnerableendpoint/EsVulnerableEndpoint.java
@@ -72,6 +72,10 @@ public class EsVulnerableEndpoint extends EsBase {
   @EsQueryable(keyword = true)
   private String base_simulation_side; // Must finish by _side
 
+  @Queryable(label = "scenario", filterable = true, dynamicValues = true)
+  @EsQueryable(keyword = true)
+  private String base_scenario_side; // Must finish by _side
+
   @Queryable(label = "agents")
   @EsQueryable(keyword = true)
   private Set<String> base_agents_side; // Must finish by _side
diff --git a/openbas-model/src/main/java/io/openbas/engine/model/vulnerableendpoint/VulnerableEndpointHandler.java b/openbas-model/src/main/java/io/openbas/engine/model/vulnerableendpoint/VulnerableEndpointHandler.java
--- a/openbas-model/src/main/java/io/openbas/engine/model/vulnerableendpoint/VulnerableEndpointHandler.java
+++ b/openbas-model/src/main/java/io/openbas/engine/model/vulnerableendpoint/VulnerableEndpointHandler.java
@@ -137,6 +137,11 @@ first convert to a LocalDateTime (which has no timezone) and
                 esVulnerableEndpoint.setBase_simulation_side(
                     endpoint.getVulnerable_endpoint_simulation());
               }
+              if (endpoint.getVulnerable_endpoint_scenario() != null) {
+                dependencies.add(endpoint.getVulnerable_endpoint_scenario());
+                esVulnerableEndpoint.setBase_scenario_side(
+                    endpoint.getVulnerable_endpoint_scenario());
+              }
               esVulnerableEndpoint.setBase_dependencies(dependencies);
               return esVulnerableEndpoint;
             })


Test patch:
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
--- a/openbas-api/src/test/java/io/openbas/utils/fixtures/RawInjectExpectationFixture.java
+++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/RawInjectExpectationFixture.java
@@ -25,7 +25,9 @@ private record TestableRawInjectExpectation(
       String agentId,
       String assetId,
       String assetGroupId,
-      Set<String> attackPatternIds)
+      Set<String> attackPatternIds,
+      String scenarioId,
+      Set<String> securityPlatformIds)
       implements RawInjectExpectation {
 
     @Override
@@ -122,6 +124,16 @@ public String getAsset_group_id() {
     public Set<String> getAttack_pattern_ids() {
       return attackPatternIds;
     }
+
+    @Override
+    public Set<String> getSecurity_platform_ids() {
+      return securityPlatformIds;
+    }
+
+    @Override
+    public String getScenario_id() {
+      return scenarioId;
+    }
   }
 
   public static RawInjectExpectation createDefaultInjectExpectation(
@@ -145,6 +157,8 @@ public static RawInjectExpectation createDefaultInjectExpectation(
         null,
         null,
         null,
+        null,
+        null,
         null);
   }
 }