Repository: OpenAEV-Platform/openaev (java)

Issue:
RBAC:  Modify the existing permission logic to add Grant on Payload/Atomic Testing
## Context
#3493 and #3094 added the possibility to add grants on AtomicTestings and Payloads but they are not used yet in the permission logic.

## Goal
The permission logic is updated to take into account grant on AtomicTestings/Payloads when verifying permissions


Discussion:

Patch:
diff --git a/openbas-api/src/main/java/io/openbas/rest/collector/service/CollectorService.java b/openbas-api/src/main/java/io/openbas/rest/collector/service/CollectorService.java
--- a/openbas-api/src/main/java/io/openbas/rest/collector/service/CollectorService.java
+++ b/openbas-api/src/main/java/io/openbas/rest/collector/service/CollectorService.java
@@ -11,9 +11,11 @@
 import jakarta.annotation.Resource;
 import jakarta.transaction.Transactional;
 import java.io.InputStream;
+import java.util.List;
 import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.jpa.repository.Query;
 import org.springframework.stereotype.Service;
 
 @Slf4j
@@ -74,4 +76,14 @@ public void register(String id, String type, String name, InputStream iconData)
       collectorRepository.save(newCollector);
     }
   }
+
+  public List<Collector> collectorsForPayload(String payloadId) {
+    return collectorRepository.findByPayloadId(payloadId);
+  }
+
+  @Query(
+      "SELECT c FROM Collector c WHERE c.detectionRemediations.payload.injector.contracts.injects.injectId = :injectId")
+  public List<Collector> collectorsForAtomicTesting(String injectId) {
+    return collectorRepository.findByInjectId(injectId);
+  }
 }
diff --git a/openbas-api/src/main/java/io/openbas/rest/document/DocumentApi.java b/openbas-api/src/main/java/io/openbas/rest/document/DocumentApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/document/DocumentApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/document/DocumentApi.java
@@ -10,7 +10,6 @@
 
 import io.openbas.aop.LogExecutionTime;
 import io.openbas.aop.RBAC;
-import io.openbas.config.OpenBASPrincipal;
 import io.openbas.database.model.*;
 import io.openbas.database.raw.RawDocument;
 import io.openbas.database.raw.RawPaginationDocument;
@@ -24,6 +23,7 @@
 import io.openbas.rest.inject.service.InjectService;
 import io.openbas.service.ChannelService;
 import io.openbas.service.FileService;
