diff --git a/openbas-api/pom.xml b/openbas-api/pom.xml index 29776599f3..82c4654571 100644 --- a/openbas-api/pom.xml +++ b/openbas-api/pom.xml @@ -6,7 +6,7 @@ io.openbas openbas-platform - 1.3.1 + 1.4.0 openbas-api @@ -35,7 +35,7 @@ io.openbas openbas-framework - 1.3.1 + 1.4.0 com.rabbitmq diff --git a/openbas-api/src/main/java/io/openbas/helper/InjectHelper.java b/openbas-api/src/main/java/io/openbas/helper/InjectHelper.java index e9b5ba25f1..eb6e8679df 100644 --- a/openbas-api/src/main/java/io/openbas/helper/InjectHelper.java +++ b/openbas-api/src/main/java/io/openbas/helper/InjectHelper.java @@ -41,7 +41,13 @@ public class InjectHelper { private List getInjectTeams(@NotNull final Inject inject) { Exercise exercise = inject.getExercise(); - return inject.isAllTeams() ? exercise.getTeams() : inject.getTeams(); + if(inject.isAllTeams()) { // In order to process expectations from players, we also need to load players into teams + exercise.getTeams().forEach(team -> Hibernate.initialize(team.getUsers())); + return exercise.getTeams(); + } else { + inject.getTeams().forEach(team -> Hibernate.initialize(team.getUsers())); + return inject.getTeams(); + } } // -- INJECTION -- diff --git a/openbas-api/src/main/java/io/openbas/importer/V1_DataImporter.java b/openbas-api/src/main/java/io/openbas/importer/V1_DataImporter.java index 6201683379..95e4a9fe55 100644 --- a/openbas-api/src/main/java/io/openbas/importer/V1_DataImporter.java +++ b/openbas-api/src/main/java/io/openbas/importer/V1_DataImporter.java @@ -558,7 +558,7 @@ private Challenge createChallenge(JsonNode nodeChallenge, Map base challenge.setName(nodeChallenge.get("challenge_name").textValue()); challenge.setCategory(nodeChallenge.get("challenge_category").textValue()); challenge.setContent(nodeChallenge.get("challenge_content").textValue()); - challenge.setScore(nodeChallenge.get("challenge_score").asInt(0)); + challenge.setScore(nodeChallenge.get("challenge_score").asDouble(0.0)); challenge.setMaxAttempts(nodeChallenge.get("challenge_max_attempts").asInt(0)); challenge.setDocuments( resolveJsonIds(nodeChallenge, "challenge_documents") diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java index f1418aa9ad..327c2fc75f 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaContract.java @@ -87,12 +87,12 @@ private ContractExpectations expectations() { Expectation preventionExpectation = new Expectation(); preventionExpectation.setType(PREVENTION); preventionExpectation.setName("Expect inject to be prevented"); - preventionExpectation.setScore(0); + preventionExpectation.setScore(100.0); // Detection Expectation detectionExpectation = new Expectation(); detectionExpectation.setType(DETECTION); detectionExpectation.setName("Expect inject to be detected"); - detectionExpectation.setScore(0); + detectionExpectation.setScore(100.0); return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); } diff --git a/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeContract.java b/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeContract.java index 12d8f1bc2a..60f91c0a0f 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeContract.java +++ b/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeContract.java @@ -6,17 +6,21 @@ import io.openbas.injector_contract.ContractorIcon; import io.openbas.injector_contract.fields.ContractElement; import io.openbas.database.model.Endpoint; +import io.openbas.injector_contract.fields.ContractExpectations; +import io.openbas.model.inject.form.Expectation; import org.springframework.stereotype.Component; import java.io.InputStream; import java.util.List; import java.util.Map; +import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.CHALLENGE; import static io.openbas.injector_contract.Contract.executableContract; import static io.openbas.injector_contract.ContractCardinality.Multiple; import static io.openbas.injector_contract.ContractDef.contractBuilder; import static io.openbas.injector_contract.fields.ContractChallenge.challengeField; import static io.openbas.injector_contract.fields.ContractAttachment.attachmentField; +import static io.openbas.injector_contract.fields.ContractExpectations.expectationsField; import static io.openbas.injector_contract.fields.ContractTeam.teamField; import static io.openbas.injector_contract.fields.ContractCheckbox.checkboxField; import static io.openbas.injector_contract.fields.ContractText.textField; @@ -61,8 +65,18 @@ public List contracts() { Kind regards,
The animation team """; + // We include the expectations for challenges + Expectation expectation = new Expectation(); + expectation.setType(CHALLENGE); + expectation.setName("Expect targets to complete the challenge(s)"); + expectation.setScore(0.0); + ContractExpectations expectationsField = expectationsField( + "expectations", "Expectations", List.of(expectation) + ); List publishInstance = contractBuilder() .mandatory(challengeField("challenges", "Challenges", Multiple)) + // Contract specific + .optional(expectationsField) .mandatory(textField("subject", "Subject", "New challenges published for ${user.email}")) .mandatory(richTextareaField("body", "Body", messageBody)) .optional(checkboxField("encrypted", "Encrypted", false)) diff --git a/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeExecutor.java index 0422f5debf..1c7b7c2eb6 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/challenge/ChallengeExecutor.java @@ -12,7 +12,7 @@ import io.openbas.model.ExecutionProcess; import io.openbas.model.Expectation; import io.openbas.model.expectation.ChallengeExpectation; -import io.openbas.rest.exception.ElementNotFoundException; +import io.openbas.model.expectation.ManualExpectation; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotNull; import org.springframework.beans.factory.annotation.Autowired; @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import static io.openbas.database.model.InjectStatusExecution.traceError; import static io.openbas.database.model.InjectStatusExecution.traceSuccess; @@ -102,7 +103,21 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin }); // Return expectations List expectations = new ArrayList<>(); - challenges.forEach(challenge -> expectations.add(new ChallengeExpectation(challenge.getScore(), challenge))); + if (!content.getExpectations().isEmpty()) { + expectations.addAll( + content.getExpectations() + .stream() + .flatMap((entry) -> switch (entry.getType()) { + case MANUAL -> Stream.of( + (Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription(), entry.isExpectationGroup()) + ); + case CHALLENGE -> challenges.stream() + .map(challenge -> (Expectation) new ChallengeExpectation(entry.getScore(), challenge, entry.isExpectationGroup())); + default -> Stream.of(); + }) + .toList() + ); + } return new ExecutionProcess(false, expectations); } else { throw new UnsupportedOperationException("Unknown contract " + contract); diff --git a/openbas-api/src/main/java/io/openbas/injectors/challenge/model/ChallengeContent.java b/openbas-api/src/main/java/io/openbas/injectors/challenge/model/ChallengeContent.java index 48f13e34b1..6b58cfe16a 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/challenge/model/ChallengeContent.java +++ b/openbas-api/src/main/java/io/openbas/injectors/challenge/model/ChallengeContent.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.openbas.injectors.email.model.EmailContent; +import io.openbas.model.inject.form.Expectation; import lombok.Getter; import lombok.Setter; @@ -15,4 +16,7 @@ public class ChallengeContent extends EmailContent { @JsonProperty("challenges") private List challenges = new ArrayList<>(); + @JsonProperty("expectations") + private List expectations = new ArrayList<>(); + } diff --git a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelContract.java b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelContract.java index c17da0b546..70ade09f5b 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelContract.java +++ b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelContract.java @@ -74,8 +74,8 @@ public List contracts() { ContractCheckbox emailingField = checkboxField("emailing", "Send email", true); Expectation expectation = new Expectation(); expectation.setType(ARTICLE); - expectation.setName("Expect teams to read the article(s)"); - expectation.setScore(0); + expectation.setName("Expect targets to read the article(s)"); + expectation.setScore(0.0); ContractExpectations expectationsField = expectationsField( "expectations", "Expectations", List.of(expectation) ); diff --git a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelExecutor.java index 3469e4bb9d..5aab36bc42 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/channel/ChannelExecutor.java @@ -118,10 +118,10 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin .stream() .flatMap((entry) -> switch (entry.getType()) { case MANUAL -> Stream.of( - (Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription()) + (Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription(), entry.isExpectationGroup()) ); case ARTICLE -> articles.stream() - .map(article -> (Expectation) new ChannelExpectation(entry.getScore(), article)); + .map(article -> (Expectation) new ChannelExpectation(entry.getScore(), article, entry.isExpectationGroup())); default -> Stream.of(); }) .toList() diff --git a/openbas-api/src/main/java/io/openbas/injectors/email/EmailExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/email/EmailExecutor.java index d159a36e12..dcc202ee77 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/email/EmailExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/email/EmailExecutor.java @@ -86,7 +86,7 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin .stream() .flatMap((entry) -> switch (entry.getType()) { case MANUAL -> - Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription())); + Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription(), entry.isExpectationGroup())); default -> Stream.of(); }) .toList(); diff --git a/openbas-api/src/main/java/io/openbas/injectors/opencti/OpenCTIExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/opencti/OpenCTIExecutor.java index a5f72ff340..8cc96b176a 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/opencti/OpenCTIExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/opencti/OpenCTIExecutor.java @@ -65,7 +65,7 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin .stream() .flatMap((entry) -> switch (entry.getType()) { case MANUAL -> - Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription())); + Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription(), entry.isExpectationGroup())); default -> Stream.of(); }) .toList(); diff --git a/openbas-api/src/main/java/io/openbas/injectors/ovh/OvhSmsExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/ovh/OvhSmsExecutor.java index e0fd99617f..1faf2ee3e7 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/ovh/OvhSmsExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/ovh/OvhSmsExecutor.java @@ -66,7 +66,7 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin .stream() .flatMap(entry -> switch (entry.getType()) { case MANUAL -> - Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription())); + Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription(), entry.isExpectationGroup())); default -> Stream.of(); }) .toList(); diff --git a/openbas-api/src/main/java/io/openbas/migration/V2_63__InjectExpectation_upgrade.java b/openbas-api/src/main/java/io/openbas/migration/V2_63__InjectExpectation_upgrade.java index 347af4935e..c31584f4d3 100644 --- a/openbas-api/src/main/java/io/openbas/migration/V2_63__InjectExpectation_upgrade.java +++ b/openbas-api/src/main/java/io/openbas/migration/V2_63__InjectExpectation_upgrade.java @@ -72,7 +72,7 @@ public static class OvhSmsContentOld { private String message; private String expectationType; - private Integer expectationScore; + private Double expectationScore; OvhSmsContentNew toNewContent() { OvhSmsContentNew content = new OvhSmsContentNew(); @@ -99,7 +99,7 @@ public static class EmailContentOld { private String inReplyTo; private boolean encrypted; private String expectationType; - private Integer expectationScore; + private Double expectationScore; EmailContent toNewContent() { EmailContent content = new EmailContent(); @@ -122,7 +122,7 @@ public static class MediaContentOld extends EmailContentOld { private List articles; private boolean expectation; - private Integer expectationScore; + private Double expectationScore; private boolean emailing; ChannelContent toNewContent() { diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_30__Score_type.java b/openbas-api/src/main/java/io/openbas/migration/V3_30__Score_type.java new file mode 100644 index 0000000000..2a2fa73d4e --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_30__Score_type.java @@ -0,0 +1,19 @@ +package io.openbas.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +import java.sql.Statement; + +@Component +public class V3_30__Score_type extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Statement select = context.getConnection().createStatement(); + select.execute("ALTER TABLE challenges alter column challenge_score type DOUBLE PRECISION;"); + select.execute("ALTER TABLE injects_expectations alter column inject_expectation_score type DOUBLE PRECISION;"); + select.execute("ALTER TABLE injects_expectations alter column inject_expectation_expected_score type DOUBLE PRECISION;"); + } +} diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_31__Add_Injects_tests_statuses.java b/openbas-api/src/main/java/io/openbas/migration/V3_31__Add_Injects_tests_statuses.java new file mode 100644 index 0000000000..b81c35a23f --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_31__Add_Injects_tests_statuses.java @@ -0,0 +1,38 @@ +package io.openbas.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.springframework.stereotype.Component; + +import java.sql.Connection; +import java.sql.Statement; + +@Component +public class V3_31__Add_Injects_tests_statuses extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Connection connection = context.getConnection(); + Statement select = connection.createStatement(); + // Create table + select.execute(""" + CREATE TABLE injects_tests_statuses ( + status_id varchar(255) NOT NULL CONSTRAINT inject_test_status_pkey PRIMARY KEY, + status_name VARCHAR(255) NOT NULL, + status_executions text, + tracking_sent_date timestamp, + tracking_ack_date timestamp, + tracking_end_date timestamp, + tracking_total_execution_time bigint, + tracking_total_count int, + tracking_total_error int, + tracking_total_success int, + status_inject VARCHAR(255) NOT NULL CONSTRAINT inject_test_status_inject_id_fkey REFERENCES injects(inject_id) ON DELETE SET NULL, + status_created_at timestamp not null default now(), + status_updated_at timestamp not null default now() + ); + CREATE INDEX idx_inject_test_inject ON injects_tests_statuses(status_inject); + """); + } + +} 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 index 6f6c551c7b..7a4aebf273 100644 --- 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 @@ -73,10 +73,11 @@ public Inject tryAtomicTesting(@PathVariable String injectId) { @GetMapping("/{injectId}/target_results/{targetId}/types/{targetType}") public List findTargetResult( - @PathVariable String targetId, @PathVariable String injectId, - @PathVariable String targetType) { - return injectExpectationService.findExpectationsByInjectAndTargetAndTargetType(injectId, targetId, targetType); + @PathVariable String targetId, + @PathVariable String targetType, + @RequestParam(required = false) String parentTargetId ) { + return injectExpectationService.findExpectationsByInjectAndTargetAndTargetType(injectId, targetId, parentTargetId, targetType); } @PutMapping("/{injectId}/tags") diff --git a/openbas-api/src/main/java/io/openbas/rest/challenge/ChallengeApi.java b/openbas-api/src/main/java/io/openbas/rest/challenge/ChallengeApi.java index 63b29398b5..3e456643d4 100644 --- a/openbas-api/src/main/java/io/openbas/rest/challenge/ChallengeApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/challenge/ChallengeApi.java @@ -1,7 +1,10 @@ package io.openbas.rest.challenge; -import io.openbas.database.model.*; +import io.openbas.database.model.Challenge; +import io.openbas.database.model.ChallengeFlag; import io.openbas.database.model.ChallengeFlag.FLAG_TYPE; +import io.openbas.database.model.Exercise; +import io.openbas.database.model.User; import io.openbas.database.repository.*; import io.openbas.rest.challenge.form.ChallengeCreateInput; import io.openbas.rest.challenge.form.ChallengeTryInput; @@ -14,16 +17,14 @@ import io.openbas.service.ChallengeService; import jakarta.transaction.Transactional; import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.AllArgsConstructor; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.time.Instant; -import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.regex.Pattern; import static io.openbas.config.OpenBASAnonymous.ANONYMOUS; import static io.openbas.database.model.User.ROLE_ADMIN; @@ -31,6 +32,7 @@ import static io.openbas.helper.StreamHelper.iterableToSet; @RestController +@AllArgsConstructor public class ChallengeApi extends RestBehavior { private ChallengeRepository challengeRepository; @@ -38,50 +40,9 @@ public class ChallengeApi extends RestBehavior { private TagRepository tagRepository; private DocumentRepository documentRepository; private ExerciseRepository exerciseRepository; - private InjectExpectationRepository injectExpectationRepository; private ChallengeService challengeService; private UserRepository userRepository; - @Autowired - public void setUserRepository(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Autowired - public void setChallengeService(ChallengeService challengeService) { - this.challengeService = challengeService; - } - - @Autowired - public void setInjectExpectationRepository(InjectExpectationRepository injectExpectationRepository) { - this.injectExpectationRepository = injectExpectationRepository; - } - - @Autowired - public void setChallengeFlagRepository(ChallengeFlagRepository challengeFlagRepository) { - this.challengeFlagRepository = challengeFlagRepository; - } - - @Autowired - public void setChallengeRepository(ChallengeRepository challengeRepository) { - this.challengeRepository = challengeRepository; - } - - @Autowired - public void setTagRepository(TagRepository tagRepository) { - this.tagRepository = tagRepository; - } - - @Autowired - public void setDocumentRepository(DocumentRepository documentRepository) { - this.documentRepository = documentRepository; - } - - @Autowired - public void setExerciseRepository(ExerciseRepository exerciseRepository) { - this.exerciseRepository = exerciseRepository; - } - @GetMapping("/api/challenges") public Iterable challenges() { return fromIterable(challengeRepository.findAll()).stream() @@ -136,25 +97,11 @@ public Challenge createChallenge(@Valid @RequestBody ChallengeCreateInput input) @GetMapping("/api/player/challenges/{exerciseId}") public ChallengesReader playerChallenges(@PathVariable String exerciseId, @RequestParam Optional userId) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); final User user = impersonateUser(userRepository, userId); if (user.getId().equals(ANONYMOUS)) { throw new UnsupportedOperationException("User must be logged or dynamic player is required"); } - ChallengesReader reader = new ChallengesReader(exercise); - List teamIds = user.getTeams().stream().map(Team::getId).toList(); - List challengeExpectations = injectExpectationRepository.findChallengeExpectations(exerciseId, - teamIds); - List challenges = challengeExpectations.stream() - .map(injectExpectation -> { - Challenge challenge = injectExpectation.getChallenge(); - challenge.setVirtualPublication(injectExpectation.getCreatedAt()); - return new ChallengeInformation(challenge, injectExpectation); - }) - .sorted(Comparator.comparing(o -> o.getChallenge().getVirtualPublication())) - .toList(); - reader.setExerciseChallenges(challenges); - return reader; + return challengeService.playerChallenges(exerciseId, user); } @GetMapping("/api/observer/challenges/{exerciseId}") @@ -174,32 +121,9 @@ public void deleteChallenge(@PathVariable String challengeId) { challengeRepository.deleteById(challengeId); } - private boolean checkFlag(ChallengeFlag flag, String value) { - switch (flag.getType()) { - case VALUE -> { - return value.equalsIgnoreCase(flag.getValue()); - } - case VALUE_CASE -> { - return value.equals(flag.getValue()); - } - case REGEXP -> { - return Pattern.compile(flag.getValue()).matcher(value).matches(); - } - default -> { - return false; - } - } - } - @PostMapping("/api/challenges/{challengeId}/try") public ChallengeResult tryChallenge(@PathVariable String challengeId, @Valid @RequestBody ChallengeTryInput input) { - Challenge challenge = challengeRepository.findById(challengeId).orElseThrow(ElementNotFoundException::new); - for (ChallengeFlag flag : challenge.getFlags()) { - if (checkFlag(flag, input.getValue())) { - return new ChallengeResult(true); - } - } - return new ChallengeResult(false); + return challengeService.tryChallenge(challengeId, input); } @PostMapping("/api/player/challenges/{exerciseId}/{challengeId}/validate") @@ -212,29 +136,6 @@ public ChallengesReader validateChallenge(@PathVariable String exerciseId, if (user.getId().equals(ANONYMOUS)) { throw new UnsupportedOperationException("User must be logged or dynamic player is required"); } - ChallengeResult challengeResult = tryChallenge(challengeId, input); - if (challengeResult.getResult()) { - List teamIds = user.getTeams().stream().map(Team::getId).toList(); - List challengeExpectations = injectExpectationRepository.findChallengeExpectations(exerciseId, - teamIds, challengeId); - challengeExpectations.forEach(injectExpectationExecution -> { - injectExpectationExecution.setUser(user); - injectExpectationExecution.setResults(List.of( - InjectExpectationResult.builder() - .sourceId("challenge") - .sourceType("challenge") - .sourceName("Challenge validation") - .result(Instant.now().toString()) - .date(Instant.now().toString()) - .score(injectExpectationExecution.getExpectedScore()) - .build() - )); - injectExpectationExecution.setScore(injectExpectationExecution.getExpectedScore()); - injectExpectationExecution.setUpdatedAt(Instant.now()); - injectExpectationRepository.save(injectExpectationExecution); - }); - } - return playerChallenges(exerciseId, userId); + return challengeService.validateChallenge(exerciseId, challengeId, input, user); } - } diff --git a/openbas-api/src/main/java/io/openbas/rest/challenge/output/ChallengeOutput.java b/openbas-api/src/main/java/io/openbas/rest/challenge/output/ChallengeOutput.java index 1298450e57..4a522d5128 100644 --- a/openbas-api/src/main/java/io/openbas/rest/challenge/output/ChallengeOutput.java +++ b/openbas-api/src/main/java/io/openbas/rest/challenge/output/ChallengeOutput.java @@ -32,7 +32,7 @@ public class ChallengeOutput { private String content; @JsonProperty("challenge_score") - private Integer score; + private Double score; @JsonProperty("challenge_max_attempts") private Integer maxAttempts; diff --git a/openbas-api/src/main/java/io/openbas/rest/challenge/response/PublicChallenge.java b/openbas-api/src/main/java/io/openbas/rest/challenge/response/PublicChallenge.java index 17af32367c..c5b65c192a 100644 --- a/openbas-api/src/main/java/io/openbas/rest/challenge/response/PublicChallenge.java +++ b/openbas-api/src/main/java/io/openbas/rest/challenge/response/PublicChallenge.java @@ -23,7 +23,7 @@ public class PublicChallenge { private String content; @JsonProperty("challenge_score") - private Integer score; + private Double score; @JsonProperty("challenge_flags") private List flags; @@ -85,11 +85,11 @@ public void setContent(String content) { this.content = content; } - public Integer getScore() { + public Double getScore() { return score; } - public void setScore(Integer score) { + public void setScore(Double score) { this.score = score; } diff --git a/openbas-api/src/main/java/io/openbas/rest/channel/ChannelApi.java b/openbas-api/src/main/java/io/openbas/rest/channel/ChannelApi.java index 740b93470b..6ea6429b96 100644 --- a/openbas-api/src/main/java/io/openbas/rest/channel/ChannelApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/channel/ChannelApi.java @@ -1,35 +1,33 @@ package io.openbas.rest.channel; -import com.fasterxml.jackson.core.JsonProcessingException; import io.openbas.database.model.*; import io.openbas.database.repository.*; -import io.openbas.injectors.channel.model.ChannelContent; import io.openbas.rest.channel.form.*; -import io.openbas.rest.channel.model.VirtualArticle; import io.openbas.rest.channel.response.ChannelReader; import io.openbas.rest.exception.ElementNotFoundException; import io.openbas.rest.helper.RestBehavior; +import io.openbas.service.ChannelService; import io.openbas.service.ScenarioService; import jakarta.transaction.Transactional; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.AllArgsConstructor; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.time.Instant; -import java.util.*; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import static io.openbas.config.OpenBASAnonymous.ANONYMOUS; import static io.openbas.database.model.User.ROLE_ADMIN; -import static io.openbas.helper.StreamHelper.fromIterable; -import static io.openbas.injectors.channel.ChannelContract.CHANNEL_PUBLISH; import static io.openbas.rest.channel.ChannelHelper.enrichArticleWithVirtualPublication; import static io.openbas.rest.scenario.ScenarioApi.SCENARIO_URI; @RestController +@AllArgsConstructor public class ChannelApi extends RestBehavior { private ExerciseRepository exerciseRepository; @@ -37,44 +35,8 @@ public class ChannelApi extends RestBehavior { private ArticleRepository articleRepository; private ChannelRepository channelRepository; private DocumentRepository documentRepository; - private InjectExpectationRepository injectExpectationExecutionRepository; private UserRepository userRepository; - - @Autowired - public void setUserRepository(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Autowired - public void setArticleRepository(ArticleRepository articleRepository) { - this.articleRepository = articleRepository; - } - - @Autowired - public void setInjectExpectationExecutionRepository( - InjectExpectationRepository injectExpectationExecutionRepository) { - this.injectExpectationExecutionRepository = injectExpectationExecutionRepository; - } - - @Autowired - public void setChannelRepository(ChannelRepository channelRepository) { - this.channelRepository = channelRepository; - } - - @Autowired - public void setExerciseRepository(ExerciseRepository exerciseRepository) { - this.exerciseRepository = exerciseRepository; - } - - @Autowired - public void setScenarioService(ScenarioService scenarioService) { - this.scenarioService = scenarioService; - } - - @Autowired - public void setDocumentRepository(DocumentRepository documentRepository) { - this.documentRepository = documentRepository; - } + private ChannelService channelService; // -- CHANNELS -- @@ -157,78 +119,11 @@ public ChannelReader playerArticles( @PathVariable String exerciseId, @PathVariable String channelId, @RequestParam Optional userId) { - ChannelReader channelReader; - Channel channel = channelRepository.findById(channelId).orElseThrow(ElementNotFoundException::new); - List injects; - - Optional exerciseOpt = exerciseRepository.findById(exerciseId); - if (exerciseOpt.isPresent()) { - Exercise exercise = exerciseOpt.get(); - channelReader = new ChannelReader(channel, exercise); - injects = exercise.getInjects(); - } else { - Scenario scenario = this.scenarioService.scenario(exerciseId); - channelReader = new ChannelReader(channel, scenario); - injects = scenario.getInjects(); - } - final User user = impersonateUser(userRepository, userId); if (user.getId().equals(ANONYMOUS)) { throw new UnsupportedOperationException("User must be logged or dynamic player is required"); } - Map toPublishArticleIdsMap = injects.stream() - .filter(inject -> inject.getInjectorContract() - .map(contract -> contract.getId().equals(CHANNEL_PUBLISH)) - .orElse(false)) - .filter(inject -> inject.getStatus().isPresent()) - .sorted(Comparator.comparing(inject -> inject.getStatus().get().getTrackingSentDate())) - .flatMap(inject -> { - Instant virtualInjectDate = inject.getStatus().get().getTrackingSentDate(); - try { - ChannelContent content = mapper.treeToValue(inject.getContent(), ChannelContent.class); - if (content.getArticles() != null) { - return content.getArticles().stream().map(article -> new VirtualArticle(virtualInjectDate, article)); - } - return null; - } catch (JsonProcessingException e) { - // Invalid channel content. - return null; - } - }) - .filter(Objects::nonNull) - .distinct() - .collect(Collectors.toMap(VirtualArticle::id, VirtualArticle::date)); - if (!toPublishArticleIdsMap.isEmpty()) { - List
publishedArticles = fromIterable(articleRepository.findAllById(toPublishArticleIdsMap.keySet())) - .stream().filter(article -> article.getChannel().equals(channel)) - .peek(article -> article.setVirtualPublication(toPublishArticleIdsMap.get(article.getId()))) - .sorted(Comparator.comparing(Article::getVirtualPublication).reversed()) - .toList(); - channelReader.setChannelArticles(publishedArticles); - // Fulfill article expectations - List finalInjects = injects; - List expectationExecutions = publishedArticles.stream() - .flatMap(article -> finalInjects.stream() - .flatMap(inject -> inject.getUserExpectationsForArticle(user, article).stream())) - .filter(exec -> exec.getResults().isEmpty()).toList(); - expectationExecutions.forEach(injectExpectationExecution -> { - injectExpectationExecution.setUser(user); - injectExpectationExecution.setResults(List.of( - InjectExpectationResult.builder() - .sourceId("media-pressure") - .sourceType("media-pressure") - .sourceName("Media pressure read") - .result(Instant.now().toString()) - .date(Instant.now().toString()) - .score(injectExpectationExecution.getExpectedScore()) - .build() - )); - injectExpectationExecution.setScore(injectExpectationExecution.getExpectedScore()); - injectExpectationExecution.setUpdatedAt(Instant.now()); - injectExpectationExecutionRepository.save(injectExpectationExecution); - }); - } - return channelReader; + return channelService.validateArticles(exerciseId, channelId, user); } // -- EXERCISES -- diff --git a/openbas-api/src/main/java/io/openbas/rest/exception/ImportException.java b/openbas-api/src/main/java/io/openbas/rest/exception/ImportException.java new file mode 100644 index 0000000000..31a4bd9fca --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/rest/exception/ImportException.java @@ -0,0 +1,15 @@ +package io.openbas.rest.exception; + +public class ImportException extends Exception { + + private final String field; + + public ImportException(String field, String message) { + super(message); + this.field = field; + } + + public String getField() { + return field; + } +} diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/form/ExpectationUpdateInput.java b/openbas-api/src/main/java/io/openbas/rest/exercise/form/ExpectationUpdateInput.java index 0a713b61df..7064c6163f 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/form/ExpectationUpdateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/form/ExpectationUpdateInput.java @@ -24,5 +24,5 @@ public class ExpectationUpdateInput { @JsonProperty("expectation_score") @NotNull - private Integer score; + private Double score; } diff --git a/openbas-api/src/main/java/io/openbas/rest/helper/RestBehavior.java b/openbas-api/src/main/java/io/openbas/rest/helper/RestBehavior.java index 1a2fa3bf2e..de5a289cad 100644 --- a/openbas-api/src/main/java/io/openbas/rest/helper/RestBehavior.java +++ b/openbas-api/src/main/java/io/openbas/rest/helper/RestBehavior.java @@ -8,9 +8,7 @@ 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.exception.FileTooBigException; -import io.openbas.rest.exception.InputValidationException; +import io.openbas.rest.exception.*; import jakarta.annotation.Resource; import lombok.extern.java.Log; import org.hibernate.exception.ConstraintViolationException; @@ -77,6 +75,18 @@ public ValidationErrorBag handleInputValidationExceptions(InputValidationExcepti return bag; } + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(ImportException.class) + public ValidationErrorBag handleBadRequestExceptions(ImportException ex) { + ValidationErrorBag bag = new ValidationErrorBag(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); + ValidationError errors = new ValidationError(); + Map errorsBag = new HashMap<>(); + errorsBag.put(ex.getField(), new ValidationContent(ex.getMessage())); + errors.setChildren(errorsBag); + bag.setErrors(errors); + return bag; + } + @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler(AccessDeniedException.class) public ValidationErrorBag handleValidationExceptions() { diff --git a/openbas-api/src/main/java/io/openbas/rest/helper/TeamHelper.java b/openbas-api/src/main/java/io/openbas/rest/helper/TeamHelper.java index cf91cf3882..9d757b21c2 100644 --- a/openbas-api/src/main/java/io/openbas/rest/helper/TeamHelper.java +++ b/openbas-api/src/main/java/io/openbas/rest/helper/TeamHelper.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -55,19 +56,21 @@ public static List rawTeamToSimplerTeam(List teams, rawTeam.getTeam_expectations().stream().map( expectation -> { // We set the inject expectation using the map we generated earlier - RawInjectExpectation raw = mapInjectExpectation.get(expectation); InjectExpectation injectExpectation = new InjectExpectation(); - injectExpectation.setScore(raw.getInject_expectation_score()); - injectExpectation.setExpectedScore(raw.getInject_expectation_expected_score()); - injectExpectation.setId(raw.getInject_expectation_id()); - injectExpectation.setExpectedScore(raw.getInject_expectation_expected_score()); - if(raw.getExercise_id() != null) { - injectExpectation.setExercise(new Exercise()); - injectExpectation.getExercise().setId(raw.getExercise_id()); - } - injectExpectation.setTeam(new Team()); - injectExpectation.getTeam().setId(rawTeam.getTeam_id()); - injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.valueOf(raw.getInject_expectation_type())); + Optional raw = Optional.ofNullable(mapInjectExpectation.get(expectation)); + raw.ifPresent(toProcess -> { + injectExpectation.setScore(toProcess.getInject_expectation_score()); + injectExpectation.setExpectedScore(toProcess.getInject_expectation_expected_score()); + injectExpectation.setId(toProcess.getInject_expectation_id()); + injectExpectation.setExpectedScore(toProcess.getInject_expectation_expected_score()); + if (toProcess.getExercise_id() != null) { + injectExpectation.setExercise(new Exercise()); + injectExpectation.getExercise().setId(toProcess.getExercise_id()); + } + injectExpectation.setTeam(new Team()); + injectExpectation.getTeam().setId(rawTeam.getTeam_id()); + injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.valueOf(toProcess.getInject_expectation_type())); + }); return injectExpectation; } ).toList() 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 index 2b6f8aa9dc..1a68d2250c 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java @@ -21,6 +21,7 @@ import io.openbas.rest.inject.service.InjectDuplicateService; import io.openbas.service.AtomicTestingService; import io.openbas.service.InjectService; +import io.openbas.service.InjectTestStatusService; import io.openbas.service.ScenarioService; import io.openbas.utils.AtomicTestingMapper; import io.openbas.utils.pagination.SearchPaginationInput; @@ -29,8 +30,6 @@ import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -49,9 +48,6 @@ import java.util.stream.StreamSupport; import static io.openbas.config.SessionHelper.currentUser; - -import io.openbas.execution.Injector; - import static io.openbas.database.model.User.ROLE_ADMIN; import static io.openbas.database.specification.CommunicationSpecification.fromInject; import static io.openbas.helper.DatabaseHelper.resolveOptionalRelation; @@ -77,7 +73,6 @@ public class InjectApi extends RestBehavior { private final Executor executor; private final InjectorContractRepository injectorContractRepository; - private ApplicationContext context; private final CommunicationRepository communicationRepository; private final ExerciseRepository exerciseRepository; private final UserRepository userRepository; @@ -94,11 +89,6 @@ public class InjectApi extends RestBehavior { private final AtomicTestingService atomicTestingService; private final InjectDuplicateService injectDuplicateService; - @Autowired - public void setContext(ApplicationContext context) { - this.context = context; - } - // -- INJECTS -- @GetMapping(INJECT_URI + "/{injectId}") @@ -169,23 +159,6 @@ public Inject tryInject(@PathVariable String injectId) { return atomicTestingService.tryInject(injectId); } - @GetMapping(INJECT_URI + "/test/{injectId}") - public InjectStatus testInject(@PathVariable String injectId) { - Inject inject = injectRepository.findById(injectId).orElseThrow(); - User user = this.userRepository.findById(currentUser().getId()).orElseThrow(); - List userInjectContexts = List.of( - this.executionContextService.executionContext(user, inject, "Direct test") - ); - Injector executor = context.getBean( - inject.getInjectorContract().map(injectorContract -> injectorContract.getInjector().getType()).orElseThrow(), - io.openbas.execution.Injector.class); - ExecutableInject injection = new ExecutableInject(false, true, inject, List.of(), inject.getAssets(), - inject.getAssetGroups(), userInjectContexts); - Execution execution = executor.executeInjection(injection); - return InjectStatus.fromExecutionTest(execution); - - } - @Transactional(rollbackFor = Exception.class) @PutMapping(INJECT_URI + "/{exerciseId}/{injectId}") @PreAuthorize("isExercisePlanner(#exerciseId)") diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/output/InjectOutput.java b/openbas-api/src/main/java/io/openbas/rest/inject/output/InjectOutput.java index 10527f90c8..69deae644c 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/output/InjectOutput.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/output/InjectOutput.java @@ -63,7 +63,7 @@ public class InjectOutput { @JsonProperty("inject_testable") public boolean canBeTested() { - return this.getInjectType().equals(EmailContract.TYPE) || this.getInjectType().equals(OvhSmsContract.TYPE); + return EmailContract.TYPE.equals(this.getInjectType()) || OvhSmsContract.TYPE.equals(this.getInjectType()); } public InjectOutput( diff --git a/openbas-api/src/main/java/io/openbas/rest/inject_test_status/InjectTestStatusApi.java b/openbas-api/src/main/java/io/openbas/rest/inject_test_status/InjectTestStatusApi.java new file mode 100644 index 0000000000..023a56f998 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/rest/inject_test_status/InjectTestStatusApi.java @@ -0,0 +1,41 @@ +package io.openbas.rest.inject_test_status; + +import io.openbas.database.model.InjectTestStatus; +import io.openbas.rest.helper.RestBehavior; +import io.openbas.service.InjectTestStatusService; +import jakarta.validation.constraints.NotBlank; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@PreAuthorize("isAdmin()") +@RequiredArgsConstructor +public class InjectTestStatusApi extends RestBehavior { + + private final InjectTestStatusService injectTestStatusService; + + @GetMapping("/api/injects/{injectId}/test") + public InjectTestStatus testInject(@PathVariable @NotBlank String injectId) { + return injectTestStatusService.testInject(injectId); + } + + @GetMapping("/api/exercise/{exerciseId}/injects/test") + public List findAllExerciseInjectTests(@PathVariable @NotBlank String exerciseId) { + return injectTestStatusService.findAllInjectTestsByExerciseId(exerciseId); + } + + @GetMapping("/api/scenario/{scenarioId}/injects/test") + public List findAllScenarioInjectTests(@PathVariable @NotBlank String scenarioId) { + return injectTestStatusService.findAllInjectTestsByScenarioId(scenarioId); + } + + @GetMapping("/api/injects/test/{testId}") + public InjectTestStatus findInjectTestStatus(@PathVariable @NotBlank String testId) { + return injectTestStatusService.findInjectTestStatusById(testId); + } +} diff --git a/openbas-api/src/main/java/io/openbas/rest/mapper/MapperApi.java b/openbas-api/src/main/java/io/openbas/rest/mapper/MapperApi.java index 14894a17e7..d6ea76a5b7 100644 --- a/openbas-api/src/main/java/io/openbas/rest/mapper/MapperApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/mapper/MapperApi.java @@ -1,12 +1,15 @@ package io.openbas.rest.mapper; +import com.fasterxml.jackson.core.type.TypeReference; import io.openbas.database.model.ImportMapper; import io.openbas.database.model.Scenario; import io.openbas.database.raw.RawPaginationImportMapper; import io.openbas.database.repository.ImportMapperRepository; import io.openbas.rest.exception.ElementNotFoundException; import io.openbas.rest.exception.FileTooBigException; +import io.openbas.rest.exception.ImportException; import io.openbas.rest.helper.RestBehavior; +import io.openbas.rest.mapper.form.ExportMapperInput; import io.openbas.rest.mapper.form.ImportMapperAddInput; import io.openbas.rest.mapper.form.ImportMapperUpdateInput; import io.openbas.rest.scenario.form.InjectsImportTestInput; @@ -16,6 +19,7 @@ import io.openbas.service.MapperService; import io.openbas.utils.pagination.SearchPaginationInput; import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletResponse; import jakarta.transaction.Transactional; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -23,12 +27,19 @@ import lombok.RequiredArgsConstructor; import org.apache.commons.io.FilenameUtils; import org.springframework.data.domain.Page; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.reactive.function.UnsupportedMediaTypeException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; import java.time.Instant; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.UUID; @@ -72,6 +83,42 @@ public ImportMapper createImportMapper(@RequestBody @Valid final ImportMapperAdd return mapperService.createAndSaveImportMapper(importMapperAddInput); } + @Secured(ROLE_ADMIN) + @PostMapping(value="/api/mappers/export") + public void exportMappers( + @RequestBody @Valid final ExportMapperInput exportMapperInput, + HttpServletResponse response) { + try { + String jsonMappers = mapperService.exportMappers(exportMapperInput.getIdsToExport()); + + String rightNow = DateTimeFormatter.ofPattern("yyyyMMdd").format(LocalDateTime.now()); + String name = exportMapperInput.getName().replace("(Import)", "").replace(" ", ""); + String exportFileName = name.length() > 15 ? name.substring(0, 15) : name; + String filename = MessageFormat.format("{0}-{1}.json", exportFileName, rightNow); + + response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename); + response.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpServletResponse.SC_OK); + + response.getOutputStream().write(jsonMappers.getBytes(StandardCharsets.UTF_8)); + response.getOutputStream().flush(); + response.getOutputStream().close(); + } catch (IOException e) { + throw new RuntimeException("Error during export", e); + } + } + + @Secured(ROLE_ADMIN) + @PostMapping("/api/mappers/import") + public void importMappers(@RequestPart("file") @NotNull MultipartFile file) throws ImportException { + try { + mapperService.importMappers(mapper.readValue(file.getInputStream().readAllBytes(), new TypeReference<>() { + })); + } catch (Exception e) { + throw new ImportException("Mapper import", "Error during import"); + } + } + @Secured(ROLE_ADMIN) @PutMapping("/api/mappers/{mapperId}") public ImportMapper updateImportMapper(@PathVariable String mapperId, @Valid @RequestBody ImportMapperUpdateInput importMapperUpdateInput) { diff --git a/openbas-api/src/main/java/io/openbas/rest/mapper/export/MapperExportMixins.java b/openbas-api/src/main/java/io/openbas/rest/mapper/export/MapperExportMixins.java new file mode 100644 index 0000000000..605824d33f --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/rest/mapper/export/MapperExportMixins.java @@ -0,0 +1,36 @@ +package io.openbas.rest.mapper.export; + +import com.fasterxml.jackson.annotation.JsonIncludeProperties; + +public class MapperExportMixins { + + private MapperExportMixins() { + + } + + @JsonIncludeProperties(value = { + "import_mapper_name", + "import_mapper_inject_type_column", + "import_mapper_inject_importers", + }) + public static class ImportMapper { + } + + @JsonIncludeProperties(value = { + "inject_importer_type_value", + "inject_importer_injector_contract", + "inject_importer_rule_attributes", + }) + public static class InjectImporter { + } + + @JsonIncludeProperties(value = { + "rule_attribute_columns", + "rule_attribute_name", + "rule_attribute_default_value", + "rule_attribute_additional_config", + }) + public static class RuleAttribute { + } + +} diff --git a/openbas-api/src/main/java/io/openbas/rest/mapper/form/ExportMapperInput.java b/openbas-api/src/main/java/io/openbas/rest/mapper/form/ExportMapperInput.java new file mode 100644 index 0000000000..91ccc25d44 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/rest/mapper/form/ExportMapperInput.java @@ -0,0 +1,21 @@ +package io.openbas.rest.mapper.form; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +import static io.openbas.config.AppConfig.MANDATORY_MESSAGE; + +@Data +public class ExportMapperInput { + + @JsonProperty("export_mapper_name") + private String name; + + @NotNull(message = MANDATORY_MESSAGE) + @JsonProperty("ids_to_export") + private List idsToExport; + +} diff --git a/openbas-api/src/main/java/io/openbas/rest/mapper/form/ImportMapperAddInput.java b/openbas-api/src/main/java/io/openbas/rest/mapper/form/ImportMapperAddInput.java index 72c9b9e560..4bff96888e 100644 --- a/openbas-api/src/main/java/io/openbas/rest/mapper/form/ImportMapperAddInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/mapper/form/ImportMapperAddInput.java @@ -15,15 +15,15 @@ public class ImportMapperAddInput { @NotBlank(message = MANDATORY_MESSAGE) - @JsonProperty("mapper_name") + @JsonProperty("import_mapper_name") private String name; @Pattern(regexp="^[A-Z]{1,2}$") - @JsonProperty("mapper_inject_type_column") + @JsonProperty("import_mapper_inject_type_column") @NotBlank private String injectTypeColumn; - @JsonProperty("mapper_inject_importers") + @JsonProperty("import_mapper_inject_importers") @NotNull private List importers = new ArrayList<>(); diff --git a/openbas-api/src/main/java/io/openbas/rest/mapper/form/ImportMapperUpdateInput.java b/openbas-api/src/main/java/io/openbas/rest/mapper/form/ImportMapperUpdateInput.java index 1a7dfe4d37..5d7461a0b9 100644 --- a/openbas-api/src/main/java/io/openbas/rest/mapper/form/ImportMapperUpdateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/mapper/form/ImportMapperUpdateInput.java @@ -15,15 +15,15 @@ public class ImportMapperUpdateInput { @NotBlank(message = MANDATORY_MESSAGE) - @JsonProperty("mapper_name") + @JsonProperty("import_mapper_name") private String name; @Pattern(regexp="^[A-Z]{1,2}$") - @JsonProperty("mapper_inject_type_column") + @JsonProperty("import_mapper_inject_type_column") @NotBlank private String injectTypeColumn; - @JsonProperty("mapper_inject_importers") + @JsonProperty("import_mapper_inject_importers") @NotNull private List importers = new ArrayList<>(); } diff --git a/openbas-api/src/main/java/io/openbas/rest/mapper/form/InjectImporterAddInput.java b/openbas-api/src/main/java/io/openbas/rest/mapper/form/InjectImporterAddInput.java index 4adb2814be..496acfba33 100644 --- a/openbas-api/src/main/java/io/openbas/rest/mapper/form/InjectImporterAddInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/mapper/form/InjectImporterAddInput.java @@ -17,7 +17,7 @@ public class InjectImporterAddInput { private String injectTypeValue; @NotBlank(message = MANDATORY_MESSAGE) - @JsonProperty("inject_importer_injector_contract_id") + @JsonProperty("inject_importer_injector_contract") private String injectorContractId; @JsonProperty("inject_importer_rule_attributes") diff --git a/openbas-api/src/main/java/io/openbas/rest/mapper/form/InjectImporterUpdateInput.java b/openbas-api/src/main/java/io/openbas/rest/mapper/form/InjectImporterUpdateInput.java index e5871037ac..1c7301e078 100644 --- a/openbas-api/src/main/java/io/openbas/rest/mapper/form/InjectImporterUpdateInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/mapper/form/InjectImporterUpdateInput.java @@ -20,7 +20,7 @@ public class InjectImporterUpdateInput { private String injectTypeValue; @NotBlank(message = MANDATORY_MESSAGE) - @JsonProperty("inject_importer_injector_contract_id") + @JsonProperty("inject_importer_injector_contract") private String injectorContractId; @JsonProperty("inject_importer_rule_attributes") diff --git a/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioImportApi.java b/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioImportApi.java index 3288c6f2ae..21318286fc 100644 --- a/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioImportApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioImportApi.java @@ -61,6 +61,10 @@ public ImportTestSummary validateImportXLSFile(@PathVariable @NotBlank final Str @Valid @RequestBody final InjectsImportInput input) { Scenario scenario = scenarioService.scenario(scenarioId); + if(input.getLaunchDate() != null) { + scenario.setRecurrenceStart(input.getLaunchDate().toInstant()); + } + // Getting the mapper to use ImportMapper importMapper = importMapperRepository .findById(UUID.fromString(input.getImportMapperId())) diff --git a/openbas-api/src/main/java/io/openbas/rest/scenario/form/InjectsImportInput.java b/openbas-api/src/main/java/io/openbas/rest/scenario/form/InjectsImportInput.java index 21d5af24cb..9376a10be1 100644 --- a/openbas-api/src/main/java/io/openbas/rest/scenario/form/InjectsImportInput.java +++ b/openbas-api/src/main/java/io/openbas/rest/scenario/form/InjectsImportInput.java @@ -5,6 +5,8 @@ import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.util.Date; + import static io.openbas.config.AppConfig.MANDATORY_MESSAGE; @Data @@ -21,4 +23,7 @@ public class InjectsImportInput { @NotNull(message = MANDATORY_MESSAGE) @JsonProperty("timezone_offset") private Integer timezoneOffset; + + @JsonProperty("launch_date") + private Date launchDate = null; } diff --git a/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java b/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java index 99f22edb1d..e5b5d9593c 100644 --- a/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java +++ b/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java @@ -79,8 +79,8 @@ public class AtomicTestingService { private EntityManager entityManager; public InjectResultDTO findById(String injectId) { - Optional statusById = injectRepository.findWithStatusById(injectId); - return statusById + Optional inject = injectRepository.findWithStatusById(injectId); + return inject .map(AtomicTestingMapper::toDtoWithTargetResults) .orElseThrow(ElementNotFoundException::new); } diff --git a/openbas-api/src/main/java/io/openbas/service/ChallengeService.java b/openbas-api/src/main/java/io/openbas/service/ChallengeService.java index 15d6f81fdc..8400f5c24d 100644 --- a/openbas-api/src/main/java/io/openbas/service/ChallengeService.java +++ b/openbas-api/src/main/java/io/openbas/service/ChallengeService.java @@ -2,91 +2,186 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.openbas.database.model.Challenge; -import io.openbas.database.model.Exercise; -import io.openbas.database.model.Inject; -import io.openbas.database.model.Scenario; +import io.openbas.database.model.*; import io.openbas.database.repository.ChallengeRepository; import io.openbas.database.repository.ExerciseRepository; +import io.openbas.database.repository.InjectExpectationRepository; import io.openbas.database.repository.InjectRepository; import io.openbas.injectors.challenge.model.ChallengeContent; +import io.openbas.rest.challenge.form.ChallengeTryInput; +import io.openbas.rest.challenge.response.ChallengeInformation; +import io.openbas.rest.challenge.response.ChallengeResult; +import io.openbas.rest.challenge.response.ChallengesReader; +import io.openbas.rest.exception.ElementNotFoundException; +import io.openbas.utils.ExpectationUtils; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; +import java.time.Instant; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import java.util.stream.Stream; import static io.openbas.helper.StreamHelper.fromIterable; import static io.openbas.injectors.challenge.ChallengeContract.CHALLENGE_PUBLISH; @Service +@AllArgsConstructor public class ChallengeService { - @Resource - protected ObjectMapper mapper; - - private ExerciseRepository exerciseRepository; - private ChallengeRepository challengeRepository; - private InjectRepository injectRepository; - - @Autowired - public void setInjectRepository(InjectRepository injectRepository) { - this.injectRepository = injectRepository; - } - - @Autowired - public void setChallengeRepository(ChallengeRepository challengeRepository) { - this.challengeRepository = challengeRepository; - } - - @Autowired - public void setExerciseRepository(ExerciseRepository exerciseRepository) { - this.exerciseRepository = exerciseRepository; - } - - public Challenge enrichChallengeWithExercisesOrScenarios(@NotNull Challenge challenge) { - List injects = fromIterable(this.injectRepository.findAllForChallengeId("%" + challenge.getId() + "%")); - List exerciseIds = injects.stream().filter(i -> i.getExercise() != null).map(i -> i.getExercise().getId()).distinct().toList(); - challenge.setExerciseIds(exerciseIds); - List scenarioIds = injects.stream().filter(i -> i.getScenario() != null).map(i -> i.getScenario().getId()).distinct().toList(); - challenge.setScenarioIds(scenarioIds); - return challenge; - } - - public Iterable getExerciseChallenges(@NotBlank final String exerciseId) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(); - return resolveChallenges(exercise.getInjects()) - .map(this::enrichChallengeWithExercisesOrScenarios) - .toList(); - } - - public Iterable getScenarioChallenges(@NotNull final Scenario scenario) { - return resolveChallenges(scenario.getInjects()) - .map(this::enrichChallengeWithExercisesOrScenarios) - .toList(); - } - - // -- PRIVATE -- - private Stream resolveChallenges(@NotNull final List injects) { - List challenges = injects.stream() - .filter(inject -> inject.getInjectorContract() - .map(contract -> contract.getId().equals(CHALLENGE_PUBLISH)) - .orElse(false)) - .filter(inject -> inject.getContent() != null) - .flatMap(inject -> { - try { - ChallengeContent content = mapper.treeToValue(inject.getContent(), ChallengeContent.class); - return content.getChallenges().stream(); - } catch (JsonProcessingException e) { - return Stream.empty(); - } - }) - .distinct() - .toList(); - - return fromIterable(this.challengeRepository.findAllById(challenges)).stream(); - } + @Resource + protected ObjectMapper mapper; + + private ExerciseRepository exerciseRepository; + private ChallengeRepository challengeRepository; + private InjectRepository injectRepository; + private InjectExpectationRepository injectExpectationRepository; + + public Challenge enrichChallengeWithExercisesOrScenarios(@NotNull Challenge challenge) { + List injects = fromIterable(this.injectRepository.findAllForChallengeId("%" + challenge.getId() + "%")); + List exerciseIds = injects.stream().filter(i -> i.getExercise() != null).map(i -> i.getExercise().getId()).distinct().toList(); + challenge.setExerciseIds(exerciseIds); + List scenarioIds = injects.stream().filter(i -> i.getScenario() != null).map(i -> i.getScenario().getId()).distinct().toList(); + challenge.setScenarioIds(scenarioIds); + return challenge; + } + + public Iterable getExerciseChallenges(@NotBlank final String exerciseId) { + Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(); + return resolveChallenges(exercise.getInjects()) + .map(this::enrichChallengeWithExercisesOrScenarios) + .toList(); + } + + public Iterable getScenarioChallenges(@NotNull final Scenario scenario) { + return resolveChallenges(scenario.getInjects()) + .map(this::enrichChallengeWithExercisesOrScenarios) + .toList(); + } + + + public ChallengeResult tryChallenge(String challengeId, ChallengeTryInput input) { + Challenge challenge = challengeRepository.findById(challengeId).orElseThrow(ElementNotFoundException::new); + for (ChallengeFlag flag : challenge.getFlags()) { + if (checkFlag(flag, input.getValue())) { + return new ChallengeResult(true); + } + } + return new ChallengeResult(false); + } + + public ChallengesReader playerChallenges(String exerciseId, User user) { + Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + ChallengesReader reader = new ChallengesReader(exercise); + List challengeExpectations = injectExpectationRepository.findChallengeExpectationsByExerciseAndUser(exerciseId, user.getId()); + + // Filter expectations by unique challenge + Set seenChallenges = new HashSet<>(); + List distinctExpectations = new ArrayList<>(); + + for (InjectExpectation expectation : challengeExpectations) { + String challengeId = expectation.getChallenge().getId(); + if (!seenChallenges.contains(challengeId)) { + seenChallenges.add(challengeId); + distinctExpectations.add(expectation); + } + } + + List challenges = distinctExpectations.stream() + .map(injectExpectation -> { + Challenge challenge = injectExpectation.getChallenge(); + challenge.setVirtualPublication(injectExpectation.getCreatedAt()); + return new ChallengeInformation(challenge, injectExpectation); + }) + .sorted(Comparator.comparing(o -> o.getChallenge().getVirtualPublication())) + .toList(); + reader.setExerciseChallenges(challenges); + return reader; + } + + public ChallengesReader validateChallenge(String exerciseId, + String challengeId, + ChallengeTryInput input, + User user) { + ChallengeResult challengeResult = tryChallenge(challengeId, input); + if (challengeResult.getResult()) { + // Find and update the expectations linked to the user + List playerExpectations = injectExpectationRepository.findByUserAndExerciseAndChallenge(user.getId(), exerciseId, challengeId); + playerExpectations.forEach(playerExpectation -> { + playerExpectation.setScore(playerExpectation.getExpectedScore()); + InjectExpectationResult expectationResult = InjectExpectationResult.builder() + .sourceId("challenge") + .sourceType("challenge") + .sourceName("Challenge validation") + .result(Instant.now().toString()) + .date(Instant.now().toString()) + .score(playerExpectation.getExpectedScore()) + .build(); + playerExpectation.getResults().add(expectationResult); + playerExpectation.setUpdatedAt(Instant.now()); + injectExpectationRepository.save(playerExpectation); + }); + + // -- VALIDATION TYPE -- + processByValidationType(exerciseId, challengeId, user, true); + } + return playerChallenges(exerciseId, user); + } + + private void processByValidationType(String exerciseId, String challengeId, User user, boolean isaNewExpectationResult) { + // Process expectations linked to teams where the user is a member + List teamIds = user.getTeams().stream().map(Team::getId).toList(); + // Find all expectations for this exercise, challenge and teams + List challengeExpectations = injectExpectationRepository.findChallengeExpectations(exerciseId, teamIds, challengeId); + // If user is null then expectation is from a team + List parentExpectations = challengeExpectations.stream().filter(exp -> exp.getUser() == null).toList(); + // If user is not null then expectation is from a player + Map> playerByTeam = challengeExpectations.stream().filter(exp -> exp.getUser() != null).collect(Collectors.groupingBy(InjectExpectation::getTeam)); + + // Depending on type of validation, We process the parent expectations: + List toUpdate = ExpectationUtils.processByValidationType(isaNewExpectationResult, challengeExpectations, parentExpectations, playerByTeam); + injectExpectationRepository.saveAll(toUpdate); + } + + // -- PRIVATE -- + private Stream resolveChallenges(@NotNull final List injects) { + List challenges = injects.stream() + .filter(inject -> inject.getInjectorContract() + .map(contract -> contract.getId().equals(CHALLENGE_PUBLISH)) + .orElse(false)) + .filter(inject -> inject.getContent() != null) + .flatMap(inject -> { + try { + ChallengeContent content = mapper.treeToValue(inject.getContent(), ChallengeContent.class); + return content.getChallenges().stream(); + } catch (JsonProcessingException e) { + return Stream.empty(); + } + }) + .distinct() + .toList(); + + return fromIterable(this.challengeRepository.findAllById(challenges)).stream(); + } + + private boolean checkFlag(ChallengeFlag flag, String value) { + switch (flag.getType()) { + case VALUE -> { + return value.equalsIgnoreCase(flag.getValue()); + } + case VALUE_CASE -> { + return value.equals(flag.getValue()); + } + case REGEXP -> { + return Pattern.compile(flag.getValue()).matcher(value).matches(); + } + default -> { + return false; + } + } + } } diff --git a/openbas-api/src/main/java/io/openbas/service/ChannelService.java b/openbas-api/src/main/java/io/openbas/service/ChannelService.java new file mode 100644 index 0000000000..3b3e2b6e4f --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/service/ChannelService.java @@ -0,0 +1,129 @@ +package io.openbas.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.openbas.database.model.*; +import io.openbas.database.repository.ArticleRepository; +import io.openbas.database.repository.ChannelRepository; +import io.openbas.database.repository.ExerciseRepository; +import io.openbas.database.repository.InjectExpectationRepository; +import io.openbas.injectors.channel.model.ChannelContent; +import io.openbas.rest.channel.model.VirtualArticle; +import io.openbas.rest.channel.response.ChannelReader; +import io.openbas.rest.exception.ElementNotFoundException; +import io.openbas.utils.ExpectationUtils; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +import static io.openbas.helper.StreamHelper.fromIterable; +import static io.openbas.injectors.channel.ChannelContract.CHANNEL_PUBLISH; + +@Service +@AllArgsConstructor +public class ChannelService { + + @Resource + protected ObjectMapper mapper; + + private InjectExpectationRepository injectExpectationExecutionRepository; + private ExerciseRepository exerciseRepository; + private ScenarioService scenarioService; + private ArticleRepository articleRepository; + private ChannelRepository channelRepository; + + + public ChannelReader validateArticles(String exerciseId, String channelId, User user) { + ChannelReader channelReader; + Channel channel = channelRepository.findById(channelId).orElseThrow(ElementNotFoundException::new); + List injects; + + Optional exerciseOpt = exerciseRepository.findById(exerciseId); + if (exerciseOpt.isPresent()) { + Exercise exercise = exerciseOpt.get(); + channelReader = new ChannelReader(channel, exercise); + injects = exercise.getInjects(); + } else { + Scenario scenario = this.scenarioService.scenario(exerciseId); + channelReader = new ChannelReader(channel, scenario); + injects = scenario.getInjects(); + } + + Map toPublishArticleIdsMap = injects.stream() + .filter(inject -> inject.getInjectorContract() + .map(contract -> contract.getId().equals(CHANNEL_PUBLISH)) + .orElse(false)) + .filter(inject -> inject.getStatus().isPresent()) + .sorted(Comparator.comparing(inject -> inject.getStatus().get().getTrackingSentDate())) + .flatMap(inject -> { + Instant virtualInjectDate = inject.getStatus().get().getTrackingSentDate(); + try { + ChannelContent content = mapper.treeToValue(inject.getContent(), ChannelContent.class); + if (content.getArticles() != null) { + return content.getArticles().stream().map(article -> new VirtualArticle(virtualInjectDate, article)); + } + return null; + } catch (JsonProcessingException e) { + // Invalid channel content. + return null; + } + }) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toMap(VirtualArticle::id, VirtualArticle::date)); + if (!toPublishArticleIdsMap.isEmpty()) { + List
publishedArticles = fromIterable(articleRepository.findAllById(toPublishArticleIdsMap.keySet())) + .stream().filter(article -> article.getChannel().equals(channel)) + .peek(article -> article.setVirtualPublication(toPublishArticleIdsMap.get(article.getId()))) + .sorted(Comparator.comparing(Article::getVirtualPublication).reversed()) + .toList(); + channelReader.setChannelArticles(publishedArticles); + // Fulfill article expectations + List finalInjects = injects; + List expectationExecutions = publishedArticles.stream() + .flatMap(article -> finalInjects.stream() + .flatMap(inject -> inject.getUserExpectationsForArticle(user, article).stream())) + .filter(exec -> exec.getResults().isEmpty()).toList(); + + // Update all expectations linked to player + expectationExecutions.forEach(injectExpectationExecution -> { + injectExpectationExecution.setResults(List.of( + InjectExpectationResult.builder() + .sourceId("media-pressure") + .sourceType("media-pressure") + .sourceName("Media pressure read") + .result(Instant.now().toString()) + .date(Instant.now().toString()) + .score(injectExpectationExecution.getExpectedScore()) + .build() + )); + injectExpectationExecution.setScore(injectExpectationExecution.getExpectedScore()); + injectExpectationExecution.setUpdatedAt(Instant.now()); + injectExpectationExecutionRepository.save(injectExpectationExecution); + }); + + // -- VALIDATION TYPE -- + processByValidationType(user, injects, publishedArticles, expectationExecutions.size()>0); + } + return channelReader; + } + + private void processByValidationType(User user, List injects, List
publishedArticles, boolean isaNewExpectationResult) { + // Process expectation linked to teams where user if part of + List injectIds = injects.stream().map(Inject::getId).toList(); + List teamIds = user.getTeams().stream().map(Team::getId).toList(); + List articleIds = publishedArticles.stream().map(Article::getId).toList(); //Articles with the same channel + // Find all expectations linked to teams' user, channel and exercise + List channelExpectations = injectExpectationExecutionRepository.findChannelExpectations(injectIds, teamIds, articleIds); + List parentExpectations = channelExpectations.stream().filter(exp -> exp.getUser() == null).toList(); + Map> playerByTeam = channelExpectations.stream().filter(exp -> exp.getUser() != null).collect(Collectors.groupingBy(InjectExpectation::getTeam)); + + // Depending on type of validation, we process the parent expectations: + List toUpdate = ExpectationUtils.processByValidationType(isaNewExpectationResult, channelExpectations, parentExpectations, playerByTeam); + injectExpectationExecutionRepository.saveAll(toUpdate); + } +} diff --git a/openbas-api/src/main/java/io/openbas/service/ExerciseExpectationService.java b/openbas-api/src/main/java/io/openbas/service/ExerciseExpectationService.java index e3e9f82e08..416c8cb7b5 100644 --- a/openbas-api/src/main/java/io/openbas/service/ExerciseExpectationService.java +++ b/openbas-api/src/main/java/io/openbas/service/ExerciseExpectationService.java @@ -6,16 +6,15 @@ import io.openbas.database.model.InjectExpectationResult; import io.openbas.database.repository.ExerciseRepository; import io.openbas.database.repository.InjectExpectationRepository; +import io.openbas.rest.exception.ElementNotFoundException; import io.openbas.rest.exercise.form.ExpectationUpdateInput; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.time.Instant; +import java.util.*; import static java.time.Instant.now; @@ -42,13 +41,19 @@ public InjectExpectation updateInjectExpectation( String result = ""; if (injectExpectation.getType() == EXPECTATION_TYPE.MANUAL) { - if (input.getScore() >= injectExpectation.getExpectedScore()) { - result = "Success"; - } else if (input.getScore() > 0) { - result = "Partial"; + if (injectExpectation.getTeam() != null && injectExpectation.getUser() == null) { //If it is a team expectation + result = input.getScore() > 0 ? "Success" : "Failed"; } else { - result = "Failed"; + if (input.getScore() >= injectExpectation.getExpectedScore()) { + result = "Success"; + } else if (input.getScore() > 0) { + result = "Partial"; + } else { + result = "Failed"; + } } + injectExpectation.getResults().clear(); + exists = Optional.empty(); } else if (injectExpectation.getType() == EXPECTATION_TYPE.DETECTION) { if (input.getScore() >= injectExpectation.getExpectedScore()) { result = "Detected"; @@ -91,7 +96,13 @@ public InjectExpectation updateInjectExpectation( } } injectExpectation.setUpdatedAt(now()); - return this.injectExpectationRepository.save(injectExpectation); + InjectExpectation updated = this.injectExpectationRepository.save(injectExpectation); + + // If The expectation is type manual, We should update expectations for teams and players + if (updated.getType() == EXPECTATION_TYPE.MANUAL && updated.getTeam() != null) { + computeExpectationsForTeamsAndPlayer(updated, result); + } + return updated; } public InjectExpectation deleteInjectExpectationResult( @@ -109,11 +120,94 @@ public InjectExpectation deleteInjectExpectationResult( if (injectExpectation.getType() == EXPECTATION_TYPE.MANUAL) { injectExpectation.setScore(null); } else { - List scores = injectExpectation.getResults().stream().map(InjectExpectationResult::getScore).filter(Objects::nonNull).toList(); - injectExpectation.setScore(!scores.isEmpty() ? Collections.max(scores) : 0); + List scores = injectExpectation.getResults().stream().map(InjectExpectationResult::getScore).filter(Objects::nonNull).toList(); + injectExpectation.setScore(!scores.isEmpty() ? Collections.max(scores) : 0.0); } } injectExpectation.setUpdatedAt(now()); - return this.injectExpectationRepository.save(injectExpectation); + InjectExpectation updated = this.injectExpectationRepository.save(injectExpectation); + + // If The expectation is type manual, We should update expectations for teams and players + if (updated.getType() == EXPECTATION_TYPE.MANUAL && updated.getTeam() != null) { + computeExpectationsForTeamsAndPlayer(updated, null); + } + + return updated; + } + + // -- VALIDATION TYPE -- + + private void computeExpectationsForTeamsAndPlayer(InjectExpectation updated, String result) { + //If the updated expectation was a player expectation, We have to update the team expectation using player expectations (based on validation type) + if (updated.getUser() != null) { + List toProcess = injectExpectationRepository.findAllByInjectAndTeamAndExpectationName(updated.getInject().getId(), updated.getTeam().getId(), updated.getName()); + InjectExpectation parentExpectation = toProcess.stream().filter(exp -> exp.getUser() == null).findFirst().orElseThrow(ElementNotFoundException::new); + int playersSize = toProcess.size() - 1; // Without Parent expectation + long zeroPlayerResponses = toProcess.stream().filter(exp -> exp.getUser() != null).filter(exp -> exp.getScore() != null).filter(exp -> exp.getScore() == 0.0).count(); + long nullPlayerResponses = toProcess.stream().filter(exp -> exp.getUser() != null).filter(exp -> exp.getScore() == null).count(); + + if (updated.isExpectationGroup()) { //If true is at least one + OptionalDouble avgAtLeastOnePlayer = toProcess.stream().filter(exp -> exp.getUser() != null).filter(exp -> exp.getScore() != null).filter(exp -> exp.getScore() > 0.0).mapToDouble(InjectExpectation::getScore).average(); + if (avgAtLeastOnePlayer.isPresent()) { //Any response is positive + parentExpectation.setScore(avgAtLeastOnePlayer.getAsDouble()); + result = "Success"; + } else { + if (zeroPlayerResponses == playersSize) { //All players had failed + parentExpectation.setScore(0.0); + result = "Failed"; + } else { + parentExpectation.setScore(null); + result = "Pending"; + } + } + } else { // all + if(nullPlayerResponses == 0){ + OptionalDouble avgAllPlayer = toProcess.stream().filter(exp -> exp.getUser() != null).mapToDouble(InjectExpectation::getScore).average(); + parentExpectation.setScore(avgAllPlayer.getAsDouble()); + result = zeroPlayerResponses > 0 ? "Failed" : "Success"; + }else{ + if(zeroPlayerResponses == 0) { + parentExpectation.setScore(null); + result = "Pending"; + }else{ + double sumAllPlayer = toProcess.stream().filter(exp -> exp.getUser() != null).filter(exp->exp.getScore() != null).mapToDouble(InjectExpectation::getScore).sum(); + parentExpectation.setScore(sumAllPlayer/playersSize); + result = "Failed"; + } + } + } + parentExpectation.setUpdatedAt(Instant.now()); + parentExpectation.getResults().clear(); + InjectExpectationResult expectationResult = InjectExpectationResult.builder() + .sourceId("player-manual-validation") + .sourceType("player-manual-validation") + .sourceName("Player Manual Validation") + .result(result) + .date(now().toString()) + .score(parentExpectation.getScore()) + .build(); + parentExpectation.getResults().add(expectationResult); + injectExpectationRepository.save(parentExpectation); + } else { + // If I update the expectation team: What happens with children? -> update expectation score for all children -> set score from InjectExpectation + List toProcess = injectExpectationRepository.findAllByInjectAndTeamAndExpectationNameAndUserIsNotNull(updated.getInject().getId(), updated.getTeam().getId(), updated.getName()); + for (InjectExpectation expectation : toProcess) { + expectation.setScore(updated.getScore()); + expectation.setUpdatedAt(Instant.now()); + expectation.getResults().clear(); + if(result != null) { + InjectExpectationResult expectationResult = InjectExpectationResult.builder() + .sourceId("team-manual-validation") + .sourceType("team-manual-validation") + .sourceName("Team Manual Validation") + .result(result) + .date(now().toString()) + .score(updated.getScore()) + .build(); + expectation.getResults().add(expectationResult); + } + injectExpectationRepository.save(expectation); + } + } } } diff --git a/openbas-api/src/main/java/io/openbas/service/InjectService.java b/openbas-api/src/main/java/io/openbas/service/InjectService.java index f146f2d1ca..8a068e67bf 100644 --- a/openbas-api/src/main/java/io/openbas/service/InjectService.java +++ b/openbas-api/src/main/java/io/openbas/service/InjectService.java @@ -728,10 +728,10 @@ private List addFields(Inject inject, RuleAttribute ruleAttribute Double columnValueExpectation = columns.stream() .map(column -> getValueAsDouble(row, column)) .reduce(0.0, Double::sum); - expectation.get().setExpectedScore(columnValueExpectation.intValue()); + expectation.get().setExpectedScore(columnValueExpectation.doubleValue()); } else { try { - expectation.get().setExpectedScore(Integer.parseInt(ruleAttribute.getDefaultValue())); + expectation.get().setExpectedScore(Double.parseDouble(ruleAttribute.getDefaultValue())); } catch (NumberFormatException exception) { List importMessages = new ArrayList<>(); importMessages.add(new ImportMessage(ImportMessage.MessageLevel.WARN, @@ -743,7 +743,7 @@ private List addFields(Inject inject, RuleAttribute ruleAttribute } } } else { - expectation.get().setExpectedScore(Integer.parseInt(ruleAttribute.getDefaultValue())); + expectation.get().setExpectedScore(Double.parseDouble(ruleAttribute.getDefaultValue())); } } else if ("name".equals(ruleAttribute.getName().split("_")[1])) { if(ruleAttribute.getColumns() != null) { diff --git a/openbas-api/src/main/java/io/openbas/service/InjectTestStatusService.java b/openbas-api/src/main/java/io/openbas/service/InjectTestStatusService.java new file mode 100644 index 0000000000..7d034558c8 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/service/InjectTestStatusService.java @@ -0,0 +1,79 @@ +package io.openbas.service; + +import io.openbas.database.model.*; +import io.openbas.database.repository.InjectRepository; +import io.openbas.database.repository.InjectTestStatusRepository; +import io.openbas.database.repository.UserRepository; +import io.openbas.execution.ExecutableInject; +import io.openbas.execution.ExecutionContext; +import io.openbas.execution.ExecutionContextService; +import io.openbas.execution.Injector; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static io.openbas.config.SessionHelper.currentUser; + +@Service +@Log +@RequiredArgsConstructor +public class InjectTestStatusService { + + private ApplicationContext context; + private final UserRepository userRepository; + private final InjectRepository injectRepository; + private final ExecutionContextService executionContextService; + private final InjectTestStatusRepository injectTestStatusRepository; + + @Autowired + public void setContext(ApplicationContext context) { + this.context = context; + } + + @Transactional + public InjectTestStatus testInject(String injectId) { + Inject inject = injectRepository.findById(injectId).orElseThrow(); + User user = this.userRepository.findById(currentUser().getId()).orElseThrow(); + List userInjectContexts = List.of( + this.executionContextService.executionContext(user, inject, "Direct test") + ); + Injector executor = context.getBean( + inject.getInjectorContract().map(injectorContract -> injectorContract.getInjector().getType()).orElseThrow(), + io.openbas.execution.Injector.class); + ExecutableInject injection = new ExecutableInject(false, true, inject, List.of(), inject.getAssets(), + inject.getAssetGroups(), userInjectContexts); + Execution execution = executor.executeInjection(injection); + + //Save inject test status + Optional injectTestStatus = this.injectTestStatusRepository.findByInject(inject); + InjectTestStatus injectTestStatusToSave = InjectTestStatus.fromExecutionTest(execution); + injectTestStatus.ifPresent(testStatus -> { + injectTestStatusToSave.setId(testStatus.getId()); + injectTestStatusToSave.setTestCreationDate(testStatus.getTestCreationDate()); + }); + injectTestStatusToSave.setInject(inject); + this.injectTestStatusRepository.save(injectTestStatusToSave); + + return injectTestStatusToSave; + } + + public List findAllInjectTestsByExerciseId(String exerciseId) { + return injectTestStatusRepository.findAllExerciseInjectTests(exerciseId); + } + + public List findAllInjectTestsByScenarioId(String scenarioId) { + return injectTestStatusRepository.findAllScenarioInjectTests(scenarioId); + } + + public InjectTestStatus findInjectTestStatusById(String testId) { + return injectTestStatusRepository.findById(testId).orElseThrow(); + } + +} diff --git a/openbas-api/src/main/java/io/openbas/service/MapperService.java b/openbas-api/src/main/java/io/openbas/service/MapperService.java index dc5f593b1c..8e31f2291c 100644 --- a/openbas-api/src/main/java/io/openbas/service/MapperService.java +++ b/openbas-api/src/main/java/io/openbas/service/MapperService.java @@ -1,14 +1,19 @@ package io.openbas.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import io.openbas.database.model.ImportMapper; import io.openbas.database.model.InjectImporter; import io.openbas.database.model.InjectorContract; import io.openbas.database.model.RuleAttribute; import io.openbas.database.repository.ImportMapperRepository; import io.openbas.database.repository.InjectorContractRepository; +import io.openbas.helper.ObjectMapperHelper; import io.openbas.rest.exception.ElementNotFoundException; +import io.openbas.rest.mapper.export.MapperExportMixins; import io.openbas.rest.mapper.form.*; import lombok.RequiredArgsConstructor; +import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Service; import java.time.Instant; @@ -20,6 +25,8 @@ import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import static java.util.stream.StreamSupport.stream; + @RequiredArgsConstructor @Service public class MapperService { @@ -101,7 +108,7 @@ public ImportMapper updateImportMapper(String mapperId, ImportMapperUpdateInput * @return The map of injector contracts by ids */ private Map getMapOfInjectorContracts(List ids) { - return StreamSupport.stream(injectorContractRepository.findAllById(ids).spliterator(), false) + return stream(injectorContractRepository.findAllById(ids).spliterator(), false) .collect(Collectors.toMap(InjectorContract::getId, Function.identity())); } @@ -176,4 +183,25 @@ private void updateInjectImporter(List injectImporter }); } + public String exportMappers(@NotNull final List idsToExport) throws JsonProcessingException { + ObjectMapper objectMapper = ObjectMapperHelper.openBASJsonMapper(); + List mappersList = StreamSupport.stream( + importMapperRepository.findAllById(idsToExport.stream().map(UUID::fromString).toList()).spliterator(), false + ).toList(); + + objectMapper.addMixIn(ImportMapper.class, MapperExportMixins.ImportMapper.class); + objectMapper.addMixIn(InjectImporter.class, MapperExportMixins.InjectImporter.class); + objectMapper.addMixIn(RuleAttribute.class, MapperExportMixins.RuleAttribute.class); + + return objectMapper.writeValueAsString(mappersList); + } + + public void importMappers(List mappers) { + importMapperRepository.saveAll( + mappers.stream() + .map(this::createImportMapper) + .peek((m) -> m.setName(m.getName() + " (Import)")) + .toList() + ); + } } diff --git a/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java b/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java index ca214705e6..4e67a33966 100644 --- a/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java +++ b/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java @@ -20,9 +20,9 @@ public class AtomicTestingUtils { public static List getTargets( - final List teams, - final List assets, - final List assetGroups) { + final List teams, + final List assets, + final List assetGroups) { List targets = new ArrayList<>(); targets.addAll(teams .stream() @@ -30,7 +30,7 @@ public static List getTargets( .toList()); targets.addAll(assets .stream() - .map(t -> new InjectTargetWithResult(TargetType.ASSETS, t.getId(), t.getName(), List.of(), Objects.equals(t.getType(), "Endpoint") ? ((Endpoint) Hibernate.unproxy(t)).getPlatform(): null)) + .map(t -> new InjectTargetWithResult(TargetType.ASSETS, t.getId(), t.getName(), List.of(), Objects.equals(t.getType(), "Endpoint") ? ((Endpoint) Hibernate.unproxy(t)).getPlatform() : null)) .toList()); targets.addAll(assetGroups .stream() @@ -41,22 +41,22 @@ public static List getTargets( } public static List getTargetsFromRaw( - final List teams, - final List assets, - final List assetGroups) { + final List teams, + final List assets, + final List assetGroups) { List targets = new ArrayList<>(); targets.addAll(teams - .stream() - .map(t -> new InjectTargetWithResult(TargetType.TEAMS, t.getTeam_id(), t.getTeam_name(), List.of(), null)) - .toList()); + .stream() + .map(t -> new InjectTargetWithResult(TargetType.TEAMS, t.getTeam_id(), t.getTeam_name(), List.of(), null)) + .toList()); targets.addAll(assets - .stream() - .map(t -> new InjectTargetWithResult(TargetType.ASSETS, t.getAsset_id(), t.getAsset_name(), List.of(), Objects.equals(t.getAsset_type(), "Endpoint") ? Endpoint.PLATFORM_TYPE.valueOf(t.getEndpoint_platform()): null)) - .toList()); + .stream() + .map(t -> new InjectTargetWithResult(TargetType.ASSETS, t.getAsset_id(), t.getAsset_name(), List.of(), Objects.equals(t.getAsset_type(), "Endpoint") ? Endpoint.PLATFORM_TYPE.valueOf(t.getEndpoint_platform()) : null)) + .toList()); targets.addAll(assetGroups - .stream() - .map(t -> new InjectTargetWithResult(TargetType.ASSETS_GROUPS, t.getAsset_group_id(), t.getAsset_group_name(), List.of(), null)) - .toList()); + .stream() + .map(t -> new InjectTargetWithResult(TargetType.ASSETS_GROUPS, t.getAsset_group_id(), t.getAsset_group_name(), List.of(), null)) + .toList()); return targets; } @@ -66,12 +66,17 @@ public static List getTargetsWithResults(final Inject in List expectations = inject.getExpectations(); List teamExpectations = new ArrayList<>(); + List playerExpectations = new ArrayList<>(); List assetExpectations = new ArrayList<>(); List assetGroupExpectations = new ArrayList<>(); expectations.forEach(expectation -> { if (expectation.getTeam() != null) { - teamExpectations.add(expectation); + if (expectation.getUser() != null) { + playerExpectations.add(expectation); + } else { + teamExpectations.add(expectation); + } } if (expectation.getAsset() != null) { assetExpectations.add(expectation); @@ -84,6 +89,13 @@ public static List getTargetsWithResults(final Inject in List targets = new ArrayList<>(); List assetsToRefine = new ArrayList<>(); + // Players + Map>> groupedByTeamAndUser = playerExpectations.stream() + .collect(Collectors.groupingBy( + InjectExpectation::getTeam, + Collectors.groupingBy(InjectExpectation::getUser) + )); + /* Match Target with expectations * */ inject.getTeams().forEach(team -> { @@ -97,7 +109,7 @@ public static List getTargetsWithResults(final Inject in team.getName(), defaultExpectationResultsByTypes, null - ); + ); targets.add(target); } }); @@ -171,7 +183,7 @@ public static List getTargetsWithResults(final Inject in ) ) .entrySet().stream() - .map(entry -> new InjectTargetWithResult(TargetType.TEAMS, entry.getKey().getId(), entry.getKey().getName(), entry.getValue(), null)) + .map(entry -> new InjectTargetWithResult(TargetType.TEAMS, entry.getKey().getId(), entry.getKey().getName(), entry.getValue(), playerExpectations.isEmpty() ? List.of() : calculateResultsforPlayers(groupedByTeamAndUser.get(entry.getKey())), null)) .toList() ); } @@ -267,6 +279,18 @@ public static List getTargetsWithResults(final Inject in return sortResults(targets); } + private static List calculateResultsforPlayers(Map> expectationsByUser) { + return expectationsByUser.entrySet().stream() + .map(userEntry -> new InjectTargetWithResult( + TargetType.PLAYER, + userEntry.getKey().getId(), + userEntry.getKey().getName(), + getExpectationResultByTypes(userEntry.getValue()), + null + )) + .toList(); + } + private static List sortResults(List targets) { return targets.stream().sorted(Comparator.comparing(InjectTargetWithResult::getName)).toList(); } @@ -327,6 +351,7 @@ public static List getRefinedExpectations(Inject inject, List .getExpectations() .stream() .filter(expectation -> targetIds.contains(expectation.getTargetId())) + .filter(expectation -> expectation.getUser() == null) // Filter expectations linked to players. For global results, We use Team expectations .toList(); } @@ -349,16 +374,32 @@ public static List getScores(final List types, final L .stream() .filter(e -> types.contains(e.getType())) .map(injectExpectation -> { - if( injectExpectation.getScore() == null ) { + if (injectExpectation.getScore() == null) { return null; } - if( injectExpectation.getScore() >= injectExpectation.getExpectedScore() ) { - return 1.0; - } - if( injectExpectation.getScore() == 0 ) { - return 0.0; + if (injectExpectation.getTeam() != null) { + if (injectExpectation.isExpectationGroup()) { + if (injectExpectation.getScore() > 0) { + return 1.0; + } else { + return 0.0; + } + } else { + if (injectExpectation.getScore() >= injectExpectation.getExpectedScore()) { + return 1.0; + } else { + return 0.0; + } + } + } else { + if (injectExpectation.getScore() >= injectExpectation.getExpectedScore()) { + return 1.0; + } + if (injectExpectation.getScore() == 0) { + return 0.0; + } + return 0.5; } - return 0.5; }) .toList(); } @@ -368,16 +409,32 @@ public static List getRawScores(final List types, fina .stream() .filter(e -> types.contains(EXPECTATION_TYPE.valueOf(e.getInject_expectation_type()))) .map(rawInjectExpectation -> { - if( rawInjectExpectation.getInject_expectation_score() == null ) { + if (rawInjectExpectation.getInject_expectation_score() == null) { return null; } - if( rawInjectExpectation.getInject_expectation_score() >= rawInjectExpectation.getInject_expectation_expected_score() ) { - return 1.0; - } - if( rawInjectExpectation.getInject_expectation_score() == 0 ) { - return 0.0; + if (rawInjectExpectation.getTeam_id() != null) { + if (rawInjectExpectation.getInject_expectation_group()) { + if (rawInjectExpectation.getInject_expectation_score() > 0) { + return 1.0; + } else { + return 0.0; + } + } else { + if (rawInjectExpectation.getInject_expectation_score() >= rawInjectExpectation.getInject_expectation_expected_score()) { + return 1.0; + } else { + return 0.0; + } + } + } else { + if (rawInjectExpectation.getInject_expectation_score() >= rawInjectExpectation.getInject_expectation_expected_score()) { + return 1.0; + } + if (rawInjectExpectation.getInject_expectation_score() == 0) { + return 0.0; + } + return 0.5; } - return 0.5; }) .toList(); } diff --git a/openbas-api/src/main/java/io/openbas/utils/ExpectationUtils.java b/openbas-api/src/main/java/io/openbas/utils/ExpectationUtils.java new file mode 100644 index 0000000000..d50d179840 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/utils/ExpectationUtils.java @@ -0,0 +1,74 @@ +package io.openbas.utils; + +import io.openbas.database.model.InjectExpectation; +import io.openbas.database.model.InjectExpectationResult; +import io.openbas.database.model.Team; +import io.openbas.rest.exception.ElementNotFoundException; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.OptionalDouble; + +public class ExpectationUtils { + + + public static List processByValidationType(boolean isaNewExpectationResult, List childrenExpectations, List parentExpectations, Map> playerByTeam) { + List updatedExpectations = new ArrayList<>(); + + childrenExpectations.stream().findAny().ifPresentOrElse(process -> { + boolean isValidationAtLeastOneTarget = process.isExpectationGroup(); + + parentExpectations.forEach(parentExpectation -> { + List toProcess = playerByTeam.get(parentExpectation.getTeam()); + int playersSize = toProcess.size(); // Without Parent expectation + long zeroPlayerResponses = toProcess.stream().filter(exp -> exp.getScore() != null).filter(exp -> exp.getScore() == 0.0).count(); + long nullPlayerResponses = toProcess.stream().filter(exp -> exp.getScore() == null).count(); + + if (isValidationAtLeastOneTarget) { // Type atLeast + OptionalDouble avgAtLeastOnePlayer = toProcess.stream().filter(exp -> exp.getScore() != null).filter(exp -> exp.getScore() > 0.0).mapToDouble(InjectExpectation::getScore).average(); + if (avgAtLeastOnePlayer.isPresent()) { //Any response is positive + parentExpectation.setScore(avgAtLeastOnePlayer.getAsDouble()); + } else { + if (zeroPlayerResponses == playersSize) { //All players had failed + parentExpectation.setScore(0.0); + } else { + parentExpectation.setScore(null); + } + } + } else { // type all + if(nullPlayerResponses == 0){ + OptionalDouble avgAllPlayer = toProcess.stream().mapToDouble(InjectExpectation::getScore).average(); + parentExpectation.setScore(avgAllPlayer.getAsDouble()); + }else{ + if(zeroPlayerResponses == 0) { + parentExpectation.setScore(null); + }else{ + double sumAllPlayer = toProcess.stream().filter(exp->exp.getScore() != null).mapToDouble(InjectExpectation::getScore).sum(); + parentExpectation.setScore(sumAllPlayer/playersSize); + } + } + } + + if(isaNewExpectationResult) { + InjectExpectationResult result = InjectExpectationResult.builder() + .sourceId("media-pressure") + .sourceType("media-pressure") + .sourceName("Media pressure read") + .result(Instant.now().toString()) + .date(Instant.now().toString()) + .score(process.getExpectedScore()) + .build(); + parentExpectation.getResults().add(result); + } + + parentExpectation.setUpdatedAt(Instant.now()); + updatedExpectations.add(parentExpectation); + }); + }, ElementNotFoundException::new); + + return updatedExpectations; + } + +} diff --git a/openbas-api/src/main/java/io/openbas/utils/ResultUtils.java b/openbas-api/src/main/java/io/openbas/utils/ResultUtils.java index b0a823e4e6..f6f2ec5951 100644 --- a/openbas-api/src/main/java/io/openbas/utils/ResultUtils.java +++ b/openbas-api/src/main/java/io/openbas/utils/ResultUtils.java @@ -26,6 +26,7 @@ public static List computeGlobalExpectationResults(@No List expectations = injects .stream() .flatMap(inject -> inject.getExpectations().stream()) + .filter(expectation -> expectation.getUser() == null) // Filter expectations linked to players .toList(); return AtomicTestingUtils.getExpectationResultByTypes(expectations); } diff --git a/openbas-api/src/test/java/io/openbas/injects/email/EmailExecutorTest.java b/openbas-api/src/test/java/io/openbas/injects/email/EmailExecutorTest.java index 528e00c696..d94dff667e 100644 --- a/openbas-api/src/test/java/io/openbas/injects/email/EmailExecutorTest.java +++ b/openbas-api/src/test/java/io/openbas/injects/email/EmailExecutorTest.java @@ -49,7 +49,7 @@ void process() throws Exception { content.setBody("A body"); Expectation expectation = new Expectation(); expectation.setName("The animation team can validate the audience reaction"); - expectation.setScore(10); + expectation.setScore(10.0); expectation.setType(InjectExpectation.EXPECTATION_TYPE.MANUAL); content.setExpectations(List.of(expectation)); Inject inject = new Inject(); diff --git a/openbas-api/src/test/java/io/openbas/rest/TeamApiTest.java b/openbas-api/src/test/java/io/openbas/rest/TeamApiTest.java index 89bed077c7..311f3f1024 100644 --- a/openbas-api/src/test/java/io/openbas/rest/TeamApiTest.java +++ b/openbas-api/src/test/java/io/openbas/rest/TeamApiTest.java @@ -130,7 +130,7 @@ void retrieveTeamsOnScenarioTest() throws Exception { void addPlayerOnTeamOnScenarioTest() throws Exception { // -- PREPARE -- User user = new User(); - user.setEmail("test@gmail.com"); + user.setEmail("testfiligran@gmail.com"); user = this.userRepository.save(user); USER_ID = user.getId(); ScenarioTeamPlayersEnableInput input = new ScenarioTeamPlayersEnableInput(); diff --git a/openbas-api/src/test/java/io/openbas/service/ExerciseExpectationServiceTest.java b/openbas-api/src/test/java/io/openbas/service/ExerciseExpectationServiceTest.java index 7c363485c2..baa025e6e7 100644 --- a/openbas-api/src/test/java/io/openbas/service/ExerciseExpectationServiceTest.java +++ b/openbas-api/src/test/java/io/openbas/service/ExerciseExpectationServiceTest.java @@ -75,7 +75,7 @@ void updateInjectExpectation() { // -- EXECUTE -- ExpectationUpdateInput input = new ExpectationUpdateInput(); - input.setScore(7); + input.setScore(7.0); InjectExpectation expectation = this.exerciseExpectationService.updateInjectExpectation(id, input); // -- ASSERT -- @@ -114,7 +114,7 @@ private void getInjectExpectation(Inject injectCreated, Team teamCreated, Exerci expectation.setTeam(teamCreated); expectation.setType(MANUAL); expectation.setName(EXPECTATION_NAME); - expectation.setExpectedScore(10); + expectation.setExpectedScore(10.0); expectation.setExercise(exerciseCreated); this.injectExpectationRepository.save(expectation); } diff --git a/openbas-api/src/test/java/io/openbas/service/InjectServiceTest.java b/openbas-api/src/test/java/io/openbas/service/InjectServiceTest.java index 2e4151ea1e..ef5e342fb4 100644 --- a/openbas-api/src/test/java/io/openbas/service/InjectServiceTest.java +++ b/openbas-api/src/test/java/io/openbas/service/InjectServiceTest.java @@ -159,10 +159,10 @@ void testImportXlsRelativeDate() throws IOException { verify(teamRepository, times(1)).save(any()); assertEquals(30 * 24 * 60 * 60, importTestSummary.getInjects().getLast().getDependsDuration()); - ObjectNode jsonNodeMail = (ObjectNode) mapper.readTree("{\"message\":\"message1\",\"expectations\":[{\"expectation_description\":\"expectation\",\"expectation_name\":\"expectation done\",\"expectation_score\":100,\"expectation_type\":\"MANUAL\",\"expectation_expectation_group\":false}]}"); + ObjectNode jsonNodeMail = (ObjectNode) mapper.readTree("{\"message\":\"message1\",\"expectations\":[{\"expectation_description\":\"expectation\",\"expectation_name\":\"expectation done\",\"expectation_score\":100.0,\"expectation_type\":\"MANUAL\",\"expectation_expectation_group\":false}]}"); assertEquals(jsonNodeMail, importTestSummary.getInjects().getFirst().getContent()); - ObjectNode jsonNodeSms = (ObjectNode) mapper.readTree("{\"subject\":\"subject\",\"body\":\"message2\",\"expectations\":[{\"expectation_description\":\"expectation\",\"expectation_name\":\"expectation done\",\"expectation_score\":100,\"expectation_type\":\"MANUAL\",\"expectation_expectation_group\":false}]}"); + ObjectNode jsonNodeSms = (ObjectNode) mapper.readTree("{\"subject\":\"subject\",\"body\":\"message2\",\"expectations\":[{\"expectation_description\":\"expectation\",\"expectation_name\":\"expectation done\",\"expectation_score\":100.0,\"expectation_type\":\"MANUAL\",\"expectation_expectation_group\":false}]}"); assertEquals(jsonNodeSms, importTestSummary.getInjects().getLast().getContent()); } } @@ -577,7 +577,7 @@ private List createRuleAttributeSms() { RuleAttribute ruleAttributeExpectationScore = new RuleAttribute(); ruleAttributeExpectationScore.setName("expectation_score"); ruleAttributeExpectationScore.setColumns("J"); - ruleAttributeExpectationScore.setDefaultValue("500"); + ruleAttributeExpectationScore.setDefaultValue("500.0"); RuleAttribute ruleAttributeExpectationName = new RuleAttribute(); ruleAttributeExpectationName.setName("expectation_name"); @@ -637,7 +637,7 @@ private List createRuleAttributeMail() { RuleAttribute ruleAttributeExpectationScore = new RuleAttribute(); ruleAttributeExpectationScore.setName("expectation_score"); ruleAttributeExpectationScore.setColumns("J"); - ruleAttributeExpectationScore.setDefaultValue("500"); + ruleAttributeExpectationScore.setDefaultValue("500.0"); RuleAttribute ruleAttributeExpectationName = new RuleAttribute(); ruleAttributeExpectationName.setName("expectation_name"); diff --git a/openbas-api/src/test/java/io/openbas/service/MapperServiceExportTest.java b/openbas-api/src/test/java/io/openbas/service/MapperServiceExportTest.java new file mode 100644 index 0000000000..e130a5823e --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/service/MapperServiceExportTest.java @@ -0,0 +1,47 @@ +package io.openbas.service; + +import io.openbas.IntegrationTest; +import io.openbas.database.model.ImportMapper; +import io.openbas.database.repository.ImportMapperRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MapperServiceExportTest extends IntegrationTest { + + @Autowired + private ImportMapperRepository importMapperRepository; + + @Autowired + private MapperService mapperService; + + @DisplayName("Test exporting a mapper") + @Test + void exportMapper() throws Exception { + // -- PREPARE -- + ImportMapper mapper = new ImportMapper(); + mapper.setName("Test Mapper"); + mapper.setInjectTypeColumn("injectType"); + mapper.setInjectImporters(new ArrayList<>()); + ImportMapper mapperSaved = this.importMapperRepository.save(mapper); + + // -- EXECUTE -- + String json = this.mapperService.exportMappers(List.of(mapperSaved.getId())); + + // -- ASSERT -- + assertEquals("[{" + + "\"import_mapper_name\":\"Test Mapper\"," + + "\"import_mapper_inject_type_column\":\"injectType\"," + + "\"import_mapper_inject_importers\":[]" + + "}]", json); + + // -- CLEAN -- + this.importMapperRepository.delete(mapperSaved); + } + +} diff --git a/openbas-api/src/test/java/io/openbas/service/MapperServiceTest.java b/openbas-api/src/test/java/io/openbas/service/MapperServiceTest.java index 696b909e40..ff1bde0929 100644 --- a/openbas-api/src/test/java/io/openbas/service/MapperServiceTest.java +++ b/openbas-api/src/test/java/io/openbas/service/MapperServiceTest.java @@ -189,7 +189,7 @@ void updateSpecificMapperWithUpdatedElements() throws Exception { when(importMapperRepository.findById(any())).thenReturn(Optional.of(importMapper)); when(importMapperRepository.save(any())).thenReturn(importMapper); when(injectorContractRepository.findAllById(any())).thenReturn(importMapper.getInjectImporters().stream().map(InjectImporter::getInjectorContract).toList()); - // -- EXECUTE -- + // -- EXECUTE -- ImportMapper response = mapperService.updateImportMapper(importMapper.getId(), importMapperInput); diff --git a/openbas-framework/pom.xml b/openbas-framework/pom.xml index e82792dfd8..c525b9bf63 100644 --- a/openbas-framework/pom.xml +++ b/openbas-framework/pom.xml @@ -6,7 +6,7 @@ io.openbas openbas-platform - 1.3.1 + 1.4.0 openbas-framework @@ -17,7 +17,7 @@ io.openbas openbas-model - 1.3.1 + 1.4.0 com.rabbitmq diff --git a/openbas-framework/src/main/java/io/openbas/asset/AssetGroupService.java b/openbas-framework/src/main/java/io/openbas/asset/AssetGroupService.java index ae56bb0935..e53246accb 100644 --- a/openbas-framework/src/main/java/io/openbas/asset/AssetGroupService.java +++ b/openbas-framework/src/main/java/io/openbas/asset/AssetGroupService.java @@ -103,7 +103,6 @@ private List computeDynamicAssets(@NotNull final List as assetGroup.setDynamicAssets(filteredAssets); } }); - return assetGroups; } diff --git a/openbas-framework/src/main/java/io/openbas/atomic_testing/TargetType.java b/openbas-framework/src/main/java/io/openbas/atomic_testing/TargetType.java index 8a05ed10d0..b6a958b42c 100644 --- a/openbas-framework/src/main/java/io/openbas/atomic_testing/TargetType.java +++ b/openbas-framework/src/main/java/io/openbas/atomic_testing/TargetType.java @@ -3,5 +3,6 @@ public enum TargetType { ASSETS, ASSETS_GROUPS, + PLAYER, TEAMS } diff --git a/openbas-framework/src/main/java/io/openbas/execution/ExecutionContextService.java b/openbas-framework/src/main/java/io/openbas/execution/ExecutionContextService.java index 9da402f4ea..8f57f94850 100644 --- a/openbas-framework/src/main/java/io/openbas/execution/ExecutionContextService.java +++ b/openbas-framework/src/main/java/io/openbas/execution/ExecutionContextService.java @@ -41,6 +41,7 @@ public ExecutionContext executionContext(@NotNull final User user, Injection inj executionContext.put(CHALLENGES_URI, baseUrl + "/challenges/" + exerciseId + queryParams); executionContext.put(SCOREBOARD_URI, baseUrl + "/scoreboard/" + exerciseId + queryParams); executionContext.put(LESSONS_URI, baseUrl + "/lessons/" + exerciseId + queryParams); + executionContext.put(EXERCISE, injection.getExercise()); fillDynamicVariable(executionContext, exerciseId); } return executionContext; diff --git a/openbas-framework/src/main/java/io/openbas/execution/Injector.java b/openbas-framework/src/main/java/io/openbas/execution/Injector.java index 5356e5cffc..88159998d9 100644 --- a/openbas-framework/src/main/java/io/openbas/execution/Injector.java +++ b/openbas-framework/src/main/java/io/openbas/execution/Injector.java @@ -22,6 +22,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static io.openbas.database.model.InjectStatusExecution.traceError; @@ -52,30 +54,49 @@ public void setFileService(FileService fileService) { public abstract ExecutionProcess process(Execution execution, ExecutableInject injection) throws Exception; private InjectExpectation expectationConverter( - @NotNull final ExecutableInject executableInject, - Expectation expectation) { + @NotNull final ExecutableInject executableInject, + Expectation expectation) { InjectExpectation expectationExecution = new InjectExpectation(); return this.expectationConverter(expectationExecution, executableInject, expectation); } + private InjectExpectation expectationConverter( - @NotNull final Team team, - @NotNull final ExecutableInject executableInject, - Expectation expectation) { + @NotNull final Team team, + @NotNull final ExecutableInject executableInject, + Expectation expectation) { InjectExpectation expectationExecution = new InjectExpectation(); expectationExecution.setTeam(team); return this.expectationConverter(expectationExecution, executableInject, expectation); } + private InjectExpectation expectationConverter( - @NotNull InjectExpectation expectationExecution, - @NotNull final ExecutableInject executableInject, - @NotNull final Expectation expectation) { + @NotNull final Team team, + @NotNull final User user, + @NotNull final ExecutableInject executableInject, + Expectation expectation) { + InjectExpectation expectationExecution = new InjectExpectation(); + expectationExecution.setTeam(team); + expectationExecution.setUser(user); + return this.expectationConverter(expectationExecution, executableInject, expectation); + } + + private InjectExpectation expectationConverter( + @NotNull InjectExpectation expectationExecution, + @NotNull final ExecutableInject executableInject, + @NotNull final Expectation expectation) { expectationExecution.setExercise(executableInject.getInjection().getExercise()); expectationExecution.setInject(executableInject.getInjection().getInject()); expectationExecution.setExpectedScore(expectation.getScore()); expectationExecution.setExpectationGroup(expectation.isExpectationGroup()); switch (expectation.type()) { - case ARTICLE -> expectationExecution.setArticle(((ChannelExpectation) expectation).getArticle()); - case CHALLENGE -> expectationExecution.setChallenge(((ChallengeExpectation) expectation).getChallenge()); + case ARTICLE -> { + expectationExecution.setName(expectation.getName()); + expectationExecution.setArticle(((ChannelExpectation) expectation).getArticle()); + } + case CHALLENGE -> { + expectationExecution.setName(expectation.getName()); + expectationExecution.setChallenge(((ChallengeExpectation) expectation).getChallenge()); + } case DOCUMENT -> expectationExecution.setType(EXPECTATION_TYPE.DOCUMENT); case TEXT -> expectationExecution.setType(EXPECTATION_TYPE.TEXT); case DETECTION -> { @@ -125,15 +146,48 @@ public Execution execute(ExecutableInject executableInject) { List assetGroups = executableInject.getAssetGroups(); if ((isScheduledInject || isAtomicTesting) && !expectations.isEmpty()) { if (!teams.isEmpty()) { - List injectExpectations = teams.stream() - .flatMap(team -> expectations.stream() - .map(expectation -> expectationConverter(team, executableInject, expectation))) - .toList(); - this.injectExpectationRepository.saveAll(injectExpectations); + List injectExpectationsByTeam; + + List injectExpectationsByUserAndTeam; + // If atomicTesting, We create expectation for every player and every team + if (isAtomicTesting) { + injectExpectationsByTeam = teams.stream() + .flatMap(team -> expectations.stream() + .map(expectation -> expectationConverter(team, executableInject, expectation))) + .collect(Collectors.toList()); + + injectExpectationsByUserAndTeam = teams.stream() + .flatMap(team -> team.getUsers().stream() + .flatMap(user -> expectations.stream() + .map(expectation -> expectationConverter(team, user, executableInject, expectation)))) + .toList(); + } else { + // Create expectations for every enabled player in every team + injectExpectationsByUserAndTeam = teams.stream() + .flatMap(team -> team.getExerciseTeamUsers().stream() + .filter(exerciseTeamUser -> exerciseTeamUser.getExercise().getId().equals(executableInject.getInjection().getExercise().getId())) + .flatMap(exerciseTeamUser -> expectations.stream() + .map(expectation -> expectationConverter(team, exerciseTeamUser.getUser(), executableInject, expectation)))) + .toList(); + + // Create a set of teams that have at least one enabled player + Set teamsWithEnabledPlayers = injectExpectationsByUserAndTeam.stream() + .map(InjectExpectation::getTeam) + .collect(Collectors.toSet()); + + // Add only the expectations where the team has at least one enabled player + injectExpectationsByTeam = teamsWithEnabledPlayers.stream() + .flatMap(team -> expectations.stream() + .map(expectation -> expectationConverter(team, executableInject, expectation))) + .collect(Collectors.toList()); + } + + injectExpectationsByTeam.addAll(injectExpectationsByUserAndTeam); + this.injectExpectationRepository.saveAll(injectExpectationsByTeam); } else if (!assets.isEmpty() || !assetGroups.isEmpty()) { List injectExpectations = expectations.stream() - .map(expectation -> expectationConverter(executableInject, expectation)) - .toList(); + .map(expectation -> expectationConverter(executableInject, expectation)) + .toList(); this.injectExpectationRepository.saveAll(injectExpectations); } } diff --git a/openbas-framework/src/main/java/io/openbas/inject_expectation/InjectExpectationService.java b/openbas-framework/src/main/java/io/openbas/inject_expectation/InjectExpectationService.java index 3d0c3288b1..a40fe015cd 100644 --- a/openbas-framework/src/main/java/io/openbas/inject_expectation/InjectExpectationService.java +++ b/openbas-framework/src/main/java/io/openbas/inject_expectation/InjectExpectationService.java @@ -51,8 +51,8 @@ public InjectExpectation computeExpectation( computeResult(expectation, sourceId, sourceType, sourceName, result, expectation.getExpectedScore()); expectation.setScore(expectation.getExpectedScore()); } else if (expectation.getScore() == null) { - computeResult(expectation, sourceId, sourceType, sourceName, result, 0); - expectation.setScore(0); + computeResult(expectation, sourceId, sourceType, sourceName, result, 0.0); + expectation.setScore(0.0); } return this.update(expectation); } @@ -71,7 +71,7 @@ public void computeExpectationGroup( success = expectationAssets.stream().allMatch((e) -> e.getExpectedScore().equals(e.getScore())); } computeResult(expectationAssetGroup, sourceId, sourceType, sourceName, success ? "SUCCESS" : "FAILED", success ? expectationAssetGroup.getExpectedScore() : 0); - expectationAssetGroup.setScore(success ? expectationAssetGroup.getExpectedScore() : 0); + expectationAssetGroup.setScore(success ? expectationAssetGroup.getExpectedScore() : 0.0); this.update(expectationAssetGroup); } @@ -176,11 +176,13 @@ public List manualExpectationsNotFill() { public List findExpectationsByInjectAndTargetAndTargetType( @NotBlank final String injectId, @NotBlank final String targetId, + @NotBlank final String parentTargetId, @NotBlank final String targetType) { try { TargetType targetTypeEnum = TargetType.valueOf(targetType); return switch (targetTypeEnum) { case TEAMS -> injectExpectationRepository.findAllByInjectAndTeam(injectId, targetId); + case PLAYER -> injectExpectationRepository.findAllByInjectAndTeamAndPlayer(injectId, parentTargetId, targetId); case ASSETS -> injectExpectationRepository.findAllByInjectAndAsset(injectId, targetId); case ASSETS_GROUPS -> injectExpectationRepository.findAllByInjectAndAssetGroup(injectId, targetId); }; diff --git a/openbas-framework/src/main/java/io/openbas/inject_expectation/InjectExpectationUtils.java b/openbas-framework/src/main/java/io/openbas/inject_expectation/InjectExpectationUtils.java index 85edd8a6b8..4b56ddc389 100644 --- a/openbas-framework/src/main/java/io/openbas/inject_expectation/InjectExpectationUtils.java +++ b/openbas-framework/src/main/java/io/openbas/inject_expectation/InjectExpectationUtils.java @@ -29,7 +29,7 @@ public static void computeResult( @NotBlank final String sourceType, @NotBlank final String sourceName, @NotBlank final String result, - @NotBlank final Integer score + @NotBlank final Double score ) { Optional exists = expectation.getResults() .stream() diff --git a/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java index 303766828b..6419739e88 100644 --- a/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java +++ b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java @@ -130,12 +130,12 @@ private ContractExpectations expectations() { Expectation preventionExpectation = new Expectation(); preventionExpectation.setType(PREVENTION); preventionExpectation.setName("Expect inject to be prevented"); - preventionExpectation.setScore(0); + preventionExpectation.setScore(100.0); // Detection Expectation detectionExpectation = new Expectation(); detectionExpectation.setType(DETECTION); detectionExpectation.setName("Expect inject to be detected"); - detectionExpectation.setScore(0); + detectionExpectation.setScore(100.0); return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); } diff --git a/openbas-framework/src/main/java/io/openbas/model/Expectation.java b/openbas-framework/src/main/java/io/openbas/model/Expectation.java index f8b9276df9..326a8e29fe 100644 --- a/openbas-framework/src/main/java/io/openbas/model/Expectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/Expectation.java @@ -5,8 +5,9 @@ public interface Expectation { EXPECTATION_TYPE type(); - Integer getScore(); + Double getScore(); default boolean isExpectationGroup() { return false; } + String getName(); } diff --git a/openbas-framework/src/main/java/io/openbas/model/expectation/ChallengeExpectation.java b/openbas-framework/src/main/java/io/openbas/model/expectation/ChallengeExpectation.java index 6eae32638d..9024e25176 100644 --- a/openbas-framework/src/main/java/io/openbas/model/expectation/ChallengeExpectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/expectation/ChallengeExpectation.java @@ -12,14 +12,23 @@ @Setter public class ChallengeExpectation implements Expectation { - private Integer score; + private Double score; private Challenge challenge; + private boolean expectationGroup; + private String name; - public ChallengeExpectation(Integer score, Challenge challenge) { - setScore(Objects.requireNonNullElse(score, 100)); + public ChallengeExpectation(Double score, Challenge challenge) { + setScore(Objects.requireNonNullElse(score, 100.0)); setChallenge(challenge); } + public ChallengeExpectation(Double score, Challenge challenge, boolean expectationGroup) { + setScore(Objects.requireNonNullElse(score, 100.0)); + setChallenge(challenge); + setName(challenge.getName()); + setExpectationGroup(expectationGroup); + } + @Override public InjectExpectation.EXPECTATION_TYPE type() { return InjectExpectation.EXPECTATION_TYPE.CHALLENGE; diff --git a/openbas-framework/src/main/java/io/openbas/model/expectation/ChannelExpectation.java b/openbas-framework/src/main/java/io/openbas/model/expectation/ChannelExpectation.java index a1682380be..120df00a78 100644 --- a/openbas-framework/src/main/java/io/openbas/model/expectation/ChannelExpectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/expectation/ChannelExpectation.java @@ -12,14 +12,23 @@ @Setter public class ChannelExpectation implements Expectation { - private Integer score; + private Double score; private Article article; + private boolean expectationGroup; + private String name; - public ChannelExpectation(Integer score, Article article) { - setScore(Objects.requireNonNullElse(score, 100)); + public ChannelExpectation(Double score, Article article) { + setScore(Objects.requireNonNullElse(score, 100.0)); setArticle(article); } + public ChannelExpectation(Double score, Article article, boolean expectationGroup) { + setScore(Objects.requireNonNullElse(score, 100.0)); + setArticle(article); + setName(article.getName()); + setExpectationGroup(expectationGroup); + } + @Override public InjectExpectation.EXPECTATION_TYPE type() { return InjectExpectation.EXPECTATION_TYPE.ARTICLE; diff --git a/openbas-framework/src/main/java/io/openbas/model/expectation/DetectionExpectation.java b/openbas-framework/src/main/java/io/openbas/model/expectation/DetectionExpectation.java index 2b0989fa94..a6cb815273 100644 --- a/openbas-framework/src/main/java/io/openbas/model/expectation/DetectionExpectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/expectation/DetectionExpectation.java @@ -20,7 +20,7 @@ @Setter public class DetectionExpectation implements Expectation { - private Integer score; + private Double score; private String name; private String description; private Asset asset; @@ -37,7 +37,7 @@ public InjectExpectation.EXPECTATION_TYPE type() { } public static DetectionExpectation detectionExpectationForAsset( - @Nullable final Integer score, + @Nullable final Double score, @NotBlank final String name, final String description, @NotNull final Asset asset, @@ -45,7 +45,7 @@ public static DetectionExpectation detectionExpectationForAsset( final List expectationSignatures ) { DetectionExpectation detectionExpectation = new DetectionExpectation(); - detectionExpectation.setScore(Objects.requireNonNullElse(score, 100)); + detectionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); detectionExpectation.setName(name); detectionExpectation.setDescription(description); detectionExpectation.setAsset(asset); @@ -55,7 +55,7 @@ public static DetectionExpectation detectionExpectationForAsset( } public static DetectionExpectation detectionExpectationForAssetGroup( - @Nullable final Integer score, + @Nullable final Double score, @NotBlank final String name, final String description, @NotNull final AssetGroup assetGroup, @@ -63,7 +63,7 @@ public static DetectionExpectation detectionExpectationForAssetGroup( final List expectationSignatures ) { DetectionExpectation detectionExpectation = new DetectionExpectation(); - detectionExpectation.setScore(Objects.requireNonNullElse(score, 100)); + detectionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); detectionExpectation.setName(name); detectionExpectation.setDescription(description); detectionExpectation.setAssetGroup(assetGroup); diff --git a/openbas-framework/src/main/java/io/openbas/model/expectation/ManualExpectation.java b/openbas-framework/src/main/java/io/openbas/model/expectation/ManualExpectation.java index f42f2d7509..1cc631c45a 100644 --- a/openbas-framework/src/main/java/io/openbas/model/expectation/ManualExpectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/expectation/ManualExpectation.java @@ -19,7 +19,7 @@ @Setter public class ManualExpectation implements Expectation { - private Integer score; + private Double score; private String name; private String description; private Asset asset; @@ -29,25 +29,32 @@ public class ManualExpectation implements Expectation { public ManualExpectation() { } - public ManualExpectation(final Integer score) { - this.score = Objects.requireNonNullElse(score, 100); + public ManualExpectation(final Double score) { + this.score = Objects.requireNonNullElse(score, 100.0); } - public ManualExpectation(final Integer score, @NotBlank final String name, final String description) { + public ManualExpectation(final Double score, @NotBlank final String name, final String description) { this(score); this.name = name; this.description = description; } + public ManualExpectation(final Double score, @NotBlank final String name, final String description, final boolean expectationGroup) { + this(score); + this.name = name; + this.description = description; + this.expectationGroup = expectationGroup; + } + public static ManualExpectation manualExpectationForAsset( - @Nullable final Integer score, + @Nullable final Double score, @NotBlank final String name, final String description, @NotNull final Asset asset, final boolean expectationGroup ) { ManualExpectation manualExpectation = new ManualExpectation(); - manualExpectation.setScore(Objects.requireNonNullElse(score, 100)); + manualExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); manualExpectation.setName(name); manualExpectation.setDescription(description); manualExpectation.setAsset(asset); @@ -56,14 +63,14 @@ public static ManualExpectation manualExpectationForAsset( } public static ManualExpectation manualExpectationForAssetGroup( - @Nullable final Integer score, + @Nullable final Double score, @NotBlank final String name, final String description, @NotNull final AssetGroup assetGroup, final boolean expectationGroup ) { ManualExpectation manualExpectation = new ManualExpectation(); - manualExpectation.setScore(Objects.requireNonNullElse(score, 100)); + manualExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); manualExpectation.setName(name); manualExpectation.setDescription(description); manualExpectation.setAssetGroup(assetGroup); diff --git a/openbas-framework/src/main/java/io/openbas/model/expectation/PreventionExpectation.java b/openbas-framework/src/main/java/io/openbas/model/expectation/PreventionExpectation.java index a71690cdd3..355ca70a3d 100644 --- a/openbas-framework/src/main/java/io/openbas/model/expectation/PreventionExpectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/expectation/PreventionExpectation.java @@ -20,7 +20,7 @@ @Setter public class PreventionExpectation implements Expectation { - private Integer score; + private Double score; private String name; private String description; private Asset asset; @@ -37,7 +37,7 @@ public EXPECTATION_TYPE type() { } public static PreventionExpectation preventionExpectationForAsset( - @Nullable final Integer score, + @Nullable final Double score, @NotBlank final String name, final String description, @NotNull final Asset asset, @@ -45,7 +45,7 @@ public static PreventionExpectation preventionExpectationForAsset( final List expectationSignatures ) { PreventionExpectation preventionExpectation = new PreventionExpectation(); - preventionExpectation.setScore(Objects.requireNonNullElse(score, 100)); + preventionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); preventionExpectation.setName(name); preventionExpectation.setDescription(description); preventionExpectation.setAsset(asset); @@ -55,7 +55,7 @@ public static PreventionExpectation preventionExpectationForAsset( } public static PreventionExpectation preventionExpectationForAssetGroup( - @Nullable final Integer score, + @Nullable final Double score, @NotBlank final String name, final String description, @NotNull final AssetGroup assetGroup, @@ -63,7 +63,7 @@ public static PreventionExpectation preventionExpectationForAssetGroup( final List expectationSignatures ) { PreventionExpectation preventionExpectation = new PreventionExpectation(); - preventionExpectation.setScore(Objects.requireNonNullElse(score, 100)); + preventionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); preventionExpectation.setName(name); preventionExpectation.setDescription(description); preventionExpectation.setAssetGroup(assetGroup); diff --git a/openbas-framework/src/main/java/io/openbas/model/inject/form/Expectation.java b/openbas-framework/src/main/java/io/openbas/model/inject/form/Expectation.java index 8027d4ff2d..ce1801671b 100644 --- a/openbas-framework/src/main/java/io/openbas/model/inject/form/Expectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/inject/form/Expectation.java @@ -17,7 +17,7 @@ public class Expectation { private String description; @JsonProperty("expectation_score") - private Integer score; + private Double score; @JsonProperty("expectation_expectation_group") private boolean expectationGroup; diff --git a/openbas-front/package.json b/openbas-front/package.json index 3a7ce7ea19..e5206060ee 100644 --- a/openbas-front/package.json +++ b/openbas-front/package.json @@ -23,7 +23,7 @@ "@redux-devtools/extension": "3.3.0", "@uiw/react-md-editor": "4.0.4", "apexcharts": "3.51.0", - "axios": "1.7.2", + "axios": "1.7.4", "ckeditor5-custom-build": "link:packages/ckeditor5-custom-build", "classnames": "2.5.1", "cronstrue": "2.50.0", diff --git a/openbas-front/src/actions/Inject.js b/openbas-front/src/actions/Inject.js index 91f733fa58..950a10f6a1 100644 --- a/openbas-front/src/actions/Inject.js +++ b/openbas-front/src/actions/Inject.js @@ -14,7 +14,7 @@ export const tryInject = (injectId) => (dispatch) => { }; export const testInject = (injectId) => { - const uri = `/api/injects/test/${injectId}`; + const uri = `/api/injects/${injectId}/test`; return simpleCall(uri); }; diff --git a/openbas-front/src/actions/atomic_testings/atomic-testing-actions.ts b/openbas-front/src/actions/atomic_testings/atomic-testing-actions.ts index a97e97e5cb..a1564a99f6 100644 --- a/openbas-front/src/actions/atomic_testings/atomic-testing-actions.ts +++ b/openbas-front/src/actions/atomic_testings/atomic-testing-actions.ts @@ -29,8 +29,11 @@ export const tryAtomicTesting = (injectId: string) => { return simpleCall(uri); }; -export const fetchTargetResult = (injectId: string, targetId: string, targetType: string) => { - const uri = `${ATOMIC_TESTING_URI}/${injectId}/target_results/${targetId}/types/${targetType}`; +export const fetchTargetResult = (injectId: string, targetId: string, targetType: string, parentTargetId ?: string) => { + let uri = `${ATOMIC_TESTING_URI}/${injectId}/target_results/${targetId}/types/${targetType}`; + if (parentTargetId) { + uri += `?parentTargetId=${encodeURIComponent(parentTargetId)}`; + } return simpleCall(uri); }; diff --git a/openbas-front/src/actions/inject_test/inject-test-actions.ts b/openbas-front/src/actions/inject_test/inject-test-actions.ts new file mode 100644 index 0000000000..c0a6b2b7f7 --- /dev/null +++ b/openbas-front/src/actions/inject_test/inject-test-actions.ts @@ -0,0 +1,17 @@ +import { simpleCall } from '../../utils/Action'; + +// eslint-disable-next-line import/prefer-default-export +export const searchExerciseInjectTests = (exerciseId: string) => { + const uri = `/api/exercise/${exerciseId}/injects/test`; + return simpleCall(uri); +}; + +export const searchScenarioInjectTests = (scenarioId: string) => { + const uri = `/api/scenario/${scenarioId}/injects/test`; + return simpleCall(uri); +}; + +export const fetchInjectTestStatus = (testId: string | undefined) => { + const uri = `/api/injects/test/${testId}`; + return simpleCall(uri); +}; diff --git a/openbas-front/src/actions/mapper/mapper-actions.ts b/openbas-front/src/actions/mapper/mapper-actions.ts index ddc376740a..c25a93504f 100644 --- a/openbas-front/src/actions/mapper/mapper-actions.ts +++ b/openbas-front/src/actions/mapper/mapper-actions.ts @@ -1,4 +1,11 @@ -import type { ImportMapperAddInput, ImportMapperUpdateInput, InjectsImportTestInput, RawPaginationImportMapper, SearchPaginationInput } from '../../utils/api-types'; +import type { + ExportMapperInput, + ImportMapperAddInput, + ImportMapperUpdateInput, + InjectsImportTestInput, + RawPaginationImportMapper, + SearchPaginationInput, +} from '../../utils/api-types'; import { simpleCall, simpleDelCall, simplePostCall, simplePutCall } from '../../utils/Action'; const XLS_MAPPER_URI = '/api/mappers'; @@ -39,3 +46,15 @@ export const testXlsFile = (importId: string, input: InjectsImportTestInput) => const uri = `${XLS_MAPPER_URI}/store/${importId}`; return simplePostCall(uri, input); }; + +export const exportMapper = (input: ExportMapperInput) => { + const uri = `${XLS_MAPPER_URI}/export`; + return simplePostCall(uri, input).then((response) => { + return { data: response.data, filename: response.headers['content-disposition'].split('filename=')[1] }; + }); +}; + +export const importMapper = (formData: FormData) => { + const uri = `${XLS_MAPPER_URI}/import`; + return simplePostCall(uri, formData); +}; diff --git a/openbas-front/src/actions/mapper/mapper.ts b/openbas-front/src/actions/mapper/mapper.ts index 42a56cba59..50217270c8 100644 --- a/openbas-front/src/actions/mapper/mapper.ts +++ b/openbas-front/src/actions/mapper/mapper.ts @@ -4,6 +4,6 @@ export type InjectImporterStore = Omit & { - inject_importers: InjectImporterStore[]; +export type ImportMapperStore = Omit & { + import_mapper_inject_importers: InjectImporterStore[]; }; diff --git a/openbas-front/src/actions/scenarios/scenario-actions.ts b/openbas-front/src/actions/scenarios/scenario-actions.ts index c3dc95433a..89477f595f 100644 --- a/openbas-front/src/actions/scenarios/scenario-actions.ts +++ b/openbas-front/src/actions/scenarios/scenario-actions.ts @@ -167,3 +167,11 @@ export const importXls = (scenarioId: Scenario['scenario_id'], importId: string, return response; }); }; + +export const dryImportXls = (scenarioId: Scenario['scenario_id'], importId: string, input: InjectsImportInput) => { + const uri = `${SCENARIO_URI}/${scenarioId}/xls/${importId}/dry`; + return simplePostCall(uri, input) + .then((response) => { + return response; + }); +}; diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTesting.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTesting.tsx index df70e62cee..e14c2a4ef4 100644 --- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTesting.tsx +++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTesting.tsx @@ -43,19 +43,23 @@ const AtomicTesting = () => { const classes = useStyles(); const { t, tPick, fldt } = useFormatter(); const [selectedTarget, setSelectedTarget] = useState(); + const [currentParentTarget, setCurrentParentTarget] = useState(); const filtering = useSearchAnFilter('', 'name', ['name']); // Fetching data const { injectResultDto } = useContext(InjectResultDtoContext); useEffect(() => { - setSelectedTarget(injectResultDto?.inject_targets[0]); + setSelectedTarget(currentParentTarget || injectResultDto?.inject_targets[0]); }, [injectResultDto]); const sortedTargets: InjectTargetWithResult[] = filtering.filterAndSort(injectResultDto?.inject_targets ?? []); // Handles - const handleTargetClick = (target: InjectTargetWithResult) => { + const handleTargetClick = (target: InjectTargetWithResult, currentParent?: InjectTargetWithResult) => { setSelectedTarget(target); + if (currentParent) { + setCurrentParentTarget(currentParent); + } }; if (!injectResultDto) { @@ -225,10 +229,12 @@ const AtomicTesting = () => { {sortedTargets.map((target) => (
- + handleTargetClick(target)} target={target} selected={selectedTarget?.id === target.id} /> {target?.children?.map((child) => ( - + handleTargetClick(child, target)} + target={child} selected={selectedTarget?.id === child.id && currentParentTarget?.id === target.id} + /> ))}
@@ -246,8 +252,9 @@ const AtomicTesting = () => { {selectedTarget && !!injectResultDto.inject_type && ( diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingDetail.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingDetail.tsx index ffd7a9fc3a..aa035cb65d 100644 --- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingDetail.tsx +++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/AtomicTestingDetail.tsx @@ -73,11 +73,13 @@ const AtomicTestingDetail: FunctionComponent = () => { { injectResultDto.inject_expectations !== undefined && injectResultDto.inject_expectations.length > 0 - ? injectResultDto.inject_expectations.map((expectation, index) => ( - - {expectation.inject_expectation_name} - - )) : + ? Array.from(new Set(injectResultDto.inject_expectations.map((expectation) => expectation.inject_expectation_name))) + .map((name, index) => ( + + {name} + + )) + : {'-'} } diff --git a/openbas-front/src/admin/components/atomic_testings/atomic_testing/TargetResultsDetail.tsx b/openbas-front/src/admin/components/atomic_testings/atomic_testing/TargetResultsDetail.tsx index 28e2419f4c..46595ed27e 100644 --- a/openbas-front/src/admin/components/atomic_testings/atomic_testing/TargetResultsDetail.tsx +++ b/openbas-front/src/admin/components/atomic_testings/atomic_testing/TargetResultsDetail.tsx @@ -6,6 +6,7 @@ import { CardActionArea, CardContent, CardHeader, + Chip, Dialog, DialogActions, DialogContent, @@ -17,6 +18,7 @@ import { MenuItem, Tab, Tabs, + Tooltip, Typography, } from '@mui/material'; import { makeStyles, useTheme } from '@mui/styles'; @@ -36,11 +38,12 @@ import ItemResult from '../../../../components/ItemResult'; import InjectIcon from '../../common/injects/InjectIcon'; import { isNotEmptyField } from '../../../../utils/utils'; import Transition from '../../../../components/common/Transition'; -import { emptyFilled } from '../../../../utils/String'; +import { emptyFilled, truncate } from '../../../../utils/String'; import DetectionPreventionExpectationsValidationForm from '../../simulations/simulation/validation/expectations/DetectionPreventionExpectationsValidationForm'; import { deleteInjectExpectationResult } from '../../../../actions/Exercise'; import { useAppDispatch } from '../../../../utils/hooks'; import type { InjectExpectationStore } from '../../../../actions/injects/Inject'; +import { isTechnicalExpectation } from '../../common/injects/expectations/ExpectationUtils'; interface Steptarget { label: string; @@ -81,6 +84,11 @@ const useStyles = makeStyles((theme) => ({ width: '100%', height: '100%', }, + score: { + fontSize: '0.75rem', + height: '20px', + padding: '0 4px', + }, })); interface Props { @@ -88,6 +96,7 @@ interface Props { lastExecutionStartDate: string, lastExecutionEndDate: string, target: InjectTargetWithResult, + parentTargetId?: string, } const TargetResultsDetailFlow: FunctionComponent = ({ @@ -95,6 +104,7 @@ const TargetResultsDetailFlow: FunctionComponent = ({ lastExecutionStartDate, lastExecutionEndDate, target, + parentTargetId, }) => { const classes = useStyles(); const dispatch = useAppDispatch(); @@ -109,7 +119,7 @@ const TargetResultsDetailFlow: FunctionComponent = ({ const [targetResults, setTargetResults] = useState([]); const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); - const initialSteps = [{ label: 'Attack started', type: '', key: 'attack-started' }, { label: 'Attack ended', type: '', key: 'attack-ended' }]; + const initialSteps = [{ label: t('Attack started'), type: '', key: 'attack-started' }, { label: t('Attack ended'), type: '', key: 'attack-ended' }]; const sortOrder = ['PREVENTION', 'DETECTION', 'MANUAL']; // Flow const layoutOptions: LayoutOptions = { @@ -190,7 +200,7 @@ const TargetResultsDetailFlow: FunctionComponent = ({ useEffect(() => { if (target) { setInitialized(false); - const steps = [...computeInitialSteps(initialSteps), ...[{ label: 'Unknown result', type: '', status: 'PENDING' }]]; + const steps = [...computeInitialSteps(initialSteps), ...[{ label: t('Unknown result'), type: '', status: 'PENDING' }]]; setNodes(steps.map((step: Steptarget, index) => ({ id: `result-${index}`, type: 'result', @@ -213,7 +223,7 @@ const TargetResultsDetailFlow: FunctionComponent = ({ labelShowBg: false, labelStyle: { fill: theme.palette.text?.primary, fontSize: 9 }, }))); - fetchTargetResult(inject.inject_id, target.id!, target.targetType!).then( + fetchTargetResult(inject.inject_id, target.id!, target.targetType!, parentTargetId).then( (result: { data: InjectExpectationsStore[] }) => setTargetResults(result.data ?? []), ); setActiveTab(0); @@ -247,6 +257,8 @@ const TargetResultsDetailFlow: FunctionComponent = ({ } return status.every((s) => s === 'SUCCESS') ? 'Attack Detected' : 'Attack Not Detected'; case 'MANUAL': + case 'ARTICLE': + case 'CHALLENGE': if (status.includes('UNKNOWN')) { return 'No Expectation for Manual'; } @@ -266,7 +278,6 @@ const TargetResultsDetailFlow: FunctionComponent = ({ return ''; } }; - const getAvatar = (injectExpectation: InjectExpectationStore, expectationResult: InjectExpectationResult) => { if (expectationResult.sourceType === 'collector') { return ( @@ -330,12 +341,26 @@ const TargetResultsDetailFlow: FunctionComponent = ({ useEffect(() => { if (initialized && targetResults && targetResults.length > 0) { const groupedBy = groupedByExpectationType(targetResults); - const newSteps = Array.from(groupedBy).map(([targetType, targetResult]) => ({ + const newSteps = Array.from(groupedBy).flatMap(([targetType, results]) => results.sort((a: InjectExpectationsStore, b: InjectExpectationsStore) => { + if (a.inject_expectation_name && b.inject_expectation_name) { + return a.inject_expectation_name.localeCompare(b.inject_expectation_name); + } if (a.inject_expectation_name && !b.inject_expectation_name) { + return -1; // a comes before b + } if (!a.inject_expectation_name && b.inject_expectation_name) { + return 1; // b comes before a + } + return a.inject_expectation_id.localeCompare(b.inject_expectation_id); + }).map((expectation: InjectExpectationStore) => ({ key: 'result', - label: getStatusLabel(targetType, targetResult.map((tr: InjectExpectationsStore) => tr.inject_expectation_status)), + label: ( + + {getStatusLabel(targetType, [expectation.inject_expectation_status])} +
{truncate(expectation.inject_expectation_name, 20)} +
+ ), type: targetType, - status: getStatus(targetResult.map((tr: InjectExpectationsStore) => tr.inject_expectation_status)), - })); + status: getStatus([expectation.inject_expectation_status]), + }))); const mergedSteps: Steptarget[] = [...computeInitialSteps(initialSteps), ...newSteps]; // Custom sorting function mergedSteps.sort((a, b) => { @@ -356,6 +381,7 @@ const TargetResultsDetailFlow: FunctionComponent = ({ background: getColor(step.status).background, }, position: { x: 0, y: 0 }, + }))); setEdges([...Array(mergedSteps.length - 1)].map((_, i) => ({ id: `result-${i}->result-${i + 1}`, @@ -392,6 +418,17 @@ const TargetResultsDetailFlow: FunctionComponent = ({ type: 'straight', markerEnd: { type: MarkerType.ArrowClosed }, }; + const getLabelOfValidationType = (injectExpectation: InjectExpectationsStore): string => { + // eslint-disable-next-line no-nested-ternary + return isTechnicalExpectation(injectExpectation.inject_expectation_type) + ? injectExpectation.inject_expectation_group + ? t('At least one asset (per group) must validate the expectation') + : t('All assets (per group) must validate the expectation') + : injectExpectation.inject_expectation_group + ? t('At least one player (per team) must validate the expectation') + : t('All players (per team) must validate the expectation'); + }; + return ( <>
@@ -426,10 +463,10 @@ const TargetResultsDetailFlow: FunctionComponent = ({ nodesFocusable={false} elementsSelectable={false} maxZoom={1} - zoomOnScroll={false} + zoomOnScroll zoomOnPinch={false} zoomOnDoubleClick={false} - panOnDrag={false} + panOnDrag defaultEdgeOptions={defaultEdgeOptions} proOptions={proOptions} /> @@ -451,88 +488,106 @@ const TargetResultsDetailFlow: FunctionComponent = ({ )} {Object.keys(sortedGroupedResults).map((targetResult, targetResultIndex) => (