Repository: OpenAEV-Platform/openaev (java)

Issue:
RBAC (backend): Add grant support for Atomic Testing/Payload
## Context
Backend does not yet enforce RBAC grants for Atomic Testing and Payload operations.

## Goal
Extend backend grant logic to cover Atomic Testing/Payload.


Discussion:

Patch:
diff --git a/openbas-api/src/main/java/io/openbas/migration/V4_20__Refactor_Grants_Table.java b/openbas-api/src/main/java/io/openbas/migration/V4_20__Refactor_Grants_Table.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/main/java/io/openbas/migration/V4_20__Refactor_Grants_Table.java
@@ -0,0 +1,135 @@
+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 V4_20__Refactor_Grants_Table extends BaseJavaMigration {
+
+  @Override
+  public void migrate(Context context) throws Exception {
+    Connection connection = context.getConnection();
+    Statement stmt = connection.createStatement();
+    stmt.execute(
+        """
+          -- Step 1: Add both new columns
+          ALTER TABLE grants ADD COLUMN grant_resource varchar(255) DEFAULT NULL;
+          ALTER TABLE grants ADD COLUMN grant_resource_type varchar(50);
+          -- Step 2: Migrate data from grant_exercise to grant_resource with type
+          UPDATE grants
+          SET grant_resource = grant_exercise,
+              grant_resource_type = 'SIMULATION'
+          WHERE grant_exercise IS NOT NULL;
+          -- Step 3: Migrate data from grant_scenario to grant_resource with type
+          UPDATE grants
+          SET grant_resource = grant_scenario,
+              grant_resource_type = 'SCENARIO'
+          WHERE grant_scenario IS NOT NULL
+            AND grant_resource IS NULL;
+          -- Step 4: Add NOT NULL constraint to grant_resource_type if desired
+          ALTER TABLE grants
+          ALTER COLUMN grant_resource_type SET NOT NULL;
+          -- Step 5: Drop the unique indexes that reference the old columns
+          DROP INDEX IF EXISTS grant_exercise;
+          DROP INDEX IF EXISTS grant_scenario;
+          -- Step 6: Drop the regular indexes on the old columns
+          DROP INDEX IF EXISTS idx_64adc7d6cae7a988;
+          -- Step 7: Drop the old columns (this will also drop their foreign key constraints)
+          ALTER TABLE grants DROP COLUMN grant_exercise;
+          ALTER TABLE grants DROP COLUMN grant_scenario;
+          -- Step 8: Create new indexes
+          CREATE INDEX idx_grant_resource ON grants (grant_resource);
+          CREATE INDEX idx_grant_resource_type ON grants (grant_resource_type);
+          CREATE INDEX idx_grant_resource_type_resource ON grants (grant_resource_type, grant_resource);
+          -- Step 9: Create a new unique index
+          CREATE UNIQUE INDEX grant_resource_unique ON grants (grant_group, grant_resource, grant_name);
+          -- Step 10: Create the new unified table for group default grants
+          CREATE TABLE groups_default_grants (
+              group_id VARCHAR(255) NOT NULL,
+              grant_type VARCHAR(255) NOT NULL,
+              grant_resource_type VARCHAR(50) NOT NULL,
+              CONSTRAINT fk_groups_default_grants_group_id
+                  FOREIGN KEY (group_id) REFERENCES groups(group_id) ON DELETE CASCADE
+          );
+          -- Step 11: Migrate data from exercises tabl
+          INSERT INTO groups_default_grants (group_id, grant_type, grant_resource_type)
+          SELECT group_id, exercises_default_grants, 'SIMULATION'
+          FROM groups_exercises_default_grants;
+          -- Step 12: Migrate data from scenarios table
+          INSERT INTO groups_default_grants (group_id, grant_type, grant_resource_type)
+          SELECT group_id, scenarios_default_grants, 'SCENARIO'
+          FROM groups_scenarios_default_grants;
+          -- Step 13: Create indexes for performance
+          CREATE INDEX idx_groups_default_grants_group_id ON groups_default_grants(group_id);
+          CREATE INDEX idx_groups_default_grants_resource_type ON groups_default_grants(grant_resource_type);
+          -- Step 14: Drop the old tables
+          DROP TABLE groups_exercises_default_grants;
+          DROP TABLE groups_scenarios_default_grants;
+          """);
+  }
+
+  /* ROLLBACK
+  -- Step 1: Drop the new indexes
+  DROP INDEX IF EXISTS grant_resource_unique;
+  DROP INDEX IF EXISTS idx_grant_resource_type_resource;
+  DROP INDEX IF EXISTS idx_grant_resource_type;
+  DROP INDEX IF EXISTS idx_grant_resource;
+  -- Step 2: Re-add the grant_exercise and grant_scenario columns
+  ALTER TABLE grants
+  ADD COLUMN grant_exercise varchar(255) DEFAULT NULL,
+  ADD COLUMN grant_scenario varchar(255) DEFAULT NULL;
+  -- Step 3: Re-add the foreign key constraints
+  ALTER TABLE grants
+  ADD CONSTRAINT fk_64adc7d6cae7a988
+  FOREIGN KEY (grant_exercise)
+  REFERENCES exercises
+  ON DELETE CASCADE;
+  ALTER TABLE grants
+  ADD CONSTRAINT scenario_fk
+  FOREIGN KEY (grant_scenario)
+  REFERENCES scenarios
+  ON DELETE CASCADE;
+  -- Step 4: Migrate data back based on grant_resource_type
+  UPDATE grants
+  SET grant_exercise = grant_resource
+  WHERE grant_resource_type = 'SIMULATION';
+  UPDATE grants
+  SET grant_scenario = grant_resource
+  WHERE grant_resource_type = 'SCENARIO';
+  -- Step 5: Re-create the original indexes
+  CREATE INDEX idx_64adc7d6cae7a988 ON grants (grant_exercise);
+  CREATE UNIQUE INDEX grant_exercise ON grants (grant_group, grant_exercise, grant_name);
+  CREATE UNIQUE INDEX grant_scenario ON grants (grant_group, grant_scenario, grant_name);
+  -- Step 6: Drop the new columns
+  ALTER TABLE grants
+  DROP COLUMN grant_resource,
+  DROP COLUMN grant_resource_type;
+  -- Step 7: Recreate the old tables
+  CREATE TABLE groups_exercises_default_grants (
+      group_id VARCHAR(255) NOT NULL,
+      exercises_default_grants VARCHAR(255),
+      CONSTRAINT fk_exercises_grants_group_id
+          FOREIGN KEY (group_id) REFERENCES groups(group_id) ON DELETE CASCADE
+  );
+  CREATE TABLE groups_scenarios_default_grants (
+      group_id VARCHAR(255) NOT NULL,
+      scenarios_default_grants VARCHAR(255),
+      CONSTRAINT fk_scenarios_grants_group_id
+          FOREIGN KEY (group_id) REFERENCES groups(group_id) ON DELETE CASCADE
+  );
+  -- Step 8: Migrate data back
+  INSERT INTO groups_exercises_default_grants (group_id, exercises_default_grants)
+  SELECT group_id, grant_type
+  FROM groups_default_grants
+  WHERE grant_resource_type = 'SIMULATION';
+  INSERT INTO groups_scenarios_default_grants (group_id, scenarios_default_grants)
+  SELECT group_id, grant_type
+  FROM groups_default_grants
+  WHERE grant_resource_type = 'SCENARIO';
+  -- Step 9: Drop the unified table
+  DROP TABLE groups_default_grants;
+    */
+}
diff --git a/openbas-api/src/main/java/io/openbas/rest/group/GroupApi.java b/openbas-api/src/main/java/io/openbas/rest/group/GroupApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/group/GroupApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/group/GroupApi.java
@@ -1,7 +1,6 @@
 package io.openbas.rest.group;
 
 import static io.openbas.utils.pagination.PaginationUtils.buildPaginationJPA;
-import static java.time.Instant.now;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.StreamSupport.stream;
 
@@ -15,6 +14,7 @@
 import io.openbas.rest.group.form.GroupUpdateUsersInput;
 import io.openbas.rest.group.form.OrganizationGrantInput;
 import io.openbas.rest.helper.RestBehavior;
+import io.openbas.service.GrantService;
 import io.openbas.service.RoleService;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import io.swagger.v3.oas.annotations.Operation;
@@ -22,61 +22,21 @@
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import jakarta.transaction.Transactional;
 import jakarta.validation.Valid;
-import java.util.Objects;
-import java.util.Optional;
 import java.util.Spliterator;
-import java.util.stream.Collectors;
-import org.springframework.beans.factory.annotation.Autowired;
+import lombok.AllArgsConstructor;
 import org.springframework.data.domain.Page;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
 import org.springframework.web.bind.annotation.*;
 
 @RestController
