Skip to content

Commit f9b9f20

Browse files
[frontend/backend] Show expectations traces (#564)
Co-authored-by: Yann <[email protected]>
1 parent c0cd6a5 commit f9b9f20

26 files changed

+1427
-119
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.openbas.migration;
2+
3+
import java.sql.Connection;
4+
import java.sql.Statement;
5+
import org.flywaydb.core.api.migration.BaseJavaMigration;
6+
import org.flywaydb.core.api.migration.Context;
7+
import org.springframework.stereotype.Component;
8+
9+
@Component
10+
public class V3_75__Add_table_injects_expectations_traces extends BaseJavaMigration {
11+
12+
@Override
13+
public void migrate(Context context) throws Exception {
14+
Connection connection = context.getConnection();
15+
Statement select = connection.createStatement();
16+
select.execute(
17+
"""
18+
CREATE TABLE injects_expectations_traces(
19+
inject_expectation_trace_id VARCHAR(255) NOT NULL CONSTRAINT injects_expectations_traces_pkey PRIMARY KEY,
20+
inject_expectation_trace_expectation VARCHAR(255) NOT NULL CONSTRAINT injects_expectations_traces_expectation_fkey REFERENCES injects_expectations (inject_expectation_id) ON DELETE CASCADE,
21+
inject_expectation_trace_source_id VARCHAR(255) NOT NULL CONSTRAINT inject_expectation_trace_source_id_fk REFERENCES assets (asset_id) ON DELETE CASCADE,
22+
inject_expectation_trace_alert_name text,
23+
inject_expectation_trace_alert_link text,
24+
inject_expectation_trace_date timestamp(0) with time zone,
25+
inject_expectation_trace_created_at timestamp(0) with time zone not null default now(),
26+
inject_expectation_trace_updated_at timestamp(0) with time zone not null default now()
27+
);
28+
CREATE INDEX idx_inject_expectation_trace_expectation ON injects_expectations_traces(inject_expectation_trace_expectation);
29+
CREATE INDEX idx_inject_expectation_trace_source_id ON injects_expectations_traces(inject_expectation_trace_source_id);
30+
""");
31+
}
32+
}

openbas-api/src/main/java/io/openbas/rest/expectation/ExpectationApi.java

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,34 @@ public InjectExpectation deleteInjectExpectationResult(
4040
return injectExpectationService.deleteInjectExpectationResult(expectationId, sourceId);
4141
}
4242

43+
@Operation(
44+
summary = "Get Inject Expectations",
45+
description =
46+
"Retrieves inject expectations of agents installed on an asset. If an expiration time is provided, it will return all expectations not expired within this timeframe independently of their results. Otherwise, it will return all expectations without any result.")
4347
@GetMapping(INJECTS_EXPECTATIONS_URI)
44-
public List<InjectExpectation> getInjectExpectationsNotFilled() {
45-
return Stream.concat(
46-
injectExpectationService.manualExpectationsNotFill().stream(),
47-
Stream.concat(
48-
injectExpectationService.preventionExpectationsNotFill().stream(),
49-
injectExpectationService.detectionExpectationsNotFill().stream()))
48+
public List<InjectExpectation> getInjectExpectationsNotFilledOrNotExpired(
49+
@RequestParam(required = false, name = "expiration_time") final Integer expirationTime) {
50+
if (expirationTime == null) {
51+
return Stream.of(
52+
injectExpectationService.manualExpectationsNotFill(),
53+
injectExpectationService.preventionExpectationsNotFill(),
54+
injectExpectationService.detectionExpectationsNotFill())
55+
.flatMap(List::stream)
56+
.toList();
57+
}
58+
59+
return Stream.of(
60+
injectExpectationService.manualExpectationsNotExpired(expirationTime),
61+
injectExpectationService.preventionExpectationsNotExpired(expirationTime),
62+
injectExpectationService.detectionExpectationsNotExpired(expirationTime))
63+
.flatMap(List::stream)
5064
.toList();
5165
}
5266

67+
@Operation(
68+
summary = "Get Inject Expectations for a Specific Source",
69+
description =
70+
"Retrieves inject expectations that have not seen any result yet of agents installed on an asset for a given source ID.")
5371
@GetMapping(INJECTS_EXPECTATIONS_URI + "/{sourceId}")
5472
public List<InjectExpectation> getInjectExpectationsNotFilledForSource(
5573
@PathVariable String sourceId) {
@@ -67,10 +85,17 @@ public List<InjectExpectation> getInjectExpectationsNotFilledForSource(
6785
"Retrieves inject expectations of agents installed on an asset for a given source ID.")
6886
@GetMapping(INJECTS_EXPECTATIONS_URI + "/assets/{sourceId}")
6987
public List<InjectExpectation> getInjectExpectationsAssetsNotFilledForSource(
70-
@PathVariable String sourceId) {
88+
@PathVariable String sourceId,
89+
@RequestParam(required = false, name = "expiration_time") final Integer expirationTime) {
90+
if (expirationTime == null) {
91+
return Stream.concat(
92+
injectExpectationService.preventionExpectationsNotFill(sourceId).stream(),
93+
injectExpectationService.detectionExpectationsNotFill(sourceId).stream())
94+
.toList();
95+
}
7196
return Stream.concat(
72-
injectExpectationService.preventionExpectationsNotFill(sourceId).stream(),
73-
injectExpectationService.detectionExpectationsNotFill(sourceId).stream())
97+
injectExpectationService.preventionExpectationsNotExpired(expirationTime).stream(),
98+
injectExpectationService.detectionExpectationsNotExpired(expirationTime).stream())
7499
.toList();
75100
}
76101

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package io.openbas.rest.inject_expectation_trace;
2+
3+
import io.openbas.database.model.Collector;
4+
import io.openbas.database.model.InjectExpectationTrace;
5+
import io.openbas.database.repository.CollectorRepository;
6+
import io.openbas.database.repository.InjectExpectationRepository;
7+
import io.openbas.rest.exception.ElementNotFoundException;
8+
import io.openbas.rest.helper.RestBehavior;
9+
import io.openbas.rest.inject_expectation_trace.form.InjectExpectationTraceInput;
10+
import io.openbas.service.InjectExpectationTraceService;
11+
import jakarta.validation.Valid;
12+
import java.util.List;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.security.access.prepost.PreAuthorize;
15+
import org.springframework.web.bind.annotation.*;
16+
17+
@RequiredArgsConstructor
18+
@RestController
19+
@RequestMapping("/api/inject-expectations-traces")
20+
@PreAuthorize("isAdmin()")
21+
public class InjectExpectationTraceApi extends RestBehavior {
22+
23+
public static final String INJECT_EXPECTATION_TRACES_URI = "/api/inject-expectations-traces";
24+
25+
private final InjectExpectationTraceService injectExpectationTraceService;
26+
private final InjectExpectationRepository injectExpectationRepository;
27+
private final CollectorRepository collectorRepository;
28+
29+
@PostMapping()
30+
public InjectExpectationTrace createInjectExpectationTraceForCollector(
31+
@Valid @RequestBody InjectExpectationTraceInput input) {
32+
InjectExpectationTrace injectExpectationTrace = new InjectExpectationTrace();
33+
injectExpectationTrace.setUpdateAttributes(input);
34+
injectExpectationTrace.setInjectExpectation(
35+
injectExpectationRepository
36+
.findById(input.getInjectExpectationId())
37+
.orElseThrow(() -> new ElementNotFoundException("Inject expectation not found")));
38+
Collector collector =
39+
collectorRepository
40+
.findById(input.getSourceId())
41+
.orElseThrow(() -> new ElementNotFoundException("Collector not found"));
42+
injectExpectationTrace.setSecurityPlatform(collector.getSecurityPlatform());
43+
return this.injectExpectationTraceService.createInjectExpectationTrace(injectExpectationTrace);
44+
}
45+
46+
@GetMapping()
47+
public List<InjectExpectationTrace> getInjectExpectationTracesFromCollector(
48+
@RequestParam String injectExpectationId, @RequestParam String sourceId) {
49+
Collector collector =
50+
collectorRepository
51+
.findById(sourceId)
52+
.orElseThrow(() -> new ElementNotFoundException("Collector not found"));
53+
return this.injectExpectationTraceService.getInjectExpectationTracesFromCollector(
54+
injectExpectationId, collector.getSecurityPlatform().getId());
55+
}
56+
57+
@GetMapping("/count")
58+
public long getAlertLinksNumber(
59+
@RequestParam String injectExpectationId,
60+
@RequestParam String sourceId,
61+
@RequestParam String expectationResultSourceType) {
62+
return this.injectExpectationTraceService.getAlertLinksNumber(
63+
injectExpectationId, sourceId, expectationResultSourceType);
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.openbas.rest.inject_expectation_trace.form;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import jakarta.validation.constraints.NotBlank;
6+
import jakarta.validation.constraints.NotNull;
7+
import java.time.Instant;
8+
import lombok.Data;
9+
10+
@Data
11+
public class InjectExpectationTraceInput {
12+
13+
@JsonProperty("inject_expectation_trace_expectation")
14+
@Schema(type = "string")
15+
@NotBlank
16+
private String injectExpectationId;
17+
18+
@NotBlank
19+
@JsonProperty("inject_expectation_trace_source_id")
20+
@Schema(type = "string")
21+
private String sourceId;
22+
23+
@NotBlank
24+
@JsonProperty("inject_expectation_trace_alert_name")
25+
private String alertName;
26+
27+
@NotBlank
28+
@JsonProperty("inject_expectation_trace_alert_link")
29+
private String alertLink;
30+
31+
@NotNull
32+
@JsonProperty("inject_expectation_trace_date")
33+
private Instant alertDate;
34+
}

openbas-api/src/main/java/io/openbas/service/InjectExpectationService.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import jakarta.validation.constraints.NotBlank;
2525
import jakarta.validation.constraints.NotNull;
2626
import java.time.Instant;
27+
import java.time.temporal.ChronoUnit;
2728
import java.util.*;
2829
import java.util.stream.Collectors;
2930
import java.util.stream.Stream;
@@ -45,7 +46,6 @@ public class InjectExpectationService {
4546
private final AssetGroupService assetGroupService;
4647
private final EndpointService endpointService;
4748
private final CollectorRepository collectorRepository;
48-
private final AgentService agentService;
4949

5050
@Resource protected ObjectMapper mapper;
5151

@@ -546,7 +546,9 @@ public void computeExpectationGroup(
546546
}
547547

548548
private boolean isSuccess(List<InjectExpectation> expectations, boolean isGroup) {
549-
if (expectations.isEmpty()) return false;
549+
if (expectations.isEmpty()) {
550+
return false;
551+
}
550552
return isGroup
551553
? expectations.stream().anyMatch(e -> e.getExpectedScore().equals(e.getScore()))
552554
: expectations.stream().allMatch(e -> e.getExpectedScore().equals(e.getScore()));
@@ -644,6 +646,17 @@ public List<InjectExpectation> expectationsForAssets(
644646

645647
// -- PREVENTION --
646648

649+
public List<InjectExpectation> preventionExpectationsNotExpired(final Integer expirationTime) {
650+
return this.injectExpectationRepository.findAll(
651+
Specification.where(
652+
InjectExpectationSpecification.type(PREVENTION)
653+
.and(InjectExpectationSpecification.agentNotNull())
654+
.and(InjectExpectationSpecification.assetNotNull())
655+
.and(
656+
InjectExpectationSpecification.from(
657+
Instant.now().minus(expirationTime, ChronoUnit.MINUTES)))));
658+
}
659+
647660
public List<InjectExpectation> preventionExpectationsNotFill(@NotBlank final String source) {
648661
return this.injectExpectationRepository
649662
.findAll(Specification.where(InjectExpectationSpecification.type(PREVENTION)))
@@ -664,6 +677,17 @@ public List<InjectExpectation> preventionExpectationsNotFill() {
664677

665678
// -- DETECTION --
666679

680+
public List<InjectExpectation> detectionExpectationsNotExpired(final Integer expirationTime) {
681+
return this.injectExpectationRepository.findAll(
682+
Specification.where(
683+
InjectExpectationSpecification.type(DETECTION)
684+
.and(InjectExpectationSpecification.agentNotNull())
685+
.and(InjectExpectationSpecification.assetNotNull())
686+
.and(
687+
InjectExpectationSpecification.from(
688+
Instant.now().minus(expirationTime, ChronoUnit.MINUTES)))));
689+
}
690+
667691
public List<InjectExpectation> detectionExpectationsNotFill(@NotBlank final String source) {
668692
return this.injectExpectationRepository
669693
.findAll(Specification.where(InjectExpectationSpecification.type(DETECTION)))
@@ -684,6 +708,17 @@ public List<InjectExpectation> detectionExpectationsNotFill() {
684708

685709
// -- MANUAL
686710

711+
public List<InjectExpectation> manualExpectationsNotExpired(final Integer expirationTime) {
712+
return this.injectExpectationRepository.findAll(
713+
Specification.where(
714+
InjectExpectationSpecification.type(MANUAL)
715+
.and(InjectExpectationSpecification.agentNotNull())
716+
.and(InjectExpectationSpecification.assetNotNull())
717+
.and(
718+
InjectExpectationSpecification.from(
719+
Instant.now().minus(expirationTime, ChronoUnit.MINUTES)))));
720+
}
721+
687722
public List<InjectExpectation> manualExpectationsNotFill(@NotBlank final String source) {
688723
return this.injectExpectationRepository
689724
.findAll(Specification.where(InjectExpectationSpecification.type(MANUAL)))
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.openbas.service;
2+
3+
import io.openbas.database.model.InjectExpectationTrace;
4+
import io.openbas.database.model.SecurityPlatform;
5+
import io.openbas.database.repository.InjectExpectationTraceRepository;
6+
import io.openbas.database.repository.SecurityPlatformRepository;
7+
import io.openbas.rest.exception.ElementNotFoundException;
8+
import jakarta.validation.constraints.NotNull;
9+
import java.util.List;
10+
import java.util.Optional;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.java.Log;
13+
import org.springframework.stereotype.Service;
14+
15+
@Service
16+
@Log
17+
@RequiredArgsConstructor
18+
public class InjectExpectationTraceService {
19+
20+
private final InjectExpectationTraceRepository injectExpectationTraceRepository;
21+
private final SecurityPlatformRepository securityPlatformRepository;
22+
private static final String COLLECTOR_TYPE = "collector";
23+
24+
public InjectExpectationTrace createInjectExpectationTrace(
25+
@NotNull InjectExpectationTrace injectExpectationTrace) {
26+
Optional<InjectExpectationTrace> existingTrace =
27+
this.injectExpectationTraceRepository
28+
.findByAlertLinkAndAlertNameAndSecurityPlatformAndInjectExpectation(
29+
injectExpectationTrace.getAlertLink(),
30+
injectExpectationTrace.getAlertName(),
31+
injectExpectationTrace.getSecurityPlatform(),
32+
injectExpectationTrace.getInjectExpectation());
33+
if (existingTrace.isPresent()) {
34+
log.info("Existing trace present, no creation");
35+
return existingTrace.get();
36+
} else {
37+
return this.injectExpectationTraceRepository.save(injectExpectationTrace);
38+
}
39+
}
40+
41+
public List<InjectExpectationTrace> getInjectExpectationTracesFromCollector(
42+
@NotNull String injectExpectationId, @NotNull String sourceId) {
43+
return this.injectExpectationTraceRepository.findByExpectationAndSecurityPlatform(
44+
injectExpectationId, sourceId);
45+
}
46+
47+
public long getAlertLinksNumber(
48+
@NotNull String injectExpectationId,
49+
@NotNull String sourceId,
50+
String expectationResultSourceType) {
51+
if (expectationResultSourceType.equalsIgnoreCase(COLLECTOR_TYPE)) {
52+
SecurityPlatform securityPlatform =
53+
securityPlatformRepository
54+
.findByExternalReference(sourceId)
55+
.orElseThrow(() -> new ElementNotFoundException("Security platform not found"));
56+
return this.injectExpectationTraceRepository.countAlerts(
57+
injectExpectationId, securityPlatform.getId());
58+
} else {
59+
return this.injectExpectationTraceRepository.countAlerts(injectExpectationId, sourceId);
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)