Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[backend] Change external reference resolution for OpenCTI integration #1048

Merged
merged 2 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions openbas-api/src/main/java/io/openbas/opencti/OpenCTIApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.openbas.opencti;

import io.openbas.database.model.Exercise;
import io.openbas.rest.exercise.form.ExerciseSimple;
import io.openbas.service.ScenarioService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import static io.openbas.database.model.User.ROLE_USER;

@RequiredArgsConstructor
@RestController
@Secured(ROLE_USER)
public class OpenCTIApi {

public static final String OPENCTI_URI = "/api/opencti/v1";

private final ScenarioService scenarioService;

@Operation(summary = "Retrieve the latest exercise by external reference ID (example: a report ID")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Found the exercise",
content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ExerciseSimple.class))
}),
@ApiResponse(responseCode = "404", description = "Exercise not found", content = @Content)
})
@GetMapping(OPENCTI_URI + "/exercises/latest/{externalReferenceId}")
public ExerciseSimple latestExerciseByExternalReference(@PathVariable @NotBlank final String externalReferenceId) {
Exercise exercise = this.scenarioService.latestExerciseByExternalReference(externalReferenceId);
return ExerciseSimple.fromExercise(exercise);

Check warning on line 40 in openbas-api/src/main/java/io/openbas/opencti/OpenCTIApi.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/opencti/OpenCTIApi.java#L39-L40

Added lines #L39 - L40 were not covered by tests
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package io.openbas.rest.scenario;

import io.openbas.database.model.Scenario;
import io.openbas.database.model.Team;
import io.openbas.database.model.TeamSimple;
import io.openbas.database.model.User;
import io.openbas.database.model.*;
import io.openbas.database.raw.RawPaginationScenario;
import io.openbas.database.repository.*;
import io.openbas.rest.exception.ElementNotFoundException;
Expand Down Expand Up @@ -108,11 +105,6 @@ public Scenario scenario(@PathVariable @NotBlank final String scenarioId) {
return scenarioService.scenario(scenarioId);
}

@GetMapping(SCENARIO_URI + "/external_reference/{externalReferenceId}")
public Scenario scenarioByExternalId(@PathVariable @NotBlank final String externalReferenceId) {
return scenarioService.scenarioByExternalReference(externalReferenceId);
}

@PutMapping(SCENARIO_URI + "/{scenarioId}")
@PreAuthorize("isScenarioPlanner(#scenarioId)")
public Scenario updateScenario(
Expand Down Expand Up @@ -176,16 +168,13 @@ public void importScenario(@RequestPart("file") @NotNull MultipartFile file) thr

// -- SIMULATION --

// region scenarios
@GetMapping(SCENARIO_URI + "/{scenarioId}/exercises")
@PreAuthorize("isScenarioObserver(#scenarioId)")
public Iterable<ExerciseSimple> scenarioExercises(@PathVariable @NotBlank final String scenarioId) {
Scenario scenario = this.scenarioService.scenario(scenarioId);
return scenario.getExercises().stream().map(ExerciseSimple::fromExercise).toList();
}

// endregion

// -- TEAMS --

@Transactional(rollbackOn = Exception.class)
Expand Down
18 changes: 10 additions & 8 deletions openbas-api/src/main/java/io/openbas/service/ScenarioService.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
Expand Down Expand Up @@ -227,12 +224,17 @@
.orElseThrow(() -> new ElementNotFoundException("Scenario not found"));
}

public Scenario scenarioByExternalReference(@NotBlank final String scenarioExternalReference) {
@Transactional(readOnly = true)
public Exercise latestExerciseByExternalReference(@NotBlank final String scenarioExternalReference) {
List<Scenario> scenarios = this.scenarioRepository.findByExternalReference(scenarioExternalReference);
if (!scenarios.isEmpty()) {
return scenarios.getFirst();
Optional<Exercise> latestEndedExercise = scenarios.stream()
.flatMap(scenario -> scenario.getExercises().stream())
.filter(exercise -> exercise.getEnd().isPresent())
.max(Comparator.comparing(exercise -> exercise.getEnd().get()));

Check warning on line 233 in openbas-api/src/main/java/io/openbas/service/ScenarioService.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/service/ScenarioService.java#L230-L233

Added lines #L230 - L233 were not covered by tests
if (latestEndedExercise.isPresent()) {
return latestEndedExercise.get();

Check warning on line 235 in openbas-api/src/main/java/io/openbas/service/ScenarioService.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/service/ScenarioService.java#L235

Added line #L235 was not covered by tests
} else {
throw new ElementNotFoundException("Scenario not found");
throw new ElementNotFoundException("Latest exercise not found");

Check warning on line 237 in openbas-api/src/main/java/io/openbas/service/ScenarioService.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/service/ScenarioService.java#L237

Added line #L237 was not covered by tests
}
}

Expand Down
82 changes: 44 additions & 38 deletions openbas-api/src/main/java/io/openbas/utils/ResultUtils.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package io.openbas.utils;

import io.openbas.utils.AtomicTestingMapper.ExpectationResultsByType;
import io.openbas.database.model.AttackPattern;
import io.openbas.database.model.Inject;
import io.openbas.database.model.InjectExpectation;
import io.openbas.rest.atomic_testing.form.InjectTargetWithResult;
import io.openbas.database.model.*;
import io.openbas.rest.inject.form.InjectExpectationResultsByAttackPattern;
import io.openbas.utils.AtomicTestingMapper.ExpectationResultsByType;
import jakarta.validation.constraints.NotNull;

import java.util.List;
Expand All @@ -14,41 +16,45 @@

public class ResultUtils {

// -- GLOBAL SCORE --

public static List<ExpectationResultsByType> computeGlobalExpectationResults(@NotNull final List<Inject> injects) {
List<InjectExpectation> expectations = injects
.stream()
.flatMap((inject) -> inject.getExpectations().stream())
.toList();
return AtomicTestingUtils.getExpectationResultByTypes(expectations);
}

public static List<InjectExpectationResultsByAttackPattern> computeInjectExpectationResults(@NotNull final List<Inject> injects) {
Map<AttackPattern, List<Inject>> groupedByAttackPattern = injects.stream()
.flatMap((inject) -> inject.getInjectorContract()
.getAttackPatterns()
.stream()
.map(attackPattern -> Map.entry(attackPattern, inject))
)
.collect(Collectors.groupingBy(
java.util.Map.Entry::getKey,
Collectors.mapping(java.util.Map.Entry::getValue, Collectors.toList())
));

return groupedByAttackPattern.entrySet()
.stream()
.map(entry -> new InjectExpectationResultsByAttackPattern(entry.getKey(), entry.getValue()))
.toList();
}

// -- TARGET --

public static List<InjectTargetWithResult> computeTargetResults(@NotNull final List<Inject> injects) {
return injects.stream()
.flatMap((inject) -> getTargetsWithResults(inject).stream())
.distinct()
.toList();
}
private ResultUtils() {
}

// -- GLOBAL SCORE --

public static List<ExpectationResultsByType> computeGlobalExpectationResults(@NotNull final List<Inject> injects) {
List<InjectExpectation> expectations = injects
.stream()
.flatMap(inject -> inject.getExpectations().stream())
.toList();
return AtomicTestingUtils.getExpectationResultByTypes(expectations);

Check warning on line 29 in openbas-api/src/main/java/io/openbas/utils/ResultUtils.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/utils/ResultUtils.java#L25-L29

Added lines #L25 - L29 were not covered by tests
}

public static List<InjectExpectationResultsByAttackPattern> computeInjectExpectationResults(
@NotNull final List<Inject> injects) {
Map<AttackPattern, List<Inject>> groupedByAttackPattern = injects.stream()
.flatMap(inject -> inject.getInjectorContract()
.getAttackPatterns()
.stream()
.map(attackPattern -> Map.entry(attackPattern, inject))

Check warning on line 38 in openbas-api/src/main/java/io/openbas/utils/ResultUtils.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/utils/ResultUtils.java#L34-L38

Added lines #L34 - L38 were not covered by tests
)
.collect(Collectors.groupingBy(

Check warning on line 40 in openbas-api/src/main/java/io/openbas/utils/ResultUtils.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/utils/ResultUtils.java#L40

Added line #L40 was not covered by tests
java.util.Map.Entry::getKey,
Collectors.mapping(java.util.Map.Entry::getValue, Collectors.toList())

Check warning on line 42 in openbas-api/src/main/java/io/openbas/utils/ResultUtils.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/utils/ResultUtils.java#L42

Added line #L42 was not covered by tests
));

return groupedByAttackPattern.entrySet()
.stream()
.map(entry -> new InjectExpectationResultsByAttackPattern(entry.getKey(), entry.getValue()))
.toList();

Check warning on line 48 in openbas-api/src/main/java/io/openbas/utils/ResultUtils.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/utils/ResultUtils.java#L45-L48

Added lines #L45 - L48 were not covered by tests
}

// -- TARGET --

public static List<InjectTargetWithResult> computeTargetResults(@NotNull final List<Inject> injects) {
return injects.stream()
.flatMap(inject -> getTargetsWithResults(inject).stream())
.distinct()
.toList();

Check warning on line 57 in openbas-api/src/main/java/io/openbas/utils/ResultUtils.java

View check run for this annotation

Codecov / codecov/patch

openbas-api/src/main/java/io/openbas/utils/ResultUtils.java#L54-L57

Added lines #L54 - L57 were not covered by tests
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface ScenarioRepository extends CrudRepository<Scenario, String>,
JpaSpecificationExecutor<Scenario> {

@NotNull
List<Scenario> findByExternalReference(@Param("externalReference") String externalReference);
List<Scenario> findByExternalReference(@Param("externalReference") final String externalReference);

@Query("select distinct s from Scenario s " +
"join s.grants as grant " +
Expand Down
Loading