+import io.openbas.service.UserService;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -71,10 +71,11 @@ public class DocumentApi extends RestBehavior {
   private final FileService fileService;
   private final InjectService injectService;
   private final ChannelService channelService;
+  private final UserService userService;
 
   private Optional<Document> resolveDocument(String documentId) {
-    OpenBASPrincipal user = currentUser();
-    if (user.isAdmin()) {
+    User user = userService.currentUser();
+    if (user.isAdminOrBypass()) {
       return documentRepository.findById(documentId);
     } else {
       return documentRepository.findByIdGranted(documentId, user.getId());
@@ -232,9 +233,9 @@ public List<RawDocument> documents() {
   @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.DOCUMENT)
   public Page<RawPaginationDocument> searchDocuments(
       @RequestBody @Valid final SearchPaginationInput searchPaginationInput) {
-    OpenBASPrincipal user = currentUser();
+    User user = userService.currentUser();
     List<Document> securityPlatformLogos = securityPlatformRepository.securityPlatformLogo();
-    if (user.isAdmin()) {
+    if (user.isAdminOrBypass()) {
       return buildPaginationJPA(
               (Specification<Document> specification, Pageable pageable) ->
                   this.documentRepository.findAll(specification, pageable),
diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/form/SimulationDetails.java b/openbas-api/src/main/java/io/openbas/rest/exercise/form/SimulationDetails.java
--- a/openbas-api/src/main/java/io/openbas/rest/exercise/form/SimulationDetails.java
+++ b/openbas-api/src/main/java/io/openbas/rest/exercise/form/SimulationDetails.java
@@ -186,7 +186,7 @@ public static SimulationDetails fromRawExercise(
               exerciseTeamsUsers.stream().map(ExerciseTeamUser::getUser).distinct().count());
     }
     details
-        .tags(exercise.getExercise_tags())
+        .tags(new HashSet<>(exercise.getExercise_tags()))
         .users(exercise.getExercise_users())
         .objectives(objectives)
         .lessonsAnswersNumber(exercise.getLessons_answers().stream().distinct().toList().size())
diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java b/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java
@@ -7,10 +7,12 @@
 import io.openbas.aop.RBAC;
 import io.openbas.aop.lock.Lock;
 import io.openbas.aop.lock.LockResourceType;
-import io.openbas.authorisation.AuthorisationService;
 import io.openbas.database.model.*;
-import io.openbas.database.repository.*;
+import io.openbas.database.repository.ExerciseRepository;
+import io.openbas.database.repository.InjectRepository;
+import io.openbas.database.repository.UserRepository;
 import io.openbas.database.specification.InjectSpecification;
+import io.openbas.database.specification.SpecificationUtils;
 import io.openbas.rest.atomic_testing.form.ExecutionTraceOutput;
 import io.openbas.rest.atomic_testing.form.InjectStatusOutput;
 import io.openbas.rest.exception.BadRequestException;
@@ -19,10 +21,13 @@
 import io.openbas.rest.exercise.exports.ExportOptions;
 import io.openbas.rest.helper.RestBehavior;
 import io.openbas.rest.inject.form.*;
-import io.openbas.rest.inject.service.*;
+import io.openbas.rest.inject.service.ExecutableInjectService;
+import io.openbas.rest.inject.service.InjectExecutionService;
+import io.openbas.rest.inject.service.InjectExportService;
+import io.openbas.rest.inject.service.InjectService;
 import io.openbas.rest.payload.form.DetectionRemediationOutput;
-import io.openbas.rest.security.SecurityExpression;
-import io.openbas.service.ImportService;
+import io.openbas.service.InjectImportService;
+import io.openbas.service.UserService;
 import io.openbas.service.targets.TargetService;
 import io.openbas.utils.FilterUtilsJpa;
 import io.openbas.utils.TargetType;
@@ -36,15 +41,18 @@
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotBlank;
 import java.io.IOException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
-import org.springframework.security.access.AccessDeniedException;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
@@ -59,18 +67,17 @@ public class InjectApi extends RestBehavior {
 
   private static final int MAX_NEXT_INJECTS = 6;
 
-  private final AuthorisationService authorisationService;
   private final ExecutableInjectService executableInjectService;
   private final ExerciseRepository exerciseRepository;
-  private final ImportService importService;
   private final InjectRepository injectRepository;
   private final InjectService injectService;
+  private final InjectImportService injectImportService;
   private final InjectExecutionService injectExecutionService;
   private final InjectExportService injectExportService;
-  private final ScenarioRepository scenarioRepository;
   private final TargetService targetService;
   private final UserRepository userRepository;
   private final PayloadMapper payloadMapper;
+  private final UserService userService;
 
   // -- INJECTS --
 
@@ -106,18 +113,19 @@ public void injectsExport(
       HttpServletResponse response)
       throws IOException {
     List<String> targetIds = injectExportRequestInput.getTargetsIds();
-    List<Inject> injects = injectRepository.findAllById(targetIds);
-
+    User currentUser = userService.currentUser();
+    List<Inject> injects =
+        injectRepository.findAll(
+            Specification.where(SpecificationUtils.<Inject>hasIdIn(targetIds))
+                .and(
+                    SpecificationUtils.hasGrantAccess(
+                        currentUser.getId(),
+                        currentUser.isAdminOrBypass(),
+                        currentUser.getCapabilities().contains(Capability.ACCESS_PAYLOADS),
+                        Grant.GRANT_TYPE.OBSERVER)));
     List<String> foundIds = injects.stream().map(Inject::getId).toList();
     List<String> missedIds =
         new ArrayList<>(targetIds.stream().filter(id -> !foundIds.contains(id)).toList());
-    missedIds.addAll(
-        injectService
-            .authorise(injects, SecurityExpression::isInjectObserver)
-            .getUnauthorised()
-            .stream()
-            .map(Inject::getId)
-            .toList());
 
     if (!missedIds.isEmpty()) {
       throw new ElementNotFoundException(String.join(", ", missedIds));
@@ -294,42 +302,7 @@ public void injectsImport(
                           .map(Enum::toString)
                           .toList())));
     }
-
-    Exercise targetExercise = null;
-    Scenario targetScenario = null;
-
-    if (input.getTarget().getType().equals(InjectImportTargetType.SIMULATION)) {
-      targetExercise =
-          exerciseRepository
-              .findById(input.getTarget().getId())
-              .orElseThrow(ElementNotFoundException::new);
-      if (!authorisationService
-          .getSecurityExpression()
-          .isSimulationPlanner(targetExercise.getId())) {
-        throw new AccessDeniedException(
-            "Insufficient privileges to act on simulation id#%s".formatted(targetExercise.getId()));
-      }
-    }
-
-    if (input.getTarget().getType().equals(InjectImportTargetType.SCENARIO)) {
-      targetScenario =
-          scenarioRepository
-              .findById(input.getTarget().getId())
-              .orElseThrow(ElementNotFoundException::new);
-      if (!authorisationService.getSecurityExpression().isScenarioPlanner(targetScenario.getId())) {
-        throw new AccessDeniedException(
-            "Insufficient privileges to act on scenario id#%s".formatted(targetScenario.getId()));
-      }
-    }
-
-    if (input.getTarget().getType().equals(InjectImportTargetType.ATOMIC_TESTING)) {
-      if (!authorisationService.getSecurityExpression().isAdmin()) {
-        throw new AccessDeniedException(
-            "Insufficient privileges: must be admin to act on atomic testing");
-      }
-    }
-
-    this.importService.handleFileImport(file, targetExercise, targetScenario);
+    this.injectImportService.importInjects(file, input);
   }
 
   @PostMapping(INJECT_URI + "/execution/reception/{injectId}")
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
@@ -14,8 +14,6 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
-import io.openbas.config.OpenBASPrincipal;
-import io.openbas.config.SessionHelper;
 import io.openbas.config.cache.LicenseCacheManager;
 import io.openbas.database.model.*;
 import io.openbas.database.repository.*;
@@ -96,7 +94,6 @@ 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;
@@ -453,10 +450,7 @@ public Specification<Inject> getInjectSpecification(
     }
     // Filter out any injects not related to resources where the user is granted with the
     // appropriate level
-    OpenBASPrincipal principal = SessionHelper.currentUser();
-    filterSpecifications =
-        filterSpecifications.and(
-            hasGrantAccessForInject(principal.getId(), principal.isAdmin(), requestedGrantLevel));
+    filterSpecifications = filterSpecifications.and(hasGrantAccessForInject(requestedGrantLevel));
     return filterSpecifications;
   }
 
@@ -1000,11 +994,23 @@ public List<DetectionRemediation> fetchDetectionRemediationsByInjectId(String in
     return payloadRepository.fetchDetectionRemediationsByInjectId(injectId);
   }
 
-  public Specification<Inject> hasGrantAccessForInject(
-      final String userId, final boolean isAdmin, Grant.GRANT_TYPE grantType) {
+  /**
+   * Chek if a user is granted on an inject. A user can be granteed on an inject if: - The inject is
+   * linked to a scenario or simulation that the user has access to - The inject is an atomic
+   * testing and the user has access to it
+   *
+   * @param grantType the grant type to check
+   * @return a Specification that checks if the user has access to the inject
+   */
+  public Specification<Inject> hasGrantAccessForInject(Grant.GRANT_TYPE grantType) {
+
+    User currentUser = userService.currentUser();
+    boolean hasCapabilityAccessAssessment =
+        currentUser.getCapabilities().contains(Capability.ACCESS_ASSESSMENT)
+            || currentUser.getCapabilities().contains(Capability.BYPASS);
 
     return (root, query, cb) -> {
-      if (isAdmin) {
+      if (currentUser.isAdmin() || hasCapabilityAccessAssessment) {
         return cb.conjunction();
       }
 
@@ -1020,11 +1026,27 @@ public Specification<Inject> hasGrantAccessForInject(
       // Create subquery for accessible scenarios
       Subquery<String> accessibleScenarios =
           SpecificationUtils.accessibleResourcesSubquery(
-              query, cb, userId, Grant.GRANT_RESOURCE_TYPE.SCENARIO, allowedGrantTypes);
+              query,
+              cb,
+              currentUser.getId(),
+              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);
+              query,
+              cb,
+              currentUser.getId(),
+              Grant.GRANT_RESOURCE_TYPE.SIMULATION,
+              allowedGrantTypes);
+      // Create subquery for accessible atomic testings
+      Subquery<String> accessibleAtomicTestings =
+          SpecificationUtils.accessibleResourcesSubquery(
+              query,
+              cb,
+              currentUser.getId(),
+              Grant.GRANT_RESOURCE_TYPE.ATOMIC_TESTING,
+              allowedGrantTypes);
 
       // Check if inject's scenario ID is accessible (null is OK)
       Predicate scenarioAccessible =
@@ -1032,11 +1054,20 @@ public Specification<Inject> hasGrantAccessForInject(
       // Check if inject's simulation ID is accessible (null is OK)
       Predicate simulationAccessible =
           cb.or(cb.isNull(simulationPath), simulationPath.get("id").in(accessibleSimulations));
+      // When both are null, check if user has atomic testing grants
+      // Assuming inject has an ID that should be in the atomic testing grants
+      Predicate atomicTestingAccessible =
+          cb.and(bothNull, root.get("id").in(accessibleAtomicTestings));
 
       // 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, simulationAccessible));
+      // 1. Both are null AND user has atomic testing grant for this inject, OR
+      // 2. User is granted on the non-null scenario/exercise linked to this inject
+      return cb.or(
+          atomicTestingAccessible,
+          cb.and(
+              cb.not(bothNull), // At least one is not null
+              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
@@ -7,10 +7,10 @@
 import static java.time.Instant.now;
 
 import io.openbas.aop.RBAC;
-import io.openbas.config.OpenBASPrincipal;
 import io.openbas.database.model.Action;
 import io.openbas.database.model.Organization;
 import io.openbas.database.model.ResourceType;
+import io.openbas.database.model.User;
 import io.openbas.database.raw.RawOrganization;
 import io.openbas.database.repository.OrganizationRepository;
 import io.openbas.database.repository.TagRepository;
@@ -19,6 +19,7 @@
 import io.openbas.rest.helper.RestBehavior;
 import io.openbas.rest.organization.form.OrganizationCreateInput;
 import io.openbas.rest.organization.form.OrganizationUpdateInput;
+import io.openbas.service.UserService;
 import io.openbas.service.organization.OrganizationService;
 import io.openbas.utils.FilterUtilsJpa;
 import io.openbas.utils.pagination.SearchPaginationInput;
@@ -41,12 +42,14 @@ public class OrganizationApi extends RestBehavior {
   private final UserRepository userRepository;
   private final OrganizationService organizationService;
 
+  private final UserService userService;
+
   @GetMapping(ORGANIZATION_URI)
   @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.ORGANIZATION)
   public Iterable<RawOrganization> organizations() {
-    OpenBASPrincipal currentUser = currentUser();
+    User currentUser = userService.currentUser();
     List<RawOrganization> organizations;
-    if (currentUser.isAdmin()) {
+    if (currentUser.isAdminOrBypass()) {
       organizations = fromIterable(organizationRepository.rawAll());
     } else {
       organizations = fromIterable(organizationRepository.rawByUser(currentUser.getId()));
diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java b/openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/payload/PayloadApi.java
@@ -1,18 +1,19 @@
 package io.openbas.rest.payload;
 
-import static io.openbas.utils.ArchitectureFilterUtils.handleArchitectureFilter;
-import static io.openbas.utils.pagination.PaginationUtils.buildPaginationJPA;
-
 import io.openbas.aop.RBAC;
 import io.openbas.database.model.*;
 import io.openbas.database.raw.RawDocument;
-import io.openbas.database.repository.*;
+import io.openbas.database.repository.PayloadRepository;
+import io.openbas.database.specification.SpecificationUtils;
+import io.openbas.helper.StreamHelper;
+import io.openbas.rest.collector.service.CollectorService;
 import io.openbas.rest.document.DocumentService;
 import io.openbas.rest.exception.ElementNotFoundException;
 import io.openbas.rest.helper.RestBehavior;
 import io.openbas.rest.payload.form.*;
 import io.openbas.rest.payload.service.*;
 import io.openbas.service.ImportService;
+import io.openbas.service.UserService;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -25,10 +26,12 @@
 import jakarta.validation.constraints.NotNull;
 import java.io.IOException;
 import java.util.List;
-import java.util.stream.StreamSupport;
 import lombok.RequiredArgsConstructor;
 import org.springframework.data.domain.Page;
+import org.springframework.data.jpa.domain.Specification;
 import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
@@ -46,15 +49,14 @@ public class PayloadApi extends RestBehavior {
   private final PayloadUpsertService payloadUpsertService;
   private final PayloadExportService payloadExportService;
   private final DocumentService documentService;
+  private final CollectorService collectorsService;
+  private final UserService userService;
 
   @PostMapping(PAYLOAD_URI + "/search")
   @RBAC(actionPerformed = Action.SEARCH, resourceType = ResourceType.PAYLOAD)
   public Page<Payload> payloads(
       @RequestBody @Valid final SearchPaginationInput searchPaginationInput) {
-    return buildPaginationJPA(
-        this.payloadRepository::findAll,
-        handleArchitectureFilter(searchPaginationInput),
-        Payload.class);
+    return this.payloadService.searchPayloads(searchPaginationInput);
   }
 
   @GetMapping(PAYLOAD_URI + "/{payloadId}")
@@ -102,6 +104,26 @@ public Payload upsertPayload(@Valid @RequestBody PayloadUpsertInput input) {
     return this.payloadUpsertService.upsertPayload(input);
   }
 
+  @PostMapping(path = PAYLOAD_URI + "/{payloadId}/export", produces = "application/zip")
+  @RBAC(
+      actionPerformed = Action.READ,
+      resourceType = ResourceType.PAYLOAD,
+      resourceId = "#payloadId")
+  public ResponseEntity<byte[]> payloadExport(@NotBlank @PathVariable String payloadId)
+      throws IOException {
+    List<String> targetIds = List.of(payloadId);
+    List<Payload> payloads = StreamHelper.fromIterable(payloadRepository.findAllById(targetIds));
+    byte[] zippedExport = payloadExportService.exportPayloadsToZip(payloads);
+    String zipName = payloadExportService.getZipFileName();
+
+    HttpHeaders headers = new HttpHeaders();
+    headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + zipName);
+    headers.add(HttpHeaders.CONTENT_TYPE, "application/zip");
+    headers.setContentLength(zippedExport.length);
+
+    return new ResponseEntity<>(zippedExport, headers, HttpStatus.OK);
+  }
+
   @PostMapping(PAYLOAD_URI + "/export")
   @RBAC(actionPerformed = Action.READ, resourceType = ResourceType.PAYLOAD)
   public void payloadsExport(
@@ -109,14 +131,22 @@ public void payloadsExport(
       HttpServletResponse response)
       throws IOException {
     List<String> targetIds = payloadExportRequestInput.getTargetsIds();
+    User currentUser = userService.currentUser();
+
     List<Payload> payloads =
-        StreamSupport.stream(payloadRepository.findAllById(targetIds).spliterator(), false)
-            .toList();
+        payloadRepository.findAll(
+            Specification.where(SpecificationUtils.<Payload>hasIdIn(targetIds))
+                .and(
+                    SpecificationUtils.hasGrantAccess(
+                        currentUser.getId(),
+                        currentUser.isAdminOrBypass(),
+                        currentUser.getCapabilities().contains(Capability.ACCESS_PAYLOADS),
+                        Grant.GRANT_TYPE.OBSERVER)));
     runPayloadExport(payloads, response);
   }
 
   @PostMapping(PAYLOAD_URI + "/import")
-  @RBAC(actionPerformed = Action.WRITE, resourceType = ResourceType.PAYLOAD)
+  @RBAC(actionPerformed = Action.CREATE, resourceType = ResourceType.PAYLOAD)
   public void importPayloads(@RequestPart("file") @NotNull MultipartFile file) throws Exception {
     this.importService.handleFileImport(file, null, null);
   }
@@ -128,9 +158,11 @@ private void runPayloadExport(List<Payload> payloads, HttpServletResponse respon
 
     response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + zipName);
     response.addHeader(HttpHeaders.CONTENT_TYPE, "application/zip");
+    response.setContentLength(zippedExport.length);
     response.setStatus(HttpServletResponse.SC_OK);
     ServletOutputStream outputStream = response.getOutputStream();
     outputStream.write(zippedExport);
+    outputStream.flush();
     outputStream.close();
   }
 
@@ -165,4 +197,20 @@ public void deprecateNonProcessedPayloadsByCollector(
   public List<RawDocument> documentsFromPayload(@PathVariable String payloadId) {
     return documentService.documentsForPayload(payloadId);
   }
+
+  @GetMapping(PAYLOAD_URI + "/{payloadId}/collectors")
+  @RBAC(
+      resourceId = "#payloadId",
+      actionPerformed = Action.READ,
+      resourceType = ResourceType.PAYLOAD)
+  @Operation(summary = "Get the Collectors used in a payload remediation")
+  @ApiResponses(
+      value = {
+        @ApiResponse(
+            responseCode = "200",
+            description = "The list of Collectors used in a payload remediation")
+      })
+  public List<Collector> collectorsFromPayload(@PathVariable String payloadId) {
+    return collectorsService.collectorsForPayload(payloadId);
+  }
 }
diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/PayloadUtils.java b/openbas-api/src/main/java/io/openbas/rest/payload/PayloadUtils.java
--- a/openbas-api/src/main/java/io/openbas/rest/payload/PayloadUtils.java
+++ b/openbas-api/src/main/java/io/openbas/rest/payload/PayloadUtils.java
@@ -18,6 +18,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.jetbrains.annotations.NotNull;
@@ -132,7 +133,8 @@ public <T extends Payload> void duplicateCommonProperties(
         "attackPatterns",
         "arguments",
         "prerequisites",
-        "detectionRemediations");
+        "detectionRemediations",
+        "grants");
     duplicate.setId(null);
     duplicate.setName(duplicateString(origin.getName()));
     duplicate.setAttackPatterns(new ArrayList<>(origin.getAttackPatterns()));
@@ -152,6 +154,20 @@ public <T extends Payload> void duplicateCommonProperties(
     if (eeService.isLicenseActive(licenseCacheManager.getEnterpriseEditionInfo())) {
       detectionRemediationUtils.copy(origin.getDetectionRemediations(), duplicate, false);
     }
+
+    // Copy grants (each one needs to be a fully new object)
+    List<Grant> grantCopies =
+        origin.getGrants().stream()
+            .map(
+                grant -> {
+                  Grant copy = new Grant();
+                  copy.setName(grant.getName());
+                  copy.setGroup(grant.getGroup());
+                  copy.setGrantResourceType(grant.getGrantResourceType());
+                  return copy;
+                })
+            .collect(Collectors.toList());
+    duplicate.setGrants(grantCopies);
   }
 
   public Payload copyProperties(PayloadCreateInput payloadInput, Payload target) {
diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadCreationService.java b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadCreationService.java
--- a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadCreationService.java
+++ b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadCreationService.java
@@ -13,6 +13,7 @@
 import io.openbas.rest.document.DocumentService;
 import io.openbas.rest.payload.PayloadUtils;
 import io.openbas.rest.payload.form.PayloadCreateInput;
+import io.openbas.service.GrantService;
 import jakarta.transaction.Transactional;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
@@ -27,6 +28,7 @@ public class PayloadCreationService {
   private final PayloadUtils payloadUtils;
 
   private final PayloadService payloadService;
+  private final GrantService grantService;
   private final Ee eeService;
   private final LicenseCacheManager licenseCacheManager;
 
@@ -61,6 +63,7 @@ private Payload create(PayloadCreateInput input, List<AttackPattern> attackPatte
       fileDrop.setFileDropFile(documentService.document(input.getFileDropFile()));
     }
 
+    this.grantService.computeGrant(payload);
     Payload saved = payloadRepository.save(payload);
     payloadService.updateInjectorContractsForPayload(saved);
     return saved;
diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadExportService.java b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadExportService.java
--- a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadExportService.java
+++ b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadExportService.java
@@ -133,6 +133,7 @@ public byte[] exportPayloadsToZip(List<Payload> payloads) throws IOException {
         parentZip.write(payloadZipStream.toByteArray());
         parentZip.closeEntry();
       }
+      parentZip.finish();
     }
     return parentOutputStream.toByteArray();
   }
diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadService.java b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadService.java
--- a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadService.java
+++ b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadService.java
@@ -13,6 +13,8 @@
 import static io.openbas.injector_contract.fields.ContractExpectations.expectationsField;
 import static io.openbas.injector_contract.fields.ContractSelect.selectFieldWithDefault;
 import static io.openbas.injector_contract.fields.ContractText.textField;
+import static io.openbas.utils.ArchitectureFilterUtils.handleArchitectureFilter;
+import static io.openbas.utils.pagination.PaginationUtils.buildPaginationJPA;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -21,6 +23,7 @@
 import io.openbas.database.repository.InjectorContractRepository;
 import io.openbas.database.repository.InjectorRepository;
 import io.openbas.database.repository.PayloadRepository;
+import io.openbas.database.specification.SpecificationUtils;
 import io.openbas.expectation.ExpectationBuilderService;
 import io.openbas.helper.SupportedLanguage;
 import io.openbas.injector_contract.Contract;
@@ -31,6 +34,8 @@
 import io.openbas.injectors.openbas.util.OpenBASObfuscationMap;
 import io.openbas.model.inject.form.Expectation;
 import io.openbas.rest.payload.PayloadUtils;
+import io.openbas.service.UserService;
+import io.openbas.utils.pagination.SearchPaginationInput;
 import jakarta.annotation.Resource;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
@@ -39,6 +44,7 @@
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.hibernate.Hibernate;
+import org.springframework.data.domain.Page;
 import org.springframework.stereotype.Service;
 
 @RequiredArgsConstructor
@@ -53,6 +59,7 @@ public class PayloadService {
   private final InjectorContractRepository injectorContractRepository;
   private final AttackPatternRepository attackPatternRepository;
   private final ExpectationBuilderService expectationBuilderService;
+  private final UserService userService;
   private final PayloadUtils payloadUtils;
 
   public void updateInjectorContractsForPayload(Payload payload) {
@@ -272,4 +279,26 @@ private static List<String> getExternalIdsToDeprecate(
         .filter(externalId -> !processedPayloadExternalIds.contains(externalId))
         .collect(Collectors.toList());
   }
+
+  /**
+   * Search payloads with pagination and architecture filter, where the user is granted. The user
+   * must have at least OBSERVER grant on the payloads to see them OR have the access capability on
+   * payloads.
+   *
+   * @param searchPaginationInput the input containing pagination and search criteria
+   * @return a paginated list of Payloads
+   */
+  public Page<Payload> searchPayloads(
+      @org.jetbrains.annotations.NotNull final SearchPaginationInput searchPaginationInput) {
+    User currentUser = userService.currentUser();
+    return buildPaginationJPA(
+        SpecificationUtils.withGrantFilter(
+            this.payloadRepository,
+            Grant.GRANT_TYPE.OBSERVER,
+            currentUser.getId(),
+            currentUser.isAdminOrBypass(),
+            currentUser.getCapabilities().contains(Capability.ACCESS_PAYLOADS)),
+        handleArchitectureFilter(searchPaginationInput),
+        Payload.class);
+  }
 }
diff --git a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadUpdateService.java b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadUpdateService.java
--- a/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadUpdateService.java
+++ b/openbas-api/src/main/java/io/openbas/rest/payload/service/PayloadUpdateService.java
@@ -15,6 +15,8 @@
 import io.openbas.rest.payload.PayloadUtils;
 import io.openbas.rest.payload.form.PayloadUpdateInput;
 import jakarta.transaction.Transactional;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
 import org.hibernate.Hibernate;
@@ -57,7 +59,12 @@ private Payload update(
     payloadUtils.copyProperties(input, payload);
 
     payload.setAttackPatterns(attackPatterns);
+    // Somehow, loading tags can create a detached error on detection remediation.
+    // Detaching the collection before and reattaching it after bypass the issue
+    List<DetectionRemediation> originalDrs = new ArrayList<>(payload.getDetectionRemediations());
+    payload.setDetectionRemediations(Collections.emptyList());
     payload.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds())));
+    payload.setDetectionRemediations(originalDrs);
 
     if (payload instanceof Executable executable) {
       executable.setExecutableFile(documentService.document(input.getExecutableFile()));
diff --git a/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioApi.java b/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioApi.java
@@ -183,7 +183,7 @@ public void exportScenario(
   // -- IMPORT --
 
   @PostMapping(SCENARIO_URI + "/import")
-  @RBAC(actionPerformed = Action.WRITE, resourceType = ResourceType.SCENARIO)
+  @RBAC(actionPerformed = Action.CREATE, resourceType = ResourceType.SCENARIO)
   public void importScenario(@RequestPart("file") @NotNull MultipartFile file) throws Exception {
     this.importService.handleFileImport(file, null, null);
   }
diff --git a/openbas-api/src/main/java/io/openbas/rest/team/AtomicTestingTeamApi.java b/openbas-api/src/main/java/io/openbas/rest/team/AtomicTestingTeamApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/team/AtomicTestingTeamApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/team/AtomicTestingTeamApi.java
@@ -27,7 +27,7 @@ public class AtomicTestingTeamApi extends RestBehavior {
   private final TeamService teamService;
 
   @PostMapping(ATOMIC_TESTING_URI + "/teams/search")
-  @RBAC(actionPerformed = Action.READ, resourceType = ResourceType.ATOMIC_TESTING)
+  @RBAC(actionPerformed = Action.READ, resourceType = ResourceType.INJECT)
   @Transactional(readOnly = true)
   public Page<TeamOutput> searchTeams(
       @RequestBody @Valid SearchPaginationInput searchPaginationInput) {
diff --git a/openbas-api/src/main/java/io/openbas/rest/team/TeamApi.java b/openbas-api/src/main/java/io/openbas/rest/team/TeamApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/team/TeamApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/team/TeamApi.java
@@ -15,7 +15,6 @@
 import io.openbas.aop.RBAC;
 import io.openbas.aop.UserRoleDescription;
 import io.openbas.aop.onboarding.Onboarding;
-import io.openbas.config.OpenBASPrincipal;
 import io.openbas.database.model.*;
 import io.openbas.database.raw.RawTeam;
 import io.openbas.database.repository.*;
@@ -29,6 +28,7 @@
 import io.openbas.rest.team.form.UpdateUsersTeamInput;
 import io.openbas.rest.team.output.TeamOutput;
 import io.openbas.service.TeamService;
+import io.openbas.service.UserService;
 import io.openbas.utils.FilterUtilsJpa;
 import io.openbas.utils.InputFilterOptions;
 import io.openbas.utils.pagination.SearchPaginationInput;
@@ -73,6 +73,7 @@ public class TeamApi extends RestBehavior {
   private final OrganizationRepository organizationRepository;
   private final TagRepository tagRepository;
   private final TeamService teamService;
+  private final UserService userService;
 
   @LogExecutionTime
   @GetMapping(TEAM_URI)
@@ -81,8 +82,8 @@ public class TeamApi extends RestBehavior {
   @Operation(summary = "List teams", description = "Return the teams")
   public Iterable<TeamSimple> getTeams() {
     List<RawTeam> teams;
-    OpenBASPrincipal currentUser = currentUser();
-    if (currentUser.isAdmin()) {
+    User currentUser = userService.currentUser();
+    if (currentUser.isAdminOrBypass()) {
       // We get all the teams as raw
       teams = fromIterable(teamRepository.rawTeams());
     } else {
diff --git a/openbas-api/src/main/java/io/openbas/rest/user/PlayerApi.java b/openbas-api/src/main/java/io/openbas/rest/user/PlayerApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/user/PlayerApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/user/PlayerApi.java
@@ -10,7 +10,6 @@
 import io.openbas.aop.LogExecutionTime;
 import io.openbas.aop.RBAC;
 import io.openbas.aop.onboarding.Onboarding;
-import io.openbas.config.OpenBASPrincipal;
 import io.openbas.config.SessionManager;
 import io.openbas.database.model.*;
 import io.openbas.database.raw.RawPlayer;
@@ -53,8 +52,8 @@ public class PlayerApi extends RestBehavior {
   @Transactional(rollbackOn = Exception.class)
   public Iterable<RawPlayer> players() {
     List<RawPlayer> players;
-    OpenBASPrincipal currentUser = currentUser();
-    if (currentUser.isAdmin()) {
+    User currentUser = userService.currentUser();
+    if (currentUser.isAdminOrBypass()) {
       players = fromIterable(userRepository.rawAllPlayers());
     } else {
       User local =
diff --git a/openbas-api/src/main/java/io/openbas/rest/user/PlayerService.java b/openbas-api/src/main/java/io/openbas/rest/user/PlayerService.java
--- a/openbas-api/src/main/java/io/openbas/rest/user/PlayerService.java
+++ b/openbas-api/src/main/java/io/openbas/rest/user/PlayerService.java
@@ -8,12 +8,12 @@
 import static io.openbas.utils.pagination.PaginationUtils.buildPaginationCriteriaBuilder;
 import static io.openbas.utils.pagination.SortUtilsCriteriaBuilder.toSortCriteriaBuilder;
 
-import io.openbas.config.OpenBASPrincipal;
 import io.openbas.database.model.Organization;
 import io.openbas.database.model.User;
 import io.openbas.database.repository.UserRepository;
 import io.openbas.rest.exception.ElementNotFoundException;
 import io.openbas.rest.user.form.player.PlayerOutput;
+import io.openbas.service.UserService;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.PersistenceContext;
@@ -37,12 +37,13 @@ public class PlayerService {
   @PersistenceContext private EntityManager entityManager;
 
   private final UserRepository userRepository;
+  private final UserService userService;
 
   public Page<PlayerOutput> playerPagination(@NotNull SearchPaginationInput searchPaginationInput) {
     TriFunction<Specification<User>, Specification<User>, Pageable, Page<PlayerOutput>>
         playersFunction;
-    OpenBASPrincipal currentUser = currentUser();
-    if (currentUser.isAdmin()) {
+    User currentUser = userService.currentUser();
+    if (currentUser.isAdminOrBypass()) {
       playersFunction = this::paginate;
     } else {
       User local =
diff --git a/openbas-api/src/main/java/io/openbas/search/FullTextSearchService.java b/openbas-api/src/main/java/io/openbas/search/FullTextSearchService.java
--- a/openbas-api/src/main/java/io/openbas/search/FullTextSearchService.java
+++ b/openbas-api/src/main/java/io/openbas/search/FullTextSearchService.java
@@ -149,13 +149,16 @@ public Page<FullTextSearchResult> fullTextSearch(
 
     // Specification building
     Specification<T> specs =
-        SpecificationUtils.<T>fullTextSearch(finalSearchTerm, searchListByClassMap.get(clazzT))
-            .and(
-                SpecificationUtils.hasGrantAccess(
-                    currentUser.getId(),
-                    currentUser.isAdminOrBypass(),
-                    currentUser.getCapabilities().contains(capaForClass),
-                    Grant.GRANT_TYPE.OBSERVER));
+        SpecificationUtils.<T>fullTextSearch(finalSearchTerm, searchListByClassMap.get(clazzT));
+    if (GrantableBase.class.isAssignableFrom(clazzT)) {
+      specs =
+          specs.and(
+              SpecificationUtils.hasGrantAccess(
+                  currentUser.getId(),
+                  currentUser.isAdminOrBypass(),
+                  currentUser.getCapabilities().contains(capaForClass),
+                  Grant.GRANT_TYPE.OBSERVER));
+    }
 
     return buildPaginationJPA(repository::findAll, searchPaginationInput, clazzT, specs)
         .map(this::transform);
diff --git a/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java b/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java
--- a/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java
+++ b/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java
@@ -14,6 +14,8 @@
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import io.openbas.database.model.*;
 import io.openbas.database.repository.*;
+import io.openbas.database.specification.InjectSpecification;
+import io.openbas.database.specification.SpecificationUtils;
 import io.openbas.injector_contract.fields.ContractFieldType;
 import io.openbas.rest.atomic_testing.form.*;
 import io.openbas.rest.exception.ElementNotFoundException;
@@ -24,7 +26,6 @@
 import io.openbas.utils.pagination.SearchPaginationInput;
 import jakarta.annotation.Resource;
 import jakarta.persistence.criteria.Join;
-import jakarta.persistence.criteria.Predicate;
 import jakarta.transaction.Transactional;
 import java.util.*;
 import java.util.stream.StreamSupport;
@@ -55,6 +56,7 @@ public class AtomicTestingService {
   private final TagRepository tagRepository;
   private final DocumentRepository documentRepository;
   private final AssetGroupService assetGroupService;
+  private final UserService userService;
 
   private final InjectSearchService injectSearchService;
   private final InjectService injectService;
@@ -226,18 +228,31 @@ public InjectResultOverviewOutput relaunch(String id) {
 
   // -- PAGINATION --
 
-  public Page<InjectResultOutput> searchAtomicTestings(
+  /**
+   * Search atomic testings with pagination and filtering. Atomic testings are injects that are not
+   * part of any scenario or exercise (both fields are null). The search only fetches data according
+   * to user permissions via the grant system.
+   *
+   * @param searchPaginationInput Pagination and filtering parameters
+   * @return A paginated list of atomic testing results
+   */
+  public Page<InjectResultOutput> searchAtomicTestingsForCurrentUser(
       @NotNull final SearchPaginationInput searchPaginationInput) {
     Map<String, Join<Base, Base>> joinMap = new HashMap<>();
 
+    // Atomic testings are injects where scenario and exercise are null. They are also subject to
+    // the grant system.
+    User currentUser = userService.currentUser();
+
     Specification<Inject> customSpec =
-        Specification.where(
-            (root, query, cb) -> {
-              Predicate predicate = cb.conjunction();
-              predicate = cb.and(predicate, cb.isNull(root.get("scenario")));
-              predicate = cb.and(predicate, cb.isNull(root.get("exercise")));
-              return predicate;
-            });
+        Specification.where(InjectSpecification.isAtomicTesting())
+            .and(
+                SpecificationUtils.hasGrantAccess(
+                    currentUser.getId(),
+                    currentUser.isAdminOrBypass(),
+                    currentUser.getCapabilities().contains(Capability.ACCESS_ASSESSMENT),
+                    Grant.GRANT_TYPE.OBSERVER));
+
     return buildPaginationCriteriaBuilder(
         (Specification<Inject> specification,
             Specification<Inject> specificationCount,
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
@@ -94,9 +94,9 @@ public void validateResourceIdForGrant(String resourceId) {
     boolean exists =
         exerciseRepository.existsById(resourceId)
             || scenarioRepository.existsById(resourceId)
+            // Atomic testings:
             || injectRepository.existsByIdAndScenarioIsNullAndExerciseIsNull(resourceId)
-            || // Atomic Testing
-            payloadRepository.existsById(resourceId);
+            || 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/InjectImportService.java b/openbas-api/src/main/java/io/openbas/service/InjectImportService.java
--- a/openbas-api/src/main/java/io/openbas/service/InjectImportService.java
+++ b/openbas-api/src/main/java/io/openbas/service/InjectImportService.java
@@ -7,10 +7,13 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.openbas.authorisation.AuthorisationService;
 import io.openbas.database.model.*;
 import io.openbas.database.repository.*;
 import io.openbas.rest.exception.BadRequestException;
 import io.openbas.rest.exception.ElementNotFoundException;
+import io.openbas.rest.inject.form.InjectImportInput;
+import io.openbas.rest.inject.form.InjectImportTargetType;
 import io.openbas.rest.scenario.response.ImportMessage;
 import io.openbas.rest.scenario.response.ImportPostSummary;
 import io.openbas.rest.scenario.response.ImportTestSummary;
@@ -58,6 +61,11 @@ public class InjectImportService {
   private final TeamRepository teamRepository;
   private final UserRepository userRepository;
 
+  private final ExerciseRepository exerciseRepository;
+  private final AuthorisationService authorisationService;
+  private final ScenarioRepository scenarioRepository;
+  private final ImportService importService;
+
   private final List<String> importReservedField = List.of("description", "title", "trigger_time");
   final Pattern relativeDayPattern = Pattern.compile("^.*[DJ]([+\\-]?[0-9]*)(.*)$");
   final Pattern relativeHourPattern = Pattern.compile("^.*[HT]([+\\-]?[0-9]*).*$");
@@ -1181,4 +1189,25 @@ private void processDateToAbsolute(Map<Integer, InjectTime> mapInstantByRowIndex
                                   - earliestInstant.getEpochSecond());
                     }));
   }
+
+  public void importInjects(MultipartFile file, InjectImportInput input) throws Exception {
+    Exercise targetExercise = null;
+    Scenario targetScenario = null;
+
+    if (input.getTarget().getType().equals(InjectImportTargetType.SIMULATION)) {
+      targetExercise =
+          exerciseRepository
+              .findById(input.getTarget().getId())
+              .orElseThrow(ElementNotFoundException::new);
+    }
+
+    if (input.getTarget().getType().equals(InjectImportTargetType.SCENARIO)) {
+      targetScenario =
+          scenarioRepository
+              .findById(input.getTarget().getId())
+              .orElseThrow(ElementNotFoundException::new);
+    }
+
+    this.importService.handleFileImport(file, targetExercise, targetScenario);
+  }
 }
diff --git a/openbas-api/src/main/java/io/openbas/service/PermissionService.java b/openbas-api/src/main/java/io/openbas/service/PermissionService.java
--- a/openbas-api/src/main/java/io/openbas/service/PermissionService.java
+++ b/openbas-api/src/main/java/io/openbas/service/PermissionService.java
@@ -30,7 +30,11 @@ public class PermissionService {
 
   private static final EnumSet<ResourceType> RESOURCES_MANAGED_BY_GRANTS =
       EnumSet.of(
-          ResourceType.SCENARIO, ResourceType.SIMULATION, ResourceType.SIMULATION_OR_SCENARIO);
+          ResourceType.SCENARIO,
+          ResourceType.SIMULATION,
+          ResourceType.SIMULATION_OR_SCENARIO,
+          ResourceType.PAYLOAD,
+          ResourceType.ATOMIC_TESTING);
 
   private static final EnumSet<ResourceType> RESOURCES_USING_PARENT_PERMISSION =
       EnumSet.of(ResourceType.INJECT, ResourceType.NOTIFICATION_RULE);
diff --git a/openbas-api/src/main/java/io/openbas/service/TeamService.java b/openbas-api/src/main/java/io/openbas/service/TeamService.java
--- a/openbas-api/src/main/java/io/openbas/service/TeamService.java
+++ b/openbas-api/src/main/java/io/openbas/service/TeamService.java
@@ -8,7 +8,6 @@
 import static io.openbas.utils.pagination.PaginationUtils.buildPaginationCriteriaBuilder;
 import static io.openbas.utils.pagination.SortUtilsCriteriaBuilder.toSortCriteriaBuilder;
 
-import io.openbas.config.OpenBASPrincipal;
 import io.openbas.database.model.Organization;
 import io.openbas.database.model.Tag;
 import io.openbas.database.model.Team;
@@ -45,6 +44,8 @@ public class TeamService {
   private final UserRepository userRepository;
   private final TeamRepository teamRepository;
 
+  private final UserService userService;
+
   public List<TeamOutput> getTeams(@NotNull List<String> teamIds) {
     List<RawTeam> rawTeams =
         teamRepository.rawTeamByIds(teamIds).stream()
@@ -70,8 +71,8 @@ public Page<TeamOutput> teamPagination(
       @NotNull SearchPaginationInput searchPaginationInput,
       @NotNull final Specification<Team> teamSpecification) {
     TriFunction<Specification<Team>, Specification<Team>, Pageable, Page<TeamOutput>> teamsFunction;
-    OpenBASPrincipal currentUser = currentUser();
-    if (currentUser.isAdmin()) {
+    User currentUser = userService.currentUser();
+    if (currentUser.isAdminOrBypass()) {
       teamsFunction =
           (Specification<Team> specification,
               Specification<Team> specificationCount,
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
--- a/openbas-api/src/main/java/io/openbas/service/organization/OrganizationService.java
+++ b/openbas-api/src/main/java/io/openbas/service/organization/OrganizationService.java
@@ -4,9 +4,10 @@
 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.model.User;
 import io.openbas.database.repository.OrganizationRepository;
+import io.openbas.service.UserService;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import lombok.RequiredArgsConstructor;
 import org.jetbrains.annotations.NotNull;
@@ -21,10 +22,12 @@ public class OrganizationService {
 
   private final OrganizationRepository organizationRepository;
 
+  private final UserService userService;
+
   public Page<Organization> organizationPagination(
       @NotNull SearchPaginationInput searchPaginationInput) {
-    OpenBASPrincipal currentUser = currentUser();
-    if (currentUser.isAdmin()) {
+    User currentUser = userService.currentUser();
+    if (currentUser.isAdminOrBypass()) {
       return buildPaginationJPA(
           (Specification<Organization> specification, Pageable pageable) ->
               this.organizationRepository.findAll(specification, pageable),
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
@@ -1,12 +1,10 @@
 package io.openbas.database.model;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import java.lang.annotation.*;
 
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
+@Inherited
 public @interface Grantable {
   /**
    * @return the GRANT_RESOURCE_TYPE associated with the annotated class
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Inject.java b/openbas-model/src/main/java/io/openbas/database/model/Inject.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Inject.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Inject.java
@@ -21,10 +21,12 @@
 import java.time.Instant;
 import java.util.*;
 import java.util.stream.Collectors;
+import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.SQLRestriction;
 import org.hibernate.annotations.UpdateTimestamp;
 import org.hibernate.annotations.UuidGenerator;
 
@@ -33,7 +35,8 @@
 @Table(name = "injects")
 @EntityListeners(ModelBaseListener.class)
 @Slf4j
-public class Inject implements Base, Injection {
+@Grantable(Grant.GRANT_RESOURCE_TYPE.ATOMIC_TESTING)
+public class Inject implements GrantableBase, Injection {
 
   public static final int SPEED_STANDARD = 1; // Standard speed define by the user.
   public static final String ID_COLUMN_NAME = "inject_id";
@@ -301,7 +304,25 @@ public void setAssetGroups(List<AssetGroup> assetGroups) {
   @Transient
   private final ResourceType resourceType = ResourceType.INJECT;
 
+  @Getter
+  @OneToMany
+  @JoinColumn(
+      name = "grant_resource",
+      referencedColumnName = "inject_id",
+      insertable = false,
+      updatable = false)
+  @SQLRestriction(
+      "grant_resource_type = 'ATOMIC_TESTING'") // Must be present in Grant.GRANT_RESOURCE_TYPE
+  @JsonIgnore
+  private List<Grant> grants = new ArrayList<>();
+
   // region transient
+  @Transient
+  @JsonIgnore
+  @Getter
+  @Setter(AccessLevel.NONE)
+  private Grant.GRANT_RESOURCE_TYPE grantResourceType = Grant.GRANT_RESOURCE_TYPE.ATOMIC_TESTING;
+
   @Transient
   public String getHeader() {
     return ofNullable(this.getExercise()).map(Exercise::getHeader).orElse("");
diff --git a/openbas-model/src/main/java/io/openbas/database/model/Payload.java b/openbas-model/src/main/java/io/openbas/database/model/Payload.java
--- a/openbas-model/src/main/java/io/openbas/database/model/Payload.java
+++ b/openbas-model/src/main/java/io/openbas/database/model/Payload.java
@@ -20,6 +20,8 @@
 import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.persistence.*;
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Table;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
@@ -28,10 +30,7 @@
 import lombok.Data;
 import lombok.Getter;
 import lombok.Setter;
-import org.hibernate.annotations.CreationTimestamp;
-import org.hibernate.annotations.Type;
-import org.hibernate.annotations.UpdateTimestamp;
-import org.hibernate.annotations.UuidGenerator;
+import org.hibernate.annotations.*;
 
 @Data
 @Entity
@@ -59,7 +58,8 @@
           value = NetworkTraffic.NETWORK_TRAFFIC_TYPE,
           schema = NetworkTraffic.class)
     })
-public class Payload implements Base {
+@Grantable(Grant.GRANT_RESOURCE_TYPE.PAYLOAD)
+public class Payload implements GrantableBase {
 
   private static final int DEFAULT_NUMBER_OF_ACTIONS_FOR_PAYLOAD = 1;
 
@@ -225,6 +225,17 @@ public enum PAYLOAD_EXECUTION_ARCH {
   @JsonProperty("payload_output_parsers")
   private Set<OutputParser> outputParsers = new HashSet<>();
 
+  @Getter
+  @OneToMany
+  @JoinColumn(
+      name = "grant_resource",
+      referencedColumnName = "payload_id",
+      insertable = false,
+      updatable = false)
+  @SQLRestriction("grant_resource_type = 'PAYLOAD'") // Must be present in Grant.GRANT_RESOURCE_TYPE
+  @JsonIgnore
+  private List<Grant> grants = new ArrayList<>();
+
   // -- AUDIT --
 
   @CreationTimestamp
diff --git a/openbas-model/src/main/java/io/openbas/database/repository/CollectorRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/CollectorRepository.java
--- a/openbas-model/src/main/java/io/openbas/database/repository/CollectorRepository.java
+++ b/openbas-model/src/main/java/io/openbas/database/repository/CollectorRepository.java
@@ -2,8 +2,11 @@
 
 import io.openbas.database.model.Collector;
 import jakarta.validation.constraints.NotNull;
+import java.util.List;
 import java.util.Optional;
+import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
 @Repository
@@ -14,4 +17,19 @@ public interface CollectorRepository extends CrudRepository<Collector, String> {
 
   @NotNull
   Optional<Collector> findByType(@NotNull String type);
+
+  @Query(
+      """
+              SELECT DISTINCT dr.collector FROM DetectionRemediation dr
+              JOIN dr.payload p
+              WHERE p.id = :payloadId
+          """)
+  List<Collector> findByPayloadId(@Param("payloadId") String payloadId);
+
+  @Query(
+      """
+          SELECT DISTINCT i.injectorContract.payload.collector FROM Inject i
+          WHERE i.id = :injectId
+          """)
+  List<Collector> findByInjectId(@Param("injectId") String injectId);
 }
diff --git a/openbas-model/src/main/java/io/openbas/database/specification/InjectSpecification.java b/openbas-model/src/main/java/io/openbas/database/specification/InjectSpecification.java
--- a/openbas-model/src/main/java/io/openbas/database/specification/InjectSpecification.java
+++ b/openbas-model/src/main/java/io/openbas/database/specification/InjectSpecification.java
@@ -78,12 +78,11 @@ public static Specification<Inject> executable() {
   }
 
   public static Specification<Inject> forAtomicTesting() {
-    return (root, query, cb) ->
-        cb.and(
-            cb.isNull(root.get("exercise")), // No exercise
-            cb.isNull(root.get("scenario")), // No scenario
-            cb.equal(root.get("status").get("name"), ExecutionStatus.QUEUING),
-            cb.notEqual(root.get("status").get("name"), ExecutionStatus.PENDING));
+    return Specification.where(isAtomicTesting())
+        .and((root, query, cb) -> cb.equal(root.get("status").get("name"), ExecutionStatus.QUEUING))
+        .and(
+            (root, query, cb) ->
+                cb.notEqual(root.get("status").get("name"), ExecutionStatus.PENDING));
   }
 
   public static Specification<Inject> pendingInjectWithThresholdMinutes(int thresholdMinutes) {
@@ -110,4 +109,9 @@ public static Specification<Inject> testable() {
     return (root, query, cb) ->
         root.get("injectorContract").get("injector").get("type").in(VALID_TESTABLE_TYPES);
   }
+
+  public static Specification<Inject> isAtomicTesting() {
+    return (root, query, cb) ->
+        cb.and(cb.isNull(root.get("scenario")), cb.isNull(root.get("exercise")));
+  }
 }
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,6 @@
 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;
@@ -13,6 +9,7 @@
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.domain.Specification;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
 public class SpecificationUtils {
 
@@ -49,20 +46,22 @@ public static <T extends Base> Specification<T> fullTextSearch(
    * otherwise.
    *
    * @param userId ID of the user performing the search
-   * @param isAdmin true id the user performing the search has admin privileges
+   * @param isAdminOrBypass true id the user performing the search has admin privileges
    * @param hasCapaForClass true id the user has the capa for the specific class to bypass grants
    * @param grantType the minimum grant type required to access the resource
    */
   public static <T extends Base> Specification<T> hasGrantAccess(
       final String userId,
-      final boolean isAdmin,
+      final boolean isAdminOrBypass,
       boolean hasCapaForClass,
       Grant.GRANT_TYPE grantType) {
     return (root, query, cb) -> {
       Class<?> entityClass = root.getJavaType();
 
       // If the user is an admin or there is no grant system associated with T
-      if (isAdmin || !GrantableBase.class.isAssignableFrom(entityClass) || hasCapaForClass) {
+      if (isAdminOrBypass
+          || !GrantableBase.class.isAssignableFrom(entityClass)
+          || hasCapaForClass) {
         return cb.conjunction(); // Always true
       }
 
@@ -131,15 +130,19 @@ public static Subquery<String> accessibleResourcesSubquery(
 
   public static <T extends GrantableBase>
       BiFunction<Specification<T>, Pageable, Page<T>> withGrantFilter(
-          BiFunction<Specification<T>, Pageable, Page<T>> findAll,
+          JpaSpecificationExecutor<T> repo,
           Grant.GRANT_TYPE grantType,
           String userId,
-          boolean isAdmin,
+          boolean isAdminOrBypass,
           boolean hasCapaForClass) {
-    return (spec, pageable) ->
-        findAll.apply(
-            spec.and(
-                SpecificationUtils.hasGrantAccess(userId, isAdmin, hasCapaForClass, grantType)),
-            pageable);
+    return (spec, pageable) -> {
+      Specification<T> grantSpec =
+          SpecificationUtils.hasGrantAccess(userId, isAdminOrBypass, hasCapaForClass, grantType);
+      return repo.findAll(spec == null ? grantSpec : spec.and(grantSpec), pageable);
+    };
+  }
+
+  public static <T extends Base> Specification<T> hasIdIn(List<String> ids) {
+    return (root, query, cb) -> root.get("id").in(ids);
   }
 }


Test patch:
diff --git a/openbas-api/src/main/java/io/openbas/rest/atomic_testing/AtomicTestingApi.java b/openbas-api/src/main/java/io/openbas/rest/atomic_testing/AtomicTestingApi.java
--- a/openbas-api/src/main/java/io/openbas/rest/atomic_testing/AtomicTestingApi.java
+++ b/openbas-api/src/main/java/io/openbas/rest/atomic_testing/AtomicTestingApi.java
@@ -3,24 +3,34 @@
 import io.openbas.aop.LogExecutionTime;
 import io.openbas.aop.RBAC;
 import io.openbas.database.model.Action;
+import io.openbas.database.model.Collector;
 import io.openbas.database.model.InjectExpectation;
 import io.openbas.database.model.ResourceType;
 import io.openbas.rest.atomic_testing.form.*;
+import io.openbas.rest.collector.service.CollectorService;
+import io.openbas.rest.exception.UnprocessableContentException;
 import io.openbas.rest.helper.RestBehavior;
+import io.openbas.rest.inject.form.InjectImportInput;
+import io.openbas.rest.inject.form.InjectImportTargetDefinition;
+import io.openbas.rest.inject.form.InjectImportTargetType;
 import io.openbas.service.AtomicTestingService;
 import io.openbas.service.InjectExpectationService;
+import io.openbas.service.InjectImportService;
 import io.openbas.utils.pagination.SearchPaginationInput;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.responses.ApiResponse;
 import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotBlank;
 import java.util.Comparator;
 import java.util.List;
 import lombok.RequiredArgsConstructor;
 import org.springframework.data.domain.Page;
+import org.springframework.http.MediaType;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 @RestController
 @RequestMapping(AtomicTestingApi.ATOMIC_TESTING_URI)
@@ -31,6 +41,8 @@ public class AtomicTestingApi extends RestBehavior {
 
   private final AtomicTestingService atomicTestingService;
   private final InjectExpectationService injectExpectationService;
+  private final CollectorService collectorsService;
+  private final InjectImportService injectImportService;
 
   @LogExecutionTime
   @PostMapping("/search")
@@ -38,25 +50,19 @@ public class AtomicTestingApi extends RestBehavior {
   @Transactional(readOnly = true)
   public Page<InjectResultOutput> findAllAtomicTestings(
       @RequestBody @Valid final SearchPaginationInput searchPaginationInput) {
-    return atomicTestingService.searchAtomicTestings(searchPaginationInput);
+    return atomicTestingService.searchAtomicTestingsForCurrentUser(searchPaginationInput);
   }
 
   @LogExecutionTime
   @GetMapping("/{injectId}")
-  @RBAC(
-      resourceId = "#injectId",
-      actionPerformed = Action.READ,
-      resourceType = ResourceType.ATOMIC_TESTING)
+  @RBAC(resourceId = "#injectId", actionPerformed = Action.READ, resourceType = ResourceType.INJECT)
   public InjectResultOverviewOutput findAtomicTesting(@PathVariable String injectId) {
     return atomicTestingService.findById(injectId);
   }
 
   @LogExecutionTime
   @GetMapping("/{injectId}/payload")
-  @RBAC(
-      resourceId = "#injectId",
-      actionPerformed = Action.READ,
-      resourceType = ResourceType.ATOMIC_TESTING)
+  @RBAC(resourceId = "#injectId", actionPerformed = Action.READ, resourceType = ResourceType.INJECT)
   public StatusPayloadOutput findAtomicTestingPayload(@PathVariable String injectId) {
     return atomicTestingService.findPayloadOutputByInjectId(injectId);
   }
@@ -121,10 +127,7 @@ public InjectResultOverviewOutput relaunchAtomicTesting(
   }
 
   @GetMapping("/{injectId}/target_results/{targetId}/types/{targetType}")
-  @RBAC(
-      resourceId = "#injectId",
-      actionPerformed = Action.READ,
-      resourceType = ResourceType.ATOMIC_TESTING)
+  @RBAC(resourceId = "#injectId", actionPerformed = Action.READ, resourceType = ResourceType.INJECT)
   public List<InjectExpectation> findTargetResult(
       @PathVariable String injectId,
       @PathVariable String targetId,
@@ -153,10 +156,7 @@ public List<InjectExpectation> findTargetResult(
         @ApiResponse(responseCode = "400", description = "An invalid target type was specified")
       })
   @GetMapping("/{injectId}/target_results/{targetId}/types/{targetType}/merged")
-  @RBAC(
-      resourceId = "#injectId",
-      actionPerformed = Action.READ,
-      resourceType = ResourceType.ATOMIC_TESTING)
+  @RBAC(resourceId = "#injectId", actionPerformed = Action.READ, resourceType = ResourceType.INJECT)
   public List<InjectExpectation> findTargetResultMerged(
       @PathVariable String injectId,
       @PathVariable String targetId,
@@ -179,4 +179,35 @@ public InjectResultOverviewOutput updateAtomicTestingTags(
       @Valid @RequestBody final AtomicTestingUpdateTagsInput input) {
     return atomicTestingService.updateAtomicTestingTags(injectId, input);
   }
+
+  @GetMapping(ATOMIC_TESTING_URI + "/{injectId}/collectors")
+  @RBAC(resourceId = "#injectId", actionPerformed = Action.READ, resourceType = ResourceType.INJECT)
+  @Operation(summary = "Get the Collectors used in an atomic testing remediation")
+  @ApiResponses(
+      value = {
+        @ApiResponse(
+            responseCode = "200",
+            description = "The list of Collectors used in an atomic testing remediation")
+      })
+  public List<Collector> collectorsFromAtomicTesting(@PathVariable String injectId) {
+    return collectorsService.collectorsForAtomicTesting(injectId);
+  }
+
+  @PostMapping(
+      path = "/import",
+      consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
+  @RBAC(actionPerformed = Action.WRITE, resourceType = ResourceType.ATOMIC_TESTING)
+  public void atomicTestingImport(
+      @RequestPart("file") MultipartFile file, HttpServletResponse response) throws Exception {
+    // find target
+    if (file == null) {
+      throw new UnprocessableContentException("Insufficient input: file is required");
+    }
+
+    InjectImportInput targetInput = new InjectImportInput();
+    targetInput.setTarget(new InjectImportTargetDefinition());
+    targetInput.getTarget().setType(InjectImportTargetType.ATOMIC_TESTING);
+
+    this.injectImportService.importInjects(file, targetInput);
+  }
 }
diff --git a/openbas-api/src/test/java/io/openbas/rest/inject/InjectImportTest.java b/openbas-api/src/test/java/io/openbas/rest/inject/InjectImportTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/inject/InjectImportTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/inject/InjectImportTest.java
@@ -33,7 +33,6 @@
 import io.openbas.utils.mockUser.WithMockAdminUser;
 import io.openbas.utils.mockUser.WithMockPlannerUser;
 import io.openbas.utils.mockUser.WithMockUnprivilegedUser;
-import io.openbas.utils.mockUser.WithMockUserFullPermissions;
 import jakarta.persistence.EntityManager;
 import java.io.IOException;
 import java.util.*;
@@ -495,7 +494,6 @@ public class WhenLackingADMINPermissionsForAtomicTests {
 
     @Test
     @DisplayName("Return NOT FOUND")
-    @WithMockUserFullPermissions // FIXME: Temporary workaround for grant issue
     public void returnNOTFOUND() throws Exception {
       byte[] exportData = getExportData(getInjectFromExerciseWrappers(), false, false, false);
 
diff --git a/openbas-api/src/test/java/io/openbas/rest/inject/service/InjectServiceTest.java b/openbas-api/src/test/java/io/openbas/rest/inject/service/InjectServiceTest.java
--- a/openbas-api/src/test/java/io/openbas/rest/inject/service/InjectServiceTest.java
+++ b/openbas-api/src/test/java/io/openbas/rest/inject/service/InjectServiceTest.java
@@ -139,6 +139,8 @@ void getInjectSpecificationWithValidSearchInput() {
     input.getSearchPaginationInput().setFilterGroup(new Filters.FilterGroup());
     input.getSearchPaginationInput().setTextSearch("test");
 
+    when(userService.currentUser()).thenReturn(new User());
+
     // Act
     Specification<Inject> specification =
         injectService.getInjectSpecification(input, Grant.GRANT_TYPE.OBSERVER);
@@ -154,6 +156,8 @@ void getInjectSpecificationWithInjectIDsToProcess() {
     InjectBulkProcessingInput input = new InjectBulkProcessingInput();
     input.setInjectIDsToProcess(List.of("id1", "id2"));
 
+    when(userService.currentUser()).thenReturn(new User());
+
     // Act
     Specification<Inject> specification =
         injectService.getInjectSpecification(input, Grant.GRANT_TYPE.OBSERVER);
@@ -170,6 +174,8 @@ void getInjectSpecificationWithInjectIDsToIgnore() {
     input.setInjectIDsToProcess(List.of("id1", "id2"));
     input.setInjectIDsToIgnore(List.of("id3"));
 
+    when(userService.currentUser()).thenReturn(new User());
+
     // Act
     Specification<Inject> specification =
         injectService.getInjectSpecification(input, Grant.GRANT_TYPE.OBSERVER);
@@ -341,6 +347,8 @@ void getInjectsAndCheckPermissionWithValidInput() {
     //noinspection unchecked
     when(injectRepository.findAll(any(Specification.class))).thenReturn(injects);
 
+    when(userService.currentUser()).thenReturn(new User());
+
     // Act
     List<Inject> result =
         injectService.getInjectsAndCheckPermission(input, Grant.GRANT_TYPE.PLANNER);
@@ -359,6 +367,8 @@ void getInjectsAndCheckPermissionWithInjectIDsToProcess() {
 
     List<Inject> injects = List.of(new Inject(), new Inject());
 
+    when(userService.currentUser()).thenReturn(new User());
+
     //noinspection unchecked
     when(injectRepository.findAll(any(Specification.class))).thenReturn(injects);
 
@@ -379,6 +389,8 @@ void getInjectsAndCheckPermissionWithInjectIDsToIgnore() {
     input.setInjectIDsToProcess(List.of("id1", "id2"));
     input.setInjectIDsToIgnore(List.of("id3"));
 
+    when(userService.currentUser()).thenReturn(new User());
+
     List<Inject> injects = List.of(new Inject(), new Inject());
 
     //noinspection unchecked
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
@@ -29,7 +29,7 @@ public void test_hasReadGrant_WHEN_has_read_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
     when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
-            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.OBSERVER.andHigher()))
+            USER_ID, RESOURCE_ID, Grant.GRANT_TYPE.OBSERVER.andHigher()))
         .thenReturn(true);
 
     assertTrue(grantService.hasReadGrant(RESOURCE_ID, user));
@@ -40,7 +40,7 @@ public void test_hasReadGrant_WHEN_has_no_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
     when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
-            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.OBSERVER.andHigher()))
+            USER_ID, RESOURCE_ID, Grant.GRANT_TYPE.OBSERVER.andHigher()))
         .thenReturn(false);
 
     assertFalse(grantService.hasReadGrant(RESOURCE_ID, user));
@@ -51,7 +51,7 @@ public void test_hasLaunchGrant_WHEN_has_read_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
     when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
-            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.LAUNCHER.andHigher()))
+            USER_ID, RESOURCE_ID, Grant.GRANT_TYPE.LAUNCHER.andHigher()))
         .thenReturn(false);
 
     assertFalse(grantService.hasLaunchGrant(RESOURCE_ID, user));
@@ -62,7 +62,7 @@ public void test_hasWriteGrant_WHEN_has_read_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
     when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
-            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.PLANNER.andHigher()))
+            USER_ID, RESOURCE_ID, Grant.GRANT_TYPE.PLANNER.andHigher()))
         .thenReturn(false);
 
     assertFalse(grantService.hasWriteGrant(RESOURCE_ID, user));
@@ -73,7 +73,7 @@ public void test_hasLaunchGrant_WHEN_has_launch_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
     when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
-            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.LAUNCHER.andHigher()))
+            USER_ID, RESOURCE_ID, Grant.GRANT_TYPE.LAUNCHER.andHigher()))
         .thenReturn(true);
 
     assertTrue(grantService.hasLaunchGrant(RESOURCE_ID, user));
@@ -84,7 +84,7 @@ public void test_hasWriteGrant_WHEN_has_write_grant() {
     User user = UserFixture.getUser();
     user.setId(USER_ID);
     when(grantRepository.existsByUserIdAndResourceIdAndNameIn(
-            RESOURCE_ID, USER_ID, Grant.GRANT_TYPE.PLANNER.andHigher()))
+            USER_ID, RESOURCE_ID, Grant.GRANT_TYPE.PLANNER.andHigher()))
         .thenReturn(true);
 
     assertTrue(grantService.hasWriteGrant(RESOURCE_ID, user));
diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingHeaderActions.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingHeaderActions.tsx
--- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingHeaderActions.tsx
+++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingHeaderActions.tsx
@@ -1,13 +1,13 @@
 import { PlayArrowOutlined, SettingsOutlined } from '@mui/icons-material';
 import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogContentText } from '@mui/material';
 import { useTheme } from '@mui/material/styles';
-import { useState } from 'react';
+import { useContext, useState } from 'react';
 import { useNavigate } from 'react-router';
 
 import { launchAtomicTesting, relaunchAtomicTesting } from '../../../../actions/atomic_testings/atomic-testing-actions';
 import { useFormatter } from '../../../../components/i18n';
 import type { InjectResultOverviewOutput } from '../../../../utils/api-types';
-import { Can } from '../../../../utils/permissions/PermissionsProvider';
+import { AbilityContext } from '../../../../utils/permissions/PermissionsProvider';
 import { ACTIONS, SUBJECTS } from '../../../../utils/permissions/types';
 import AtomicTestingPopover from './AtomicTestingPopover';
 import AtomicTestingUpdate from './AtomicTestingUpdate';
@@ -21,6 +21,7 @@ const AtomicTestingHeaderActions = ({ injectResultOverview, setInjectResultOverv
   const { t } = useFormatter();
   const theme = useTheme();
   const navigate = useNavigate();
+  const ability = useContext(AbilityContext);
 
   const [edition, setEdition] = useState(false);
   const [openDialog, setOpenDialog] = useState(false);
@@ -59,7 +60,9 @@ const AtomicTestingHeaderActions = ({ injectResultOverview, setInjectResultOverv
   function getActionButton(injectResultOverviewOutput: InjectResultOverviewOutput) {
     if (!injectResultOverviewOutput.inject_injector_contract) return null;
 
-    if (injectResultOverviewOutput.inject_ready) {
+    const hasManageAbility = ability.can(ACTIONS.MANAGE, SUBJECTS.ASSESSMENT) || ability.can(ACTIONS.MANAGE, SUBJECTS.RESOURCE, injectResultOverview.inject_id);
+    const hasLaunchAbility = ability.can(ACTIONS.LAUNCH, SUBJECTS.ASSESSMENT) || ability.can(ACTIONS.LAUNCH, SUBJECTS.RESOURCE, injectResultOverview.inject_id);
+    if (injectResultOverviewOutput.inject_ready && hasLaunchAbility) {
       const launchOrRelaunchKey = !injectResultOverviewOutput.inject_status?.status_id ? 'Launch now' : 'Relaunch now';
       return (
         <Button
@@ -77,7 +80,7 @@ const AtomicTestingHeaderActions = ({ injectResultOverview, setInjectResultOverv
           {t(launchOrRelaunchKey)}
         </Button>
       );
-    } else {
+    } else if (hasManageAbility) {
       return (
         <>
           <Button
@@ -93,6 +96,8 @@ const AtomicTestingHeaderActions = ({ injectResultOverview, setInjectResultOverv
           <AtomicTestingUpdate open={edition} handleClose={handleCloseEdit} atomic={injectResultOverviewOutput} />
         </>
       );
+    } else {
+      return (<></>);
     }
   }
 
@@ -128,12 +133,12 @@ const AtomicTestingHeaderActions = ({ injectResultOverview, setInjectResultOverv
     );
   }
 
+  const hasAbility = ability.can(ACTIONS.ACCESS, SUBJECTS.ASSESSMENT) || ability.can(ACTIONS.ACCESS, SUBJECTS.RESOURCE, injectResultOverview.inject_id);
+
   return (
     <>
       <Box display="flex" flexDirection="row" alignItems="center">
-        <Can I={ACTIONS.LAUNCH} a={SUBJECTS.ATOMIC_TESTING}>
-          {getActionButton(injectResultOverview)}
-        </Can>
+        {hasAbility && getActionButton(injectResultOverview)}
         <AtomicTestingPopover
           atomic={injectResultOverview}
           actions={['Export', 'Update', 'Duplicate', 'Delete']}
diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingPopover.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingPopover.tsx
--- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingPopover.tsx
+++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingPopover.tsx
@@ -92,12 +92,12 @@ const AtomicTestingPopover: FunctionComponent<Props> = ({
   if (actions.includes('Update') && atomic.inject_injector_contract !== null) entries.push({
     label: 'Update',
     action: () => handleOpenEdit(),
-    userRight: ability.can(ACTIONS.MANAGE, SUBJECTS.ATOMIC_TESTING),
+    userRight: ability.can(ACTIONS.MANAGE, SUBJECTS.RESOURCE, atomic.inject_id),
   });
   if (actions.includes('Duplicate') && atomic.inject_injector_contract !== null) entries.push({
     label: 'Duplicate',
     action: () => handleOpenDuplicate(),
-    userRight: ability.can(ACTIONS.MANAGE, SUBJECTS.ATOMIC_TESTING),
+    userRight: ability.can(ACTIONS.MANAGE, SUBJECTS.ASSESSMENT),
   });
   if (actions.includes('Export') && atomic.inject_injector_contract !== null) entries.push({
     label: t('inject_export_json_single'),
@@ -107,7 +107,7 @@ const AtomicTestingPopover: FunctionComponent<Props> = ({
   if (actions.includes('Delete')) entries.push({
     label: 'Delete',
     action: () => handleOpenDelete(),
-    userRight: ability.can(ACTIONS.DELETE, SUBJECTS.ATOMIC_TESTING),
+    userRight: ability.can(ACTIONS.DELETE, SUBJECTS.RESOURCE, atomic.inject_id),
   });
 
   return (
diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingRemediations.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingRemediations.tsx
--- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingRemediations.tsx
+++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingRemediations.tsx
@@ -2,19 +2,24 @@ import { Box, Paper, Tab, Tabs, Typography } from '@mui/material';
 import { useTheme } from '@mui/material/styles';
 // eslint-disable-next-line import/no-named-as-default
 import DOMPurify from 'dompurify';
-import { type SyntheticEvent, useEffect, useMemo, useState } from 'react';
+import { type SyntheticEvent, useContext, useEffect, useMemo, useState } from 'react';
 import { useLocation, useParams } from 'react-router';
 import { makeStyles } from 'tss-react/mui';
 
+import { fetchCollectorsForAtomicTesting } from '../../../../actions/atomic_testings/atomic-testing-actions';
 import { fetchCollectors } from '../../../../actions/Collector';
 import type { CollectorHelper } from '../../../../actions/collectors/collector-helper';
 import { fetchPayloadDetectionRemediationsByInject } from '../../../../actions/injects/inject-action';
 import { useFormatter } from '../../../../components/i18n';
+import Loader from '../../../../components/Loader';
 import { COLLECTOR_LIST } from '../../../../constants/Entities';
 import { useHelper } from '../../../../store';
 import { type Collector, type DetectionRemediationOutput, type InjectResultOverviewOutput } from '../../../../utils/api-types';
 import { useAppDispatch } from '../../../../utils/hooks';
 import useDataLoader from '../../../../utils/hooks/useDataLoader';
+import { AbilityContext } from '../../../../utils/permissions/PermissionsProvider';
+import RestrictionAccess from '../../../../utils/permissions/RestrictionAccess';
+import { ACTIONS, SUBJECTS } from '../../../../utils/permissions/types';
 
 const useStyles = makeStyles()(theme => ({
   paperContainer: {
@@ -35,12 +40,26 @@ const AtomicTestingRemediations = () => {
   const [activeTab, setActiveTab] = useState<number>(0);
   const [detectionRemediations, setDetectionRemediations] = useState<DetectionRemediationOutput[]>([]);
   const [hasFetchedRemediations, setHasFetchedRemediations] = useState(false);
+  const ability = useContext(AbilityContext);
 
   const isRemediationTab = location.pathname.includes('/remediations');
 
+  const hasPlatformSettingsCapabilities = ability.can(ACTIONS.ACCESS, SUBJECTS.PLATFORM_SETTINGS);
+  const [loading, setLoading] = useState(false);
+
   const { collectors } = useHelper((helper: CollectorHelper) => ({ collectors: helper.getCollectors() }));
   useDataLoader(() => {
-    dispatch(fetchCollectors());
+    if (hasPlatformSettingsCapabilities) {
+      setLoading(true);
+      dispatch(fetchCollectors()).finally(() => {
+        setLoading(false);
+      });
+    } else if (injectId) {
+      setLoading(true);
+      dispatch(fetchCollectorsForAtomicTesting(injectId)).finally(() => {
+        setLoading(false);
+      });
+    }
   });
 
   // Filter valid collectors
@@ -80,73 +99,85 @@ const AtomicTestingRemediations = () => {
     );
   }, [tabs, activeTab, detectionRemediations]);
 
-  if (tabs.length === 0) {
-    return <Typography>{t('Loading collectors...')}</Typography>;
-  }
-
   return (
     <>
       <Typography variant="h5" gutterBottom>{t('Security platform')}</Typography>
-      <Tabs value={activeTab} onChange={handleActiveTabChange} aria-label="collector tabs">
-        {tabs.map((tab, index) => (
-          <Tab
-            key={tab.collector_type}
-            label={(
-              <Box display="flex" alignItems="center">
-                <img
-                  src={`/api/images/collectors/${tab.collector_type}`}
-                  alt={tab.collector_type}
-                  style={{
-                    width: 20,
-                    height: 20,
-                    borderRadius: 4,
-                    marginRight: theme.spacing(2),
-                  }}
-                />
-                {tab.collector_name}
-              </Box>
-            )}
-            value={index}
-          />
-        ))}
-      </Tabs>
+      {loading && <Loader variant="inElement" />}
+      {(hasPlatformSettingsCapabilities || injectId) ? (
+        <>
+          {tabs.length === 0
+            ? (
+                <Paper className={classes.paperContainer} variant="outlined">
+                  <Typography variant="body2" color="textSecondary" sx={{ padding: 2 }}>
+                    {t('No collector configured.')}
+                  </Typography>
+                </Paper>
+              ) : (
+                <>
+                  <Tabs value={activeTab} onChange={handleActiveTabChange} aria-label="collector tabs">
+                    {tabs.map((tab, index) => (
+                      <Tab
+                        key={tab.collector_type}
+                        label={(
+                          <Box display="flex" alignItems="center">
+                            <img
+                              src={`/api/images/collectors/${tab.collector_type}`}
+                              alt={tab.collector_type}
+                              style={{
+                                width: 20,
+                                height: 20,
+                                borderRadius: 4,
+                                marginRight: theme.spacing(2),
+                              }}
+                            />
+                            {tab.collector_name}
+                          </Box>
+                        )}
+                        value={index}
+                      />
+                    ))}
+                  </Tabs>
 
-      <Paper className={classes.paperContainer} variant="outlined">
-        {activeCollectorRemediations.length === 0 ? (
-          <Typography sx={{ padding: 2 }} variant="body2" color="textSecondary" gutterBottom>
-            {t('No detection rule available for this security platform yet.')}
-          </Typography>
-        ) : (
-          activeCollectorRemediations.map((rem) => {
-            const content = rem.detection_remediation_values?.trim();
+                  <Paper className={classes.paperContainer} variant="outlined">
+                    {activeCollectorRemediations.length === 0 ? (
+                      <Typography sx={{ padding: 2 }} variant="body2" color="textSecondary" gutterBottom>
+                        {t('No detection rule available for this security platform yet.')}
+                      </Typography>
+                    ) : (
+                      activeCollectorRemediations.map((rem) => {
+                        const content = rem.detection_remediation_values?.trim();
 
-            return (
-              <Box sx={{ padding: 2 }} key={rem.detection_remediation_id}>
-                {content ? (
-                  <>
-                    <Typography
-                      sx={{ paddingBottom: 2 }}
-                      variant="body2"
-                      fontWeight="bold"
-                      gutterBottom
-                    >
-                      {`${t('Detection Rule')}: `}
-                    </Typography>
-                    <div
-                      dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(rem.detection_remediation_values.replace(/\n/g, '<br />')) }}
-                    >
-                    </div>
-                  </>
-                ) : (
-                  <Typography variant="body2" color="textSecondary" gutterBottom>
-                    {t('No detection rule available for this security platform yet.')}
-                  </Typography>
-                )}
-              </Box>
-            );
-          })
-        )}
-      </Paper>
+                        return (
+                          <Box sx={{ padding: 2 }} key={rem.detection_remediation_id}>
+                            {content ? (
+                              <>
+                                <Typography
+                                  sx={{ paddingBottom: 2 }}
+                                  variant="body2"
+                                  fontWeight="bold"
+                                  gutterBottom
+                                >
+                                  {`${t('Detection Rule')}: `}
+                                </Typography>
+                                <div
+                                  dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(rem.detection_remediation_values.replace(/\n/g, '<br />')) }}
+                                >
+                                </div>
+                              </>
+                            ) : (
+                              <Typography variant="body2" color="textSecondary" gutterBottom>
+                                {t('No detection rule available for this security platform yet.')}
+                              </Typography>
+                            )}
+                          </Box>
+                        );
+                      })
+                    )}
+                  </Paper>
+                </>
+              )}
+        </>
+      ) : (<RestrictionAccess restrictedField="collectors" />)}
     </>
   );
 };
diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/target_result/InjectExpectationCard.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/target_result/InjectExpectationCard.tsx
--- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/target_result/InjectExpectationCard.tsx
+++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/target_result/InjectExpectationCard.tsx
@@ -1,6 +1,6 @@
 import { AddModeratorOutlined, InventoryOutlined, MoreVertOutlined } from '@mui/icons-material';
 import { Chip, IconButton, Menu, MenuItem, Tooltip, Typography } from '@mui/material';
-import { useState } from 'react';
+import { useContext, useState } from 'react';
 import { makeStyles } from 'tss-react/mui';
 
 import { fetchInjectResultOverviewOutput } from '../../../../../actions/atomic_testings/atomic-testing-actions';
@@ -11,7 +11,10 @@ import { useFormatter } from '../../../../../components/i18n';
 import ItemResult from '../../../../../components/ItemResult';
 import type { InjectExpectationResult, InjectResultOverviewOutput } from '../../../../../utils/api-types';
 import { useAppDispatch } from '../../../../../utils/hooks';
+import { AbilityContext } from '../../../../../utils/permissions/PermissionsProvider';
+import { ACTIONS, INHERITED_CONTEXT, SUBJECTS } from '../../../../../utils/permissions/types';
 import { emptyFilled } from '../../../../../utils/String';
+import { PermissionsContext } from '../../../common/Context';
 import type { InjectExpectationsStore } from '../../../common/injects/expectations/Expectation';
 import {
   HUMAN_EXPECTATION,
@@ -46,6 +49,8 @@ const InjectExpectationCard = ({ inject, injectExpectation, onUpdateInjectExpect
   const { t } = useFormatter();
   const { classes } = useStyles();
   const dispatch = useAppDispatch();
+  const ability = useContext(AbilityContext);
+  const { permissions, inherited_context } = useContext(PermissionsContext);
 
   const [anchorEditButton, setAnchorEditButton] = useState<null | HTMLElement>(null);
   const openEditButtonMenu = Boolean(anchorEditButton);
@@ -143,6 +148,10 @@ const InjectExpectationCard = ({ inject, injectExpectation, onUpdateInjectExpect
     }
   };
 
+  const canManage = ability.can(ACTIONS.MANAGE, SUBJECTS.ASSESSMENT)
+    || (inherited_context == INHERITED_CONTEXT.NONE && ability.can(ACTIONS.MANAGE, SUBJECTS.RESOURCE, inject.inject_id))
+    || permissions.canManage;
+
   return (
     <>
       <Paper>
@@ -170,7 +179,7 @@ const InjectExpectationCard = ({ inject, injectExpectation, onUpdateInjectExpect
           {((isManualExpectation(injectExpectation.inject_expectation_type)
             && injectExpectation.inject_expectation_results
             && injectExpectation.inject_expectation_results.length === 0)
-          || ['DETECTION', 'PREVENTION'].includes(injectExpectation.inject_expectation_type)) && (
+          || ['DETECTION', 'PREVENTION'].includes(injectExpectation.inject_expectation_type)) && canManage && (
             <Tooltip title={t('Add a result')}>
               <IconButton
                 aria-label="Add"