+@AllArgsConstructor
 public class GroupApi extends RestBehavior {
 
-  private ExerciseRepository exerciseRepository;
-  private ScenarioRepository scenarioRepository;
-  private GrantRepository grantRepository;
-  private OrganizationRepository organizationRepository;
-  private GroupRepository groupRepository;
-  private UserRepository userRepository;
-  private RoleService roleService;
-
-  @Autowired
-  public void setOrganizationRepository(OrganizationRepository organizationRepository) {
-    this.organizationRepository = organizationRepository;
-  }
-
-  @Autowired
-  public void setGrantRepository(GrantRepository grantRepository) {
-    this.grantRepository = grantRepository;
-  }
-
-  @Autowired
-  public void setExerciseRepository(ExerciseRepository exerciseRepository) {
-    this.exerciseRepository = exerciseRepository;
-  }
-
-  @Autowired
-  public void setScenarioRepository(ScenarioRepository scenarioRepository) {
-    this.scenarioRepository = scenarioRepository;
-  }
-
-  @Autowired
-  public void setUserRepository(UserRepository userRepository) {
-    this.userRepository = userRepository;
-  }
-
-  @Autowired
-  public void setGroupRepository(GroupRepository groupRepository) {
-    this.groupRepository = groupRepository;
-  }
-
-  @Autowired
-  public void setRoleService(RoleService roleService) {
-    this.roleService = roleService;
-  }
+  private final GrantRepository grantRepository;
+  private final OrganizationRepository organizationRepository;
+  private final GroupRepository groupRepository;
+  private final UserRepository userRepository;
+  private final RoleService roleService;
+  private final GrantService grantService;
 
   @GetMapping("/api/groups")
   @RBAC(actionPerformed = Action.READ, resourceType = ResourceType.USER_GROUP)
@@ -87,11 +47,7 @@ public Iterable<Group> groups() {
   @PostMapping("/api/groups/search")
   @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.USER_GROUP)
   public Page<Group> users(@RequestBody @Valid final SearchPaginationInput searchPaginationInput) {
-    return buildPaginationJPA(
-        (Specification<Group> specification, Pageable pageable) ->
-            this.groupRepository.findAll(specification, pageable),
-        searchPaginationInput,
-        Group.class);
+    return buildPaginationJPA(this.groupRepository::findAll, searchPaginationInput, Group.class);
   }
 
   @GetMapping("/api/groups/{groupId}")
@@ -109,8 +65,7 @@ public Group group(@PathVariable String groupId) {
   public Group createGroup(@Valid @RequestBody GroupCreateInput input) {
     Group group = new Group();
     group.setUpdateAttributes(input);
-    group.setExercisesDefaultGrants(input.defaultExerciseGrants());
-    group.setScenariosDefaultGrants(input.defaultScenarioGrants());
+    group.setDefaultGrants(input.getDefaultGrants());
     return groupRepository.save(group);
   }
 
@@ -126,20 +81,7 @@ public Group updateGroupUsers(
     Spliterator<User> userSpliterator =
         userRepository.findAllById(input.getUserIds()).spliterator();
     group.setUsers(stream(userSpliterator, false).collect(toList()));
-    Group savedGroup = groupRepository.save(group);
-    // Publish exercises impacted by this group change.
-    exerciseRepository.saveAll(
-        savedGroup.getGrants().stream()
-            .map(Grant::getExercise)
-            .filter(Objects::nonNull)
-            .collect(Collectors.toList()));
-    // Publish scenarios impacted by this group change.
-    scenarioRepository.saveAll(
-        savedGroup.getGrants().stream()
-            .map(Grant::getScenario)
-            .filter(Objects::nonNull)
-            .collect(Collectors.toList()));
-    return savedGroup;
+    return groupRepository.save(group);
   }
 
   @PutMapping("/api/groups/{groupId}/roles")
@@ -186,8 +128,7 @@ public Group updateGroupInformation(
       @PathVariable String groupId, @Valid @RequestBody GroupCreateInput input) {
     Group group = groupRepository.findById(groupId).orElseThrow(ElementNotFoundException::new);
     group.setUpdateAttributes(input);
-    group.setExercisesDefaultGrants(input.defaultExerciseGrants());
-    group.setScenariosDefaultGrants(input.defaultScenarioGrants());
+    group.setDefaultGrants(input.getDefaultGrants());
     return groupRepository.save(group);
   }
 
@@ -197,51 +138,35 @@ public Group updateGroupInformation(
       actionPerformed = Action.WRITE,
       resourceType = ResourceType.USER_GROUP)
   @Transactional(rollbackOn = Exception.class)
-  public Grant groupGrant(@PathVariable String groupId, @Valid @RequestBody GroupGrantInput input) {
-    if (input.getExerciseId() == null && input.getScenarioId() == null) {
-      throw new IllegalArgumentException("At least one of exercise or scenario should be present");
-    }
+  public Group groupGrant(@PathVariable String groupId, @Valid @RequestBody GroupGrantInput input) {
+    // Validate the resourceId
+    grantService.validateResourceIdForGrant(input.getResourceId());
 
     // Resolve dependencies
     Group group = groupRepository.findById(groupId).orElseThrow(ElementNotFoundException::new);
-    Optional<Exercise> exerciseOpt =
-        input.getExerciseId() == null
-            ? Optional.empty()
-            : exerciseRepository.findById(input.getExerciseId());
-    Optional<Scenario> scenarioOpt =
-        input.getScenarioId() == null
-            ? Optional.empty()
-            : scenarioRepository.findById(input.getScenarioId());
 
     // Create the grant
     Grant grant = new Grant();
     grant.setName(input.getName());
     grant.setGroup(group);
-    if (exerciseOpt.isPresent()) {
-      grant.setExercise(exerciseOpt.get());
-    }
-    if (scenarioOpt.isPresent()) {
-      grant.setScenario(scenarioOpt.get());
-    }
-    Grant savedGrant = grantRepository.save(grant);
+    grant.setResourceId(input.getResourceId());
+    grant.setGrantResourceType(input.getResourceType());
 
-    // Exercise
-    if (exerciseOpt.isPresent()) {
-      Exercise exercise = exerciseOpt.get();
-      exercise.getGrants().add(savedGrant);
-      exercise.setUpdatedAt(now());
-      exerciseRepository.save(exercise);
-    }
-
-    // Scenario
-    if (scenarioOpt.isPresent()) {
-      Scenario scenario = scenarioOpt.get();
-      scenario.getGrants().add(savedGrant);
-      scenario.setUpdatedAt(now());
-      scenarioRepository.save(scenario);
-    }
+    group.getGrants().add(grant);
+    return groupRepository.save(group);
+  }
 
-    return savedGrant;
+  @DeleteMapping("/api/groups/{groupId}/grants/{grantId}")
+  @RBAC(
+      resourceId = "#groupId",
+      actionPerformed = Action.WRITE,
+      resourceType = ResourceType.USER_GROUP)
+  @Transactional(rollbackOn = Exception.class)
+  public Group deleteGrant(@PathVariable String groupId, @PathVariable String grantId) {
+    Group group = groupRepository.findById(groupId).orElseThrow(ElementNotFoundException::new);
+    Grant grant = grantRepository.findById(grantId).orElseThrow(ElementNotFoundException::new);
+    group.getGrants().remove(grant);
+    return this.groupRepository.save(group);
   }
 
   @PostMapping("/api/groups/{groupId}/organizations")
@@ -276,16 +201,6 @@ public Group deleteGroupOrganization(
     return groupRepository.save(group);
   }
 
-  @DeleteMapping("/api/grants/{grantId}")
-  @RBAC(
-      resourceId = "#grantId",
-      actionPerformed = Action.WRITE,
-      resourceType = ResourceType.USER_GROUP)
-  @Transactional(rollbackOn = Exception.class)
-  public void deleteGrant(@PathVariable String grantId) {
-    grantRepository.deleteById(grantId);
-  }
-
   @DeleteMapping("/api/groups/{groupId}")
   @RBAC(
       resourceId = "#groupId",
diff --git a/openbas-api/src/main/java/io/openbas/rest/group/form/GroupCreateInput.java b/openbas-api/src/main/java/io/openbas/rest/group/form/GroupCreateInput.java
--- a/openbas-api/src/main/java/io/openbas/rest/group/form/GroupCreateInput.java
+++ b/openbas-api/src/main/java/io/openbas/rest/group/form/GroupCreateInput.java
@@ -3,10 +3,10 @@
 import static io.openbas.config.AppConfig.MANDATORY_MESSAGE;
 
 import com.fasterxml.jackson.annotation.JsonProperty;
-import io.openbas.database.model.Grant;
+import io.openbas.database.model.DefaultGrant;
 import jakarta.validation.constraints.NotBlank;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
 import lombok.Getter;
 import lombok.Setter;
 
@@ -21,40 +21,9 @@ public class GroupCreateInput {
   @JsonProperty("group_description")
   private String description;
 
+  @JsonProperty("group_default_grants")
+  private Set<DefaultGrant> defaultGrants = new HashSet<>();
+
   @JsonProperty("group_default_user_assign")
   private boolean defaultUserAssignation;
-
-  @JsonProperty("group_default_exercise_observer")
-  private boolean defaultExerciseObserver;
-
-  @JsonProperty("group_default_exercise_planner")
-  private boolean defaultExercisePlanner;
-
-  @JsonProperty("group_default_scenario_observer")
-  private boolean defaultScenarioObserver;
-
-  @JsonProperty("group_default_scenario_planner")
-  private boolean defaultScenarioPlanner;
-
-  public List<Grant.GRANT_TYPE> defaultExerciseGrants() {
-    List<Grant.GRANT_TYPE> grants = new ArrayList<>();
-    if (this.defaultExercisePlanner) {
-      grants.add(Grant.GRANT_TYPE.PLANNER);
-    }
-    if (this.defaultExerciseObserver) {
-      grants.add(Grant.GRANT_TYPE.OBSERVER);
-    }
-    return grants;
-  }
-
-  public List<Grant.GRANT_TYPE> defaultScenarioGrants() {
-    List<Grant.GRANT_TYPE> grants = new ArrayList<>();
-    if (this.defaultScenarioPlanner) {
-      grants.add(Grant.GRANT_TYPE.PLANNER);
-    }
-    if (this.defaultScenarioObserver) {
-      grants.add(Grant.GRANT_TYPE.OBSERVER);
-    }
-    return grants;
-  }
 }
diff --git a/openbas-api/src/main/java/io/openbas/rest/group/form/GroupGrantInput.java b/openbas-api/src/main/java/io/openbas/rest/group/form/GroupGrantInput.java
--- a/openbas-api/src/main/java/io/openbas/rest/group/form/GroupGrantInput.java
+++ b/openbas-api/src/main/java/io/openbas/rest/group/form/GroupGrantInput.java
@@ -12,9 +12,9 @@ public class GroupGrantInput {
   @JsonProperty("grant_name")
   private Grant.GRANT_TYPE name;
 
-  @JsonProperty("grant_exercise")
-  private String exerciseId;
+  @JsonProperty("grant_resource")
+  private String resourceId;
 
-  @JsonProperty("grant_scenario")
-  private String scenarioId;
+  @JsonProperty("grant_resource_type")
+  private Grant.GRANT_RESOURCE_TYPE resourceType;
 }
diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/service/InjectService.java b/openbas-api/src/main/java/io/openbas/rest/inject/service/InjectService.java
--- a/openbas-api/src/main/java/io/openbas/rest/inject/service/InjectService.java
+++ b/openbas-api/src/main/java/io/openbas/rest/inject/service/InjectService.java
@@ -96,6 +96,7 @@ public class InjectService {
   private final TagRepository tagRepository;
   private final DocumentRepository documentRepository;
   private final PayloadRepository payloadRepository;
+  private final GrantService grantService;
 
   private final LicenseCacheManager licenseCacheManager;
   @Resource protected ObjectMapper mapper;
@@ -1009,37 +1010,33 @@ public Specification<Inject> hasGrantAccessForInject(
 
       // Check if both are null - automatically granted
       Path<Object> scenarioPath = root.get("scenario");
-      Path<Object> exercisePath = root.get("exercise");
-      Predicate bothNull = cb.and(cb.isNull(scenarioPath), cb.isNull(exercisePath));
+      Path<Object> simulationPath = root.get("exercise");
+      // Check if both are null
+      Predicate bothNull = cb.and(cb.isNull(scenarioPath), cb.isNull(simulationPath));
 
       // Get allowed grant types
       List<Grant.GRANT_TYPE> allowedGrantTypes = grantType.andHigher();
+
       // Create subquery for accessible scenarios
       Subquery<String> accessibleScenarios =
-          SpecificationUtils.accessibleScenariosSubquery(query, cb, userId, allowedGrantTypes);
-      // Create subquery for accessible exercises
-      Subquery<String> accessibleExercises =
-          SpecificationUtils.accessibleSimulationsSubquery(query, cb, userId, allowedGrantTypes);
+          SpecificationUtils.accessibleResourcesSubquery(
+              query, cb, userId, Grant.GRANT_RESOURCE_TYPE.SCENARIO, allowedGrantTypes);
+      // Create subquery for accessible simulations
+      Subquery<String> accessibleSimulations =
+          SpecificationUtils.accessibleResourcesSubquery(
+              query, cb, userId, Grant.GRANT_RESOURCE_TYPE.SIMULATION, allowedGrantTypes);
+
       // Check if inject's scenario ID is accessible (null is OK)
-      Predicate scenarioAccessible;
-      if (scenarioPath != null) {
-        scenarioAccessible =
-            cb.or(cb.isNull(scenarioPath), scenarioPath.get("id").in(accessibleScenarios));
-      } else {
-        scenarioAccessible = cb.conjunction(); // Always true if no scenario field
-      }
-      // Check if inject's exercise ID is accessible (null is OK)
-      Predicate exerciseAccessible;
-      if (exercisePath != null) {
-        exerciseAccessible =
-            cb.or(cb.isNull(exercisePath), exercisePath.get("id").in(accessibleExercises));
-      } else {
-        exerciseAccessible = cb.conjunction(); // Always true if no exercise field
-      }
+      Predicate scenarioAccessible =
+          cb.or(cb.isNull(scenarioPath), scenarioPath.get("id").in(accessibleScenarios));
+      // Check if inject's simulation ID is accessible (null is OK)
+      Predicate simulationAccessible =
+          cb.or(cb.isNull(simulationPath), simulationPath.get("id").in(accessibleSimulations));
+
       // Inject is accessible if:
       // 1. Both scenario and exercise are null (automatically granted), OR
       // 2. User has access to the non-null scenario/exercise
-      return cb.or(bothNull, cb.and(scenarioAccessible, exerciseAccessible));
+      return cb.or(bothNull, cb.and(scenarioAccessible, simulationAccessible));
     };
   }
 }
diff --git a/openbas-api/src/main/java/io/openbas/rest/organization/OrganizationApi.java b/openbas-api/src/main/java/io/openbas/rest/organization/OrganizationApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/organization/OrganizationApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/organization/OrganizationApi.java
@@ -19,37 +19,27 @@
 import io.openbas.rest.helper.RestBehavior;
 import io.openbas.rest.organization.form.OrganizationCreateInput;
 import io.openbas.rest.organization.form.OrganizationUpdateInput;
+import io.openbas.service.organization.OrganizationService;
 import io.openbas.utils.FilterUtilsJpa;
+import io.openbas.utils.pagination.SearchPaginationInput;
 import jakarta.transaction.Transactional;
 import jakarta.validation.Valid;
 import java.util.List;
-import org.springframework.beans.factory.annotation.Autowired;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Sort;
 import org.springframework.web.bind.annotation.*;
 
 @RestController
+@RequiredArgsConstructor
 public class OrganizationApi extends RestBehavior {
 
   public static final String ORGANIZATION_URI = "/api/organizations";
 
-  private OrganizationRepository organizationRepository;
-  private TagRepository tagRepository;
-  private UserRepository userRepository;
-
-  @Autowired
-  public void setUserRepository(UserRepository userRepository) {
-    this.userRepository = userRepository;
-  }
-
-  @Autowired
-  public void setTagRepository(TagRepository tagRepository) {
-    this.tagRepository = tagRepository;
-  }
-
-  @Autowired
-  public void setOrganizationRepository(OrganizationRepository organizationRepository) {
-    this.organizationRepository = organizationRepository;
-  }
+  private final OrganizationRepository organizationRepository;
+  private final TagRepository tagRepository;
+  private final UserRepository userRepository;
+  private final OrganizationService organizationService;
 
   @GetMapping(ORGANIZATION_URI)
   @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.ORGANIZATION)
@@ -64,6 +54,13 @@ public Iterable<RawOrganization> organizations() {
     return organizations;
   }
 
+  @PostMapping(ORGANIZATION_URI + "/search")
+  @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.ORGANIZATION)
+  public Page<Organization> organizations(
+      @RequestBody @Valid final SearchPaginationInput searchPaginationInput) {
+    return this.organizationService.organizationPagination(searchPaginationInput);
+  }
+
   @PostMapping(ORGANIZATION_URI)
   @RBAC(actionPerformed = Action.CREATE, resourceType = ResourceType.ORGANIZATION)
   @Transactional(rollbackOn = Exception.class)
diff --git a/openbas-api/src/main/java/io/openbas/service/GrantService.java b/openbas-api/src/main/java/io/openbas/service/GrantService.java
--- a/openbas-api/src/main/java/io/openbas/service/GrantService.java
+++ b/openbas-api/src/main/java/io/openbas/service/GrantService.java
@@ -2,66 +2,44 @@
 
 import static io.openbas.helper.StreamHelper.fromIterable;
 
-import io.openbas.database.model.Exercise;
-import io.openbas.database.model.Grant;
-import io.openbas.database.model.Group;
-import io.openbas.database.model.Scenario;
-import io.openbas.database.model.User;
-import io.openbas.database.repository.GrantRepository;
-import io.openbas.database.repository.GroupRepository;
+import io.openbas.database.model.*;
+import io.openbas.database.repository.*;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
-import java.util.EnumSet;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 import reactor.util.function.Tuples;
 
 @Service
 @RequiredArgsConstructor
 public class GrantService {
-  private static final EnumSet<Grant.GRANT_TYPE> READ_AUTHORIZED_GRANTS =
-      EnumSet.of(Grant.GRANT_TYPE.OBSERVER, Grant.GRANT_TYPE.PLANNER, Grant.GRANT_TYPE.LAUNCHER);
-  private static final EnumSet<Grant.GRANT_TYPE> WRITE_AUTHORIZED_GRANTS =
-      EnumSet.of(Grant.GRANT_TYPE.PLANNER, Grant.GRANT_TYPE.LAUNCHER);
+
   private final GroupRepository groupRepository;
   private final GrantRepository grantRepository;
+  private final ExerciseRepository exerciseRepository;
+  private final ScenarioRepository scenarioRepository;
+  private final InjectRepository injectRepository;
+  private final PayloadRepository payloadRepository;
 
-  public void computeGrant(@NotNull Exercise exercise) {
+  public <T extends GrantableBase> void computeGrant(@NotNull T resource) {
+    // Extract grant resource type
+    Grant.GRANT_RESOURCE_TYPE grantResourceType =
+        GrantableBase.getGrantResourceType(resource.getClass());
     // Find automatic groups to grants
     List<Group> groups = fromIterable(this.groupRepository.findAll());
     List<Grant> grants =
         groups.stream()
-            .filter(group -> !group.getExercisesDefaultGrants().isEmpty())
-            .flatMap(
-                group ->
-                    group.getExercisesDefaultGrants().stream()
-                        .distinct()
-                        .map(s -> Tuples.of(group, s)))
-            .map(
-                tuple -> {
-                  Grant grant = new Grant();
-                  grant.setGroup(tuple.getT1());
-                  grant.setName(tuple.getT2());
-                  grant.setExercise(exercise);
-                  return grant;
+            .filter(
+                group -> {
+                  List<Grant.GRANT_TYPE> defaultGrants =
+                      group.getDefaultGrantsMap().get(grantResourceType);
+                  return defaultGrants != null && !defaultGrants.isEmpty();
                 })
-            .toList();
-    if (!grants.isEmpty()) {
-      Iterable<Grant> exerciseGrants = this.grantRepository.saveAll(grants);
-      exercise.setGrants(fromIterable(exerciseGrants));
-    }
-  }
-
-  public void computeGrant(@NotNull Scenario scenario) {
-    // Find automatic groups to grants
-    List<Group> groups = fromIterable(this.groupRepository.findAll());
-    List<Grant> grants =
-        groups.stream()
-            .filter(group -> !group.getScenariosDefaultGrants().isEmpty())
             .flatMap(
                 group ->
-                    group.getScenariosDefaultGrants().stream()
+                    group.getDefaultGrantsMap().get(grantResourceType).stream()
                         .distinct()
                         .map(s -> Tuples.of(group, s)))
             .map(
@@ -69,13 +47,15 @@ public void computeGrant(@NotNull Scenario scenario) {
                   Grant grant = new Grant();
                   grant.setGroup(tuple.getT1());
                   grant.setName(tuple.getT2());
-                  grant.setScenario(scenario);
+                  grant.setResourceId(resource.getId());
+                  grant.setGrantResourceType(grantResourceType);
                   return grant;
                 })
             .toList();
+
     if (!grants.isEmpty()) {
-      Iterable<Grant> scenarioGrants = this.grantRepository.saveAll(grants);
-      scenario.setGrants(fromIterable(scenarioGrants));
+      Iterable<Grant> savedGrants = this.grantRepository.saveAll(grants);
+      resource.setGrants(fromIterable(savedGrants));
     }
   }
 
@@ -95,11 +75,31 @@ private boolean hasGrant(
       @NotBlank final String resourceId,
       @NotNull final User user,
       @NotNull final Grant.GRANT_TYPE grantType) {
-    return this.grantRepository.rawByResourceIdAndUserId(resourceId, user.getId()).stream()
-        .anyMatch(
-            rawGrant -> {
-              Grant.GRANT_TYPE rawToGrant = Grant.GRANT_TYPE.valueOf(rawGrant.getGrant_name());
-              return rawToGrant.getPriority() >= grantType.getPriority();
-            });
+    return this.grantRepository.existsByUserIdAndResourceIdAndNameIn(
+        resourceId, user.getId(), grantType.andHigher());
+  }
+
+  /**
+   * Validates that the resource ID is not blank and exists in one of the grantable resource
+   * repositories.
+   *
+   * @param resourceId the resource ID to validate
+   * @throws IllegalArgumentException if the resource ID is blank or does not exist
+   */
+  public void validateResourceIdForGrant(String resourceId) {
+    if (StringUtils.isBlank(resourceId)) {
+      throw new IllegalArgumentException("A valid resource ID should be present");
+    }
+
+    boolean exists =
+        exerciseRepository.existsById(resourceId)
+            || scenarioRepository.existsById(resourceId)
+            || injectRepository.existsByIdAndScenarioIsNullAndExerciseIsNull(resourceId)
+            || // Atomic Testing
+            payloadRepository.existsById(resourceId);
+
+    if (!exists) {
+      throw new IllegalArgumentException("A valid resource ID should be present");
+    }
   }
 }
diff --git a/openbas-api/src/main/java/io/openbas/service/ScenarioToExerciseService.java b/openbas-api/src/main/java/io/openbas/service/ScenarioToExerciseService.java
--- a/openbas-api/src/main/java/io/openbas/service/ScenarioToExerciseService.java
+++ b/openbas-api/src/main/java/io/openbas/service/ScenarioToExerciseService.java
@@ -67,6 +67,8 @@ public Exercise toExercise(
     Exercise exerciseSaved = this.exerciseRepository.save(exercise);
 
     // Grants
+    Grant.GRANT_RESOURCE_TYPE grantResourceTypeForSimulation =
+        GrantableBase.getGrantResourceType(Exercise.class);
     List<Grant> exerciseGrants =
         scenario.getGrants().stream()
             .map(
@@ -74,7 +76,8 @@ public Exercise toExercise(
                   Grant grant = new Grant();
                   grant.setName(scenarioGrant.getName());
                   grant.setGroup(scenarioGrant.getGroup());
-                  grant.setExercise(exerciseSaved);
+                  grant.setResourceId(exerciseSaved.getId());
+                  grant.setGrantResourceType(grantResourceTypeForSimulation);
                   return this.grantRepository.save(grant);
                 })
             .toList();
diff --git a/openbas-api/src/main/java/io/openbas/service/organization/OrganizationService.java b/openbas-api/src/main/java/io/openbas/service/organization/OrganizationService.java
new file mode 100644
--- /dev/null
+++ b/openbas-api/src/main/java/io/openbas/service/organization/OrganizationService.java
@@ -0,0 +1,42 @@
+package io.openbas.service.organization;
+
+import static io.openbas.config.SessionHelper.currentUser;
+import static io.openbas.database.specification.OrganizationSpecification.findGrantedFor;
+import static io.openbas.utils.pagination.PaginationUtils.buildPaginationJPA;
+
+import io.openbas.config.OpenBASPrincipal;
+import io.openbas.database.model.Organization;
+import io.openbas.database.repository.OrganizationRepository;
+import io.openbas.utils.pagination.SearchPaginationInput;
+import lombok.RequiredArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class OrganizationService {
+
+  private final OrganizationRepository organizationRepository;
+
+  public Page<Organization> organizationPagination(
+      @NotNull SearchPaginationInput searchPaginationInput) {
+    OpenBASPrincipal currentUser = currentUser();
+    if (currentUser.isAdmin()) {
+      return buildPaginationJPA(
+          (Specification<Organization> specification, Pageable pageable) ->
+              this.organizationRepository.findAll(specification, pageable),
+          searchPaginationInput,
+          Organization.class);
+    } else {
+      return buildPaginationJPA(
+          (Specification<Organization> specification, Pageable pageable) ->
+              this.organizationRepository.findAll(
+                  findGrantedFor(currentUser.getId()).and(specification), pageable),
+          searchPaginationInput,
+          Organization.class);
+    }
+  }
+}
diff --git a/openbas-model/src/main/java/io/openbas/database/model/DefaultGrant.java b/openbas-model/src/main/java/io/openbas/database/model/DefaultGrant.java
new file mode 100644
--- /dev/null
+++ b/openbas-model/src/main/java/io/openbas/database/model/DefaultGrant.java
@@ -0,0 +1,27 @@
+package io.openbas.database.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import lombok.*;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@EqualsAndHashCode
+@Embeddable
+public class DefaultGrant {
+
+  @JsonProperty("grant_type")
+  @Column(name = "grant_type", nullable = false)
+  @Enumerated(EnumType.STRING)
+  private Grant.GRANT_TYPE grantType;
+
+  @JsonProperty("grant_resource_type")
+  @Column(name = "grant_resource_type", nullable = false, length = 50)
+  @Enumerated(EnumType.STRING)
+  private Grant.GRANT_RESOURCE_TYPE grantResourceType;
+}
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Exercise.java b/openbas-model/src/main/java/io/openbas/database/model/Exercise.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Exercise.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Exercise.java
@@ -27,6 +27,7 @@
 import lombok.Getter;
 import lombok.Setter;
 import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.SQLRestriction;
 import org.hibernate.annotations.UpdateTimestamp;
 import org.hibernate.annotations.UuidGenerator;
 
@@ -34,8 +35,8 @@
 @Entity
 @Table(name = "exercises")
 @EntityListeners(ModelBaseListener.class)
-@Grantable(grantFieldName = "exercise")
-public class Exercise implements Base {
+@Grantable(Grant.GRANT_RESOURCE_TYPE.SIMULATION)
+public class Exercise implements GrantableBase {
 
   @Getter
   @Id
@@ -201,7 +202,14 @@ public void setScenario(Scenario scenario) {
   private CustomDashboard customDashboard;
 
   @Getter
-  @OneToMany(mappedBy = "exercise", fetch = FetchType.EAGER)
+  @OneToMany(fetch = FetchType.EAGER)
+  @JoinColumn(
+      name = "grant_resource",
+      referencedColumnName = "exercise_id",
+      insertable = false,
+      updatable = false)
+  @SQLRestriction(
+      "grant_resource_type = 'SIMULATION'") // Must be present in Grant.GRANT_RESOURCE_TYPE
   @JsonIgnore
   private List<Grant> grants = new ArrayList<>();
 
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Grant.java b/openbas-model/src/main/java/io/openbas/database/model/Grant.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Grant.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Grant.java
@@ -1,6 +1,5 @@
 package io.openbas.database.model;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import io.openbas.helper.MonoIdDeserializer;
@@ -22,6 +21,14 @@
 @Table(name = "grants")
 public class Grant implements Base {
 
+  public enum GRANT_RESOURCE_TYPE {
+    SCENARIO,
+    SIMULATION,
+    ATOMIC_TESTING,
+    PAYLOAD,
+    UNKNOWN;
+  }
+
   public enum GRANT_TYPE {
     OBSERVER(1),
     PLANNER(2),
@@ -76,19 +83,15 @@ public List<GRANT_TYPE> andHigher() {
   @Schema(type = "string")
   private Group group;
 
-  @ManyToOne(fetch = FetchType.LAZY)
-  @JoinColumn(name = "grant_exercise")
-  @JsonSerialize(using = MonoIdDeserializer.class)
-  @JsonProperty("grant_exercise")
+  @Column(name = "grant_resource")
+  @JsonProperty("grant_resource")
   @Schema(type = "string")
-  private Exercise exercise;
+  private String resourceId;
 
-  @ManyToOne(fetch = FetchType.LAZY)
-  @JoinColumn(name = "grant_scenario")
-  @JsonSerialize(using = MonoIdDeserializer.class)
-  @JsonProperty("grant_scenario")
-  @Schema(type = "string")
-  private Scenario scenario;
+  @Enumerated(EnumType.STRING)
+  @Column(name = "grant_resource_type")
+  @JsonProperty("grant_resource_type")
+  private GRANT_RESOURCE_TYPE grantResourceType = GRANT_RESOURCE_TYPE.UNKNOWN;
 
   @Override
   public boolean isUserHasAccess(User user) {
@@ -107,11 +110,4 @@ public boolean equals(Object o) {
   public int hashCode() {
     return Objects.hash(id);
   }
-
-  @JsonIgnore
-  public String getResourceId() {
-    return this.getScenario() != null
-        ? this.getScenario().getId()
-        : this.getExercise() != null ? this.getExercise().getId() : null;
-  }
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Grantable.java b/openbas-model/src/main/java/io/openbas/database/model/Grantable.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Grantable.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Grantable.java
@@ -5,19 +5,11 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-/**
- * Annotation to mark a resource type that can be granted permissions. This is used to link a class
- * to an RBAC ResourceType and to specify on which Grant field the link is done. For example, for
- * the Scenario class, the annotation would be: @Grantable(grantFieldName = "scenario"). Where
- * "scenario" is the name of the field in the Grant entity that corresponds to this resource type.
- */
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface Grantable {
   /**
-   * The name of the field in Grant entity that corresponds to this resource type.
-   *
-   * @return the associated grant field name
+   * @return the GRANT_RESOURCE_TYPE associated with the annotated class
    */
-  String grantFieldName();
+  Grant.GRANT_RESOURCE_TYPE value();
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/model/GrantableBase.java b/openbas-model/src/main/java/io/openbas/database/model/GrantableBase.java
new file mode 100644
--- /dev/null
+++ b/openbas-model/src/main/java/io/openbas/database/model/GrantableBase.java
@@ -0,0 +1,19 @@
+package io.openbas.database.model;
+
+import java.util.List;
+
+public interface GrantableBase extends Base {
+
+  static Grant.GRANT_RESOURCE_TYPE getGrantResourceType(Class<?> clazz) {
+    Grantable annotation = clazz.getAnnotation(Grantable.class);
+    if (annotation == null) {
+      throw new IllegalArgumentException(
+          "Missing @GrantResource annotation on class: " + clazz.getName());
+    }
+    return annotation.value();
+  }
+
+  List<Grant> getGrants();
+
+  void setGrants(List<Grant> grants);
+}
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Group.java b/openbas-model/src/main/java/io/openbas/database/model/Group.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Group.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Group.java
@@ -1,6 +1,7 @@
 package io.openbas.database.model;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import io.openbas.annotation.Queryable;
@@ -12,6 +13,7 @@
 import jakarta.persistence.*;
 import jakarta.validation.constraints.NotBlank;
 import java.util.*;
+import java.util.stream.Collectors;
 import lombok.Getter;
 import lombok.Setter;
 import org.hibernate.annotations.Fetch;
@@ -48,23 +50,17 @@ public class Group implements Base {
   @JsonProperty("group_default_user_assign")
   private boolean defaultUserAssignation;
 
-  @JsonProperty("group_default_exercise_assign")
-  @Enumerated(EnumType.STRING)
-  @ElementCollection
-  @CollectionTable(
-      name = "groups_exercises_default_grants",
-      joinColumns = @JoinColumn(name = "group_id"))
-  private List<Grant.GRANT_TYPE> exercisesDefaultGrants = new ArrayList<>();
-
-  @JsonProperty("group_default_scenario_assign")
-  @Enumerated(EnumType.STRING)
-  @ElementCollection
-  @CollectionTable(
-      name = "groups_scenarios_default_grants",
-      joinColumns = @JoinColumn(name = "group_id"))
-  private List<Grant.GRANT_TYPE> scenariosDefaultGrants = new ArrayList<>();
-
-  @OneToMany(mappedBy = "group", fetch = FetchType.EAGER)
+  @ElementCollection(fetch = FetchType.EAGER)
+  @CollectionTable(name = "groups_default_grants", joinColumns = @JoinColumn(name = "group_id"))
+  @JsonProperty("group_default_grants")
+  @JsonInclude(JsonInclude.Include.NON_NULL)
+  private Set<DefaultGrant> defaultGrants = new HashSet<>();
+
+  @OneToMany(
+      mappedBy = "group",
+      fetch = FetchType.EAGER,
+      cascade = CascadeType.ALL,
+      orphanRemoval = true)
   @JsonProperty("group_grants")
   @JsonSerialize(using = MultiModelDeserializer.class)
   @Fetch(value = FetchMode.SUBSELECT)
@@ -106,24 +102,33 @@ public class Group implements Base {
   private final ResourceType resourceType = ResourceType.USER_GROUP;
 
   // region transient
-  @JsonProperty("group_default_exercise_planner")
-  public boolean isDefaultExercisePlanner() {
-    return exercisesDefaultGrants.contains(Grant.GRANT_TYPE.PLANNER);
-  }
-
-  @JsonProperty("group_default_exercise_observer")
-  public boolean isDefaultExerciseObserver() {
-    return exercisesDefaultGrants.contains(Grant.GRANT_TYPE.OBSERVER);
+  // Convert to Map for easier access
+  @Transient
+  @JsonIgnore
+  public Map<Grant.GRANT_RESOURCE_TYPE, List<Grant.GRANT_TYPE>> getDefaultGrantsMap() {
+    return defaultGrants.stream()
+        .collect(
+            Collectors.groupingBy(
+                DefaultGrant::getGrantResourceType,
+                Collectors.mapping(DefaultGrant::getGrantType, Collectors.toList())));
   }
 
-  @JsonProperty("group_default_scenario_planner")
-  public boolean isDefaultScenarioPlanner() {
-    return scenariosDefaultGrants.contains(Grant.GRANT_TYPE.PLANNER);
+  @JsonIgnore
+  @Transient
+  public List<Grant.GRANT_TYPE> getSimulationsDefaultGrants() {
+    return defaultGrants.stream()
+        .filter(dg -> dg.getGrantResourceType() == Grant.GRANT_RESOURCE_TYPE.SIMULATION)
+        .map(DefaultGrant::getGrantType)
+        .collect(Collectors.toList());
   }
 
-  @JsonProperty("group_default_scenario_observer")
-  public boolean isDefaultScenarioObserver() {
-    return scenariosDefaultGrants.contains(Grant.GRANT_TYPE.OBSERVER);
+  @JsonIgnore
+  @Transient
+  public List<Grant.GRANT_TYPE> getScenariosDefaultGrants() {
+    return defaultGrants.stream()
+        .filter(dg -> dg.getGrantResourceType() == Grant.GRANT_RESOURCE_TYPE.SCENARIO)
+        .map(DefaultGrant::getGrantType)
+        .collect(Collectors.toList());
   }
 
   // endregion
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Organization.java b/openbas-model/src/main/java/io/openbas/database/model/Organization.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Organization.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Organization.java
@@ -53,6 +53,7 @@ public class Organization implements Base {
   @Column(name = "organization_updated_at")
   @JsonProperty("organization_updated_at")
   @NotNull
+  @Queryable(sortable = true)
   private Instant updatedAt = now();
 
   @OneToMany(mappedBy = "organization", fetch = FetchType.LAZY)
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Scenario.java b/openbas-model/src/main/java/io/openbas/database/model/Scenario.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Scenario.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Scenario.java
@@ -25,6 +25,7 @@
 import lombok.Getter;
 import lombok.Setter;
 import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.SQLRestriction;
 import org.hibernate.annotations.UpdateTimestamp;
 import org.hibernate.annotations.UuidGenerator;
 
@@ -37,8 +38,8 @@
       name = "Scenario.tags-injects",
       attributeNodes = {@NamedAttributeNode("tags"), @NamedAttributeNode("injects")})
 })
-@Grantable(grantFieldName = "scenario")
-public class Scenario implements Base {
+@Grantable(Grant.GRANT_RESOURCE_TYPE.SCENARIO)
+public class Scenario implements GrantableBase {
 
   public enum SEVERITY {
     @JsonProperty("low")
@@ -159,7 +160,15 @@ public enum SEVERITY {
   @Schema(type = "string")
   private CustomDashboard customDashboard;
 
-  @OneToMany(mappedBy = "scenario", fetch = FetchType.EAGER)
+  @Getter
+  @OneToMany(fetch = FetchType.EAGER)
+  @JoinColumn(
+      name = "grant_resource",
+      referencedColumnName = "scenario_id",
+      insertable = false,
+      updatable = false)
+  @SQLRestriction(
+      "grant_resource_type = 'SCENARIO'") // Must be present in Grant.GRANT_RESOURCE_TYPE
   @JsonIgnore
   private List<Grant> grants = new ArrayList<>();
 
diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawGrant.java b/openbas-model/src/main/java/io/openbas/database/raw/RawGrant.java
--- a/openbas-model/src/main/java/io/openbas/database/raw/RawGrant.java
+++ b/openbas-model/src/main/java/io/openbas/database/raw/RawGrant.java
@@ -1,9 +1,15 @@
 package io.openbas.database.raw;
 
+import io.openbas.database.model.Grant;
+
 public interface RawGrant {
   String getGrant_id();
 
   String getGrant_name();
 
   String getUser_id();
+
+  String getGrant_resource();
+
+  Grant.GRANT_RESOURCE_TYPE getGrant_resource_type();
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawUserAuth.java b/openbas-model/src/main/java/io/openbas/database/raw/RawUserAuth.java
--- a/openbas-model/src/main/java/io/openbas/database/raw/RawUserAuth.java
+++ b/openbas-model/src/main/java/io/openbas/database/raw/RawUserAuth.java
@@ -8,7 +8,5 @@ public interface RawUserAuth {
 
   boolean getUser_admin();
 
-  Set<String> getUser_grant_exercises();
-
-  Set<String> getUser_grant_scenarios();
+  Set<RawGrant> getUser_grants();
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/DocumentRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/DocumentRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/DocumentRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/DocumentRepository.java
@@ -162,7 +162,7 @@ List<RawDocument> rawAllDocumentsBySecurityPlatformId(
               + "left join scenarios sc on sc.scenario_id = scdoc.scenario_id "
               + "left join documents_tags tagdoc on d.document_id = tagdoc.document_id "
               + "left join tags tg on tg.tag_id = tagdoc.tag_id "
-              + "left join grants grt on grt.grant_exercise = exdoc.exercise_id "
+              + "left join grants grt ON grt.grant_resource = exdoc.exercise_id AND grt.grant_resource_type = 'SIMULATION' "
               + "left join groups grp on grt.grant_group = grp.group_id "
               + "left join users_groups usgrp on grp.group_id = usgrp.group_id "
               + "left outer join users u on usgrp.user_id = u.user_id "
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
@@ -29,13 +29,6 @@ public interface ExerciseRepository
   @Query(value = "select e from Exercise e where e.status = 'SCHEDULED' and e.start <= :start")
   List<Exercise> findAllShouldBeInRunningState(@Param("start") Instant start);
 
-  @Query(
-      "select distinct e from Exercise e "
-          + "join e.grants as grant "
-          + "join grant.group.users as user "
-          + "where user.id = :userId")
-  List<Exercise> findAllGranted(@Param("userId") String userId);
-
   @Override
   @Query(
       "select count(distinct e) from Exercise e "
@@ -118,8 +111,8 @@ List<Object[]> userCountGroupByWeek(
               + "FROM injects_expectations ie "
               + "INNER JOIN injects ON ie.inject_id = injects.inject_id "
               + "INNER JOIN exercises e ON injects.inject_exercise = e.exercise_id "
-              + "INNER join grants ON grants.grant_exercise = e.exercise_id "
-              + "INNER join groups ON grants.grant_group = groups.group_id "
+              + "INNER JOIN grants ON grants.grant_resource = e.exercise_id AND grants.grant_resource_type = 'SIMULATION' "
+              + "INNER JOIN groups ON grants.grant_group = groups.group_id "
               + "INNER JOIN users_groups ON groups.group_id = users_groups.group_id "
               + "WHERE e.exercise_created_at > :from and e.exercise_start_date is not null "
               + "AND users_groups.user_id = :userId ;",
@@ -163,8 +156,8 @@ Iterable<RawGlobalInjectExpectation> rawGlobalInjectExpectationResultsFromDate(
               + "LEFT JOIN injects_expectations ie ON exercises.exercise_id = ie.exercise_id "
               + "INNER JOIN injectors_contracts ic ON injects.inject_injector_contract = ic.injector_contract_id "
               + "INNER JOIN injectors_contracts_attack_patterns icap ON ic.injector_contract_id = icap.injector_contract_id "
-              + "INNER join grants ON grants.grant_exercise = exercises.exercise_id "
-              + "INNER join groups ON grants.grant_group = groups.group_id "
+              + "INNER JOIN grants ON grants.grant_resource = exercises.exercise_id AND grants.grant_resource_type = 'SIMULATION' "
+              + "INNER JOIN groups ON grants.grant_group = groups.group_id "
               + "INNER JOIN users_groups ON groups.group_id = users_groups.group_id "
               + "WHERE exercises.exercise_created_at > :from and exercises.exercise_start_date is not null "
               + "AND users_groups.user_id = :userId ;",
@@ -218,8 +211,8 @@ Iterable<RawGlobalInjectExpectation> rawGrantedInjectExpectationResultsFromDate(
               + "LEFT JOIN injects_expectations ie ON ex.exercise_id = ie.exercise_id "
               + "LEFT JOIN injects ON ie.inject_id = injects.inject_id "
               + "LEFT JOIN exercises_tags et ON et.exercise_id = ex.exercise_id "
-              + "INNER join grants ON grants.grant_exercise = ex.exercise_id "
-              + "INNER join groups ON grants.grant_group = groups.group_id "
+              + "INNER JOIN grants ON grants.grant_resource = ex.exercise_id AND grants.grant_resource_type = 'SIMULATION' "
+              + "INNER JOIN groups ON grants.grant_group = groups.group_id "
               + "INNER JOIN users_groups ON groups.group_id = users_groups.group_id "
               + "WHERE users_groups.user_id = :userId "
               + "GROUP BY ex.exercise_id ;",
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/GrantRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/GrantRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/GrantRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/GrantRepository.java
@@ -24,20 +24,20 @@ public interface GrantRepository
               + "FROM grants "
               + "LEFT JOIN groups ON grants.grant_group = groups.group_id "
               + "LEFT JOIN users_groups ON groups.group_id = grants.grant_group "
-              + "WHERE grants.grant_exercise IN :ids ;",
+              + "WHERE grants.grant_resource_type = 'SIMULATION' and grants.grant_resource IN :ids ;",
       nativeQuery = true)
   List<RawGrant> rawByExerciseIds(@Param("ids") List<String> ids);
 
   @Query(
-      value =
-          "SELECT users_groups.user_id, grants.grant_name, grants.grant_id "
-              + "FROM grants "
-              + "LEFT JOIN groups ON grants.grant_group = groups.group_id "
-              + "LEFT JOIN users_groups ON groups.group_id = grants.grant_group "
-              + "WHERE (grants.grant_exercise = :resourceId "
-              + "OR grants.grant_scenario = :resourceId)"
-              + "AND users_groups.user_id = :userId;",
-      nativeQuery = true)
-  List<RawGrant> rawByResourceIdAndUserId(
-      @Param("resourceId") String resourceId, @Param("userId") String userId);
+      "SELECT CASE WHEN COUNT(g) > 0 THEN true ELSE false END "
+          + "FROM Grant g "
+          + "JOIN g.group gr "
+          + "JOIN gr.users u "
+          + "WHERE u.id = :userId "
+          + "AND g.resourceId = :resourceId "
+          + "AND g.name IN :grantTypes")
+  boolean existsByUserIdAndResourceIdAndNameIn(
+      @Param("userId") String userId,
+      @Param("resourceId") String resourceId,
+      @Param("grantTypes") List<Grant.GRANT_TYPE> grantTypes);
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/InjectRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/InjectRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/InjectRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/InjectRepository.java
@@ -213,7 +213,7 @@ List<Object[]> globalCountGroupByAttackPatternInExercise(
           "select icap.attack_pattern_id, count(distinct i) as countInjects from injects i "
               + "join injectors_contracts_attack_patterns icap ON icap.injector_contract_id = i.inject_injector_contract "
               + "join exercises e on e.exercise_id = i.inject_exercise "
-              + "inner join grants ON grants.grant_exercise = e.exercise_id "
+              + "inner join grants ON grants.grant_resource = e.exercise_id AND grants.grant_resource_type = 'SIMULATION' "
               + "inner join groups ON grants.grant_group = groups.group_id "
               + "inner join users_groups ON groups.group_id = users_groups.group_id "
               + "join injects_statuses injectStatus ON injectStatus.status_inject = i.inject_id "
@@ -344,4 +344,12 @@ List<Object[]> findAllByTitleLinkedToFindingsWithContext(
    */
   @Query("SELECT CASE WHEN COUNT(i) > 0 THEN true ELSE false END FROM Inject i WHERE i.id = :id")
   boolean existsByIdWithoutLoading(@Param("id") String id);
+
+  /**
+   * Check if an Inject exists by its ID, where the Inject is an atomic testing.
+   *
+   * @param id ID of the inject to check
+   * @return true if the Inject exists and is an atomic testing, false otherwise
+   */
+  boolean existsByIdAndScenarioIsNullAndExerciseIsNull(String id);
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java
@@ -71,13 +71,6 @@ public interface ScenarioRepository
   List<RawExerciseSimple> rawAllByExternalReference(
       @Param("externalReference") String externalReference);
 
-  @Query(
-      "select distinct s from Scenario s "
-          + "join s.grants as grant "
-          + "join grant.group.users as user "
-          + "where user.id = :userId")
-  List<Scenario> findAllGranted(@Param("userId") String userId);
-
   @Override
   @Query(
       "select count(distinct u) from User u "
@@ -107,8 +100,8 @@ List<RawExerciseSimple> rawAllByExternalReference(
           "SELECT sce.scenario_id, sce.scenario_name, sce.scenario_subtitle, array_agg(sct.tag_id) FILTER (WHERE sct.tag_id IS NOT NULL) as scenario_tags "
               + "FROM scenarios sce "
               + "LEFT JOIN scenarios_tags sct ON sct.scenario_id = sce.scenario_id "
-              + "INNER join grants ON grants.grant_scenario = sce.scenario_id "
-              + "INNER join groups ON grants.grant_group = groups.group_id "
+              + "INNER JOIN grants ON grants.grant_resource = sce.scenario_id AND grants.grant_resource_type = 'SCENARIO' "
+              + "INNER JOIN groups ON grants.grant_group = groups.group_id "
               + "INNER JOIN users_groups ON groups.group_id = users_groups.group_id "
               + "WHERE users_groups.user_id = :userId "
               + "GROUP BY sce.scenario_id",
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/UserRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/UserRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/UserRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/UserRepository.java
@@ -79,8 +79,7 @@ void createAdmin(
   @Query(
       value =
           "select us.user_id, us.user_admin, "
-              + "array_remove(array_agg(grt.grant_exercise), null) as user_grant_exercises, "
-              + "array_remove(array_agg(grt.grant_scenario), null) as user_grant_scenarios "
+              + "array_remove(array_agg(grt.grant_resource), NULL) as user_grants "
               + "from users us "
               + "left join users_groups usr_grp on us.user_id = usr_grp.user_id "
               + "left join grants grt on grt.grant_group = usr_grp.group_id "
diff --git a/openbas-model/src/main/java/io/openbas/database/specification/DocumentSpecification.java b/openbas-model/src/main/java/io/openbas/database/specification/DocumentSpecification.java
--- a/openbas-model/src/main/java/io/openbas/database/specification/DocumentSpecification.java
+++ b/openbas-model/src/main/java/io/openbas/database/specification/DocumentSpecification.java
@@ -7,6 +7,8 @@
 
 public class DocumentSpecification {
 
+  private DocumentSpecification() {}
+
   public static Specification<Document> findGrantedFor(@NotBlank final String userId) {
     return (root, query, criteriaBuilder) -> {
       Path<Object> exercisePath =
diff --git a/openbas-model/src/main/java/io/openbas/database/specification/OrganizationSpecification.java b/openbas-model/src/main/java/io/openbas/database/specification/OrganizationSpecification.java
--- a/openbas-model/src/main/java/io/openbas/database/specification/OrganizationSpecification.java
+++ b/openbas-model/src/main/java/io/openbas/database/specification/OrganizationSpecification.java
@@ -2,6 +2,9 @@
 
 import io.openbas.database.model.Organization;
 import jakarta.annotation.Nullable;
+import jakarta.persistence.criteria.JoinType;
+import jakarta.persistence.criteria.Path;
+import jakarta.validation.constraints.NotBlank;
 import org.springframework.data.jpa.domain.Specification;
 
 public class OrganizationSpecification {
@@ -11,4 +14,12 @@ private OrganizationSpecification() {}
   public static Specification<Organization> byName(@Nullable final String searchText) {
     return UtilsSpecification.byName(searchText, "name");
   }
+
+  public static Specification<Organization> findGrantedFor(@NotBlank final String userId) {
+    return (root, query, cb) -> {
+      query.distinct(true);
+      Path<?> path = root.join("groups", JoinType.INNER).join("users", JoinType.INNER).get("id");
+      return cb.equal(path, userId);
+    };
+  }
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/specification/SpecificationUtils.java b/openbas-model/src/main/java/io/openbas/database/specification/SpecificationUtils.java
--- a/openbas-model/src/main/java/io/openbas/database/specification/SpecificationUtils.java
+++ b/openbas-model/src/main/java/io/openbas/database/specification/SpecificationUtils.java
@@ -1,10 +1,17 @@
 package io.openbas.database.specification;
 
 import io.openbas.database.model.*;
+import io.openbas.database.model.Base;
+import io.openbas.database.model.Grant;
+import io.openbas.database.model.Group;
+import io.openbas.database.model.User;
 import jakarta.persistence.criteria.*;
 import jakarta.validation.constraints.NotBlank;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.function.BiFunction;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.domain.Specification;
 
 public class SpecificationUtils {
@@ -54,15 +61,13 @@ public static <T extends Base> Specification<T> hasGrantAccess(
     return (root, query, cb) -> {
       Class<?> entityClass = root.getJavaType();
 
-      // Check if the entity has the @GrantFilter annotation
-      Grantable grantFilter = entityClass.getAnnotation(Grantable.class);
-
-      // If the user is an admin or there is no grant system associated
-      if (isAdmin || grantFilter == null || hasCapaForClass) {
+      // If the user is an admin or there is no grant system associated with T
+      if (isAdmin || !GrantableBase.class.isAssignableFrom(entityClass) || hasCapaForClass) {
         return cb.conjunction(); // Always true
       }
 
       List<Grant.GRANT_TYPE> allowedGrantTypes = grantType.andHigher();
+      Grant.GRANT_RESOURCE_TYPE resourceType = GrantableBase.getGrantResourceType(entityClass);
 
       // Add grant filtering
       // Create subquery to find all resource IDs the user can access
@@ -74,14 +79,13 @@ public static <T extends Base> Specification<T> hasGrantAccess(
       // Join to users (Group has a 'users' collection)
       Join<Group, User> userTable = groupTable.join("users");
       // Join to the resource table (Grant class has a '{resource}' field of type ResourceType)
-      Join<Grant, ? extends Base> grantColumnTable = grantTable.join(grantFilter.grantFieldName());
-      // We want to SELECT the id from the joined Scenario entity
-      // NOT from Grant directly since Grant doesn't have resource ID
-      accessibleResources.select(grantColumnTable.get("id"));
+      accessibleResources.select(grantTable.get("resourceId"));
       // WHERE the user in the join matches our userId AND grant name is of the given value
       accessibleResources.where(
           cb.and(
-              cb.equal(userTable.get("id"), userId), grantTable.get("name").in(allowedGrantTypes)));
+              cb.equal(userTable.get("id"), userId),
+              cb.equal(grantTable.get("grantResourceType"), resourceType),
+              grantTable.get("name").in(allowedGrantTypes)));
       // Now use this subquery in main query
       // "Include only scenarios whose ID is in our subquery results"
       return root.get("id").in(accessibleResources);
@@ -89,66 +93,53 @@ public static <T extends Base> Specification<T> hasGrantAccess(
   }
 
   /**
-   * Creates a subquery that returns all scenario IDs that the user has access to through grants.
+   * Creates a subquery that returns all resource IDs of a specific type that the user has access to
+   * through grants. Can be used to get injects belonging to a scenario or simulation where the user
+   * is granted, for example.
    *
    * @param query The parent criteria query used to create the subquery
    * @param cb The criteria builder for constructing query predicates
-   * @param userId The ID of the user whose accessible scenario are being queried
+   * @param userId The ID of the user whose accessible resources are being queried
+   * @param resourceType The type of resource to filter by (e.g., "SCENARIO", "EXERCISE")
    * @param allowedGrantTypes List of grant types that provide access (e.g., OBSERVER and higher)
-   * @return A subquery that selects exercise IDs the user can access
+   * @return A subquery that selects resource IDs the user can access
    */
-  public static Subquery<String> accessibleScenariosSubquery(
+  public static Subquery<String> accessibleResourcesSubquery(
       CriteriaQuery<?> query,
       CriteriaBuilder cb,
       String userId,
+      Grant.GRANT_RESOURCE_TYPE resourceType,
       List<Grant.GRANT_TYPE> allowedGrantTypes) {
 
-    Subquery<String> accessibleScenarios = query.subquery(String.class);
-    Root<Grant> grantTable = accessibleScenarios.from(Grant.class);
+    Subquery<String> accessibleResources = query.subquery(String.class);
+    Root<Grant> grantTable = accessibleResources.from(Grant.class);
     Join<Grant, Group> groupTable = grantTable.join("group");
     Join<Group, User> userTable = groupTable.join("users");
-    Join<Grant, Scenario> scenarioTable = grantTable.join("scenario");
 
-    // Select scenario ID from the joined scenario
-    accessibleScenarios.select(scenarioTable.get("id"));
+    // Select resourceId directly since we're not joining to the resource table
+    accessibleResources.select(grantTable.get("resourceId"));
 
-    // WHERE user matches AND grant type is allowed
-    accessibleScenarios.where(
+    // WHERE user matches AND resourceType matches AND grant type is allowed
+    accessibleResources.where(
         cb.and(
-            cb.equal(userTable.get("id"), userId), grantTable.get("name").in(allowedGrantTypes)));
+            cb.equal(userTable.get("id"), userId),
+            cb.equal(grantTable.get("grantResourceType"), resourceType.name()),
+            grantTable.get("name").in(allowedGrantTypes)));
 
-    return accessibleScenarios;
+    return accessibleResources;
   }
 
-  /**
-   * Creates a subquery that returns all simulation IDs that the user has access to through grants.
-   *
-   * @param query The parent criteria query used to create the subquery
-   * @param cb The criteria builder for constructing query predicates
-   * @param userId The ID of the user whose accessible simulations are being queried
-   * @param allowedGrantTypes List of grant types that provide access (e.g., OBSERVER and higher)
-   * @return A subquery that selects exercise IDs the user can access
-   */
-  public static Subquery<String> accessibleSimulationsSubquery(
-      CriteriaQuery<?> query,
-      CriteriaBuilder cb,
-      String userId,
-      List<Grant.GRANT_TYPE> allowedGrantTypes) {
-
-    Subquery<String> accessibleExercises = query.subquery(String.class);
-    Root<Grant> grantTable = accessibleExercises.from(Grant.class);
-    Join<Grant, Group> groupTable = grantTable.join("group");
-    Join<Group, User> userTable = groupTable.join("users");
-    Join<Grant, Exercise> exerciseTable = grantTable.join("exercise");
-
-    // Select exercise ID from the joined exercise
-    accessibleExercises.select(exerciseTable.get("id"));
-
-    // WHERE user matches AND grant type is allowed
-    accessibleExercises.where(
-        cb.and(
-            cb.equal(userTable.get("id"), userId), grantTable.get("name").in(allowedGrantTypes)));
-
-    return accessibleExercises;
+  public static <T extends GrantableBase>
+      BiFunction<Specification<T>, Pageable, Page<T>> withGrantFilter(
+          BiFunction<Specification<T>, Pageable, Page<T>> findAll,
+          Grant.GRANT_TYPE grantType,
+          String userId,
+          boolean isAdmin,
+          boolean hasCapaForClass) {
+    return (spec, pageable) ->
+        findAll.apply(
+            spec.and(
+                SpecificationUtils.hasGrantAccess(userId, isAdmin, hasCapaForClass, grantType)),
+            pageable);
   }
 }
diff --git a/openbas-model/src/main/java/io/openbas/service/ElasticService.java b/openbas-model/src/main/java/io/openbas/service/ElasticService.java
--- a/openbas-model/src/main/java/io/openbas/service/ElasticService.java
+++ b/openbas-model/src/main/java/io/openbas/service/ElasticService.java
@@ -21,6 +21,7 @@
 import io.openbas.database.model.CustomDashboardParameters;
 import io.openbas.database.model.Filters;
 import io.openbas.database.model.IndexingStatus;
+import io.openbas.database.raw.RawGrant;
 import io.openbas.database.raw.RawUserAuth;
 import io.openbas.database.repository.IndexingStatusRepository;
 import io.openbas.driver.ElasticDriver;
@@ -214,10 +215,9 @@ private Query buildQueryRestrictions(RawUserAuth user) {
     if (user.getUser_admin()) {
       return null;
     }
-    Set<String> scenarioIds = user.getUser_grant_scenarios();
-    Set<String> exerciseIds = user.getUser_grant_exercises();
-    List<String> restrictions = Stream.concat(exerciseIds.stream(), scenarioIds.stream()).toList();
-    List<FieldValue> values = restrictions.stream().map(FieldValue::of).toList();
+    Set<String> grantedResourceIds =
+        user.getUser_grants().stream().map(RawGrant::getGrant_resource).collect(Collectors.toSet());
+    List<FieldValue> values = grantedResourceIds.stream().map(FieldValue::of).toList();
     BoolQuery.Builder authQuery = new BoolQuery.Builder();
     Query compliantField =
         TermsQuery.of(
diff --git a/openbas-model/src/main/java/io/openbas/service/OpenSearchService.java b/openbas-model/src/main/java/io/openbas/service/OpenSearchService.java
--- a/openbas-model/src/main/java/io/openbas/service/OpenSearchService.java
+++ b/openbas-model/src/main/java/io/openbas/service/OpenSearchService.java
@@ -12,6 +12,7 @@
 import io.openbas.database.model.CustomDashboardParameters;
 import io.openbas.database.model.Filters;
 import io.openbas.database.model.IndexingStatus;
+import io.openbas.database.raw.RawGrant;
 import io.openbas.database.raw.RawUserAuth;
 import io.openbas.database.repository.IndexingStatusRepository;
 import io.openbas.driver.OpenSearchDriver;
@@ -250,10 +251,9 @@ private Query buildQueryRestrictions(RawUserAuth user) {
     if (user.getUser_admin()) {
       return null;
     }
-    Set<String> scenarioIds = user.getUser_grant_scenarios();
-    Set<String> exerciseIds = user.getUser_grant_exercises();
-    List<String> restrictions = Stream.concat(exerciseIds.stream(), scenarioIds.stream()).toList();
-    List<FieldValue> values = restrictions.stream().map(FieldValue::of).toList();
+    Set<String> grantedResource =
+        user.getUser_grants().stream().map(RawGrant::getGrant_resource).collect(Collectors.toSet());
+    List<FieldValue> values = grantedResource.stream().map(FieldValue::of).toList();
     BoolQuery.Builder authQuery = new BoolQuery.Builder();
     Query compliantField =
         TermsQuery.of(


Test patch:
diff --git a/openbas-api/src/test/java/io/openbas/rest/ChallengeApiTest.java b/openbas-api/src/test/java/io/openbas/rest/ChallengeApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/ChallengeApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/ChallengeApiTest.java
@@ -21,7 +21,7 @@
 import io.openbas.database.repository.InjectRepository;
 import io.openbas.database.repository.InjectorContractRepository;
 import io.openbas.service.ScenarioService;
-import io.openbas.utils.mockUser.WithMockObserverUser;
+import io.openbas.utils.mockUser.WithMockAdminUser;
 import jakarta.annotation.Resource;
 import java.util.List;
 import org.junit.jupiter.api.*;
@@ -57,7 +57,7 @@ void afterAll() {
   @DisplayName("Retrieve challenges for scenario")
   @Test
   @Order(1)
-  @WithMockObserverUser
+  @WithMockAdminUser
   void retrieveChallengesVariableForScenarioTest() throws Exception {
     // -- PREPARE --
     Scenario scenario = createDefaultCrisisScenario();
diff --git a/openbas-api/src/test/java/io/openbas/rest/ChannelApiTest.java b/openbas-api/src/test/java/io/openbas/rest/ChannelApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/ChannelApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/ChannelApiTest.java
@@ -18,7 +18,7 @@
 import io.openbas.rest.channel.form.ArticleCreateInput;
 import io.openbas.rest.channel.form.ArticleUpdateInput;
 import io.openbas.service.ScenarioService;
-import io.openbas.utils.mockUser.WithMockPlannerUser;
+import io.openbas.utils.mockUser.WithMockAdminUser;
 import org.junit.jupiter.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -55,7 +55,7 @@ void afterAll() {
   @DisplayName("Create article for scenario")
   @Test
   @Order(1)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void createArticleForScenarioTest() throws Exception {
     // -- PREPARE --
     Scenario scenario = new Scenario();
@@ -95,7 +95,7 @@ void createArticleForScenarioTest() throws Exception {
   @DisplayName("Retrieve articles for scenario")
   @Test
   @Order(2)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void retrieveArticlesForScenarioTest() throws Exception {
     // -- EXECUTE --
     String response =
@@ -115,7 +115,7 @@ void retrieveArticlesForScenarioTest() throws Exception {
   @DisplayName("Update article for scenario")
   @Test
   @Order(3)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void updateArticleForScenarioTest() throws Exception {
     // -- PREPARE --
     ArticleUpdateInput articleUpdateInput = new ArticleUpdateInput();
@@ -144,7 +144,7 @@ void updateArticleForScenarioTest() throws Exception {
   @DisplayName("Delete article for scenario")
   @Test
   @Order(4)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void deleteArticleForScenarioTest() throws Exception {
     // -- EXECUTE 1 ASSERT --
     this.mvc
diff --git a/openbas-api/src/test/java/io/openbas/rest/ExerciseLessonsApiTest.java b/openbas-api/src/test/java/io/openbas/rest/ExerciseLessonsApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/ExerciseLessonsApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/ExerciseLessonsApiTest.java
@@ -22,7 +22,7 @@
 import io.openbas.rest.exercise.service.ExerciseService;
 import io.openbas.rest.lessons.form.LessonsSendInput;
 import io.openbas.service.MailingService;
-import io.openbas.utils.mockUser.WithMockPlannerUser;
+import io.openbas.utils.mockUser.WithMockAdminUser;
 import java.util.List;
 import org.junit.jupiter.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -72,7 +72,7 @@ private LessonsCategory getLessonCategory() {
 
   @DisplayName("Send surveys for exercise lessons")
   @Test
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void sendExerciseLessonsTest() throws Exception {
 
     // -- PREPARE --
diff --git a/openbas-api/src/test/java/io/openbas/rest/UserApiTest.java b/openbas-api/src/test/java/io/openbas/rest/UserApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/UserApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/UserApiTest.java
@@ -230,11 +230,13 @@ void given_user_with_several_grant_on_same_resource_should_return_highest_grant(
     group = groupRepository.save(group);
 
     Grant grantObserver = new Grant();
-    grantObserver.setScenario(scenario);
+    grantObserver.setResourceId(scenario.getId());
+    grantObserver.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SCENARIO);
     grantObserver.setGroup(group);
     grantObserver.setName(Grant.GRANT_TYPE.OBSERVER);
     Grant grantPlanner = new Grant();
-    grantPlanner.setScenario(scenario);
+    grantPlanner.setResourceId(scenario.getId());
+    grantPlanner.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SCENARIO);
     grantPlanner.setGroup(group);
     grantPlanner.setName(Grant.GRANT_TYPE.PLANNER);
     grantRepository.saveAll(List.of(grantObserver, grantPlanner));
diff --git a/openbas-api/src/test/java/io/openbas/rest/VariableApiTest.java b/openbas-api/src/test/java/io/openbas/rest/VariableApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/VariableApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/VariableApiTest.java
@@ -16,8 +16,7 @@
 import io.openbas.database.repository.ScenarioRepository;
 import io.openbas.database.repository.VariableRepository;
 import io.openbas.service.ScenarioService;
-import io.openbas.utils.mockUser.WithMockObserverUser;
-import io.openbas.utils.mockUser.WithMockPlannerUser;
+import io.openbas.utils.mockUser.WithMockAdminUser;
 import org.junit.jupiter.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -51,7 +50,7 @@ void afterAll() {
   @DisplayName("Create variable for scenario succeed")
   @Test
   @Order(1)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void createVariableForScenarioTest() throws Exception {
     // -- PREPARE --
     Scenario scenario = new Scenario();
@@ -96,7 +95,7 @@ void createVariableForScenarioTest() throws Exception {
   @DisplayName("Retrieve variables for scenario")
   @Test
   @Order(2)
-  @WithMockObserverUser
+  @WithMockAdminUser
   void retrieveVariableForScenarioTest() throws Exception {
     // -- EXECUTE --
     String response =
@@ -116,7 +115,7 @@ void retrieveVariableForScenarioTest() throws Exception {
   @DisplayName("Update variable for scenario")
   @Test
   @Order(3)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void updateVariableForScenarioTest() throws Exception {
     // -- PREPARE --
     String response =
@@ -155,7 +154,7 @@ void updateVariableForScenarioTest() throws Exception {
   @DisplayName("Delete variable for scenario")
   @Test
   @Order(4)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void deleteVariableForScenarioTest() throws Exception {
     // -- EXECUTE 1 ASSERT --
     this.mvc
diff --git a/openbas-api/src/test/java/io/openbas/rest/exercise/ExerciseApiTest.java b/openbas-api/src/test/java/io/openbas/rest/exercise/ExerciseApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/exercise/ExerciseApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/exercise/ExerciseApiTest.java
@@ -284,6 +284,7 @@ void given_crowdstrike_should_not_launchExercise() throws Exception {
 
     @Test
     @DisplayName("Throw license restricted error when schedule exercise with Tanium")
+    @WithMockAdminUser
     void given_tanium_should_not_scheduleExercise() throws Exception {
       Exercise exercise = getExercise(executorFixture.getTaniumExecutor());
       ExerciseUpdateStartDateInput input = new ExerciseUpdateStartDateInput();
@@ -299,6 +300,7 @@ void given_tanium_should_not_scheduleExercise() throws Exception {
 
     @Test
     @DisplayName("Throw license restricted error when add Tanium on scheduled scenario")
+    @WithMockAdminUser
     void given_taniumAsset_should_not_beAddedToScheduledExercise() throws Exception {
       Exercise exercise = getExercise(null);
 
diff --git a/openbas-api/src/test/java/io/openbas/rest/exercise/ExerciseInjectTestApiTest.java b/openbas-api/src/test/java/io/openbas/rest/exercise/ExerciseInjectTestApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/exercise/ExerciseInjectTestApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/exercise/ExerciseInjectTestApiTest.java
@@ -23,7 +23,6 @@
 import io.openbas.utils.fixtures.composers.InjectComposer;
 import io.openbas.utils.fixtures.composers.InjectTestStatusComposer;
 import io.openbas.utils.mockUser.WithMockAdminUser;
-import io.openbas.utils.mockUser.WithMockPlannerUser;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import java.util.List;
 import org.junit.jupiter.api.*;
@@ -116,7 +115,7 @@ void should_return_test_status_by_testId() throws Exception {
 
     @Test
     @DisplayName("Should return test status when testing a specific inject")
-    @WithMockPlannerUser
+    @WithMockAdminUser
     void should_return_test_status_when_testing_specific_inject() throws Exception {
       mvc.perform(
               get(
@@ -145,7 +144,7 @@ void should_return_test_statuses_when_bulk_testing_with_inject_ids() throws Exce
 
     @Test
     @DisplayName("Should return 200 when deleting an inject test status")
-    @WithMockPlannerUser
+    @WithMockAdminUser
     void should_return_200_when_fetching_deleting_an_inject_test_status() throws Exception {
       mvc.perform(
               delete(
diff --git a/openbas-api/src/test/java/io/openbas/rest/inject/InjectApiTest.java b/openbas-api/src/test/java/io/openbas/rest/inject/InjectApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/inject/InjectApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/inject/InjectApiTest.java
@@ -41,7 +41,6 @@
 import io.openbas.utils.fixtures.*;
 import io.openbas.utils.fixtures.composers.*;
 import io.openbas.utils.mockUser.WithMockAdminUser;
-import io.openbas.utils.mockUser.WithMockPlannerUser;
 import jakarta.annotation.Resource;
 import jakarta.mail.Session;
 import jakarta.mail.internet.MimeMessage;
@@ -222,7 +221,7 @@ void deleteInjectsForScenarioTest() throws Exception {
 
   @DisplayName("Add an inject for simulation")
   @Test
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void addInjectForSimulationTest() throws Exception {
     // -- PREPARE --
     InjectInput input = new InjectInput();
@@ -249,7 +248,7 @@ void addInjectForSimulationTest() throws Exception {
 
   @DisplayName("Update inject for simulation")
   @Test
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void updateInjectForSimulationTest() throws Exception {
     // -- PREPARE --
     InjectInput injectInput = new InjectInput();
diff --git a/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectApiTest.java b/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectApiTest.java
@@ -25,8 +25,6 @@
 import io.openbas.utils.fixtures.composers.*;
 import io.openbas.utils.fixtures.files.AttackPatternFixture;
 import io.openbas.utils.mockUser.WithMockAdminUser;
-import io.openbas.utils.mockUser.WithMockObserverUser;
-import io.openbas.utils.mockUser.WithMockPlannerUser;
 import jakarta.servlet.ServletException;
 import jakarta.transaction.Transactional;
 import java.util.ArrayList;
@@ -101,7 +99,7 @@ void afterAll() {
   @DisplayName("Add an inject for scenario")
   @Test
   @Order(1)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void addInjectForScenarioTest() throws Exception {
     // -- PREPARE --
     InjectInput input = new InjectInput();
@@ -136,7 +134,7 @@ void addInjectForScenarioTest() throws Exception {
   @DisplayName("Retrieve injects for scenario")
   @Test
   @Order(2)
-  @WithMockObserverUser
+  @WithMockAdminUser
   void retrieveInjectsForScenarioTest() throws Exception {
     // -- EXECUTE --
     String response =
@@ -156,7 +154,7 @@ void retrieveInjectsForScenarioTest() throws Exception {
   @DisplayName("Retrieve inject for scenario")
   @Test
   @Order(3)
-  @WithMockObserverUser
+  @WithMockAdminUser
   void retrieveInjectForScenarioTest() throws Exception {
     // -- EXECUTE --
     String response =
@@ -176,7 +174,7 @@ void retrieveInjectForScenarioTest() throws Exception {
   @DisplayName("Update inject for scenario")
   @Test
   @Order(4)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void updateInjectForScenarioTest() throws Exception {
     // -- PREPARE --
     Inject inject = injectRepository.findById(SCENARIO_INJECT_ID).orElseThrow();
@@ -207,7 +205,7 @@ void updateInjectForScenarioTest() throws Exception {
   @DisplayName("Delete inject for scenario")
   @Test
   @Order(5)
-  @WithMockPlannerUser
+  @WithMockAdminUser
   void deleteInjectForScenarioTest() throws Exception {
     // -- EXECUTE 1 ASSERT --
     mvc.perform(delete(SCENARIO_URI + "/" + SCENARIO.getId() + "/injects/" + SCENARIO_INJECT_ID))
diff --git a/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectTestApiTest.java b/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectTestApiTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectTestApiTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/scenario/ScenarioInjectTestApiTest.java
@@ -23,7 +23,6 @@
 import io.openbas.utils.fixtures.composers.InjectTestStatusComposer;
 import io.openbas.utils.fixtures.composers.ScenarioComposer;
 import io.openbas.utils.mockUser.WithMockAdminUser;
-import io.openbas.utils.mockUser.WithMockPlannerUser;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import java.util.List;
 import org.junit.jupiter.api.*;
@@ -87,7 +86,7 @@ class ScenarioPlannerAccess {
 
     @Test
     @DisplayName("Should return paginated inject test results when inject tests exist")
-    @WithMockPlannerUser
+    @WithMockAdminUser
     void should_return_paginated_results_when_inject_tests_exist() throws Exception {
       SearchPaginationInput searchPaginationInput = new SearchPaginationInput();
       String response =
@@ -145,7 +144,7 @@ void should_return_test_statuses_when_bulk_testing_with_inject_ids() throws Exce
 
     @Test
     @DisplayName("Should return 200 when deleting an inject test status")
-    @WithMockPlannerUser
+    @WithMockAdminUser
     void should_return_200_when_fetching_deleting_an_inject_test_status() throws Exception {
       mvc.perform(
               delete(
diff --git a/openbas-api/src/test/java/io/openbas/service/GrantServiceTest.java b/openbas-api/src/test/java/io/openbas/service/GrantServiceTest.java
--- a/openbas-api/src/test/java/io/openbas/service/GrantServiceTest.java
+++ b/openbas-api/src/test/java/io/openbas/service/GrantServiceTest.java
@@ -7,10 +7,8 @@
 import io.openbas.IntegrationTest;
 import io.openbas.database.model.Grant;
 import io.openbas.database.model.User;
-import io.openbas.database.raw.RawGrant;
 import io.openbas.database.repository.GrantRepository;
 import io.openbas.utils.fixtures.UserFixture;
-import java.util.List;
 import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
@@ -30,8 +28,9 @@ public class GrantServiceTest extends IntegrationTest {
   public void test_hasReadGrant_WHEN_has_read_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
-    when(grantRepository.rawByResourceIdAndUserId(RESOURCE_ID, USER_ID))
-        .thenReturn(List.of(getRawGant(USER_ID, Grant.GRANT_TYPE.OBSERVER)));
+    when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
+            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.OBSERVER.andHigher()))
+        .thenReturn(true);
 
     assertTrue(grantService.hasReadGrant(RESOURCE_ID, user));
   }
@@ -40,7 +39,9 @@ public void test_hasReadGrant_WHEN_has_read_grant() {
   public void test_hasReadGrant_WHEN_has_no_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
-    when(grantRepository.rawByResourceIdAndUserId(RESOURCE_ID, USER_ID)).thenReturn(List.of());
+    when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
+            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.OBSERVER.andHigher()))
+        .thenReturn(false);
 
     assertFalse(grantService.hasReadGrant(RESOURCE_ID, user));
   }
@@ -49,8 +50,9 @@ public void test_hasReadGrant_WHEN_has_no_grant() {
   public void test_hasLaunchGrant_WHEN_has_read_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
-    when(grantRepository.rawByResourceIdAndUserId(RESOURCE_ID, USER_ID))
-        .thenReturn(List.of(getRawGant(USER_ID, Grant.GRANT_TYPE.OBSERVER)));
+    when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
+            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.LAUNCHER.andHigher()))
+        .thenReturn(false);
 
     assertFalse(grantService.hasLaunchGrant(RESOURCE_ID, user));
   }
@@ -59,8 +61,9 @@ public void test_hasLaunchGrant_WHEN_has_read_grant() {
   public void test_hasWriteGrant_WHEN_has_read_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
-    when(grantRepository.rawByResourceIdAndUserId(RESOURCE_ID, USER_ID))
-        .thenReturn(List.of(getRawGant(USER_ID, Grant.GRANT_TYPE.OBSERVER)));
+    when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
+            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.PLANNER.andHigher()))
+        .thenReturn(false);
 
     assertFalse(grantService.hasWriteGrant(RESOURCE_ID, user));
   }
@@ -69,8 +72,9 @@ public void test_hasWriteGrant_WHEN_has_read_grant() {
   public void test_hasLaunchGrant_WHEN_has_launch_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
-    when(grantRepository.rawByResourceIdAndUserId(RESOURCE_ID, USER_ID))
-        .thenReturn(List.of(getRawGant(USER_ID, Grant.GRANT_TYPE.LAUNCHER)));
+    when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
+            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.LAUNCHER.andHigher()))
+        .thenReturn(true);
 
     assertTrue(grantService.hasLaunchGrant(RESOURCE_ID, user));
   }
@@ -79,28 +83,10 @@ public void test_hasLaunchGrant_WHEN_has_launch_grant() {
   public void test_hasWriteGrant_WHEN_has_write_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
-    when(grantRepository.rawByResourceIdAndUserId(RESOURCE_ID, USER_ID))
-        .thenReturn(List.of(getRawGant(USER_ID, Grant.GRANT_TYPE.PLANNER)));
+    when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
+            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.PLANNER.andHigher()))
+        .thenReturn(true);
 
     assertTrue(grantService.hasWriteGrant(RESOURCE_ID, user));
   }
-
-  private RawGrant getRawGant(final String userId, final Grant.GRANT_TYPE grantType) {
-    return new RawGrant() {
-      @Override
-      public String getGrant_id() {
-        return "grantId";
-      }
-
-      @Override
-      public String getGrant_name() {
-        return grantType.name();
-      }
-
-      @Override
-      public String getUser_id() {
-        return userId;
-      }
-    };
-  }
 }
diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/GrantFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/GrantFixture.java
--- a/openbas-api/src/test/java/io/openbas/utils/fixtures/GrantFixture.java
+++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/GrantFixture.java
@@ -11,14 +11,16 @@ public static Grant getGrantForSimulation(Exercise simulation) {
   public static Grant getGrantForSimulation(Exercise simulation, Grant.GRANT_TYPE grantType) {
     Grant grant = new Grant();
     grant.setName(grantType);
-    grant.setExercise(simulation);
+    grant.setResourceId(simulation.getId());
+    grant.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SIMULATION);
     return grant;
   }
 
   public static Grant getGrantForSimulation(Exercise simulation, Group group) {
     Grant grant = new Grant();
     grant.setName(Grant.GRANT_TYPE.PLANNER);
-    grant.setExercise(simulation);
+    grant.setResourceId(simulation.getId());
+    grant.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SIMULATION);
     grant.setGroup(group);
     return grant;
   }
@@ -30,7 +32,8 @@ public static Grant getGrantForScenario(Scenario scenario) {
   public static Grant getGrantForScenario(Scenario scenario, Group group) {
     Grant grant = new Grant();
     grant.setName(Grant.GRANT_TYPE.PLANNER);
-    grant.setScenario(scenario);
+    grant.setResourceId(scenario.getId());
+    grant.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SCENARIO);
     grant.setGroup(group);
     return grant;
   }
@@ -38,7 +41,8 @@ public static Grant getGrantForScenario(Scenario scenario, Group group) {
   public static Grant getGrantForScenario(Scenario scenario, Grant.GRANT_TYPE grantType) {
     Grant grant = new Grant();
     grant.setName(grantType);
-    grant.setScenario(scenario);
+    grant.setResourceId(scenario.getId());
+    grant.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SCENARIO);
     return grant;
   }
 }
diff --git a/openbas-api/src/test/java/io/openbas/utils/helpers/GrantHelper.java b/openbas-api/src/test/java/io/openbas/utils/helpers/GrantHelper.java
--- a/openbas-api/src/test/java/io/openbas/utils/helpers/GrantHelper.java
+++ b/openbas-api/src/test/java/io/openbas/utils/helpers/GrantHelper.java
@@ -36,7 +36,8 @@ private Grant createGrantForGroup(Group group) {
   public void grantExerciseObserver(Exercise exercise) {
     for (Group group : getAmbientSecurityContextGroups()) {
       Grant grant = createGrantForGroup(group);
-      grant.setExercise(exercise);
+      grant.setResourceId(exercise.getId());
+      grant.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SIMULATION);
       grant.setName(Grant.GRANT_TYPE.OBSERVER);
       this.grantRepository.save(grant);
     }
@@ -45,7 +46,8 @@ public void grantExerciseObserver(Exercise exercise) {
   public void grantExercisePlanner(Exercise exercise) {
     for (Group group : getAmbientSecurityContextGroups()) {
       Grant grant = createGrantForGroup(group);
-      grant.setExercise(exercise);
+      grant.setResourceId(exercise.getId());
+      grant.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SIMULATION);
       grant.setName(Grant.GRANT_TYPE.PLANNER);
       this.grantRepository.save(grant);
     }
@@ -54,7 +56,8 @@ public void grantExercisePlanner(Exercise exercise) {
   public void grantScenarioObserver(Scenario scenario) {
     for (Group group : getAmbientSecurityContextGroups()) {
       Grant grant = createGrantForGroup(group);
-      grant.setScenario(scenario);
+      grant.setResourceId(scenario.getId());
+      grant.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SCENARIO);
       grant.setName(Grant.GRANT_TYPE.OBSERVER);
       this.grantRepository.save(grant);
     }
@@ -63,7 +66,8 @@ public void grantScenarioObserver(Scenario scenario) {
   public void grantScenarioPlanner(Scenario scenario) {
     for (Group group : getAmbientSecurityContextGroups()) {
       Grant grant = createGrantForGroup(group);
-      grant.setScenario(scenario);
+      grant.setResourceId(scenario.getId());
+      grant.setGrantResourceType(Grant.GRANT_RESOURCE_TYPE.SCENARIO);
       grant.setName(Grant.GRANT_TYPE.PLANNER);
       this.grantRepository.save(grant);
     }
diff --git a/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockObserverUserSecurityContextFactory.java b/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockObserverUserSecurityContextFactory.java
--- a/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockObserverUserSecurityContextFactory.java
+++ b/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockObserverUserSecurityContextFactory.java
@@ -3,6 +3,7 @@
 import static io.openbas.database.model.Grant.GRANT_TYPE.OBSERVER;
 import static io.openbas.service.UserService.buildAuthenticationToken;
 
+import io.openbas.database.model.DefaultGrant;
 import io.openbas.database.model.Grant;
 import io.openbas.database.model.Group;
 import io.openbas.database.model.User;
@@ -64,8 +65,12 @@ private void createObserverMockUser() {
     if (groupOpt.isEmpty()) {
       Group newGroup = new Group();
       newGroup.setName(MOCK_OBSERVER_GROUP);
-      newGroup.setScenariosDefaultGrants(List.of(OBSERVER));
-      newGroup.setExercisesDefaultGrants(List.of(OBSERVER));
+      newGroup
+          .getDefaultGrants()
+          .add(new DefaultGrant(OBSERVER, Grant.GRANT_RESOURCE_TYPE.SCENARIO));
+      newGroup
+          .getDefaultGrants()
+          .add(new DefaultGrant(OBSERVER, Grant.GRANT_RESOURCE_TYPE.SIMULATION));
       group = this.groupRepository.save(newGroup);
       // Create grant
       Grant grant = new Grant();
diff --git a/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockPlannerUserSecurityContextFactory.java b/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockPlannerUserSecurityContextFactory.java
--- a/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockPlannerUserSecurityContextFactory.java
+++ b/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockPlannerUserSecurityContextFactory.java
@@ -3,6 +3,7 @@
 import static io.openbas.database.model.Grant.GRANT_TYPE.PLANNER;
 import static io.openbas.service.UserService.buildAuthenticationToken;
 
+import io.openbas.database.model.DefaultGrant;
 import io.openbas.database.model.Grant;
 import io.openbas.database.model.Group;
 import io.openbas.database.model.User;
@@ -63,8 +64,12 @@ private void createPlannerMockUser() {
     if (groupOpt.isEmpty()) {
       Group newGroup = new Group();
       newGroup.setName(MOCK_PLANNER_GROUP);
-      newGroup.setScenariosDefaultGrants(List.of(PLANNER));
-      newGroup.setExercisesDefaultGrants(List.of(PLANNER));
+      newGroup
+          .getDefaultGrants()
+          .add(new DefaultGrant(Grant.GRANT_TYPE.PLANNER, Grant.GRANT_RESOURCE_TYPE.SCENARIO));
+      newGroup
+          .getDefaultGrants()
+          .add(new DefaultGrant(Grant.GRANT_TYPE.PLANNER, Grant.GRANT_RESOURCE_TYPE.SIMULATION));
       group = this.groupRepository.save(newGroup);
       // Create grant
       Grant grant = new Grant();
diff --git a/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockUserFullPermissionsSecurityContextFactory.java b/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockUserFullPermissionsSecurityContextFactory.java
--- a/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockUserFullPermissionsSecurityContextFactory.java
+++ b/openbas-api/src/test/java/io/openbas/utils/mockUser/WithMockUserFullPermissionsSecurityContextFactory.java
@@ -105,8 +105,12 @@ public void createPlannerMockUser() {
     if (groupOpt.isEmpty()) {
       Group newGroup = new Group();
       newGroup.setName(MOCK_FULL_PERMISSION_GROUP);
-      newGroup.setScenariosDefaultGrants(List.of(PLANNER));
-      newGroup.setExercisesDefaultGrants(List.of(PLANNER));
+      newGroup
+          .getDefaultGrants()
+          .add(new DefaultGrant(Grant.GRANT_TYPE.PLANNER, Grant.GRANT_RESOURCE_TYPE.SCENARIO));
+      newGroup
+          .getDefaultGrants()
+          .add(new DefaultGrant(Grant.GRANT_TYPE.PLANNER, Grant.GRANT_RESOURCE_TYPE.SIMULATION));
       newGroup.setRoles(List.of(role));
       group = this.groupRepository.save(newGroup);
       // Create grant