diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java b/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java index 04b12369e9..33694f3cc4 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java @@ -105,6 +105,7 @@ public class ExerciseApi extends RestBehavior { private final ChallengeService challengeService; private final VariableService variableService; private final ExerciseService exerciseService; + // endregion // region logs @@ -146,6 +147,7 @@ public Log updateLog( public void deleteLog(@PathVariable String exerciseId, @PathVariable String logId) { logRepository.deleteById(logId); } + // endregion // region dryruns @@ -157,12 +159,18 @@ public Iterable dryruns(@PathVariable String exerciseId) { @PostMapping(EXERCISE_URI + "/{exerciseId}/dryruns") @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) - public Dryrun createDryrun(@PathVariable String exerciseId, @Valid @RequestBody DryrunCreateInput input) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + public Dryrun createDryrun( + @PathVariable String exerciseId, @Valid @RequestBody DryrunCreateInput input) { + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); List userIds = input.getUserIds(); - List users = userIds.isEmpty() ? List.of( - userRepository.findById(currentUser().getId()).orElseThrow(ElementNotFoundException::new)) - : fromIterable(userRepository.findAllById(userIds)); + List users = + userIds.isEmpty() + ? List.of( + userRepository + .findById(currentUser().getId()) + .orElseThrow(ElementNotFoundException::new)) + : fromIterable(userRepository.findAllById(userIds)); return dryrunService.provisionDryrun(exercise, users, input.getName()); } @@ -183,8 +191,10 @@ public void deleteDryrun(@PathVariable String exerciseId, @PathVariable String d @GetMapping(EXERCISE_URI + "/{exerciseId}/dryruns/{dryrunId}/dryinjects") @PreAuthorize("isExerciseObserver(#exerciseId)") - public List dryrunInjects(@PathVariable String exerciseId, @PathVariable String dryrunId) { - return dryInjectRepository.findAll(DryInjectSpecification.fromDryRun(dryrunId));} + public List dryrunInjects( + @PathVariable String exerciseId, @PathVariable String dryrunId) { + return dryInjectRepository.findAll(DryInjectSpecification.fromDryRun(dryrunId)); + } // endregion @@ -196,14 +206,16 @@ public Iterable comchecks(@PathVariable String exercise) { @GetMapping(EXERCISE_URI + "/{exercise}/comchecks/{comcheck}") public Comcheck comcheck(@PathVariable String exercise, @PathVariable String comcheck) { - Specification filters = ComcheckSpecification.fromExercise(exercise) - .and(ComcheckSpecification.id(comcheck)); + Specification filters = + ComcheckSpecification.fromExercise(exercise).and(ComcheckSpecification.id(comcheck)); return comcheckRepository.findOne(filters).orElseThrow(ElementNotFoundException::new); } @GetMapping(EXERCISE_URI + "/{exercise}/comchecks/{comcheck}/statuses") - public List comcheckStatuses(@PathVariable String exercise, @PathVariable String comcheck) { - return comcheck(exercise, comcheck).getComcheckStatus();} + public List comcheckStatuses( + @PathVariable String exercise, @PathVariable String comcheck) { + return comcheck(exercise, comcheck).getComcheckStatus(); + } // endregion @@ -224,8 +236,7 @@ public Iterable getExerciseTeams(@PathVariable String exerciseId) { @PutMapping(EXERCISE_URI + "/{exerciseId}/teams/add") @PreAuthorize("isExercisePlanner(#exerciseId)") public Iterable addExerciseTeams( - @PathVariable String exerciseId, - @Valid @RequestBody ExerciseUpdateTeamsInput input) { + @PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateTeamsInput input) { Exercise exercise = this.exerciseService.exercise(exerciseId); // Add teams to exercise List teams = exercise.getTeams(); @@ -240,8 +251,8 @@ public Iterable addExerciseTeams( @Transactional(rollbackOn = Exception.class) @PutMapping(EXERCISE_URI + "/{exerciseId}/teams/remove") @PreAuthorize("isExercisePlanner(#exerciseId)") - public Iterable removeExerciseTeams(@PathVariable String exerciseId, - @Valid @RequestBody ExerciseUpdateTeamsInput input) { + public Iterable removeExerciseTeams( + @PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateTeamsInput input) { return this.exerciseService.removeTeams(exerciseId, input.getTeamIds()); } @@ -249,8 +260,7 @@ public Iterable removeExerciseTeams(@PathVariable String exerciseId, @PutMapping(EXERCISE_URI + "/{exerciseId}/teams/replace") @PreAuthorize("isExercisePlanner(#exerciseId)") public Iterable replaceExerciseTeams( - @PathVariable String exerciseId, - @Valid @RequestBody ExerciseUpdateTeamsInput input) { + @PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateTeamsInput input) { Exercise exercise = this.exerciseService.exercise(exerciseId); // Replace teams from exercise List teams = fromIterable(this.teamRepository.findAllById(input.getTeamIds())); @@ -268,72 +278,97 @@ public Iterable getPlayersByExercise(@PathVariable String exerciseId) @Transactional(rollbackOn = Exception.class) @PutMapping(EXERCISE_URI + "/{exerciseId}/teams/{teamId}/players/enable") @PreAuthorize("isExercisePlanner(#exerciseId)") - public Exercise enableExerciseTeamPlayers(@PathVariable String exerciseId, @PathVariable String teamId, + public Exercise enableExerciseTeamPlayers( + @PathVariable String exerciseId, + @PathVariable String teamId, @Valid @RequestBody ExerciseTeamPlayersEnableInput input) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); Team team = teamRepository.findById(teamId).orElseThrow(ElementNotFoundException::new); - input.getPlayersIds().forEach(playerId -> { - ExerciseTeamUser exerciseTeamUser = new ExerciseTeamUser(); - exerciseTeamUser.setExercise(exercise); - exerciseTeamUser.setTeam(team); - exerciseTeamUser.setUser(userRepository.findById(playerId).orElseThrow(ElementNotFoundException::new)); - exerciseTeamUserRepository.save(exerciseTeamUser); - }); + input + .getPlayersIds() + .forEach( + playerId -> { + ExerciseTeamUser exerciseTeamUser = new ExerciseTeamUser(); + exerciseTeamUser.setExercise(exercise); + exerciseTeamUser.setTeam(team); + exerciseTeamUser.setUser( + userRepository.findById(playerId).orElseThrow(ElementNotFoundException::new)); + exerciseTeamUserRepository.save(exerciseTeamUser); + }); return exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); } @Transactional(rollbackOn = Exception.class) @PutMapping(EXERCISE_URI + "/{exerciseId}/teams/{teamId}/players/disable") @PreAuthorize("isExercisePlanner(#exerciseId)") - public Exercise disableExerciseTeamPlayers(@PathVariable String exerciseId, @PathVariable String teamId, + public Exercise disableExerciseTeamPlayers( + @PathVariable String exerciseId, + @PathVariable String teamId, @Valid @RequestBody ExerciseTeamPlayersEnableInput input) { - input.getPlayersIds().forEach(playerId -> { - ExerciseTeamUserId exerciseTeamUserId = new ExerciseTeamUserId(); - exerciseTeamUserId.setExerciseId(exerciseId); - exerciseTeamUserId.setTeamId(teamId); - exerciseTeamUserId.setUserId(playerId); - exerciseTeamUserRepository.deleteById(exerciseTeamUserId); - }); + input + .getPlayersIds() + .forEach( + playerId -> { + ExerciseTeamUserId exerciseTeamUserId = new ExerciseTeamUserId(); + exerciseTeamUserId.setExerciseId(exerciseId); + exerciseTeamUserId.setTeamId(teamId); + exerciseTeamUserId.setUserId(playerId); + exerciseTeamUserRepository.deleteById(exerciseTeamUserId); + }); return exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); } @Transactional(rollbackOn = Exception.class) @PutMapping(EXERCISE_URI + "/{exerciseId}/teams/{teamId}/players/add") @PreAuthorize("isExercisePlanner(#exerciseId)") - public Exercise addExerciseTeamPlayers(@PathVariable String exerciseId, @PathVariable String teamId, + public Exercise addExerciseTeamPlayers( + @PathVariable String exerciseId, + @PathVariable String teamId, @Valid @RequestBody ExerciseTeamPlayersEnableInput input) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); Team team = teamRepository.findById(teamId).orElseThrow(ElementNotFoundException::new); Iterable teamUsers = userRepository.findAllById(input.getPlayersIds()); team.getUsers().addAll(fromIterable(teamUsers)); teamRepository.save(team); - input.getPlayersIds().forEach(playerId -> { - ExerciseTeamUser exerciseTeamUser = new ExerciseTeamUser(); - exerciseTeamUser.setExercise(exercise); - exerciseTeamUser.setTeam(team); - exerciseTeamUser.setUser(userRepository.findById(playerId).orElseThrow(ElementNotFoundException::new)); - exerciseTeamUserRepository.save(exerciseTeamUser); - }); + input + .getPlayersIds() + .forEach( + playerId -> { + ExerciseTeamUser exerciseTeamUser = new ExerciseTeamUser(); + exerciseTeamUser.setExercise(exercise); + exerciseTeamUser.setTeam(team); + exerciseTeamUser.setUser( + userRepository.findById(playerId).orElseThrow(ElementNotFoundException::new)); + exerciseTeamUserRepository.save(exerciseTeamUser); + }); return exercise; } @PutMapping(EXERCISE_URI + "/{exerciseId}/teams/{teamId}/players/remove") @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) - public Exercise removeExerciseTeamPlayers(@PathVariable String exerciseId, @PathVariable String teamId, + public Exercise removeExerciseTeamPlayers( + @PathVariable String exerciseId, + @PathVariable String teamId, @Valid @RequestBody ExerciseTeamPlayersEnableInput input) { Team team = teamRepository.findById(teamId).orElseThrow(ElementNotFoundException::new); Iterable teamUsers = userRepository.findAllById(input.getPlayersIds()); team.getUsers().removeAll(fromIterable(teamUsers)); teamRepository.save(team); - input.getPlayersIds().forEach(playerId -> { - ExerciseTeamUserId exerciseTeamUserId = new ExerciseTeamUserId(); - exerciseTeamUserId.setExerciseId(exerciseId); - exerciseTeamUserId.setTeamId(teamId); - exerciseTeamUserId.setUserId(playerId); - exerciseTeamUserRepository.deleteById(exerciseTeamUserId); - }); - return exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new);} + input + .getPlayersIds() + .forEach( + playerId -> { + ExerciseTeamUserId exerciseTeamUserId = new ExerciseTeamUserId(); + exerciseTeamUserId.setExerciseId(exerciseId); + exerciseTeamUserId.setTeamId(teamId); + exerciseTeamUserId.setUserId(playerId); + exerciseTeamUserRepository.deleteById(exerciseTeamUserId); + }); + return exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + } // endregion @@ -358,9 +393,10 @@ public Exercise duplicateExercise(@PathVariable @NotBlank final String exerciseI @PutMapping(EXERCISE_URI + "/{exerciseId}") @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) - public Exercise updateExerciseInformation(@PathVariable String exerciseId, - @Valid @RequestBody ExerciseUpdateInput input) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + public Exercise updateExerciseInformation( + @PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateInput input) { + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); exercise.setTags(iterableToSet(this.tagRepository.findAllById(input.getTagIds()))); exercise.setUpdateAttributes(input); return exerciseRepository.save(exercise); @@ -369,9 +405,11 @@ public Exercise updateExerciseInformation(@PathVariable String exerciseId, @PutMapping(EXERCISE_URI + "/{exerciseId}/start_date") @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) - public Exercise updateExerciseStart(@PathVariable String exerciseId, - @Valid @RequestBody ExerciseUpdateStartDateInput input) throws InputValidationException { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + public Exercise updateExerciseStart( + @PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateStartDateInput input) + throws InputValidationException { + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); if (!exercise.getStatus().equals(ExerciseStatus.SCHEDULED)) { String message = "Change date is only possible in scheduling state"; throw new InputValidationException("exercise_start_date", message); @@ -383,9 +421,10 @@ public Exercise updateExerciseStart(@PathVariable String exerciseId, @PutMapping(EXERCISE_URI + "/{exerciseId}/tags") @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) - public Exercise updateExerciseTags(@PathVariable String exerciseId, - @Valid @RequestBody ExerciseUpdateTagsInput input) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + public Exercise updateExerciseTags( + @PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateTagsInput input) { + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); exercise.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); return exerciseRepository.save(exercise); } @@ -393,9 +432,10 @@ public Exercise updateExerciseTags(@PathVariable String exerciseId, @PutMapping(EXERCISE_URI + "/{exerciseId}/logos") @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) - public Exercise updateExerciseLogos(@PathVariable String exerciseId, - @Valid @RequestBody ExerciseUpdateLogoInput input) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + public Exercise updateExerciseLogos( + @PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateLogoInput input) { + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); exercise.setLogoDark(documentRepository.findById(input.getLogoDark()).orElse(null)); exercise.setLogoLight(documentRepository.findById(input.getLogoLight()).orElse(null)); return exerciseRepository.save(exercise); @@ -404,9 +444,10 @@ public Exercise updateExerciseLogos(@PathVariable String exerciseId, @PutMapping(EXERCISE_URI + "/{exerciseId}/lessons") @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) - public Exercise updateExerciseLessons(@PathVariable String exerciseId, - @Valid @RequestBody LessonsInput input) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + public Exercise updateExerciseLessons( + @PathVariable String exerciseId, @Valid @RequestBody LessonsInput input) { + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); exercise.setLessonsAnonymized(input.isLessonsAnonymized()); return exerciseRepository.save(exercise); } @@ -425,103 +466,134 @@ public ExerciseDetails exercise(@PathVariable String exerciseId) { // We get the raw exercise RawExercise rawExercise = exerciseRepository.rawDetailsById(exerciseId); // We get the injects linked to this exercise - List rawInjects = injectRepository.findRawByIds( - rawExercise.getInject_ids().stream().distinct().toList()); + List rawInjects = + injectRepository.findRawByIds(rawExercise.getInject_ids().stream().distinct().toList()); // We get the tuple exercise/team/user - List listRawExerciseTeamUsers = exerciseTeamUserRepository.rawByExerciseIds( - List.of(exerciseId)); + List listRawExerciseTeamUsers = + exerciseTeamUserRepository.rawByExerciseIds(List.of(exerciseId)); // We get the objectives of this exercise List rawObjectives = objectiveRepository.rawByExerciseIds(List.of(exerciseId)); // We make a map of the Evaluations by objective - Map> mapEvaluationsByObjective = evaluationRepository - .rawByObjectiveIds(rawObjectives.stream() - .map(RawObjective::getObjective_id).toList()).stream() - .collect(Collectors.groupingBy(RawEvaluation::getEvaluation_objective)); + Map> mapEvaluationsByObjective = + evaluationRepository + .rawByObjectiveIds(rawObjectives.stream().map(RawObjective::getObjective_id).toList()) + .stream() + .collect(Collectors.groupingBy(RawEvaluation::getEvaluation_objective)); // We make a map of grants of users id by type of grant (Planner, Observer) - Map> rawGrants = grantRepository.rawByExerciseIds(List.of(exerciseId)).stream() - .collect(Collectors.groupingBy(RawGrant::getGrant_name)); + Map> rawGrants = + grantRepository.rawByExerciseIds(List.of(exerciseId)).stream() + .collect(Collectors.groupingBy(RawGrant::getGrant_name)); // We get all the kill chain phases - List killChainPhase = StreamSupport.stream( - killChainPhaseRepository.findAllById( - rawInjects.stream().flatMap(rawInject -> rawInject.getInject_kill_chain_phases().stream()).toList()) - .spliterator(), + List killChainPhase = + StreamSupport.stream( + killChainPhaseRepository + .findAllById( + rawInjects.stream() + .flatMap(rawInject -> rawInject.getInject_kill_chain_phases().stream()) + .toList()) + .spliterator(), false) .collect(Collectors.toList()); // We create objectives and fill them with evaluations - List objectives = rawObjectives.stream().map(rawObjective -> { - Objective objective = new Objective(); - if (mapEvaluationsByObjective.get(rawObjective.getObjective_id()) != null) { - objective.setEvaluations(mapEvaluationsByObjective.get(rawObjective.getObjective_id()).stream().map( - rawEvaluation -> { - Evaluation evaluation = new Evaluation(); - evaluation.setId(rawEvaluation.getEvaluation_id()); - evaluation.setScore(rawEvaluation.getEvaluation_score()); - return evaluation; - }) - .toList()); - } - return objective;}) - .toList(); - - List listExerciseTeamUsers = listRawExerciseTeamUsers.stream().map( - ExerciseTeamUser::fromRawExerciseTeamUser - ).toList(); + List objectives = + rawObjectives.stream() + .map( + rawObjective -> { + Objective objective = new Objective(); + if (mapEvaluationsByObjective.get(rawObjective.getObjective_id()) != null) { + objective.setEvaluations( + mapEvaluationsByObjective.get(rawObjective.getObjective_id()).stream() + .map( + rawEvaluation -> { + Evaluation evaluation = new Evaluation(); + evaluation.setId(rawEvaluation.getEvaluation_id()); + evaluation.setScore(rawEvaluation.getEvaluation_score()); + return evaluation; + }) + .toList()); + } + return objective; + }) + .toList(); + + List listExerciseTeamUsers = + listRawExerciseTeamUsers.stream().map(ExerciseTeamUser::fromRawExerciseTeamUser).toList(); // From the raw injects, we recreate Injects with minimal objects for calculations - List injects = rawInjects.stream().map(rawInject -> { - Inject inject = new Inject(); - if (rawInject.getInject_scenario() != null) { - inject.setScenario(new Scenario()); - inject.getScenario().setId(rawInject.getInject_scenario()); - } - // We set the communications - inject.setCommunications(rawInject.getInject_communications().stream().map(com -> { - Communication communication = new Communication(); - communication.setId(com); - return communication;}) - .toList()); - // We set the status too - if (rawInject.getStatus_name() != null) { - InjectStatus injectStatus = new InjectStatus(); - injectStatus.setName(ExecutionStatus.valueOf(rawInject.getStatus_name())); - inject.setStatus(injectStatus); - } - // We recreate an exercise out of the raw exercise - Exercise exercise = new Exercise(); - exercise.setStatus(ExerciseStatus.valueOf(rawExercise.getExercise_status())); - exercise.setStart(rawExercise.getExercise_start_date()); - exercise.setPauses( - // We set the pauses as they are used for calculations - pauseRepository.rawAllForExercise(exerciseId).stream().map(rawPause -> { - Pause pause = new Pause(); - pause.setExercise(new Exercise()); - pause.getExercise().setId(exerciseId); - pause.setDate(rawPause.getPause_date()); - pause.setId(rawPause.getPause_id()); - pause.setDuration(rawPause.getPause_duration()); - return pause; - }) - .toList()); - exercise.setCurrentPause(rawExercise.getExercise_pause_date()); - inject.setExercise(exercise); - return inject;}) - .toList(); + List injects = + rawInjects.stream() + .map( + rawInject -> { + Inject inject = new Inject(); + if (rawInject.getInject_scenario() != null) { + inject.setScenario(new Scenario()); + inject.getScenario().setId(rawInject.getInject_scenario()); + } + // We set the communications + inject.setCommunications( + rawInject.getInject_communications().stream() + .map( + com -> { + Communication communication = new Communication(); + communication.setId(com); + return communication; + }) + .toList()); + // We set the status too + if (rawInject.getStatus_name() != null) { + InjectStatus injectStatus = new InjectStatus(); + injectStatus.setName(ExecutionStatus.valueOf(rawInject.getStatus_name())); + inject.setStatus(injectStatus); + } + // We recreate an exercise out of the raw exercise + Exercise exercise = new Exercise(); + exercise.setStatus(ExerciseStatus.valueOf(rawExercise.getExercise_status())); + exercise.setStart(rawExercise.getExercise_start_date()); + exercise.setPauses( + // We set the pauses as they are used for calculations + pauseRepository.rawAllForExercise(exerciseId).stream() + .map( + rawPause -> { + Pause pause = new Pause(); + pause.setExercise(new Exercise()); + pause.getExercise().setId(exerciseId); + pause.setDate(rawPause.getPause_date()); + pause.setId(rawPause.getPause_id()); + pause.setDuration(rawPause.getPause_duration()); + return pause; + }) + .toList()); + exercise.setCurrentPause(rawExercise.getExercise_pause_date()); + inject.setExercise(exercise); + return inject; + }) + .toList(); // We create an ExerciseDetails object and populate it - ExerciseDetails detail = ExerciseDetails.fromRawExercise(rawExercise, injects, listExerciseTeamUsers, objectives); + ExerciseDetails detail = + ExerciseDetails.fromRawExercise(rawExercise, injects, listExerciseTeamUsers, objectives); detail.setPlatforms( - rawInjects.stream().flatMap(inject -> inject.getInject_platforms().stream()).distinct().toList()); + rawInjects.stream() + .flatMap(inject -> inject.getInject_platforms().stream()) + .distinct() + .toList()); detail.setCommunicationsNumber( - rawInjects.stream().mapToLong(rawInject -> rawInject.getInject_communications().size()).sum()); + rawInjects.stream() + .mapToLong(rawInject -> rawInject.getInject_communications().size()) + .sum()); detail.setKillChainPhases(killChainPhase); if (rawGrants.get(Grant.GRANT_TYPE.OBSERVER.name()) != null) { - detail.setObservers(rawGrants.get(Grant.GRANT_TYPE.OBSERVER.name()).stream().map(RawGrant::getUser_id) - .collect(Collectors.toSet())); + detail.setObservers( + rawGrants.get(Grant.GRANT_TYPE.OBSERVER.name()).stream() + .map(RawGrant::getUser_id) + .collect(Collectors.toSet())); } if (rawGrants.get(Grant.GRANT_TYPE.PLANNER.name()) != null) { - detail.setPlanners(rawGrants.get(Grant.GRANT_TYPE.PLANNER.name()).stream().map(RawGrant::getUser_id) - .collect(Collectors.toSet())); + detail.setPlanners( + rawGrants.get(Grant.GRANT_TYPE.PLANNER.name()).stream() + .map(RawGrant::getUser_id) + .collect(Collectors.toSet())); } return detail; @@ -549,11 +621,15 @@ public List injectResults( @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) public Exercise deleteDocument(@PathVariable String exerciseId, @PathVariable String documentId) { - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); exercise.setUpdatedAt(now()); - Document doc = documentRepository.findById(documentId).orElseThrow(ElementNotFoundException::new); - Set docExercises = doc.getExercises().stream().filter(ex -> !ex.getId().equals(exerciseId)) - .collect(Collectors.toSet()); + Document doc = + documentRepository.findById(documentId).orElseThrow(ElementNotFoundException::new); + Set docExercises = + doc.getExercises().stream() + .filter(ex -> !ex.getId().equals(exerciseId)) + .collect(Collectors.toSet()); if (docExercises.isEmpty()) { // Document is no longer associate to any exercise, delete it documentRepository.delete(doc); @@ -572,18 +648,20 @@ public Exercise deleteDocument(@PathVariable String exerciseId, @PathVariable St @PreAuthorize("isExercisePlanner(#exerciseId)") @Transactional(rollbackOn = Exception.class) public Exercise changeExerciseStatus( - @PathVariable String exerciseId, - @Valid @RequestBody ExerciseUpdateStatusInput input) { + @PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateStatusInput input) { ExerciseStatus status = input.getStatus(); - Exercise exercise = this.exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + Exercise exercise = + this.exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); // Check if next status is possible List nextPossibleStatus = exercise.nextPossibleStatus(); if (!nextPossibleStatus.contains(status)) { - throw new UnsupportedOperationException("Exercise cant support moving to status " + status.name()); + throw new UnsupportedOperationException( + "Exercise cant support moving to status " + status.name()); } // In case of rescheduled of an exercise. boolean isCloseState = - ExerciseStatus.CANCELED.equals(exercise.getStatus()) || ExerciseStatus.FINISHED.equals(exercise.getStatus()); + ExerciseStatus.CANCELED.equals(exercise.getStatus()) + || ExerciseStatus.FINISHED.equals(exercise.getStatus()); if (isCloseState && ExerciseStatus.SCHEDULED.equals(status)) { exercise.setStart(null); exercise.setEnd(null); @@ -592,31 +670,44 @@ public Exercise changeExerciseStatus( pauseRepository.deleteAll(pauseRepository.findAllForExercise(exerciseId)); // Reset injects outcome, communications and expectations this.injectStatusRepository.deleteAllById( - exercise.getInjects() - .stream() + exercise.getInjects().stream() .map(Inject::getStatus) .map(i -> i.map(InjectStatus::getId).orElse("")) .toList()); exercise.getInjects().forEach(Inject::clean); // Reset lessons learned answers - List lessonsAnswers = lessonsCategoryRepository - .findAll(LessonsCategorySpecification.fromExercise(exerciseId)).stream().flatMap( - lessonsCategory -> lessonsQuestionRepository.findAll( - LessonsQuestionSpecification.fromCategory(lessonsCategory.getId())).stream().flatMap( - lessonsQuestion -> lessonsAnswerRepository.findAll( - LessonsAnswerSpecification.fromQuestion(lessonsQuestion.getId())).stream())).toList(); + List lessonsAnswers = + lessonsCategoryRepository + .findAll(LessonsCategorySpecification.fromExercise(exerciseId)) + .stream() + .flatMap( + lessonsCategory -> + lessonsQuestionRepository + .findAll( + LessonsQuestionSpecification.fromCategory(lessonsCategory.getId())) + .stream() + .flatMap( + lessonsQuestion -> + lessonsAnswerRepository + .findAll( + LessonsAnswerSpecification.fromQuestion( + lessonsQuestion.getId())) + .stream())) + .toList(); lessonsAnswerRepository.deleteAll(lessonsAnswers); // Delete exercise transient files (communications, ...) fileService.deleteDirectory(exerciseId); } // In case of manual start - if (ExerciseStatus.SCHEDULED.equals(exercise.getStatus()) && ExerciseStatus.RUNNING.equals(status)) { + if (ExerciseStatus.SCHEDULED.equals(exercise.getStatus()) + && ExerciseStatus.RUNNING.equals(status)) { Instant nextMinute = now().truncatedTo(MINUTES).plus(1, MINUTES); exercise.setStart(nextMinute); } // If exercise move from pause to running state, // we log the pause date to be able to recompute inject dates. - if (ExerciseStatus.PAUSED.equals(exercise.getStatus()) && ExerciseStatus.RUNNING.equals(status)) { + if (ExerciseStatus.PAUSED.equals(exercise.getStatus()) + && ExerciseStatus.RUNNING.equals(status)) { Instant lastPause = exercise.getCurrentPause().orElseThrow(ElementNotFoundException::new); exercise.setCurrentPause(null); Pause pause = new Pause(); @@ -626,11 +717,13 @@ public Exercise changeExerciseStatus( pauseRepository.save(pause); } // If pause is asked, just set the pause date. - if (ExerciseStatus.RUNNING.equals(exercise.getStatus()) && ExerciseStatus.PAUSED.equals(status)) { + if (ExerciseStatus.RUNNING.equals(exercise.getStatus()) + && ExerciseStatus.PAUSED.equals(status)) { exercise.setCurrentPause(Instant.now()); } // Cancelation - if (ExerciseStatus.RUNNING.equals(exercise.getStatus()) && ExerciseStatus.CANCELED.equals(status)) { + if (ExerciseStatus.RUNNING.equals(exercise.getStatus()) + && ExerciseStatus.CANCELED.equals(status)) { exercise.setEnd(now()); } exercise.setUpdatedAt(now()); @@ -646,31 +739,31 @@ public List exercises() { @LogExecutionTime @PostMapping(EXERCISE_URI + "/search") - public Page exercises(@RequestBody @Valid final SearchPaginationInput searchPaginationInput) { + public Page exercises( + @RequestBody @Valid final SearchPaginationInput searchPaginationInput) { Map> joinMap = new HashMap<>(); if (currentUser().isAdmin()) { return buildPaginationCriteriaBuilder( - (Specification specification, Specification specificationCount, Pageable pageable) -> this.exerciseService.exercises( - specification, - specificationCount, - pageable, - joinMap - ), + (Specification specification, + Specification specificationCount, + Pageable pageable) -> + this.exerciseService.exercises(specification, specificationCount, pageable, joinMap), searchPaginationInput, Exercise.class); } else { return buildPaginationCriteriaBuilder( - (Specification specification, Specification specificationCount, Pageable pageable) -> this.exerciseService.exercises( - findGrantedFor(currentUser().getId()).and(specification), - findGrantedFor(currentUser().getId()).and(specificationCount), - pageable, - joinMap - ), + (Specification specification, + Specification specificationCount, + Pageable pageable) -> + this.exerciseService.exercises( + findGrantedFor(currentUser().getId()).and(specification), + findGrantedFor(currentUser().getId()).and(specificationCount), + pageable, + joinMap), searchPaginationInput, - Exercise.class - ); + Exercise.class); } } @@ -691,33 +784,41 @@ public Iterable exerciseCommunications(@PathVariable String exerc @GetMapping("/api/communications/attachment") // @PreAuthorize("isExerciseObserver(#exerciseId)") - public void downloadAttachment(@RequestParam String file, HttpServletResponse response) throws IOException { - FileContainer fileContainer = fileService.getFileContainer(file).orElseThrow(ElementNotFoundException::new); - response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileContainer.getName()); + public void downloadAttachment(@RequestParam String file, HttpServletResponse response) + throws IOException { + FileContainer fileContainer = + fileService.getFileContainer(file).orElseThrow(ElementNotFoundException::new); + response.addHeader( + HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileContainer.getName()); response.addHeader(HttpHeaders.CONTENT_TYPE, fileContainer.getContentType()); response.setStatus(HttpServletResponse.SC_OK); - fileContainer.getInputStream().transferTo(response.getOutputStream());} + fileContainer.getInputStream().transferTo(response.getOutputStream()); + } // endregion // region import/export @GetMapping(EXERCISE_URI + "/{exerciseId}/export") @PreAuthorize("isExerciseObserver(#exerciseId)") - public void exerciseExport(@NotBlank @PathVariable final String exerciseId, + public void exerciseExport( + @NotBlank @PathVariable final String exerciseId, @RequestParam(required = false) final boolean isWithTeams, @RequestParam(required = false) final boolean isWithPlayers, - @RequestParam(required = false) final boolean isWithVariableValues, HttpServletResponse response) + @RequestParam(required = false) final boolean isWithVariableValues, + HttpServletResponse response) throws IOException { // Setup the mapper for export List documentIds = new ArrayList<>(); ObjectMapper objectMapper = mapper.copy(); if (!isWithPlayers) { - objectMapper.addMixIn(ExerciseFileExport.class, ExerciseExportMixins.ExerciseFileExport.class); + objectMapper.addMixIn( + ExerciseFileExport.class, ExerciseExportMixins.ExerciseFileExport.class); } // Start exporting exercise ExerciseFileExport importExport = new ExerciseFileExport(); importExport.setVersion(1); - Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + Exercise exercise = + exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); objectMapper.addMixIn(Exercise.class, ExerciseExportMixins.Exercise.class); // Build the export importExport.setExercise(exercise); @@ -734,30 +835,32 @@ public void exerciseExport(@NotBlank @PathVariable final String exerciseId, importExport.setLessonsCategories(lessonsCategories); objectMapper.addMixIn(LessonsCategory.class, ExerciseExportMixins.LessonsCategory.class); // Lessons questions - List lessonsQuestions = lessonsCategories.stream() - .flatMap(category -> category.getQuestions().stream()).toList(); + List lessonsQuestions = + lessonsCategories.stream().flatMap(category -> category.getQuestions().stream()).toList(); importExport.setLessonsQuestions(lessonsQuestions); objectMapper.addMixIn(LessonsQuestion.class, ExerciseExportMixins.LessonsQuestion.class); if (isWithTeams) { // Teams List teams = exercise.getTeams(); importExport.setTeams(teams); - objectMapper.addMixIn(Team.class, + objectMapper.addMixIn( + Team.class, isWithPlayers ? ExerciseExportMixins.Team.class : ExerciseExportMixins.EmptyTeam.class); exerciseTags.addAll(teams.stream().flatMap(team -> team.getTags().stream()).toList()); } if (isWithPlayers) { // players - List players = exercise.getTeams().stream().flatMap(team -> team.getUsers().stream()).distinct().toList(); + List players = + exercise.getTeams().stream() + .flatMap(team -> team.getUsers().stream()) + .distinct() + .toList(); exerciseTags.addAll(players.stream().flatMap(user -> user.getTags().stream()).toList()); importExport.setUsers(players); objectMapper.addMixIn(User.class, ExerciseExportMixins.User.class); // organizations - List organizations = players.stream() - .map(User::getOrganization) - .filter(Objects::nonNull) - .distinct() - .toList(); + List organizations = + players.stream().map(User::getOrganization).filter(Objects::nonNull).distinct().toList(); exerciseTags.addAll(organizations.stream().flatMap(org -> org.getTags().stream()).toList()); importExport.setOrganizations(organizations); objectMapper.addMixIn(Organization.class, ExerciseExportMixins.Organization.class); @@ -768,22 +871,31 @@ public void exerciseExport(@NotBlank @PathVariable final String exerciseId, importExport.setInjects(injects); objectMapper.addMixIn(Inject.class, ExerciseExportMixins.Inject.class); // Documents - exerciseTags.addAll(exercise.getDocuments().stream().flatMap(doc -> doc.getTags().stream()).toList()); + exerciseTags.addAll( + exercise.getDocuments().stream().flatMap(doc -> doc.getTags().stream()).toList()); // Articles / Channels List
articles = exercise.getArticles(); importExport.setArticles(articles); objectMapper.addMixIn(Article.class, ExerciseExportMixins.Article.class); List channels = articles.stream().map(Article::getChannel).distinct().toList(); - documentIds.addAll(channels.stream().flatMap(channel -> channel.getLogos().stream()).map(Document::getId).toList()); + documentIds.addAll( + channels.stream() + .flatMap(channel -> channel.getLogos().stream()) + .map(Document::getId) + .toList()); importExport.setChannels(channels); objectMapper.addMixIn(Channel.class, ExerciseExportMixins.Channel.class); // Challenges List challenges = fromIterable(challengeService.getExerciseChallenges(exerciseId)); importExport.setChallenges(challenges); documentIds.addAll( - challenges.stream().flatMap(challenge -> challenge.getDocuments().stream()).map(Document::getId).toList()); + challenges.stream() + .flatMap(challenge -> challenge.getDocuments().stream()) + .map(Document::getId) + .toList()); objectMapper.addMixIn(Challenge.class, ExerciseExportMixins.Challenge.class); - exerciseTags.addAll(challenges.stream().flatMap(challenge -> challenge.getTags().stream()).toList()); + exerciseTags.addAll( + challenges.stream().flatMap(challenge -> challenge.getTags().stream()).toList()); // Tags importExport.setTags(exerciseTags.stream().distinct().toList()); objectMapper.addMixIn(Tag.class, ExerciseExportMixins.Tag.class); @@ -796,13 +908,14 @@ public void exerciseExport(@NotBlank @PathVariable final String exerciseId, objectMapper.addMixIn(Variable.class, VariableMixin.class); } // Build the response - String infos = "(" - + (isWithTeams ? "with_teams" : "no_teams") - + " & " - + (isWithPlayers ? "with_players" : "no_players") - + " & " - + (isWithVariableValues ? "with_variable_values" : "no_variable_values") - +")"; + String infos = + "(" + + (isWithTeams ? "with_teams" : "no_teams") + + " & " + + (isWithPlayers ? "with_players" : "no_players") + + " & " + + (isWithVariableValues ? "with_variable_values" : "no_variable_values") + + ")"; String zipName = (exercise.getName() + "_" + now().toString()) + "_" + infos + ".zip"; response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + zipName); response.addHeader(HttpHeaders.CONTENT_TYPE, "application/zip"); @@ -814,22 +927,26 @@ public void exerciseExport(@NotBlank @PathVariable final String exerciseId, zipExport.write(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(importExport)); zipExport.closeEntry(); // Add the documents - documentIds.stream().distinct().forEach(docId -> { - Document doc = documentRepository.findById(docId).orElseThrow(ElementNotFoundException::new); - Optional docStream = fileService.getFile(doc); - if (docStream.isPresent()) { - try { - ZipEntry zipDoc = new ZipEntry(doc.getTarget()); - zipDoc.setComment(EXPORT_ENTRY_ATTACHMENT); - byte[] data = docStream.get().readAllBytes(); - zipExport.putNextEntry(zipDoc); - zipExport.write(data); - zipExport.closeEntry(); - } catch (IOException e) { - LOGGER.log(Level.SEVERE, e.getMessage(), e); - } - } - }); + documentIds.stream() + .distinct() + .forEach( + docId -> { + Document doc = + documentRepository.findById(docId).orElseThrow(ElementNotFoundException::new); + Optional docStream = fileService.getFile(doc); + if (docStream.isPresent()) { + try { + ZipEntry zipDoc = new ZipEntry(doc.getTarget()); + zipDoc.setComment(EXPORT_ENTRY_ATTACHMENT); + byte[] data = docStream.get().readAllBytes(); + zipExport.putNextEntry(zipDoc); + zipExport.write(data); + zipExport.closeEntry(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + } + }); zipExport.finish(); zipExport.close(); } diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/service/ExerciseService.java b/openbas-api/src/main/java/io/openbas/rest/exercise/service/ExerciseService.java index 7c725f136c..7ca5044d96 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/service/ExerciseService.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/service/ExerciseService.java @@ -3,7 +3,7 @@ import static io.openbas.config.SessionHelper.currentUser; import static io.openbas.database.criteria.GenericCriteria.countQuery; import static io.openbas.utils.Constants.ARTICLES; -import static io.openbas.utils.JpaUtils.createJoinArrayAggOnId; +import static io.openbas.utils.JpaUtils.arrayAggOnId; import static io.openbas.utils.StringUtils.duplicateString; import static io.openbas.utils.pagination.SortUtilsCriteriaBuilder.toSortCriteriaBuilder; import static java.time.Instant.now; @@ -45,19 +45,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import java.time.Instant; -import java.util.*; -import java.util.stream.Collectors; - -import static io.openbas.config.SessionHelper.currentUser; -import static io.openbas.database.criteria.GenericCriteria.countQuery; -import static io.openbas.utils.Constants.ARTICLES; -import static io.openbas.utils.JpaUtils.arrayAggOnId; -import static io.openbas.utils.StringUtils.duplicateString; -import static io.openbas.utils.pagination.SortUtilsCriteriaBuilder.toSortCriteriaBuilder; -import static java.time.Instant.now; -import static java.util.Optional.ofNullable; - @RequiredArgsConstructor @Validated @Service @@ -154,7 +141,10 @@ public Page exercises( // -- SELECT -- - private void select(CriteriaBuilder cb, CriteriaQuery cq, Root exerciseRoot, + private void select( + CriteriaBuilder cb, + CriteriaQuery cq, + Root exerciseRoot, Map> joinMap) { // Array aggregations Join exerciseTagsJoin = exerciseRoot.join("tags", JoinType.LEFT); diff --git a/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioExerciseApi.java b/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioExerciseApi.java index 09f695db15..8f53f1ebb5 100644 --- a/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioExerciseApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/scenario/ScenarioExerciseApi.java @@ -14,6 +14,8 @@ import jakarta.persistence.criteria.Join; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; +import java.util.HashMap; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; @@ -21,14 +23,6 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; -import java.util.HashMap; -import java.util.Map; - -import static io.openbas.database.model.User.ROLE_USER; -import static io.openbas.database.specification.ExerciseSpecification.fromScenario; -import static io.openbas.rest.scenario.ScenarioApi.SCENARIO_URI; -import static io.openbas.utils.pagination.PaginationUtils.buildPaginationCriteriaBuilder; - @RestController @Secured(ROLE_USER) @RequiredArgsConstructor @@ -52,12 +46,14 @@ public Iterable scenarioExercises( @RequestBody @Valid final SearchPaginationInput searchPaginationInput) { Map> joinMap = new HashMap<>(); return buildPaginationCriteriaBuilder( - (Specification specification, Specification specificationCount, Pageable pageable) -> this.exerciseService.exercises( - fromScenario(scenarioId).and(specification), - fromScenario(scenarioId).and(specificationCount), - pageable, - joinMap - ), + (Specification specification, + Specification specificationCount, + Pageable pageable) -> + this.exerciseService.exercises( + fromScenario(scenarioId).and(specification), + fromScenario(scenarioId).and(specificationCount), + pageable, + joinMap), searchPaginationInput, Exercise.class); }