From 28284a461c4b4a34c7730bff999b62465e7e2559 Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Wed, 25 Sep 2024 09:01:52 +0200 Subject: [PATCH 01/14] [backend|frontend]Add the ability to customize the expiration time for expectations --- .../ExpectationsExpirationManagerService.java | 135 ++++++++++-------- ...d_column_expiration_time_expectations.java | 32 +++++ .../injects/expectations/Expectation.ts | 7 + .../expectations/ExpectationFormCreate.tsx | 63 +++++++- .../expectations/ExpectationFormUpdate.tsx | 60 +++++++- .../expectations/ExpectationFormUtils.ts | 9 +- .../expectations/ExpectationPopover.tsx | 17 ++- .../expectations/InjectAddExpectation.tsx | 16 ++- openbas-front/src/utils/Localization.js | 2 + openbas-front/src/utils/api-types.d.ts | 2 + .../database/model/InjectExpectation.java | 6 + 11 files changed, 266 insertions(+), 83 deletions(-) create mode 100644 openbas-api/src/main/java/io/openbas/migration/V3_40__Add_column_expiration_time_expectations.java diff --git a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java index 64fc861136..476256d357 100644 --- a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java +++ b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java @@ -20,74 +20,83 @@ @Log public class ExpectationsExpirationManagerService { - private final InjectExpectationService injectExpectationService; - private final ExpectationsExpirationManagerConfig config; + private final InjectExpectationService injectExpectationService; + private final ExpectationsExpirationManagerConfig config; - @Transactional(rollbackFor = Exception.class) - public void computeExpectations() { - List expectations = this.injectExpectationService.expectationsNotFill(); - if (!expectations.isEmpty()) { - this.computeExpectationsForAssets(expectations); - this.computeExpectationsForAssetGroups(expectations); - this.computeExpectations(expectations); - } + @Transactional(rollbackFor = Exception.class) + public void computeExpectations() { + List expectations = this.injectExpectationService.expectationsNotFill(); + if (!expectations.isEmpty()) { + this.computeExpectationsForAssets(expectations); + this.computeExpectationsForAssetGroups(expectations); + this.computeExpectations(expectations); } + } - // -- PRIVATE -- + // -- PRIVATE -- - private void computeExpectations(@NotNull final List expectations) { - List expectationAssets = expectations.stream().toList(); - expectationAssets.forEach((expectation) -> { - // Maximum time for detection - if (isExpired(expectation, this.config.getExpirationTimeInMinute())) { - String result = computeFailedMessage(expectation.getType()); - this.injectExpectationService.computeExpectation( - expectation, - this.config.getId(), - "collector", - PRODUCT_NAME, - result, - false - ); - } - }); - } + private void computeExpectations(@NotNull final List expectations) { + List expectationAssets = expectations.stream().toList(); + expectationAssets.forEach((expectation) -> { + Long userExpirationTime = expectation.getExpirationTime(); + if (userExpirationTime != null) { + // Maximum time for detection + if (isExpired(expectation, Math.toIntExact(userExpirationTime / 60))) { + String result = computeFailedMessage(expectation.getType()); + this.injectExpectationService.computeExpectation( + expectation, + this.config.getId(), + "collector", + PRODUCT_NAME, + result, + false + ); + } + } - private void computeExpectationsForAssets(@NotNull final List expectations) { - List expectationAssets = expectations.stream().filter(e -> e.getAsset() != null).toList(); - expectationAssets.forEach((expectation) -> { - // Maximum time for detection - if (isExpired(expectation, this.config.getAssetExpirationTimeInMinute())) { - String result = computeFailedMessage(expectation.getType()); - this.injectExpectationService.computeExpectation( - expectation, - this.config.getId(), - "collector", - PRODUCT_NAME, - result, - false - ); - } - }); - } + }); + } - private void computeExpectationsForAssetGroups(@NotNull final List expectations) { - List expectationAssetGroups = expectations.stream().filter(e -> e.getAssetGroup() != null).toList(); - expectationAssetGroups.forEach((expectationAssetGroup -> { - List expectationAssets = this.injectExpectationService.expectationsForAssets( - expectationAssetGroup.getInject(), expectationAssetGroup.getAssetGroup(), expectationAssetGroup.getType() - ); - // Every expectation assets are filled - if (expectationAssets.stream().noneMatch(e -> e.getResults().isEmpty())) { - this.injectExpectationService.computeExpectationGroup( - expectationAssetGroup, - expectationAssets, - this.config.getId(), - "collector", - PRODUCT_NAME - ); - } - })); - } + private void computeExpectationsForAssets(@NotNull final List expectations) { + List expectationAssets = expectations.stream().filter(e -> e.getAsset() != null).toList(); + expectationAssets.forEach((expectation) -> { + Long userExpirationTime = expectation.getExpirationTime(); + if (userExpirationTime != null) { + // Maximum time for detection + if (isExpired(expectation, Math.toIntExact(userExpirationTime / 60))) { + String result = computeFailedMessage(expectation.getType()); + this.injectExpectationService.computeExpectation( + expectation, + this.config.getId(), + "collector", + PRODUCT_NAME, + result, + false + ); + } + } + + }); + } + + private void computeExpectationsForAssetGroups(@NotNull final List expectations) { + List expectationAssetGroups = expectations.stream().filter(e -> e.getAssetGroup() != null) + .toList(); + expectationAssetGroups.forEach((expectationAssetGroup -> { + List expectationAssets = this.injectExpectationService.expectationsForAssets( + expectationAssetGroup.getInject(), expectationAssetGroup.getAssetGroup(), expectationAssetGroup.getType() + ); + // Every expectation assets are filled + if (expectationAssets.stream().noneMatch(e -> e.getResults().isEmpty())) { + this.injectExpectationService.computeExpectationGroup( + expectationAssetGroup, + expectationAssets, + this.config.getId(), + "collector", + PRODUCT_NAME + ); + } + })); + } } diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_40__Add_column_expiration_time_expectations.java b/openbas-api/src/main/java/io/openbas/migration/V3_40__Add_column_expiration_time_expectations.java new file mode 100644 index 0000000000..0472813e00 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/migration/V3_40__Add_column_expiration_time_expectations.java @@ -0,0 +1,32 @@ +package io.openbas.migration; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; + +import java.sql.Connection; +import java.sql.Statement; + +public class V3_40__Add_column_expiration_time_expectations extends BaseJavaMigration { + + @Override + public void migrate(Context context) throws Exception { + Statement select = context.getConnection().createStatement(); + Connection connection = context.getConnection(); + Statement statement = connection.createStatement(); + long technicalMinutesExpirationTime = 60L; + long manualMinutesExpirationTime = 360L; + + select.execute("ALTER TABLE injects_expectations ADD inject_expiration_time bigint;"); + select.execute( + "UPDATE injects_expectations SET inject_expiration_time = " + technicalMinutesExpirationTime + " " + + "WHERE inject_expectation_type = 'DETECTION' OR inject_expectation_type = 'PREVENTION';"); + + select.execute( + "UPDATE injects_expectations SET inject_expiration_time = " + manualMinutesExpirationTime + " " + + "WHERE inject_expectation_type = 'MANUAL' OR inject_expectation_type = 'CHALLENGE' " + + "OR inject_expectation_type = 'ARTICLE' OR inject_expectation_type = 'DOCUMENT' OR inject_expectation_type = 'TEXT';"); + + select.execute( + "ALTER TABLE injects_expectations ALTER COLUMN inject_expiration_time SET NOT NULL;"); + } +} diff --git a/openbas-front/src/admin/components/common/injects/expectations/Expectation.ts b/openbas-front/src/admin/components/common/injects/expectations/Expectation.ts index c1a2b677b1..6b72376c13 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/Expectation.ts +++ b/openbas-front/src/admin/components/common/injects/expectations/Expectation.ts @@ -14,6 +14,13 @@ export interface ExpectationInput { expectation_description?: string; expectation_score: number; expectation_expectation_group: boolean; + expectation_expiration_time: number; +} + +export interface ExpectationInputForm extends Omit { + expiration_time_days: number; + expiration_time_hours: number; + expiration_time_minutes: number; } export enum ExpectationType { diff --git a/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormCreate.tsx b/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormCreate.tsx index 3c5197d3f3..3d2cf79032 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormCreate.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormCreate.tsx @@ -1,14 +1,15 @@ import React, { FunctionComponent, SyntheticEvent, useEffect } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { Alert, Button, InputLabel, MenuItem, Select as MUISelect, TextField as MuiTextField, Typography } from '@mui/material'; +import { Alert, Button, InputLabel, MenuItem, Select as MUISelect, TextField, TextField as MuiTextField, Typography } from '@mui/material'; import { makeStyles } from '@mui/styles'; -import type { ExpectationInput } from './Expectation'; +import { ExpectationInput, ExpectationInputForm } from './Expectation'; import { formProps, infoMessage } from './ExpectationFormUtils'; import { useFormatter } from '../../../../../components/i18n'; import type { Theme } from '../../../../../components/Theme'; import ExpectationGroupField from './field/ExpectationGroupField'; import { isTechnicalExpectation } from './ExpectationUtils'; import ScaleBar from '../../../../../components/scalebar/ScaleBar'; +import { splitDuration } from '../../../../../utils/Time'; const useStyles = makeStyles((theme: Theme) => ({ marginTop_2: { @@ -20,11 +21,26 @@ const useStyles = makeStyles((theme: Theme) => ({ gap: theme.spacing(2), marginTop: theme.spacing(2), }, + duration: { + marginTop: 20, + width: '100%', + display: 'flex', + justifyContent: 'space-between', + border: `1px solid ${theme.palette.primary.main}`, + borderRadius: 4, + padding: 15, + }, + trigger: { + fontFamily: 'Consolas, monaco, monospace', + fontSize: 12, + paddingTop: 15, + color: theme.palette.primary.main, + }, })); interface Props { predefinedExpectations: ExpectationInput[]; - onSubmit: SubmitHandler; + onSubmit: SubmitHandler; handleClose: () => void; } @@ -45,6 +61,7 @@ const ExpectationFormCreate: FunctionComponent = ({ expectation_description: predefinedExpectation.expectation_description ?? '', expectation_score: predefinedExpectation.expectation_score > 0 ? predefinedExpectation.expectation_score : 100, expectation_expectation_group: predefinedExpectation.expectation_expectation_group ?? false, + expectation_expiration_time: predefinedExpectation.expectation_expiration_time, }; } return { @@ -53,11 +70,19 @@ const ExpectationFormCreate: FunctionComponent = ({ expectation_description: '', expectation_score: 100, expectation_expectation_group: false, + expectation_expiration_time: 3600, }; }; const predefinedTypes = predefinedExpectations.map((e) => e.expectation_type); - const initialValues = computeValuesFromType(predefinedTypes[0]); + const valuesFromComputedTypes = computeValuesFromType(predefinedTypes[0]); + const expirationTime = splitDuration(valuesFromComputedTypes.expectation_expiration_time || 0); + const initialValues: ExpectationInputForm = { + ...valuesFromComputedTypes, + expiration_time_days: parseInt(expirationTime.days, 10), + expiration_time_hours: parseInt(expirationTime.hours, 10), + expiration_time_minutes: parseInt(expirationTime.minutes, 10), + }; const { register, @@ -66,8 +91,8 @@ const ExpectationFormCreate: FunctionComponent = ({ watch, reset, getValues, - control, - } = useForm(formProps(initialValues, t)); + } = useForm(formProps(initialValues, t)); + const { control } = useForm(); const watchType = watch('expectation_type'); const handleSubmitWithoutPropagation = (e: SyntheticEvent) => { @@ -128,6 +153,32 @@ const ExpectationFormCreate: FunctionComponent = ({ } inputProps={register('expectation_description')} /> +
+
+ {t('Expiration time')} +
+ + + +
{t('Scores')} diff --git a/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormUpdate.tsx b/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormUpdate.tsx index 5b9118844d..728d440dfc 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormUpdate.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormUpdate.tsx @@ -1,13 +1,14 @@ import React, { FunctionComponent, SyntheticEvent } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; -import { Alert, Button, InputLabel, MenuItem, Select as MUISelect, TextField as MuiTextField } from '@mui/material'; +import { Alert, Button, InputLabel, MenuItem, Select as MUISelect, TextField, TextField as MuiTextField } from '@mui/material'; import { makeStyles } from '@mui/styles'; import { formProps, infoMessage } from './ExpectationFormUtils'; -import type { ExpectationInput } from './Expectation'; +import { ExpectationInput, ExpectationInputForm } from './Expectation'; import { useFormatter } from '../../../../../components/i18n'; import type { Theme } from '../../../../../components/Theme'; import ExpectationGroupField from './field/ExpectationGroupField'; import { isTechnicalExpectation } from './ExpectationUtils'; +import { splitDuration } from '../../../../../utils/Time'; const useStyles = makeStyles((theme: Theme) => ({ marginTop_2: { @@ -19,10 +20,25 @@ const useStyles = makeStyles((theme: Theme) => ({ gap: theme.spacing(2), marginTop: theme.spacing(2), }, + duration: { + marginTop: 20, + width: '100%', + display: 'flex', + justifyContent: 'space-between', + border: `1px solid ${theme.palette.primary.main}`, + borderRadius: 4, + padding: 15, + }, + trigger: { + fontFamily: 'Consolas, monaco, monospace', + fontSize: 12, + paddingTop: 15, + color: theme.palette.primary.main, + }, })); interface Props { - onSubmit: SubmitHandler; + onSubmit: SubmitHandler; handleClose: () => void; initialValues: ExpectationInput; } @@ -35,13 +51,21 @@ const ExpectationFormUpdate: FunctionComponent = ({ const { t } = useFormatter(); const classes = useStyles(); + const expirationTime = splitDuration(initialValues.expectation_expiration_time || 0); + const formInitialValues: ExpectationInputForm = { + ...initialValues, + expiration_time_days: parseInt(expirationTime.days, 10), + expiration_time_hours: parseInt(expirationTime.hours, 10), + expiration_time_minutes: parseInt(expirationTime.minutes, 10), + }; + const { register, handleSubmit, formState: { errors, isSubmitting, isValid }, getValues, - control, - } = useForm(formProps(initialValues, t)); + } = useForm(formProps(formInitialValues, t)); + const { control } = useForm(); const handleSubmitWithoutPropagation = (e: SyntheticEvent) => { e.preventDefault(); @@ -96,6 +120,32 @@ const ExpectationFormUpdate: FunctionComponent = ({ } inputProps={register('expectation_description')} /> +
+
+ {t('Expiration time')} +
+ + + +
string) => { if (type === 'ARTICLE') { @@ -14,14 +14,17 @@ export const infoMessage = (type: string, t: (key: string) => string) => { return ''; }; -export const formProps = (initialValues: ExpectationInput, t: (key: string) => string): UseFormProps => ({ +export const formProps = (initialValues: ExpectationInputForm, t: (key: string) => string): UseFormProps => ({ mode: 'onTouched', - resolver: zodResolver(zodImplement().with({ + resolver: zodResolver(zodImplement().with({ expectation_type: z.string(), expectation_name: z.string().min(1, { message: t('Should not be empty') }), expectation_description: z.string().optional(), expectation_score: z.coerce.number().min(1, 'Score must be greater than 0'), expectation_expectation_group: z.coerce.boolean(), + expiration_time_days: z.coerce.number().min(0), + expiration_time_hours: z.coerce.number().min(0), + expiration_time_minutes: z.coerce.number().min(0), })), defaultValues: initialValues, }); diff --git a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx index 2c8016ac01..057854417c 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx @@ -4,7 +4,7 @@ import { MoreVert } from '@mui/icons-material'; import Transition from '../../../../../components/common/Transition'; import { useFormatter } from '../../../../../components/i18n'; import ExpectationFormUpdate from './ExpectationFormUpdate'; -import type { ExpectationInput } from './Expectation'; +import type { ExpectationInput, ExpectationInputForm } from './Expectation'; import Dialog from '../../../../../components/common/Dialog'; import { PermissionsContext } from '../../Context'; @@ -35,6 +35,7 @@ const ExpectationPopover: FunctionComponent = ({ expectation_description: expectation.expectation_description ?? '', expectation_score: expectation.expectation_score ?? 100, expectation_expectation_group: expectation.expectation_expectation_group ?? false, + expectation_expiration_time: expectation.expectation_expiration_time ?? 21600, }; // Popover @@ -51,8 +52,18 @@ const ExpectationPopover: FunctionComponent = ({ }; const handleCloseEdit = () => setOpenEdit(false); - const onSubmitEdit = (data: ExpectationInput) => { - handleUpdate(data, index); + const onSubmitEdit = (data: ExpectationInputForm) => { + const values: ExpectationInput = { + expectation_type: data.expectation_type, + expectation_name: data.expectation_name, + expectation_description: data.expectation_description, + expectation_score: data.expectation_score, + expectation_expectation_group: data.expectation_expectation_group, + expectation_expiration_time: data.expiration_time_days * 3600 * 24 + + data.expiration_time_hours * 3600 + + data.expiration_time_minutes * 60, + }; + handleUpdate(values, index); handleCloseEdit(); }; diff --git a/openbas-front/src/admin/components/common/injects/expectations/InjectAddExpectation.tsx b/openbas-front/src/admin/components/common/injects/expectations/InjectAddExpectation.tsx index 20713644e2..68c8fe4c79 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/InjectAddExpectation.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/InjectAddExpectation.tsx @@ -6,7 +6,7 @@ import type { Theme } from '../../../../../components/Theme'; import { useFormatter } from '../../../../../components/i18n'; import Dialog from '../../../../../components/common/Dialog'; import ExpectationFormCreate from './ExpectationFormCreate'; -import type { ExpectationInput } from './Expectation'; +import type { ExpectationInput, ExpectationInputForm } from './Expectation'; import { PermissionsContext } from '../../Context'; const useStyles = makeStyles((theme: Theme) => ({ @@ -41,8 +41,18 @@ const InjectAddExpectation: FunctionComponent = ({ const handleClose = () => setOpenDialog(false); // Form - const onSubmit = (data: ExpectationInput) => { - handleAddExpectation(data); + const onSubmit = (data: ExpectationInputForm) => { + const values: ExpectationInput = { + expectation_type: data.expectation_type, + expectation_name: data.expectation_name, + expectation_description: data.expectation_description, + expectation_score: data.expectation_score, + expectation_expectation_group: data.expectation_expectation_group, + expectation_expiration_time: data.expiration_time_days * 3600 * 24 + + data.expiration_time_hours * 3600 + + data.expiration_time_minutes * 60, + }; + handleAddExpectation(values); handleClose(); }; diff --git a/openbas-front/src/utils/Localization.js b/openbas-front/src/utils/Localization.js index 2d67fe8591..a5654569c9 100644 --- a/openbas-front/src/utils/Localization.js +++ b/openbas-front/src/utils/Localization.js @@ -584,6 +584,7 @@ const i18n = { 'Send the questionnaire': 'Envoyer le questionnaire', 'Reset answers': 'Réinitialiser les réponses', hours: 'heures', + 'Expiration time': 'Temps d\'expiration', Apply: 'Appliquer', players: 'joueurs', Explanation: 'Explication', @@ -1593,6 +1594,7 @@ const i18n = { Disabled: '禁用', Days: '天', Hours: '小时', + 'Expiration time': '过期时间', Minutes: '分钟', Seconds: '秒', 'Try the inject': '尝试注入', diff --git a/openbas-front/src/utils/api-types.d.ts b/openbas-front/src/utils/api-types.d.ts index 66e42ffe0a..24fee3ef6d 100644 --- a/openbas-front/src/utils/api-types.d.ts +++ b/openbas-front/src/utils/api-types.d.ts @@ -1138,6 +1138,8 @@ export interface InjectExpectation { /** @format date-time */ inject_expectation_updated_at?: string; inject_expectation_user?: User; + /** @format int64 */ + inject_expiration_time: number; listened?: boolean; targetId?: string; } diff --git a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java index d32495c1ad..d80297a5f7 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java +++ b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java @@ -117,6 +117,12 @@ public EXPECTATION_STATUS getResponse() { @NotNull private Double expectedScore; + @Setter + @Column(name = "inject_expiration_time") + @JsonProperty("inject_expiration_time") + @NotNull + private Long expirationTime = 60L; + @Setter @Column(name = "inject_expectation_created_at") @JsonProperty("inject_expectation_created_at") From 03b2ac03892f2af29f11a16e77415ce6696cc8e1 Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Wed, 25 Sep 2024 18:06:31 +0200 Subject: [PATCH 02/14] Adjusting expiration time in expectations --- .../ExpectationsExpirationManagerService.java | 46 +- .../injectors/caldera/CalderaExecutor.java | 535 ++++++++++-------- .../challenge/ChallengeExecutor.java | 168 +++--- .../injectors/channel/ChannelExecutor.java | 4 +- .../injectors/email/EmailExecutor.java | 32 +- .../openbas/OpenBASImplantExecutor.java | 329 ++++++----- .../injectors/opencti/OpenCTIExecutor.java | 16 +- .../openbas/injectors/ovh/OvhSmsExecutor.java | 16 +- ..._column_expiration_time_expectations.java} | 2 +- .../java/io/openbas/execution/Injector.java | 405 ++++++------- .../java/io/openbas/model/Expectation.java | 17 +- .../expectation/ChallengeExpectation.java | 13 +- .../model/expectation/ChannelExpectation.java | 7 +- .../expectation/DetectionExpectation.java | 99 ++-- .../model/expectation/ManualExpectation.java | 42 +- .../expectation/PreventionExpectation.java | 99 ++-- .../model/inject/form/Expectation.java | 3 + .../atomic_testing/TargetResultsDetail.tsx | 118 ++-- .../expectations/ExpectationPopover.tsx | 2 +- openbas-front/src/utils/Localization.js | 2 + .../database/model/InjectExpectation.java | 2 +- 21 files changed, 1056 insertions(+), 901 deletions(-) rename openbas-api/src/main/java/io/openbas/migration/{V3_40__Add_column_expiration_time_expectations.java => V3_41__Add_column_expiration_time_expectations.java} (95%) diff --git a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java index 476256d357..220376c518 100644 --- a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java +++ b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java @@ -40,19 +40,16 @@ private void computeExpectations(@NotNull final List expectat List expectationAssets = expectations.stream().toList(); expectationAssets.forEach((expectation) -> { Long userExpirationTime = expectation.getExpirationTime(); - if (userExpirationTime != null) { - // Maximum time for detection - if (isExpired(expectation, Math.toIntExact(userExpirationTime / 60))) { - String result = computeFailedMessage(expectation.getType()); - this.injectExpectationService.computeExpectation( - expectation, - this.config.getId(), - "collector", - PRODUCT_NAME, - result, - false - ); - } + if (isExpired(expectation, Math.toIntExact(userExpirationTime / 60))) { + String result = computeFailedMessage(expectation.getType()); + this.injectExpectationService.computeExpectation( + expectation, + this.config.getId(), + "collector", + PRODUCT_NAME, + result, + false + ); } }); @@ -62,19 +59,16 @@ private void computeExpectationsForAssets(@NotNull final List List expectationAssets = expectations.stream().filter(e -> e.getAsset() != null).toList(); expectationAssets.forEach((expectation) -> { Long userExpirationTime = expectation.getExpirationTime(); - if (userExpirationTime != null) { - // Maximum time for detection - if (isExpired(expectation, Math.toIntExact(userExpirationTime / 60))) { - String result = computeFailedMessage(expectation.getType()); - this.injectExpectationService.computeExpectation( - expectation, - this.config.getId(), - "collector", - PRODUCT_NAME, - result, - false - ); - } + if (isExpired(expectation, Math.toIntExact(userExpirationTime / 60))) { + String result = computeFailedMessage(expectation.getType()); + this.injectExpectationService.computeExpectation( + expectation, + this.config.getId(), + "collector", + PRODUCT_NAME, + result, + false + ); } }); diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java index 62d4a2939a..11f4d77cc0 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaExecutor.java @@ -47,269 +47,328 @@ @RequiredArgsConstructor @Log public class CalderaExecutor extends Injector { - private final int RETRY_NUMBER = 20; - private final CalderaInjectorConfig config; - private final CalderaInjectorService calderaService; - private final EndpointService endpointService; - private final AssetGroupService assetGroupService; - private final InjectRepository injectRepository; + private final int RETRY_NUMBER = 20; - @Override - @Transactional - public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) throws Exception { - CalderaInjectContent content = contentConvert(injection, CalderaInjectContent.class); - String obfuscator = content.getObfuscator() != null ? content.getObfuscator() : "base64"; - Inject inject = this.injectRepository.findById(injection.getInjection().getInject().getId()).orElseThrow(); + private final CalderaInjectorConfig config; + private final CalderaInjectorService calderaService; + private final EndpointService endpointService; + private final AssetGroupService assetGroupService; + private final InjectRepository injectRepository; - Map assets = this.resolveAllAssets(injection); - // Execute inject for all assets - if (assets.isEmpty()) { - execution.addTrace(traceError("Found 0 asset to execute the ability on (likely this inject does not have any target or the targeted asset is inactive and has been purged)")); - } + @Override + @Transactional + public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) + throws Exception { + CalderaInjectContent content = contentConvert(injection, CalderaInjectContent.class); + String obfuscator = content.getObfuscator() != null ? content.getObfuscator() : "base64"; + Inject inject = this.injectRepository.findById(injection.getInjection().getInject().getId()).orElseThrow(); - List asyncIds = new ArrayList<>(); - List expectations = new ArrayList<>(); - List> additionalFields = new ArrayList<>(); + Map assets = this.resolveAllAssets(injection); + // Execute inject for all assets + if (assets.isEmpty()) { + execution.addTrace(traceError( + "Found 0 asset to execute the ability on (likely this inject does not have any target or the targeted asset is inactive and has been purged)")); + } - inject.getInjectorContract().ifPresentOrElse(injectorContract -> { - ObjectNode rawContent = injection.getInjection().getInject().getContent(); - ObjectNode contractContent = injectorContract.getConvertedContent(); - List contractTextFields = StreamSupport.stream(contractContent.get("fields").spliterator(), false) - .filter(contractElement -> contractElement.get("type").asText().equals("text")) - .toList(); + List asyncIds = new ArrayList<>(); + List expectations = new ArrayList<>(); + List> additionalFields = new ArrayList<>(); - if (!contractTextFields.isEmpty()) { - contractTextFields.forEach(jsonField -> { - String key = jsonField.get("key").asText(); - if (rawContent.get(key) != null) { - Map additionalField = new HashMap<>(); - additionalField.put("trait", key); - additionalField.put("value", rawContent.get(key).asText()); - additionalFields.add(additionalField); - } - }); - } + inject.getInjectorContract().ifPresentOrElse(injectorContract -> { + ObjectNode rawContent = injection.getInjection().getInject().getContent(); + ObjectNode contractContent = injectorContract.getConvertedContent(); + List contractTextFields = StreamSupport.stream(contractContent.get("fields").spliterator(), false) + .filter(contractElement -> contractElement.get("type").asText().equals("text")) + .toList(); - String contract; - if (injectorContract.getPayload() != null) { - // This is a payload, need to create the ability on the fly - List abilities = calderaService.abilities().stream().filter(ability -> ability.getName().equals(injectorContract.getPayload().getId())).toList(); - if (!abilities.isEmpty()) { - calderaService.deleteAbility(abilities.getFirst()); - } - Ability abilityToExecute = calderaService.createAbility(injectorContract.getPayload()); - contract = abilityToExecute.getAbility_id(); - } else { - contract = injectorContract.getId(); + if (!contractTextFields.isEmpty()) { + contractTextFields.forEach(jsonField -> { + String key = jsonField.get("key").asText(); + if (rawContent.get(key) != null) { + Map additionalField = new HashMap<>(); + additionalField.put("trait", key); + additionalField.put("value", rawContent.get(key).asText()); + additionalFields.add(additionalField); + } + }); + } + + String contract; + if (injectorContract.getPayload() != null) { + // This is a payload, need to create the ability on the fly + List abilities = calderaService.abilities().stream() + .filter(ability -> ability.getName().equals(injectorContract.getPayload().getId())).toList(); + if (!abilities.isEmpty()) { + calderaService.deleteAbility(abilities.getFirst()); } - assets.forEach((asset, aBoolean) -> { - try { - Endpoint executionEndpoint = this.findAndRegisterAssetForExecution(injection.getInjection().getInject(), asset); - if (executionEndpoint != null) { - if (Arrays.stream(injectorContract.getPlatforms()).anyMatch(s -> s.equals(executionEndpoint.getPlatform()))) { - String result = this.calderaService.exploit(obfuscator, executionEndpoint.getExternalReference(), contract, additionalFields); - if (result.contains("complete")) { - ExploitResult exploitResult = this.calderaService.exploitResult(executionEndpoint.getExternalReference(), contract); - asyncIds.add(exploitResult.getLinkId()); - execution.addTrace(traceInfo(EXECUTION_TYPE_COMMAND, exploitResult.getCommand())); - // Compute expectations - boolean isInGroup = assets.get(executionEndpoint.getParent()); - List injectExpectationSignatures = new ArrayList<>(); - if (injectorContract.getPayload() != null) { - switch (injectorContract.getPayload().getType()) { - case "Command": - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_PROCESS_NAME).value(executionEndpoint.getProcessName()).build()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_COMMAND_LINE).value(exploitResult.getCommand()).build()); - break; - case "Executable": - Executable payloadExecutable = (Executable) Hibernate.unproxy(injectorContract.getPayload()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_FILE_NAME).value(payloadExecutable.getExecutableFile().getName()).build()); - // TODO File hash - break; - case "FileDrop": - FileDrop payloadFileDrop = (FileDrop) Hibernate.unproxy(injectorContract.getPayload()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_FILE_NAME).value(payloadFileDrop.getFileDropFile().getName()).build()); - // TODO File hash - break; - case "DnsResolution": - DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(injectorContract.getPayload()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_HOSTNAME).value(payloadDnsResolution.getHostname().split("\\r?\\n")[0]).build()); - break; - default: - throw new UnsupportedOperationException("Payload type " + injectorContract.getPayload().getType() + " is not supported"); - } - } else { - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_PROCESS_NAME).value(executionEndpoint.getProcessName()).build()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_COMMAND_LINE).value(exploitResult.getCommand()).build()); - } - computeExpectationsForAsset(expectations, content, executionEndpoint.getParent(), isInGroup, injectExpectationSignatures); - execution.addTrace(traceInfo("Caldera executed the ability on asset " + asset.getName() + " using " + executionEndpoint.getProcessName() + " (paw: " + executionEndpoint.getExternalReference() + ", linkID: " + exploitResult.getLinkId() + ")")); - } else { - execution.addTrace(traceError("Caldera failed to execute the ability on asset " + asset.getName() + " (" + result + ")")); - } - } else { - execution.addTrace(traceError("Caldera failed to execute ability on asset " + asset.getName() + " (platform is not compatible: " + executionEndpoint.getPlatform().name() + ")")); - } + Ability abilityToExecute = calderaService.createAbility(injectorContract.getPayload()); + contract = abilityToExecute.getAbility_id(); + } else { + contract = injectorContract.getId(); + } + assets.forEach((asset, aBoolean) -> { + try { + Endpoint executionEndpoint = this.findAndRegisterAssetForExecution(injection.getInjection().getInject(), + asset); + if (executionEndpoint != null) { + if (Arrays.stream(injectorContract.getPlatforms()) + .anyMatch(s -> s.equals(executionEndpoint.getPlatform()))) { + String result = this.calderaService.exploit(obfuscator, executionEndpoint.getExternalReference(), + contract, additionalFields); + if (result.contains("complete")) { + ExploitResult exploitResult = this.calderaService.exploitResult( + executionEndpoint.getExternalReference(), contract); + asyncIds.add(exploitResult.getLinkId()); + execution.addTrace(traceInfo(EXECUTION_TYPE_COMMAND, exploitResult.getCommand())); + // Compute expectations + boolean isInGroup = assets.get(executionEndpoint.getParent()); + List injectExpectationSignatures = new ArrayList<>(); + if (injectorContract.getPayload() != null) { + switch (injectorContract.getPayload().getType()) { + case "Command": + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_PROCESS_NAME) + .value(executionEndpoint.getProcessName()).build()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_COMMAND_LINE) + .value(exploitResult.getCommand()).build()); + break; + case "Executable": + Executable payloadExecutable = (Executable) Hibernate.unproxy(injectorContract.getPayload()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_FILE_NAME) + .value(payloadExecutable.getExecutableFile().getName()).build()); + // TODO File hash + break; + case "FileDrop": + FileDrop payloadFileDrop = (FileDrop) Hibernate.unproxy(injectorContract.getPayload()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_FILE_NAME) + .value(payloadFileDrop.getFileDropFile().getName()).build()); + // TODO File hash + break; + case "DnsResolution": + DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy( + injectorContract.getPayload()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_HOSTNAME) + .value(payloadDnsResolution.getHostname().split("\\r?\\n")[0]).build()); + break; + default: + throw new UnsupportedOperationException( + "Payload type " + injectorContract.getPayload().getType() + " is not supported"); + } } else { - execution.addTrace(traceError("Caldera failed to execute the ability on asset " + asset.getName() + " (temporary injector not spawned correctly)")); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_PROCESS_NAME) + .value(executionEndpoint.getProcessName()).build()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_COMMAND_LINE) + .value(exploitResult.getCommand()).build()); } - } catch (Exception e) { - execution.addTrace(traceError("Caldera failed to execute the ability on asset " + asset.getName() + " (" + e.getMessage() + ")")); - log.severe(Arrays.toString(e.getStackTrace())); + computeExpectationsForAsset(expectations, content, executionEndpoint.getParent(), isInGroup, + injectExpectationSignatures); + execution.addTrace(traceInfo("Caldera executed the ability on asset " + asset.getName() + " using " + + executionEndpoint.getProcessName() + " (paw: " + executionEndpoint.getExternalReference() + + ", linkID: " + exploitResult.getLinkId() + ")")); + } else { + execution.addTrace(traceError( + "Caldera failed to execute the ability on asset " + asset.getName() + " (" + result + ")")); + } + } else { + execution.addTrace(traceError( + "Caldera failed to execute ability on asset " + asset.getName() + " (platform is not compatible: " + + executionEndpoint.getPlatform().name() + ")")); } - }); + } else { + execution.addTrace(traceError("Caldera failed to execute the ability on asset " + asset.getName() + + " (temporary injector not spawned correctly)")); + } + } catch (Exception e) { + execution.addTrace(traceError( + "Caldera failed to execute the ability on asset " + asset.getName() + " (" + e.getMessage() + ")")); + log.severe(Arrays.toString(e.getStackTrace())); + } + }); }, - ()->execution.addTrace(traceError("Inject does not have a contract"))); + () -> execution.addTrace(traceError("Inject does not have a contract"))); - if (asyncIds.isEmpty()) { - throw new UnsupportedOperationException("Caldera failed to execute the ability due to above errors"); - } - - List assetGroups = injection.getAssetGroups(); - assetGroups.forEach((assetGroup -> computeExpectationsForAssetGroup(expectations, content, assetGroup, new ArrayList<>()))); - String message = "Caldera executed the ability on " + asyncIds.size() + " asset(s)"; - execution.addTrace(traceInfo(message, asyncIds)); - return new ExecutionProcess(true, expectations); + if (asyncIds.isEmpty()) { + throw new UnsupportedOperationException("Caldera failed to execute the ability due to above errors"); } - @Override - public InjectStatusCommandLine getCommandsLines(String externalId) { - InjectStatusCommandLine commandLine = new InjectStatusCommandLine(); - Set contents = new HashSet<>(); - Set cleanCommands = new HashSet<>(); - Ability ability = calderaService.findAbilityById(externalId); - if(ability != null) { - ability.getExecutors().forEach(executor -> { - if(executor.getCommand() != null && !executor.getCommand().isBlank()) { - contents.add(executor.getCommand()); - } - if(executor.getCleanup() != null && !executor.getCleanup().isEmpty()) { - cleanCommands.addAll(executor.getCleanup()); - } - }); + List assetGroups = injection.getAssetGroups(); + assetGroups.forEach( + (assetGroup -> computeExpectationsForAssetGroup(expectations, content, assetGroup, new ArrayList<>()))); + String message = "Caldera executed the ability on " + asyncIds.size() + " asset(s)"; + execution.addTrace(traceInfo(message, asyncIds)); + return new ExecutionProcess(true, expectations); + } + + @Override + public InjectStatusCommandLine getCommandsLines(String externalId) { + InjectStatusCommandLine commandLine = new InjectStatusCommandLine(); + Set contents = new HashSet<>(); + Set cleanCommands = new HashSet<>(); + Ability ability = calderaService.findAbilityById(externalId); + if (ability != null) { + ability.getExecutors().forEach(executor -> { + if (executor.getCommand() != null && !executor.getCommand().isBlank()) { + contents.add(executor.getCommand()); } - commandLine.setExternalId(externalId); - commandLine.setContent(contents.stream().toList()); - commandLine.setCleanupCommand(cleanCommands.stream().toList()); - return commandLine; + if (executor.getCleanup() != null && !executor.getCleanup().isEmpty()) { + cleanCommands.addAll(executor.getCleanup()); + } + }); } + commandLine.setExternalId(externalId); + commandLine.setContent(contents.stream().toList()); + commandLine.setCleanupCommand(cleanCommands.stream().toList()); + return commandLine; + } - // -- PRIVATE -- + // -- PRIVATE -- - private Map resolveAllAssets(@NotNull final ExecutableInject inject) { - Map assets = new HashMap<>(); - inject.getAssets().forEach((asset -> { - assets.put(asset, false); - })); - inject.getAssetGroups().forEach((assetGroup -> { - List assetsFromGroup = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); - // Verify asset validity - assetsFromGroup.forEach((asset) -> { - assets.put(asset, true); - }); - })); - return assets; - } + private Map resolveAllAssets(@NotNull final ExecutableInject inject) { + Map assets = new HashMap<>(); + inject.getAssets().forEach((asset -> { + assets.put(asset, false); + })); + inject.getAssetGroups().forEach((assetGroup -> { + List assetsFromGroup = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); + // Verify asset validity + assetsFromGroup.forEach((asset) -> { + assets.put(asset, true); + }); + })); + return assets; + } - private Endpoint findAndRegisterAssetForExecution(@NotNull final Inject inject, @NotNull final Asset asset) throws InterruptedException { - Endpoint endpointForExecution = null; - if (!asset.getType().equals("Endpoint")) { - log.log(Level.SEVERE, "Caldera failed to execute ability on the asset because type is not supported: " + asset.getType()); - return null; - } - log.log(Level.INFO, "Trying to find an available executor for " + asset.getName()); - Endpoint assetEndpoint = (Endpoint) Hibernate.unproxy(asset); - for (int i = 0; i < RETRY_NUMBER; i++) { - // Find an executor agent matching the asset - log.log(Level.INFO, "Listing agents..."); - List agents = this.calderaService.agents().stream().filter(agent -> - agent.getExe_name().contains("implant") - && (now().toEpochMilli() - Time.toInstant(agent.getCreated()).toEpochMilli()) < Asset.ACTIVE_THRESHOLD - && (agent.getHost().equals(assetEndpoint.getHostname()) || agent.getHost().split("\\.")[0].equals(assetEndpoint.getHostname().split("\\.")[0])) - && Arrays.stream(assetEndpoint.getIps()).anyMatch(s -> Arrays.stream(agent.getHost_ip_addrs()).toList().contains(s)) - ).toList(); - log.log(Level.INFO, "List return with " + agents.size() + " agents"); - if (!agents.isEmpty()) { - for (Agent agent : agents) { - // Check in the database if not exist - Optional resolvedExistingEndpoint = this.endpointService.findByExternalReference(agent.getPaw()); - if (resolvedExistingEndpoint.isEmpty()) { - log.log(Level.INFO, "Agent found and not present in the database, creating it..."); - Endpoint newEndpoint = new Endpoint(); - newEndpoint.setInject(inject); - newEndpoint.setParent(asset); - newEndpoint.setName(assetEndpoint.getName()); - newEndpoint.setIps(assetEndpoint.getIps()); - newEndpoint.setHostname(assetEndpoint.getHostname()); - newEndpoint.setPlatform(assetEndpoint.getPlatform()); - newEndpoint.setArch(assetEndpoint.getArch()); - newEndpoint.setExternalReference(agent.getPaw()); - newEndpoint.setExecutor(assetEndpoint.getExecutor()); - newEndpoint.setProcessName(agent.getExe_name()); - endpointForExecution = this.endpointService.createEndpoint(newEndpoint); - break; - } - } - } - if (endpointForExecution != null) { - break; - } - Thread.sleep(5000); + private Endpoint findAndRegisterAssetForExecution(@NotNull final Inject inject, @NotNull final Asset asset) + throws InterruptedException { + Endpoint endpointForExecution = null; + if (!asset.getType().equals("Endpoint")) { + log.log(Level.SEVERE, + "Caldera failed to execute ability on the asset because type is not supported: " + asset.getType()); + return null; + } + log.log(Level.INFO, "Trying to find an available executor for " + asset.getName()); + Endpoint assetEndpoint = (Endpoint) Hibernate.unproxy(asset); + for (int i = 0; i < RETRY_NUMBER; i++) { + // Find an executor agent matching the asset + log.log(Level.INFO, "Listing agents..."); + List agents = this.calderaService.agents().stream().filter(agent -> + agent.getExe_name().contains("implant") + && (now().toEpochMilli() - Time.toInstant(agent.getCreated()).toEpochMilli()) < Asset.ACTIVE_THRESHOLD + && (agent.getHost().equals(assetEndpoint.getHostname()) || agent.getHost().split("\\.")[0].equals( + assetEndpoint.getHostname().split("\\.")[0])) + && Arrays.stream(assetEndpoint.getIps()) + .anyMatch(s -> Arrays.stream(agent.getHost_ip_addrs()).toList().contains(s)) + ).toList(); + log.log(Level.INFO, "List return with " + agents.size() + " agents"); + if (!agents.isEmpty()) { + for (Agent agent : agents) { + // Check in the database if not exist + Optional resolvedExistingEndpoint = this.endpointService.findByExternalReference(agent.getPaw()); + if (resolvedExistingEndpoint.isEmpty()) { + log.log(Level.INFO, "Agent found and not present in the database, creating it..."); + Endpoint newEndpoint = new Endpoint(); + newEndpoint.setInject(inject); + newEndpoint.setParent(asset); + newEndpoint.setName(assetEndpoint.getName()); + newEndpoint.setIps(assetEndpoint.getIps()); + newEndpoint.setHostname(assetEndpoint.getHostname()); + newEndpoint.setPlatform(assetEndpoint.getPlatform()); + newEndpoint.setArch(assetEndpoint.getArch()); + newEndpoint.setExternalReference(agent.getPaw()); + newEndpoint.setExecutor(assetEndpoint.getExecutor()); + newEndpoint.setProcessName(agent.getExe_name()); + endpointForExecution = this.endpointService.createEndpoint(newEndpoint); + break; + } } - return endpointForExecution; + } + if (endpointForExecution != null) { + break; + } + Thread.sleep(5000); } + return endpointForExecution; + } - /** - * In case of direct asset, we have an individual expectation for the asset - */ - private void computeExpectationsForAsset(@NotNull final List expectations, @NotNull final CalderaInjectContent content, @NotNull final Asset asset, final boolean expectationGroup, final List injectExpectationSignatures) { - if (!content.getExpectations().isEmpty()) { - expectations.addAll(content.getExpectations().stream().flatMap((expectation) -> switch (expectation.getType()) { - case PREVENTION -> - Stream.of(preventionExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), asset, expectationGroup, injectExpectationSignatures)); // expectationGroup usefully in front-end - case DETECTION -> - Stream.of(detectionExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), asset, expectationGroup, injectExpectationSignatures)); - case MANUAL -> - Stream.of(manualExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), asset, expectationGroup)); - default -> Stream.of(); - }).toList()); - } + /** + * In case of direct asset, we have an individual expectation for the asset + */ + private void computeExpectationsForAsset(@NotNull final List expectations, + @NotNull final CalderaInjectContent content, @NotNull final Asset asset, final boolean expectationGroup, + final List injectExpectationSignatures) { + if (!content.getExpectations().isEmpty()) { + expectations.addAll(content.getExpectations().stream().flatMap((expectation) -> switch (expectation.getType()) { + case PREVENTION -> Stream.of( + preventionExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), + asset, expectationGroup, expectation.getExpirationTime(), + injectExpectationSignatures)); // expectationGroup usefully in front-end + case DETECTION -> Stream.of( + detectionExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), + asset, expectationGroup, expectation.getExpirationTime(), injectExpectationSignatures)); + case MANUAL -> Stream.of( + manualExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), + asset, expectation.getExpirationTime(), expectationGroup)); + default -> Stream.of(); + }).toList()); } + } - /** - * In case of asset group if expectation group -> we have an expectation for the group and one for each asset if not - * expectation group -> we have an individual expectation for each asset - */ - private void computeExpectationsForAssetGroup(@NotNull final List expectations, @NotNull final CalderaInjectContent content, @NotNull final AssetGroup assetGroup, final List injectExpectationSignatures) { - if (!content.getExpectations().isEmpty()) { - expectations.addAll(content.getExpectations().stream().flatMap((expectation) -> switch (expectation.getType()) { - case PREVENTION -> { - // Verify that at least one asset in the group has been executed - List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); - if (assets.stream().anyMatch((asset) -> expectations.stream().filter(e -> EXPECTATION_TYPE.PREVENTION == e.type()).anyMatch((e) -> ((PreventionExpectation) e).getAsset() != null && ((PreventionExpectation) e).getAsset().getId().equals(asset.getId())))) { - yield Stream.of(preventionExpectationForAssetGroup(expectation.getScore(), expectation.getName(), expectation.getDescription(), assetGroup, expectation.isExpectationGroup(), injectExpectationSignatures)); - } - yield Stream.of(); - } - case DETECTION -> { - // Verify that at least one asset in the group has been executed - List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); - if (assets.stream().anyMatch((asset) -> expectations.stream().filter(e -> EXPECTATION_TYPE.DETECTION == e.type()).anyMatch((e) -> ((DetectionExpectation) e).getAsset() != null && ((DetectionExpectation) e).getAsset().getId().equals(asset.getId())))) { - yield Stream.of(detectionExpectationForAssetGroup(expectation.getScore(), expectation.getName(), expectation.getDescription(), assetGroup, expectation.isExpectationGroup(), injectExpectationSignatures)); - } - yield Stream.of(); - } - case MANUAL -> { - // Verify that at least one asset in the group has been executed - List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); - if (assets.stream().anyMatch((asset) -> expectations.stream().filter(e -> EXPECTATION_TYPE.MANUAL == e.type()).anyMatch((e) -> ((ManualExpectation) e).getAsset() != null && ((ManualExpectation) e).getAsset().getId().equals(asset.getId())))) { - yield Stream.of(manualExpectationForAssetGroup(expectation.getScore(), expectation.getName(), expectation.getDescription(), assetGroup, expectation.isExpectationGroup())); - } - yield Stream.of(); - } - default -> Stream.of(); - }).toList()); + /** + * In case of asset group if expectation group -> we have an expectation for the group and one for each asset if not + * expectation group -> we have an individual expectation for each asset + */ + private void computeExpectationsForAssetGroup(@NotNull final List expectations, + @NotNull final CalderaInjectContent content, @NotNull final AssetGroup assetGroup, + final List injectExpectationSignatures) { + if (!content.getExpectations().isEmpty()) { + expectations.addAll(content.getExpectations().stream().flatMap((expectation) -> switch (expectation.getType()) { + case PREVENTION -> { + // Verify that at least one asset in the group has been executed + List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); + if (assets.stream().anyMatch( + (asset) -> expectations.stream().filter(e -> EXPECTATION_TYPE.PREVENTION == e.type()).anyMatch( + (e) -> ((PreventionExpectation) e).getAsset() != null && ((PreventionExpectation) e).getAsset() + .getId().equals(asset.getId())))) { + yield Stream.of(preventionExpectationForAssetGroup(expectation.getScore(), expectation.getName(), + expectation.getDescription(), assetGroup, expectation.isExpectationGroup(), + expectation.getExpirationTime(), injectExpectationSignatures)); + } + yield Stream.of(); + } + case DETECTION -> { + // Verify that at least one asset in the group has been executed + List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); + if (assets.stream().anyMatch( + (asset) -> expectations.stream().filter(e -> EXPECTATION_TYPE.DETECTION == e.type()).anyMatch( + (e) -> ((DetectionExpectation) e).getAsset() != null && ((DetectionExpectation) e).getAsset().getId() + .equals(asset.getId())))) { + yield Stream.of(detectionExpectationForAssetGroup(expectation.getScore(), expectation.getName(), + expectation.getDescription(), assetGroup, expectation.isExpectationGroup(), + expectation.getExpirationTime(), injectExpectationSignatures)); + } + yield Stream.of(); + } + case MANUAL -> { + // Verify that at least one asset in the group has been executed + List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); + if (assets.stream().anyMatch((asset) -> expectations.stream().filter(e -> EXPECTATION_TYPE.MANUAL == e.type()) + .anyMatch((e) -> ((ManualExpectation) e).getAsset() != null && ((ManualExpectation) e).getAsset().getId() + .equals(asset.getId())))) { + yield Stream.of(manualExpectationForAssetGroup(expectation.getScore(), expectation.getName(), + expectation.getDescription(), assetGroup, expectation.getExpirationTime(), + expectation.isExpectationGroup())); + } + yield Stream.of(); } + default -> Stream.of(); + }).toList()); } + } } 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 1c7b7c2eb6..8bd81eeed4 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 @@ -32,99 +32,99 @@ @Component(ChallengeContract.TYPE) public class ChallengeExecutor extends Injector { - @Resource - private OpenBASConfig openBASConfig; + @Resource + private OpenBASConfig openBASConfig; - private ChallengeRepository challengeRepository; + private ChallengeRepository challengeRepository; - private EmailService emailService; + private EmailService emailService; - @Value("${openbas.mail.imap.enabled}") - private boolean imapEnabled; + @Value("${openbas.mail.imap.enabled}") + private boolean imapEnabled; - @Autowired - public void setChallengeRepository(ChallengeRepository challengeRepository) { - this.challengeRepository = challengeRepository; - } + @Autowired + public void setChallengeRepository(ChallengeRepository challengeRepository) { + this.challengeRepository = challengeRepository; + } - @Autowired - public void setEmailService(EmailService emailService) { - this.emailService = emailService; - } + @Autowired + public void setEmailService(EmailService emailService) { + this.emailService = emailService; + } - private String buildChallengeUri(ExecutionContext context, Exercise exercise, Challenge challenge) { - String userId = context.getUser().getId(); - String challengeId = challenge.getId(); - String exerciseId = exercise.getId(); - return openBASConfig.getBaseUrl() + "/challenges/" + exerciseId + "?user=" + userId + "&challenge=" + challengeId; - } + private String buildChallengeUri(ExecutionContext context, Exercise exercise, Challenge challenge) { + String userId = context.getUser().getId(); + String challengeId = challenge.getId(); + String exerciseId = exercise.getId(); + return openBASConfig.getBaseUrl() + "/challenges/" + exerciseId + "?user=" + userId + "&challenge=" + challengeId; + } - @Override - public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) { - try { - ChallengeContent content = contentConvert(injection, ChallengeContent.class); - List challenges = fromIterable(challengeRepository.findAllById(content.getChallenges())); - String contract = injection - .getInjection() - .getInject() - .getInjectorContract() - .map(InjectorContract::getId) - .orElseThrow(() -> new UnsupportedOperationException("Inject does not have a contract")); + @Override + public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) { + try { + ChallengeContent content = contentConvert(injection, ChallengeContent.class); + List challenges = fromIterable(challengeRepository.findAllById(content.getChallenges())); + String contract = injection + .getInjection() + .getInject() + .getInjectorContract() + .map(InjectorContract::getId) + .orElseThrow(() -> new UnsupportedOperationException("Inject does not have a contract")); - if (contract.equals(CHALLENGE_PUBLISH)) { - // Challenge publishing is only linked to execution date of this inject. - String challengeNames = challenges.stream().map(Challenge::getName).collect(Collectors.joining(",")); - String publishedMessage = "Challenges (" + challengeNames + ") marked as published"; - execution.addTrace(traceSuccess(publishedMessage)); - // Send the publication message. - Exercise exercise = injection.getInjection().getExercise(); - String from = exercise.getFrom(); - List replyTos = exercise.getReplyTos(); - List users = injection.getUsers(); - List documents = injection.getInjection().getInject().getDocuments().stream() - .filter(InjectDocument::isAttached).map(InjectDocument::getDocument).toList(); - List attachments = resolveAttachments(execution, injection, documents); - String message = content.buildMessage(injection, imapEnabled); - boolean encrypted = content.isEncrypted(); - users.forEach(userInjectContext -> { - try { - // Put the challenges variables in the injection context - List challengeVariables = challenges.stream() - .map(challenge -> new ChallengeVariable(challenge.getId(), challenge.getName(), - buildChallengeUri(userInjectContext, exercise, challenge))) - .toList(); - userInjectContext.put("challenges", challengeVariables); - // Send the email. - emailService.sendEmail(execution, userInjectContext, from, replyTos, content.getInReplyTo(), encrypted, - content.getSubject(), message, attachments); - } catch (Exception e) { - execution.addTrace(traceError(e.getMessage())); - } - }); - // Return expectations - List expectations = new ArrayList<>(); - 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); - } - } catch (Exception e) { + if (contract.equals(CHALLENGE_PUBLISH)) { + // Challenge publishing is only linked to execution date of this inject. + String challengeNames = challenges.stream().map(Challenge::getName).collect(Collectors.joining(",")); + String publishedMessage = "Challenges (" + challengeNames + ") marked as published"; + execution.addTrace(traceSuccess(publishedMessage)); + // Send the publication message. + Exercise exercise = injection.getInjection().getExercise(); + String from = exercise.getFrom(); + List replyTos = exercise.getReplyTos(); + List users = injection.getUsers(); + List documents = injection.getInjection().getInject().getDocuments().stream() + .filter(InjectDocument::isAttached).map(InjectDocument::getDocument).toList(); + List attachments = resolveAttachments(execution, injection, documents); + String message = content.buildMessage(injection, imapEnabled); + boolean encrypted = content.isEncrypted(); + users.forEach(userInjectContext -> { + try { + // Put the challenges variables in the injection context + List challengeVariables = challenges.stream() + .map(challenge -> new ChallengeVariable(challenge.getId(), challenge.getName(), + buildChallengeUri(userInjectContext, exercise, challenge))) + .toList(); + userInjectContext.put("challenges", challengeVariables); + // Send the email. + emailService.sendEmail(execution, userInjectContext, from, replyTos, content.getInReplyTo(), encrypted, + content.getSubject(), message, attachments); + } catch (Exception e) { execution.addTrace(traceError(e.getMessage())); + } + }); + // Return expectations + List expectations = new ArrayList<>(); + if (!content.getExpectations().isEmpty()) { + expectations.addAll( + content.getExpectations() + .stream() + .flatMap((entry) -> switch (entry.getType()) { + case MANUAL -> Stream.of( + (Expectation) new ManualExpectation(entry) + ); + case CHALLENGE -> challenges.stream() + .map(challenge -> (Expectation) new ChallengeExpectation(entry, challenge)); + default -> Stream.of(); + }) + .toList() + ); } - return new ExecutionProcess(false, List.of()); + return new ExecutionProcess(false, expectations); + } else { + throw new UnsupportedOperationException("Unknown contract " + contract); + } + } catch (Exception e) { + execution.addTrace(traceError(e.getMessage())); } + return new ExecutionProcess(false, List.of()); + } } 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 5aab36bc42..aa700c10d3 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(), entry.isExpectationGroup()) + (Expectation) new ManualExpectation(entry) ); case ARTICLE -> articles.stream() - .map(article -> (Expectation) new ChannelExpectation(entry.getScore(), article, entry.isExpectationGroup())); + .map(article -> (Expectation) new ChannelExpectation(entry, article)); 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 dcc202ee77..248f40d909 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 @@ -38,7 +38,8 @@ public void setEmailService(EmailService emailService) { this.emailService = emailService; } - private void sendMulti(Execution execution, List users, String from, List replyTos, String inReplyTo, + private void sendMulti(Execution execution, List users, String from, List replyTos, + String inReplyTo, String subject, String message, List attachments) { try { emailService.sendEmail(execution, users, from, replyTos, inReplyTo, subject, message, attachments); @@ -47,11 +48,13 @@ private void sendMulti(Execution execution, List users, String } } - private void sendSingle(Execution execution, List users, String from, List replyTos, String inReplyTo, + private void sendSingle(Execution execution, List users, String from, List replyTos, + String inReplyTo, boolean mustBeEncrypted, String subject, String message, List attachments) { users.forEach(user -> { try { - emailService.sendEmail(execution, user, from, replyTos, inReplyTo, mustBeEncrypted, subject, message, attachments); + emailService.sendEmail(execution, user, from, replyTos, inReplyTo, mustBeEncrypted, subject, message, + attachments); } catch (Exception e) { execution.addTrace(traceError(e.getMessage())); } @@ -59,7 +62,8 @@ private void sendSingle(Execution execution, List users, Strin } @Override - public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) throws Exception { + public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) + throws Exception { Inject inject = injection.getInjection().getInject(); EmailContent content = contentConvert(injection, EmailContent.class); List documents = inject.getDocuments().stream().filter(InjectDocument::isAttached) @@ -78,18 +82,20 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin String from = exercise != null ? exercise.getFrom() : this.openBASConfig.getDefaultMailer(); List replyTos = exercise != null ? exercise.getReplyTos() : List.of(this.openBASConfig.getDefaultReplyTo()); //noinspection SwitchStatementWithTooFewBranches - switch (inject.getInjectorContract().map(InjectorContract::getId).orElseThrow(() -> new UnsupportedOperationException("Inject does not have a contract"))) { + switch (inject.getInjectorContract().map(InjectorContract::getId) + .orElseThrow(() -> new UnsupportedOperationException("Inject does not have a contract"))) { case EMAIL_GLOBAL -> sendMulti(execution, users, from, replyTos, inReplyTo, subject, message, attachments); - default -> sendSingle(execution, users, from, replyTos, inReplyTo, mustBeEncrypted, subject, message, attachments); + default -> + sendSingle(execution, users, from, replyTos, inReplyTo, mustBeEncrypted, subject, message, attachments); } List expectations = content.getExpectations() - .stream() - .flatMap((entry) -> switch (entry.getType()) { - case MANUAL -> - Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription(), entry.isExpectationGroup())); - default -> Stream.of(); - }) - .toList(); + .stream() + .flatMap((entry) -> switch (entry.getType()) { + case MANUAL -> Stream.of( + (Expectation) new ManualExpectation(entry)); + default -> Stream.of(); + }) + .toList(); return new ExecutionProcess(false, expectations); } } diff --git a/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASImplantExecutor.java b/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASImplantExecutor.java index 9f51c5f4e2..bdcdf18e2c 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASImplantExecutor.java +++ b/openbas-api/src/main/java/io/openbas/injectors/openbas/OpenBASImplantExecutor.java @@ -38,161 +38,200 @@ @Log public class OpenBASImplantExecutor extends Injector { - private final AssetGroupService assetGroupService; - private final InjectRepository injectRepository; - private final InjectStatusRepository injectStatusRepository; + private final AssetGroupService assetGroupService; + private final InjectRepository injectRepository; + private final InjectStatusRepository injectStatusRepository; - private Map resolveAllAssets(@NotNull final ExecutableInject inject) { - Map assets = new HashMap<>(); - inject.getAssets().forEach((asset -> { - assets.put(asset, false); - })); - inject.getAssetGroups().forEach((assetGroup -> { - List assetsFromGroup = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); - // Verify asset validity - assetsFromGroup.forEach((asset) -> { - assets.put(asset, true); - }); - })); - return assets; - } + private Map resolveAllAssets(@NotNull final ExecutableInject inject) { + Map assets = new HashMap<>(); + inject.getAssets().forEach((asset -> { + assets.put(asset, false); + })); + inject.getAssetGroups().forEach((assetGroup -> { + List assetsFromGroup = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); + // Verify asset validity + assetsFromGroup.forEach((asset) -> { + assets.put(asset, true); + }); + })); + return assets; + } - /** - * In case of direct asset, we have an individual expectation for the asset - */ - private void computeExpectationsForAsset(@NotNull final List expectations, @NotNull final OpenBASImplantInjectContent content, @NotNull final Asset asset, final boolean expectationGroup, final List injectExpectationSignatures) { - if (!content.getExpectations().isEmpty()) { - expectations.addAll(content.getExpectations().stream().flatMap((expectation) -> switch (expectation.getType()) { - case PREVENTION -> - Stream.of(preventionExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), asset, expectationGroup, injectExpectationSignatures)); // expectationGroup usefully in front-end - case DETECTION -> - Stream.of(detectionExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), asset, expectationGroup, injectExpectationSignatures)); - case MANUAL -> - Stream.of(manualExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), asset, expectationGroup)); - default -> Stream.of(); - }).toList()); - } + /** + * In case of direct asset, we have an individual expectation for the asset + */ + private void computeExpectationsForAsset(@NotNull final List expectations, + @NotNull final OpenBASImplantInjectContent content, @NotNull final Asset asset, final boolean expectationGroup, + final List injectExpectationSignatures) { + if (!content.getExpectations().isEmpty()) { + expectations.addAll(content.getExpectations().stream().flatMap((expectation) -> switch (expectation.getType()) { + case PREVENTION -> Stream.of( + preventionExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), + asset, expectationGroup, expectation.getExpirationTime(), + injectExpectationSignatures)); // expectationGroup usefully in front-end + case DETECTION -> Stream.of( + detectionExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), + asset, expectationGroup, expectation.getExpirationTime(), injectExpectationSignatures)); + case MANUAL -> Stream.of( + manualExpectationForAsset(expectation.getScore(), expectation.getName(), expectation.getDescription(), + asset, expectation.getExpirationTime(), expectationGroup)); + default -> Stream.of(); + }).toList()); } + } - /** - * In case of asset group if expectation group -> we have an expectation for the group and one for each asset if not - * expectation group -> we have an individual expectation for each asset - */ - private void computeExpectationsForAssetGroup(@NotNull final List expectations, @NotNull final OpenBASImplantInjectContent content, @NotNull final AssetGroup assetGroup, final List injectExpectationSignatures) { - if (!content.getExpectations().isEmpty()) { - expectations.addAll(content.getExpectations().stream().flatMap((expectation) -> switch (expectation.getType()) { - case PREVENTION -> { - // Verify that at least one asset in the group has been executed - List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); - if (assets.stream().anyMatch((asset) -> expectations.stream().filter(e -> InjectExpectation.EXPECTATION_TYPE.PREVENTION == e.type()).anyMatch((e) -> ((PreventionExpectation) e).getAsset() != null && ((PreventionExpectation) e).getAsset().getId().equals(asset.getId())))) { - yield Stream.of(preventionExpectationForAssetGroup(expectation.getScore(), expectation.getName(), expectation.getDescription(), assetGroup, expectation.isExpectationGroup(), injectExpectationSignatures)); - } - yield Stream.of(); - } - case DETECTION -> { - // Verify that at least one asset in the group has been executed - List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); - if (assets.stream().anyMatch((asset) -> expectations.stream().filter(e -> InjectExpectation.EXPECTATION_TYPE.DETECTION == e.type()).anyMatch((e) -> ((DetectionExpectation) e).getAsset() != null && ((DetectionExpectation) e).getAsset().getId().equals(asset.getId())))) { - yield Stream.of(detectionExpectationForAssetGroup(expectation.getScore(), expectation.getName(), expectation.getDescription(), assetGroup, expectation.isExpectationGroup(), injectExpectationSignatures)); - } - yield Stream.of(); - } - case MANUAL -> { - // Verify that at least one asset in the group has been executed - List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); - if (assets.stream().anyMatch((asset) -> expectations.stream().filter(e -> InjectExpectation.EXPECTATION_TYPE.MANUAL == e.type()).anyMatch((e) -> ((ManualExpectation) e).getAsset() != null && ((ManualExpectation) e).getAsset().getId().equals(asset.getId())))) { - yield Stream.of(manualExpectationForAssetGroup(expectation.getScore(), expectation.getName(), expectation.getDescription(), assetGroup, expectation.isExpectationGroup())); - } - yield Stream.of(); - } - default -> Stream.of(); - }).toList()); + /** + * In case of asset group if expectation group -> we have an expectation for the group and one for each asset if not + * expectation group -> we have an individual expectation for each asset + */ + private void computeExpectationsForAssetGroup(@NotNull final List expectations, + @NotNull final OpenBASImplantInjectContent content, @NotNull final AssetGroup assetGroup, + final List injectExpectationSignatures) { + if (!content.getExpectations().isEmpty()) { + expectations.addAll(content.getExpectations().stream().flatMap((expectation) -> switch (expectation.getType()) { + case PREVENTION -> { + // Verify that at least one asset in the group has been executed + List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); + if (assets.stream().anyMatch( + (asset) -> expectations.stream().filter(e -> InjectExpectation.EXPECTATION_TYPE.PREVENTION == e.type()) + .anyMatch( + (e) -> ((PreventionExpectation) e).getAsset() != null && ((PreventionExpectation) e).getAsset() + .getId().equals(asset.getId())))) { + yield Stream.of(preventionExpectationForAssetGroup(expectation.getScore(), expectation.getName(), + expectation.getDescription(), assetGroup, expectation.isExpectationGroup(), + expectation.getExpirationTime(), injectExpectationSignatures)); + } + yield Stream.of(); + } + case DETECTION -> { + // Verify that at least one asset in the group has been executed + List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); + if (assets.stream().anyMatch( + (asset) -> expectations.stream().filter(e -> InjectExpectation.EXPECTATION_TYPE.DETECTION == e.type()) + .anyMatch( + (e) -> ((DetectionExpectation) e).getAsset() != null && ((DetectionExpectation) e).getAsset() + .getId().equals(asset.getId())))) { + yield Stream.of(detectionExpectationForAssetGroup(expectation.getScore(), expectation.getName(), + expectation.getDescription(), assetGroup, expectation.isExpectationGroup(), + expectation.getExpirationTime(), injectExpectationSignatures)); + } + yield Stream.of(); + } + case MANUAL -> { + // Verify that at least one asset in the group has been executed + List assets = this.assetGroupService.assetsFromAssetGroup(assetGroup.getId()); + if (assets.stream().anyMatch( + (asset) -> expectations.stream().filter(e -> InjectExpectation.EXPECTATION_TYPE.MANUAL == e.type()) + .anyMatch( + (e) -> ((ManualExpectation) e).getAsset() != null && ((ManualExpectation) e).getAsset().getId() + .equals(asset.getId())))) { + yield Stream.of(manualExpectationForAssetGroup(expectation.getScore(), expectation.getName(), + expectation.getDescription(), assetGroup, expectation.getExpirationTime(), + expectation.isExpectationGroup())); + } + yield Stream.of(); } + default -> Stream.of(); + }).toList()); } + } - @Override - public ExecutionProcess process(Execution execution, ExecutableInject injection) throws Exception { - Inject inject = this.injectRepository.findById(injection.getInjection().getInject().getId()).orElseThrow(); - Map assets = this.resolveAllAssets(injection); + @Override + public ExecutionProcess process(Execution execution, ExecutableInject injection) throws Exception { + Inject inject = this.injectRepository.findById(injection.getInjection().getInject().getId()).orElseThrow(); + Map assets = this.resolveAllAssets(injection); - // Check assets target - if (assets.isEmpty()) { - execution.addTrace(traceError("Found 0 asset to execute the ability on (likely this inject does not have any target or the targeted asset is inactive and has been purged)")); - } + // Check assets target + if (assets.isEmpty()) { + execution.addTrace(traceError( + "Found 0 asset to execute the ability on (likely this inject does not have any target or the targeted asset is inactive and has been purged)")); + } - // Compute expectations - OpenBASImplantInjectContent content = contentConvert(injection, OpenBASImplantInjectContent.class); + // Compute expectations + OpenBASImplantInjectContent content = contentConvert(injection, OpenBASImplantInjectContent.class); - List expectations = new ArrayList<>(); - assets.forEach((asset, isInGroup) -> { - List injectExpectationSignatures = new ArrayList<>(); + List expectations = new ArrayList<>(); + assets.forEach((asset, isInGroup) -> { + List injectExpectationSignatures = new ArrayList<>(); - inject.getInjectorContract().ifPresent(injectorContract -> { - if (injectorContract.getPayload() != null) { - // Put the correct number in inject status - int totalActionsCount = 0; - switch (injectorContract.getPayload().getType()) { - case "Command": - Command payloadCommand = (Command) Hibernate.unproxy(injectorContract.getPayload()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_PROCESS_NAME).value("obas-implant-" + inject.getId()).build()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_COMMAND_LINE).value(payloadCommand.getContent()).build()); - totalActionsCount = totalActionsCount + 1; - if (payloadCommand.getPrerequisites() != null) { - totalActionsCount = totalActionsCount + payloadCommand.getPrerequisites().size(); - } - if (payloadCommand.getCleanupCommand() != null) { - totalActionsCount = totalActionsCount + 1; - } - break; - case "Executable": - Executable payloadExecutable = (Executable) Hibernate.unproxy(injectorContract.getPayload()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_FILE_NAME).value(payloadExecutable.getExecutableFile().getName()).build()); - totalActionsCount = totalActionsCount + 2; - if (payloadExecutable.getPrerequisites() != null) { - totalActionsCount = totalActionsCount + payloadExecutable.getPrerequisites().size(); - } - if (payloadExecutable.getCleanupCommand() != null) { - totalActionsCount = totalActionsCount + 1; - } - // TODO File hash - break; - case "FileDrop": - FileDrop payloadFileDrop = (FileDrop) Hibernate.unproxy(injectorContract.getPayload()); - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_FILE_NAME).value(payloadFileDrop.getFileDropFile().getName()).build()); - totalActionsCount = totalActionsCount + 1; - if (payloadFileDrop.getPrerequisites() != null) { - totalActionsCount = totalActionsCount + payloadFileDrop.getPrerequisites().size(); - } - if (payloadFileDrop.getCleanupCommand() != null) { - totalActionsCount = totalActionsCount + 1; - } - // TODO File hash - break; - case "DnsResolution": - DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(injectorContract.getPayload()); - // TODO this is only generating the signature for the first hostname - // Problem is: we are not supporting multiple signatures of the same type with "AND" parameters, and this can be in multiple alerts downstream in security platforms - // Tech pain to refine - injectExpectationSignatures.add(InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_HOSTNAME).value(payloadDnsResolution.getHostname().split("\\r?\\n")[0]).build()); - totalActionsCount = totalActionsCount + payloadDnsResolution.getHostname().split("\\r?\\n").length; - if (payloadDnsResolution.getPrerequisites() != null) { - totalActionsCount = totalActionsCount + payloadDnsResolution.getPrerequisites().size(); - } - if (payloadDnsResolution.getCleanupCommand() != null) { - totalActionsCount = totalActionsCount + 1; - } - break; - default: - throw new UnsupportedOperationException("Payload type " + injectorContract.getPayload().getType() + " is not supported"); - } - execution.setExpectedCount(totalActionsCount); - } - }); - computeExpectationsForAsset(expectations, content, asset, isInGroup, injectExpectationSignatures); - }); + inject.getInjectorContract().ifPresent(injectorContract -> { + if (injectorContract.getPayload() != null) { + // Put the correct number in inject status + int totalActionsCount = 0; + switch (injectorContract.getPayload().getType()) { + case "Command": + Command payloadCommand = (Command) Hibernate.unproxy(injectorContract.getPayload()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_PROCESS_NAME) + .value("obas-implant-" + inject.getId()).build()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_COMMAND_LINE) + .value(payloadCommand.getContent()).build()); + totalActionsCount = totalActionsCount + 1; + if (payloadCommand.getPrerequisites() != null) { + totalActionsCount = totalActionsCount + payloadCommand.getPrerequisites().size(); + } + if (payloadCommand.getCleanupCommand() != null) { + totalActionsCount = totalActionsCount + 1; + } + break; + case "Executable": + Executable payloadExecutable = (Executable) Hibernate.unproxy(injectorContract.getPayload()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_FILE_NAME) + .value(payloadExecutable.getExecutableFile().getName()).build()); + totalActionsCount = totalActionsCount + 2; + if (payloadExecutable.getPrerequisites() != null) { + totalActionsCount = totalActionsCount + payloadExecutable.getPrerequisites().size(); + } + if (payloadExecutable.getCleanupCommand() != null) { + totalActionsCount = totalActionsCount + 1; + } + // TODO File hash + break; + case "FileDrop": + FileDrop payloadFileDrop = (FileDrop) Hibernate.unproxy(injectorContract.getPayload()); + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_FILE_NAME) + .value(payloadFileDrop.getFileDropFile().getName()).build()); + totalActionsCount = totalActionsCount + 1; + if (payloadFileDrop.getPrerequisites() != null) { + totalActionsCount = totalActionsCount + payloadFileDrop.getPrerequisites().size(); + } + if (payloadFileDrop.getCleanupCommand() != null) { + totalActionsCount = totalActionsCount + 1; + } + // TODO File hash + break; + case "DnsResolution": + DnsResolution payloadDnsResolution = (DnsResolution) Hibernate.unproxy(injectorContract.getPayload()); + // TODO this is only generating the signature for the first hostname + // Problem is: we are not supporting multiple signatures of the same type with "AND" parameters, and this can be in multiple alerts downstream in security platforms + // Tech pain to refine + injectExpectationSignatures.add( + InjectExpectationSignature.builder().type(EXPECTATION_SIGNATURE_TYPE_HOSTNAME) + .value(payloadDnsResolution.getHostname().split("\\r?\\n")[0]).build()); + totalActionsCount = totalActionsCount + payloadDnsResolution.getHostname().split("\\r?\\n").length; + if (payloadDnsResolution.getPrerequisites() != null) { + totalActionsCount = totalActionsCount + payloadDnsResolution.getPrerequisites().size(); + } + if (payloadDnsResolution.getCleanupCommand() != null) { + totalActionsCount = totalActionsCount + 1; + } + break; + default: + throw new UnsupportedOperationException( + "Payload type " + injectorContract.getPayload().getType() + " is not supported"); + } + execution.setExpectedCount(totalActionsCount); + } + }); + computeExpectationsForAsset(expectations, content, asset, isInGroup, injectExpectationSignatures); + }); - List assetGroups = injection.getAssetGroups(); - assetGroups.forEach((assetGroup -> computeExpectationsForAssetGroup(expectations, content, assetGroup, new ArrayList<>()))); - return new ExecutionProcess(true, expectations); - } + List assetGroups = injection.getAssetGroups(); + assetGroups.forEach( + (assetGroup -> computeExpectationsForAssetGroup(expectations, content, assetGroup, new ArrayList<>()))); + return new ExecutionProcess(true, expectations); + } } 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 8cc96b176a..158833e610 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 @@ -45,7 +45,8 @@ private void createReport(Execution execution, String name, String description, } @Override - public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) throws Exception { + public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) + throws Exception { Inject inject = injection.getInjection().getInject(); CaseContent content = contentConvert(injection, CaseContent.class); List documents = inject.getDocuments().stream().filter(InjectDocument::isAttached) @@ -62,13 +63,12 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin }); List expectations = content.getExpectations() - .stream() - .flatMap((entry) -> switch (entry.getType()) { - case MANUAL -> - Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription(), entry.isExpectationGroup())); - default -> Stream.of(); - }) - .toList(); + .stream() + .flatMap((entry) -> switch (entry.getType()) { + case MANUAL -> Stream.of((Expectation) new ManualExpectation(entry)); + default -> Stream.of(); + }) + .toList(); return new ExecutionProcess(false, expectations); } } 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 1faf2ee3e7..f22e9af12a 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 @@ -31,7 +31,8 @@ public class OvhSmsExecutor extends Injector { private final OvhSmsService smsService; @Override - public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) throws Exception { + public ExecutionProcess process(@NotNull final Execution execution, @NotNull final ExecutableInject injection) + throws Exception { Inject inject = injection.getInjection().getInject(); OvhSmsContent content = contentConvert(injection, OvhSmsContent.class); String smsMessage = content.buildMessage(inject.getFooter(), inject.getHeader()); @@ -63,13 +64,12 @@ public ExecutionProcess process(@NotNull final Execution execution, @NotNull fin }); if (isSmsSent.get()) { List expectations = content.getExpectations() - .stream() - .flatMap(entry -> switch (entry.getType()) { - case MANUAL -> - Stream.of((Expectation) new ManualExpectation(entry.getScore(), entry.getName(), entry.getDescription(), entry.isExpectationGroup())); - default -> Stream.of(); - }) - .toList(); + .stream() + .flatMap(entry -> switch (entry.getType()) { + case MANUAL -> Stream.of((Expectation) new ManualExpectation(entry)); + default -> Stream.of(); + }) + .toList(); return new ExecutionProcess(false, expectations); } return new ExecutionProcess(false, Collections.emptyList()); diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_40__Add_column_expiration_time_expectations.java b/openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java similarity index 95% rename from openbas-api/src/main/java/io/openbas/migration/V3_40__Add_column_expiration_time_expectations.java rename to openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java index 0472813e00..ee525c6ccb 100644 --- a/openbas-api/src/main/java/io/openbas/migration/V3_40__Add_column_expiration_time_expectations.java +++ b/openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java @@ -6,7 +6,7 @@ import java.sql.Connection; import java.sql.Statement; -public class V3_40__Add_column_expiration_time_expectations extends BaseJavaMigration { +public class V3_41__Add_column_expiration_time_expectations extends BaseJavaMigration { @Override public void migrate(Context context) throws Exception { 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 44dc6bbdaa..562d25d919 100644 --- a/openbas-framework/src/main/java/io/openbas/execution/Injector.java +++ b/openbas-framework/src/main/java/io/openbas/execution/Injector.java @@ -30,227 +30,232 @@ public abstract class Injector { - @Resource - protected ObjectMapper mapper; - private FileService fileService; - private DocumentRepository documentRepository; - private InjectExpectationRepository injectExpectationRepository; + @Resource + protected ObjectMapper mapper; + private FileService fileService; + private DocumentRepository documentRepository; + private InjectExpectationRepository injectExpectationRepository; - @Autowired - public void setInjectExpectationRepository(InjectExpectationRepository injectExpectationRepository) { - this.injectExpectationRepository = injectExpectationRepository; - } + @Autowired + public void setInjectExpectationRepository(InjectExpectationRepository injectExpectationRepository) { + this.injectExpectationRepository = injectExpectationRepository; + } - @Autowired - public void setDocumentRepository(DocumentRepository documentRepository) { - this.documentRepository = documentRepository; - } + @Autowired + public void setDocumentRepository(DocumentRepository documentRepository) { + this.documentRepository = documentRepository; + } - @Autowired - public void setFileService(FileService fileService) { - this.fileService = fileService; - } + @Autowired + public void setFileService(FileService fileService) { + this.fileService = fileService; + } - public abstract ExecutionProcess process(Execution execution, ExecutableInject injection) throws Exception; + public abstract ExecutionProcess process(Execution execution, ExecutableInject injection) throws Exception; - public InjectStatusCommandLine getCommandsLines(String externalId) { - return null; - } + public InjectStatusCommandLine getCommandsLines(String externalId) { + return null; + } - private InjectExpectation expectationConverter( - @NotNull final ExecutableInject executableInject, - Expectation expectation) { - InjectExpectation expectationExecution = new InjectExpectation(); - return this.expectationConverter(expectationExecution, executableInject, expectation); - } + private InjectExpectation expectationConverter( + @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) { - InjectExpectation expectationExecution = new InjectExpectation(); - expectationExecution.setTeam(team); - return this.expectationConverter(expectationExecution, executableInject, expectation); - } + private InjectExpectation expectationConverter( + @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 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 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.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 -> { - DetectionExpectation detectionExpectation = (DetectionExpectation) expectation; - expectationExecution.setName(detectionExpectation.getName()); - expectationExecution.setDetection(detectionExpectation.getAsset(), detectionExpectation.getAssetGroup()); - expectationExecution.setSignatures(detectionExpectation.getInjectExpectationSignatures()); - } - case PREVENTION -> { - PreventionExpectation preventionExpectation = (PreventionExpectation) expectation; - expectationExecution.setName(preventionExpectation.getName()); - expectationExecution.setPrevention(preventionExpectation.getAsset(), preventionExpectation.getAssetGroup()); - expectationExecution.setSignatures(preventionExpectation.getInjectExpectationSignatures()); - } - case MANUAL -> { - ManualExpectation manualExpectation = (ManualExpectation) expectation; - expectationExecution.setName(((ManualExpectation) expectation).getName()); - expectationExecution.setManual(manualExpectation.getAsset(), manualExpectation.getAssetGroup()); - expectationExecution.setDescription(((ManualExpectation) expectation).getDescription()); - } - default -> throw new IllegalStateException("Unexpected value: " + expectation); - } - return expectationExecution; + 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()); + expectationExecution.setExpirationTime(expectation.getExpirationTime()); + switch (expectation.type()) { + 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 -> { + DetectionExpectation detectionExpectation = (DetectionExpectation) expectation; + expectationExecution.setName(detectionExpectation.getName()); + expectationExecution.setDetection(detectionExpectation.getAsset(), detectionExpectation.getAssetGroup()); + expectationExecution.setSignatures(detectionExpectation.getInjectExpectationSignatures()); + } + case PREVENTION -> { + PreventionExpectation preventionExpectation = (PreventionExpectation) expectation; + expectationExecution.setName(preventionExpectation.getName()); + expectationExecution.setPrevention(preventionExpectation.getAsset(), preventionExpectation.getAssetGroup()); + expectationExecution.setSignatures(preventionExpectation.getInjectExpectationSignatures()); + } + case MANUAL -> { + ManualExpectation manualExpectation = (ManualExpectation) expectation; + expectationExecution.setName(((ManualExpectation) expectation).getName()); + expectationExecution.setManual(manualExpectation.getAsset(), manualExpectation.getAssetGroup()); + expectationExecution.setDescription(((ManualExpectation) expectation).getDescription()); + } + default -> throw new IllegalStateException("Unexpected value: " + expectation); } + return expectationExecution; + } - @Transactional - public Execution execute(ExecutableInject executableInject) { - Execution execution = new Execution(executableInject.isRuntime()); - try { - boolean isScheduledInject = !executableInject.isDirect(); - boolean isAtomicTesting = executableInject.getInjection().getInject().isAtomicTesting(); - // If empty content, inject must be rejected - if (executableInject.getInjection().getInject().getContent() == null) { - throw new UnsupportedOperationException("Inject is empty"); - } - // If inject is too old, reject the execution - if (isScheduledInject && !isInInjectableRange(executableInject.getInjection())) { - throw new UnsupportedOperationException("Inject is now too old for execution"); - } - // Process the execution - ExecutionProcess executionProcess = process(execution, executableInject); - execution.setAsync(executionProcess.isAsync()); - List expectations = executionProcess.getExpectations(); - // Create the expectations - List teams = executableInject.getTeams(); - List assets = executableInject.getAssets(); - List assetGroups = executableInject.getAssetGroups(); - if ((isScheduledInject || isAtomicTesting) && !expectations.isEmpty()) { - if (!teams.isEmpty()) { - List injectExpectationsByTeam; + @Transactional + public Execution execute(ExecutableInject executableInject) { + Execution execution = new Execution(executableInject.isRuntime()); + try { + boolean isScheduledInject = !executableInject.isDirect(); + boolean isAtomicTesting = executableInject.getInjection().getInject().isAtomicTesting(); + // If empty content, inject must be rejected + if (executableInject.getInjection().getInject().getContent() == null) { + throw new UnsupportedOperationException("Inject is empty"); + } + // If inject is too old, reject the execution + if (isScheduledInject && !isInInjectableRange(executableInject.getInjection())) { + throw new UnsupportedOperationException("Inject is now too old for execution"); + } + // Process the execution + ExecutionProcess executionProcess = process(execution, executableInject); + execution.setAsync(executionProcess.isAsync()); + List expectations = executionProcess.getExpectations(); + // Create the expectations + List teams = executableInject.getTeams(); + List assets = executableInject.getAssets(); + List assetGroups = executableInject.getAssetGroups(); + if ((isScheduledInject || isAtomicTesting) && !expectations.isEmpty()) { + if (!teams.isEmpty()) { + 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()); + 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(); + 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()); + // 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()); - } + // 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(); - this.injectExpectationRepository.saveAll(injectExpectations); - } - } - } catch (Exception e) { - execution.addTrace(traceError(e.getMessage())); - } finally { - execution.stop(); + injectExpectationsByTeam.addAll(injectExpectationsByUserAndTeam); + this.injectExpectationRepository.saveAll(injectExpectationsByTeam); + } else if (!assets.isEmpty() || !assetGroups.isEmpty()) { + List injectExpectations = expectations.stream() + .map(expectation -> expectationConverter(executableInject, expectation)) + .toList(); + this.injectExpectationRepository.saveAll(injectExpectations); } - return execution; + } + } catch (Exception e) { + execution.addTrace(traceError(e.getMessage())); + } finally { + execution.stop(); } + return execution; + } - public Execution executeInjection(ExecutableInject executableInject) { - return execute(executableInject); - } + public Execution executeInjection(ExecutableInject executableInject) { + return execute(executableInject); + } - // region utils - private boolean isInInjectableRange(Injection injection) { - Instant now = Instant.now(); - Instant start = now.minus(Duration.parse("PT1H")); - Instant injectWhen = injection.getDate().orElseThrow(); - return injectWhen.isAfter(start) && injectWhen.isBefore(now); - } + // region utils + private boolean isInInjectableRange(Injection injection) { + Instant now = Instant.now(); + Instant start = now.minus(Duration.parse("PT1H")); + Instant injectWhen = injection.getDate().orElseThrow(); + return injectWhen.isAfter(start) && injectWhen.isBefore(now); + } - public T contentConvert(@NotNull final ExecutableInject injection, @NotNull final Class converter) throws Exception { - Inject inject = injection.getInjection().getInject(); - ObjectNode content = inject.getContent(); - return this.mapper.treeToValue(content, converter); - } + public T contentConvert(@NotNull final ExecutableInject injection, @NotNull final Class converter) + throws Exception { + Inject inject = injection.getInjection().getInject(); + ObjectNode content = inject.getContent(); + return this.mapper.treeToValue(content, converter); + } - public List resolveAttachments(Execution execution, ExecutableInject injection, List documents) { - List resolved = new ArrayList<>(); - // Add attachments from direct configuration - injection.getDirectAttachments().forEach(doc -> { - try { - byte[] content = IOUtils.toByteArray(doc.getInputStream()); - resolved.add(new DataAttachment(doc.getName(), doc.getOriginalFilename(), content, doc.getContentType())); - } catch (Exception e) { - String message = "Error getting direct attachment " + doc.getName(); - execution.addTrace(traceError(message)); - } - }); - // Add attachments from configuration - documents.forEach(attachment -> { - String documentId = attachment.getId(); - Optional askedDocument = documentRepository.findById(documentId); - try { - Document doc = askedDocument.orElseThrow(); - InputStream fileInputStream = fileService.getFile(doc).orElseThrow(); - byte[] content = IOUtils.toByteArray(fileInputStream); - resolved.add(new DataAttachment(documentId, doc.getName(), content, doc.getType())); - } catch (Exception e) { - // Can't fetch the attachments, ignore - String docInfo = askedDocument.map(Document::getName).orElse(documentId); - String message = "Error getting doc attachment " + docInfo; - execution.addTrace(traceError(message)); - } - }); - return resolved; - } - // endregion + public List resolveAttachments(Execution execution, ExecutableInject injection, + List documents) { + List resolved = new ArrayList<>(); + // Add attachments from direct configuration + injection.getDirectAttachments().forEach(doc -> { + try { + byte[] content = IOUtils.toByteArray(doc.getInputStream()); + resolved.add(new DataAttachment(doc.getName(), doc.getOriginalFilename(), content, doc.getContentType())); + } catch (Exception e) { + String message = "Error getting direct attachment " + doc.getName(); + execution.addTrace(traceError(message)); + } + }); + // Add attachments from configuration + documents.forEach(attachment -> { + String documentId = attachment.getId(); + Optional askedDocument = documentRepository.findById(documentId); + try { + Document doc = askedDocument.orElseThrow(); + InputStream fileInputStream = fileService.getFile(doc).orElseThrow(); + byte[] content = IOUtils.toByteArray(fileInputStream); + resolved.add(new DataAttachment(documentId, doc.getName(), content, doc.getType())); + } catch (Exception e) { + // Can't fetch the attachments, ignore + String docInfo = askedDocument.map(Document::getName).orElse(documentId); + String message = "Error getting doc attachment " + docInfo; + execution.addTrace(traceError(message)); + } + }); + return resolved; + } + // endregion } 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 326a8e29fe..770c3ee470 100644 --- a/openbas-framework/src/main/java/io/openbas/model/Expectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/Expectation.java @@ -4,10 +4,15 @@ public interface Expectation { - EXPECTATION_TYPE type(); - Double getScore(); - default boolean isExpectationGroup() { - return false; - } - String getName(); + EXPECTATION_TYPE type(); + + Double getScore(); + + default boolean isExpectationGroup() { + return false; + } + + String getName(); + + Long getExpirationTime(); } 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 9024e25176..df7b275c36 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 @@ -16,17 +16,14 @@ public class ChallengeExpectation implements Expectation { private Challenge challenge; private boolean expectationGroup; private String name; + private Long expirationTime; - 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)); + public ChallengeExpectation(io.openbas.model.inject.form.Expectation expectation, Challenge challenge) { + setScore(Objects.requireNonNullElse(expectation.getScore(), 100.0)); setChallenge(challenge); setName(challenge.getName()); - setExpectationGroup(expectationGroup); + setExpectationGroup(expectation.isExpectationGroup()); + setExpirationTime(expectation.getExpirationTime()); } @Override 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 120df00a78..8f7776b7be 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 @@ -16,17 +16,20 @@ public class ChannelExpectation implements Expectation { private Article article; private boolean expectationGroup; private String name; + private Long expirationTime; public ChannelExpectation(Double score, Article article) { setScore(Objects.requireNonNullElse(score, 100.0)); setArticle(article); } - public ChannelExpectation(Double score, Article article, boolean expectationGroup) { + + public ChannelExpectation(io.openbas.model.inject.form.Expectation expectation, Article article) { setScore(Objects.requireNonNullElse(score, 100.0)); setArticle(article); setName(article.getName()); - setExpectationGroup(expectationGroup); + setExpectationGroup(expectation.isExpectationGroup()); + setExpirationTime(expectation.getExpirationTime()); } @Override 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 a6cb815273..98ad040547 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,56 +20,61 @@ @Setter public class DetectionExpectation implements Expectation { - private Double score; - private String name; - private String description; - private Asset asset; - private AssetGroup assetGroup; - private boolean expectationGroup; - private List injectExpectationSignatures; + private Double score; + private String name; + private String description; + private Asset asset; + private AssetGroup assetGroup; + private boolean expectationGroup; + private Long expirationTime; + private List injectExpectationSignatures; - private DetectionExpectation() { - } + private DetectionExpectation() { + } - @Override - public InjectExpectation.EXPECTATION_TYPE type() { - return DETECTION; - } + @Override + public InjectExpectation.EXPECTATION_TYPE type() { + return DETECTION; + } - public static DetectionExpectation detectionExpectationForAsset( - @Nullable final Double score, - @NotBlank final String name, - final String description, - @NotNull final Asset asset, - final boolean expectationGroup, - final List expectationSignatures - ) { - DetectionExpectation detectionExpectation = new DetectionExpectation(); - detectionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); - detectionExpectation.setName(name); - detectionExpectation.setDescription(description); - detectionExpectation.setAsset(asset); - detectionExpectation.setExpectationGroup(expectationGroup); - detectionExpectation.setInjectExpectationSignatures(expectationSignatures); - return detectionExpectation; - } + public static DetectionExpectation detectionExpectationForAsset( + @Nullable final Double score, + @NotBlank final String name, + final String description, + @NotNull final Asset asset, + final boolean expectationGroup, + final Long expirationTime, + final List expectationSignatures + ) { + DetectionExpectation detectionExpectation = new DetectionExpectation(); + detectionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); + detectionExpectation.setName(name); + detectionExpectation.setDescription(description); + detectionExpectation.setAsset(asset); + detectionExpectation.setExpectationGroup(expectationGroup); + detectionExpectation.setExpirationTime(expirationTime); + detectionExpectation.setInjectExpectationSignatures(expectationSignatures); + return detectionExpectation; + } - public static DetectionExpectation detectionExpectationForAssetGroup( - @Nullable final Double score, - @NotBlank final String name, - final String description, - @NotNull final AssetGroup assetGroup, - final boolean expectationGroup, - final List expectationSignatures - ) { - DetectionExpectation detectionExpectation = new DetectionExpectation(); - detectionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); - detectionExpectation.setName(name); - detectionExpectation.setDescription(description); - detectionExpectation.setAssetGroup(assetGroup); - detectionExpectation.setExpectationGroup(expectationGroup); - detectionExpectation.setInjectExpectationSignatures(expectationSignatures); - return detectionExpectation; - } + public static DetectionExpectation detectionExpectationForAssetGroup( + @Nullable final Double score, + @NotBlank final String name, + final String description, + @NotNull final AssetGroup assetGroup, + final boolean expectationGroup, + final Long expirationTime, + final List expectationSignatures + ) { + DetectionExpectation detectionExpectation = new DetectionExpectation(); + detectionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); + detectionExpectation.setName(name); + detectionExpectation.setDescription(description); + detectionExpectation.setAssetGroup(assetGroup); + detectionExpectation.setExpectationGroup(expectationGroup); + detectionExpectation.setExpirationTime(expirationTime); + detectionExpectation.setInjectExpectationSignatures(expectationSignatures); + return detectionExpectation; + } } 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 1cc631c45a..23f75c9130 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 @@ -25,6 +25,7 @@ public class ManualExpectation implements Expectation { private Asset asset; private AssetGroup assetGroup; private boolean expectationGroup; + private Long expirationTime; public ManualExpectation() { } @@ -33,47 +34,46 @@ public ManualExpectation(final Double score) { this.score = Objects.requireNonNullElse(score, 100.0); } - 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 ManualExpectation(io.openbas.model.inject.form.Expectation expectation) { + this(expectation.getScore()); + this.name = expectation.getName(); + this.description = expectation.getDescription(); + this.expectationGroup = expectation.isExpectationGroup(); + this.expirationTime = expectation.getExpirationTime(); } public static ManualExpectation manualExpectationForAsset( - @Nullable final Double score, - @NotBlank final String name, - final String description, - @NotNull final Asset asset, - final boolean expectationGroup + @Nullable final Double score, + @NotBlank final String name, + final String description, + @NotNull final Asset asset, + final Long expirationTime, + final boolean expectationGroup ) { ManualExpectation manualExpectation = new ManualExpectation(); manualExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); manualExpectation.setName(name); manualExpectation.setDescription(description); manualExpectation.setAsset(asset); + manualExpectation.setExpirationTime(expirationTime); manualExpectation.setExpectationGroup(expectationGroup); return manualExpectation; } public static ManualExpectation manualExpectationForAssetGroup( - @Nullable final Double score, - @NotBlank final String name, - final String description, - @NotNull final AssetGroup assetGroup, - final boolean expectationGroup + @Nullable final Double score, + @NotBlank final String name, + final String description, + @NotNull final AssetGroup assetGroup, + final Long expirationTime, + final boolean expectationGroup ) { ManualExpectation manualExpectation = new ManualExpectation(); manualExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); manualExpectation.setName(name); manualExpectation.setDescription(description); manualExpectation.setAssetGroup(assetGroup); + manualExpectation.setExpirationTime(expirationTime); manualExpectation.setExpectationGroup(expectationGroup); return manualExpectation; } 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 355ca70a3d..13d75df542 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,56 +20,61 @@ @Setter public class PreventionExpectation implements Expectation { - private Double score; - private String name; - private String description; - private Asset asset; - private AssetGroup assetGroup; - private boolean expectationGroup; - private List injectExpectationSignatures; + private Double score; + private String name; + private String description; + private Asset asset; + private AssetGroup assetGroup; + private boolean expectationGroup; + private Long expirationTime; + private List injectExpectationSignatures; - private PreventionExpectation() { - } + private PreventionExpectation() { + } - @Override - public EXPECTATION_TYPE type() { - return PREVENTION; - } + @Override + public EXPECTATION_TYPE type() { + return PREVENTION; + } - public static PreventionExpectation preventionExpectationForAsset( - @Nullable final Double score, - @NotBlank final String name, - final String description, - @NotNull final Asset asset, - final boolean expectationGroup, - final List expectationSignatures - ) { - PreventionExpectation preventionExpectation = new PreventionExpectation(); - preventionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); - preventionExpectation.setName(name); - preventionExpectation.setDescription(description); - preventionExpectation.setAsset(asset); - preventionExpectation.setExpectationGroup(expectationGroup); - preventionExpectation.setInjectExpectationSignatures(expectationSignatures); - return preventionExpectation; - } + public static PreventionExpectation preventionExpectationForAsset( + @Nullable final Double score, + @NotBlank final String name, + final String description, + @NotNull final Asset asset, + final boolean expectationGroup, + final Long expirationTime, + final List expectationSignatures + ) { + PreventionExpectation preventionExpectation = new PreventionExpectation(); + preventionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); + preventionExpectation.setName(name); + preventionExpectation.setDescription(description); + preventionExpectation.setAsset(asset); + preventionExpectation.setExpectationGroup(expectationGroup); + preventionExpectation.setExpirationTime(expirationTime); + preventionExpectation.setInjectExpectationSignatures(expectationSignatures); + return preventionExpectation; + } - public static PreventionExpectation preventionExpectationForAssetGroup( - @Nullable final Double score, - @NotBlank final String name, - final String description, - @NotNull final AssetGroup assetGroup, - final boolean expectationGroup, - final List expectationSignatures - ) { - PreventionExpectation preventionExpectation = new PreventionExpectation(); - preventionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); - preventionExpectation.setName(name); - preventionExpectation.setDescription(description); - preventionExpectation.setAssetGroup(assetGroup); - preventionExpectation.setExpectationGroup(expectationGroup); - preventionExpectation.setInjectExpectationSignatures(expectationSignatures); - return preventionExpectation; - } + public static PreventionExpectation preventionExpectationForAssetGroup( + @Nullable final Double score, + @NotBlank final String name, + final String description, + @NotNull final AssetGroup assetGroup, + final boolean expectationGroup, + final Long expirationTime, + final List expectationSignatures + ) { + PreventionExpectation preventionExpectation = new PreventionExpectation(); + preventionExpectation.setScore(Objects.requireNonNullElse(score, 100.0)); + preventionExpectation.setName(name); + preventionExpectation.setDescription(description); + preventionExpectation.setAssetGroup(assetGroup); + preventionExpectation.setExpectationGroup(expectationGroup); + preventionExpectation.setExpirationTime(expirationTime); + preventionExpectation.setInjectExpectationSignatures(expectationSignatures); + return preventionExpectation; + } } 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 ce1801671b..39f77bfdf4 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 @@ -22,4 +22,7 @@ public class Expectation { @JsonProperty("expectation_expectation_group") private boolean expectationGroup; + @JsonProperty("expectation_expiration_time") + private Long expirationTime; + } 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 0a09be2f2b..e9c2f6b5c9 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 @@ -44,6 +44,7 @@ import { useAppDispatch } from '../../../../utils/hooks'; import type { InjectExpectationStore } from '../../../../actions/injects/Inject'; import { NodeResultStep } from './types/nodes/NodeResultStep'; import { isTechnicalExpectation } from '../../common/injects/expectations/ExpectationUtils'; +import { splitDuration } from '../../../../utils/Time'; interface Steptarget { label: string; @@ -86,6 +87,18 @@ const useStyles = makeStyles((theme) => ({ height: '20px', padding: '0 4px', }, + duration: { + fontSize: 12, + lineHeight: '12px', + height: 20, + float: 'left', + marginRight: 7, + borderRadius: 4, + width: 180, + backgroundColor: 'rgba(0, 177, 255, 0.08)', + color: '#00b1ff', + border: '1px solid #00b1ff', + }, })); interface Props { @@ -527,49 +540,68 @@ const TargetResultsDetailFlow: FunctionComponent = ({ {t('Results')} - {injectExpectation.inject_expectation_results && injectExpectation.inject_expectation_results.map((expectationResult, index) => ( - - - - { - ev.stopPropagation(); - setAnchorEls({ ...anchorEls, [`${injectExpectation.inject_expectation_id}-${expectationResult.sourceId}`]: ev.currentTarget }); - }} - aria-haspopup="true" - size="large" - disabled={['collector', 'media-pressure', 'challenge'].includes(expectationResult.sourceType ?? 'unknown')} - > - - - setAnchorEls({ ...anchorEls, [`${injectExpectation.inject_expectation_id}-${expectationResult.sourceId}`]: null })} - > - handleOpenResultEdition(injectExpectation, expectationResult)}> - {t('Update')} - - handleOpenResultDeletion(injectExpectation, expectationResult)}> - {t('Delete')} - - - - } - title={expectationResult.sourceName ? t(expectationResult.sourceName) : t('Unknown')} - subheader={nsdt(expectationResult.date)} - /> - - - - - - - ))} + {injectExpectation.inject_expectation_results && injectExpectation.inject_expectation_results.map((expectationResult, index) => { + const duration = splitDuration(injectExpectation.inject_expiration_time || 0); + return ( + + + + { + ev.stopPropagation(); + setAnchorEls({ ...anchorEls, [`${injectExpectation.inject_expectation_id}-${expectationResult.sourceId}`]: ev.currentTarget }); + }} + aria-haspopup="true" + size="large" + disabled={['collector', 'media-pressure', 'challenge'].includes(expectationResult.sourceType ?? 'unknown')} + > + + + setAnchorEls({ ...anchorEls, [`${injectExpectation.inject_expectation_id}-${expectationResult.sourceId}`]: null })} + > + handleOpenResultEdition(injectExpectation, expectationResult)}> + {t('Update')} + + handleOpenResultDeletion(injectExpectation, expectationResult)}> + {t('Delete')} + + + + } + title={expectationResult.sourceName ? t(expectationResult.sourceName) : t('Unknown')} + subheader={ + <> +
{nsdt(expectationResult.date)}
+
+ {t('Expired after')} + +
+ + + + } + /> + + + + +
+
+ ); + })} {(['DETECTION', 'PREVENTION'].includes(injectExpectation.inject_expectation_type) || (injectExpectation.inject_expectation_type === 'MANUAL' && injectExpectation.inject_expectation_results && injectExpectation.inject_expectation_results.length === 0)) && ( diff --git a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx index 057854417c..bf905b90f3 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx @@ -35,7 +35,7 @@ const ExpectationPopover: FunctionComponent = ({ expectation_description: expectation.expectation_description ?? '', expectation_score: expectation.expectation_score ?? 100, expectation_expectation_group: expectation.expectation_expectation_group ?? false, - expectation_expiration_time: expectation.expectation_expiration_time ?? 21600, + expectation_expiration_time: expectation.expectation_expiration_time || ((expectation.expectation_type === 'DETECTION' || expectation.expectation_type === 'PREVENTION') ? 21600 : 3600), }; // Popover diff --git a/openbas-front/src/utils/Localization.js b/openbas-front/src/utils/Localization.js index a5654569c9..9a3ff8ef90 100644 --- a/openbas-front/src/utils/Localization.js +++ b/openbas-front/src/utils/Localization.js @@ -462,6 +462,7 @@ const i18n = { Running: 'En cours', Failure: 'Echec', Expired: 'Expiré', + 'Expired after': 'Expiré après', Anonymized: 'Anonymisé', 'No media pressure entry in this channel yet.': 'Encore aucune entrée de pression médiatique dans ce média.', @@ -1826,6 +1827,7 @@ const i18n = { Running: '运行中', Failure: '失败', Expired: '过期', + 'Expired after': '过期时间', Anonymized: '匿名', 'No media pressure entry in this channel yet.': '这个频道中还没有媒体项.', diff --git a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java index d80297a5f7..04f7c7ae25 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java +++ b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java @@ -121,7 +121,7 @@ public EXPECTATION_STATUS getResponse() { @Column(name = "inject_expiration_time") @JsonProperty("inject_expiration_time") @NotNull - private Long expirationTime = 60L; + private Long expirationTime; @Setter @Column(name = "inject_expectation_created_at") From 256742811abf69fbc4434321d13b95c14dccc49c Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Thu, 26 Sep 2024 10:42:39 +0200 Subject: [PATCH 03/14] Resolve drone errors --- .drone.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 875104c0e9..97050fb578 100644 --- a/.drone.yml +++ b/.drone.yml @@ -10,11 +10,11 @@ steps: MINIO_ENDPOINT: minio MINIO_PORT: 9000 commands: - - mvn clean install -q -DskipTests + - mvn clean install -DskipTests - cd openbas-api - - mvn test -q + - mvn test - cd ../openbas-framework - - mvn test -q + - mvn test - name: frontend-tests image: node:20-alpine From 134620fafc78a407305aa651a8bee39886ecd364 Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Thu, 26 Sep 2024 10:49:11 +0200 Subject: [PATCH 04/14] Resolve drone errors --- .drone.yml | 6 +++--- .../V3_41__Add_column_expiration_time_expectations.java | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 97050fb578..875104c0e9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -10,11 +10,11 @@ steps: MINIO_ENDPOINT: minio MINIO_PORT: 9000 commands: - - mvn clean install -DskipTests + - mvn clean install -q -DskipTests - cd openbas-api - - mvn test + - mvn test -q - cd ../openbas-framework - - mvn test + - mvn test -q - name: frontend-tests image: node:20-alpine diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java b/openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java index ee525c6ccb..9eb137d157 100644 --- a/openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java +++ b/openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java @@ -2,10 +2,12 @@ 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_41__Add_column_expiration_time_expectations extends BaseJavaMigration { @Override From 674107110788b3b8b51e032a7d1c05fd3499699b Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Thu, 26 Sep 2024 12:18:49 +0200 Subject: [PATCH 05/14] Resolve drone errors --- .../openbas/utils/fixtures/InjectExpectationFixture.java | 5 +++++ .../common/injects/expectations/ExpectationPopover.tsx | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java index ee3d3ebe70..5908045eeb 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java @@ -13,6 +13,7 @@ public static InjectExpectation createPreventionInjectExpectation(Team team, Inj injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.PREVENTION); injectExpectation.setTeam(team); injectExpectation.setExpectedScore(100.0); + injectExpectation.setExpirationTime(21600L); return injectExpectation; } @@ -22,6 +23,7 @@ public static InjectExpectation createDetectionInjectExpectation(Team team, Inje injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.DETECTION); injectExpectation.setTeam(team); injectExpectation.setExpectedScore(100.0); + injectExpectation.setExpirationTime(21600L); return injectExpectation; } @@ -31,6 +33,7 @@ public static InjectExpectation createManualInjectExpectation(Team team, Inject injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.MANUAL); injectExpectation.setTeam(team); injectExpectation.setExpectedScore(100.0); + injectExpectation.setExpirationTime(3600L); return injectExpectation; } @@ -40,6 +43,7 @@ public static InjectExpectation createArticleInjectExpectation(Team team, Inject injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.ARTICLE); injectExpectation.setTeam(team); injectExpectation.setExpectedScore(100.0); + injectExpectation.setExpirationTime(3600L); return injectExpectation; } @@ -50,6 +54,7 @@ public static InjectExpectation createManualInjectExpectationWithExercise(Team t injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.MANUAL); injectExpectation.setTeam(team); injectExpectation.setExpectedScore(100.0); + injectExpectation.setExpirationTime(3600L); injectExpectation.setExercise(exercise); injectExpectation.setName(expectationName); return injectExpectation; diff --git a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx index bf905b90f3..8b57b53fa9 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx @@ -29,13 +29,20 @@ const ExpectationPopover: FunctionComponent = ({ const [openDelete, setOpenDelete] = useState(false); const [openEdit, setOpenEdit] = useState(false); + const getExpirationTime = (expirationTime: number): number => { + if (expirationTime !== null || expirationTime !== undefined) { + return expirationTime; + } + return (expectation.expectation_type === 'DETECTION' || expectation.expectation_type === 'PREVENTION') ? 21600 : 3600; + }; + const initialValues = { expectation_type: expectation.expectation_type ?? '', expectation_name: expectation.expectation_name ?? '', expectation_description: expectation.expectation_description ?? '', expectation_score: expectation.expectation_score ?? 100, expectation_expectation_group: expectation.expectation_expectation_group ?? false, - expectation_expiration_time: expectation.expectation_expiration_time || ((expectation.expectation_type === 'DETECTION' || expectation.expectation_type === 'PREVENTION') ? 21600 : 3600), + expectation_expiration_time: getExpirationTime(expectation.expectation_expiration_time), }; // Popover From 74be32394bc7341465ed8e02983e7290e5de4b50 Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Thu, 26 Sep 2024 17:02:42 +0200 Subject: [PATCH 06/14] Add expiration time on automatic expectations --- .../injectors/caldera/CalderaContract.java | 244 +++++++-------- .../openbas/integrations/PayloadService.java | 283 +++++++++--------- 2 files changed, 270 insertions(+), 257 deletions(-) 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 cfbb416e4f..31eeb141aa 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 @@ -38,126 +38,130 @@ @RequiredArgsConstructor public class CalderaContract extends Contractor { - public static final String TYPE = "openbas_caldera"; - - private final CalderaInjectorConfig config; - private final CalderaInjectorService injectorCalderaService; - - @Override - public boolean isExpose() { - return this.config.isEnable(); + public static final String TYPE = "openbas_caldera"; + + private final CalderaInjectorConfig config; + private final CalderaInjectorService injectorCalderaService; + + @Override + public boolean isExpose() { + return this.config.isEnable(); + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public ContractConfig getConfig() { + Map labels = Map.of(en, "Caldera", fr, "Caldera"); + return new ContractConfig(TYPE, labels, "#8b0000", "#8b0000", "/img/icon-caldera.png", isExpose()); + } + + @Override + public List contracts() { + if (this.config.isEnable()) { + ContractConfig contractConfig = getConfig(); + // Add contract based on abilities + return new ArrayList<>(abilityContracts(contractConfig)); } - - @Override - public String getType() { - return TYPE; - } - - @Override - public ContractConfig getConfig() { - Map labels = Map.of(en, "Caldera", fr, "Caldera"); - return new ContractConfig(TYPE, labels, "#8b0000", "#8b0000", "/img/icon-caldera.png", isExpose()); - } - - @Override - public List contracts() { - if (this.config.isEnable()) { - ContractConfig contractConfig = getConfig(); - // Add contract based on abilities - return new ArrayList<>(abilityContracts(contractConfig)); + return List.of(); + } + + // -- PRIVATE -- + + private ContractSelect obfuscatorField() { + List obfuscators = this.injectorCalderaService.obfuscators(); + Map obfuscatorChoices = obfuscators.stream() + .collect(Collectors.toMap(Obfuscator::getName, Obfuscator::getName)); + return selectFieldWithDefault( + "obfuscator", + "Obfuscators", + obfuscatorChoices, + "base64" + ); + } + + private ContractExpectations expectations() { + // Prevention + Expectation preventionExpectation = new Expectation(); + preventionExpectation.setType(PREVENTION); + preventionExpectation.setName("Expect inject to be prevented"); + preventionExpectation.setScore(100.0); + preventionExpectation.setExpirationTime(21600L); + // Detection + Expectation detectionExpectation = new Expectation(); + detectionExpectation.setType(DETECTION); + detectionExpectation.setName("Expect inject to be detected"); + detectionExpectation.setScore(100.0); + detectionExpectation.setExpirationTime(21600L); + return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); + } + + private List abilityContracts(@NotNull final ContractConfig contractConfig) { + // Fields + ContractSelect obfuscatorField = obfuscatorField(); + ContractAsset assetField = assetField("assets", "Assets", Multiple); + ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", Multiple); + ContractExpectations expectationsField = expectations(); + + List abilities = this.injectorCalderaService.abilities().stream() + .filter(ability -> !ability.getTactic().equals("openbas")).toList(); + // Build contracts + return abilities.stream().map((ability -> { + ContractDef builder = contractBuilder(); + builder.mandatoryGroup(assetField, assetGroupField); + builder.optional(obfuscatorField); + builder.optional(expectationsField); + List platforms = new ArrayList<>(); + ability.getExecutors().forEach(executor -> { + String command = executor.getCommand(); + if (command != null && !command.isEmpty()) { + Matcher matcher = Pattern.compile("#\\{(.*?)\\}").matcher(command); + while (matcher.find()) { + if (!matcher.group(1).isEmpty()) { + builder.mandatory(ContractText.textField(matcher.group(1), matcher.group(1))); + } + } } - return List.of(); - } - - // -- PRIVATE -- - - private ContractSelect obfuscatorField() { - List obfuscators = this.injectorCalderaService.obfuscators(); - Map obfuscatorChoices = obfuscators.stream().collect(Collectors.toMap(Obfuscator::getName, Obfuscator::getName)); - return selectFieldWithDefault( - "obfuscator", - "Obfuscators", - obfuscatorChoices, - "base64" - ); - } - - private ContractExpectations expectations() { - // Prevention - Expectation preventionExpectation = new Expectation(); - preventionExpectation.setType(PREVENTION); - preventionExpectation.setName("Expect inject to be prevented"); - preventionExpectation.setScore(100.0); - // Detection - Expectation detectionExpectation = new Expectation(); - detectionExpectation.setType(DETECTION); - detectionExpectation.setName("Expect inject to be detected"); - detectionExpectation.setScore(100.0); - return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); - } - - private List abilityContracts(@NotNull final ContractConfig contractConfig) { - // Fields - ContractSelect obfuscatorField = obfuscatorField(); - ContractAsset assetField = assetField("assets", "Assets", Multiple); - ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", Multiple); - ContractExpectations expectationsField = expectations(); - - List abilities = this.injectorCalderaService.abilities().stream().filter(ability -> !ability.getTactic().equals("openbas")).toList(); - // Build contracts - return abilities.stream().map((ability -> { - ContractDef builder = contractBuilder(); - builder.mandatoryGroup(assetField, assetGroupField); - builder.optional(obfuscatorField); - builder.optional(expectationsField); - List platforms = new ArrayList<>(); - ability.getExecutors().forEach(executor -> { - String command = executor.getCommand(); - if (command != null && !command.isEmpty()) { - Matcher matcher = Pattern.compile("#\\{(.*?)\\}").matcher(command); - while (matcher.find()) { - if (!matcher.group(1).isEmpty()) { - builder.mandatory(ContractText.textField(matcher.group(1), matcher.group(1))); - } - } - } - if (!executor.getPlatform().equals("unknown")) { - PLATFORM_TYPE platform = toPlatform(executor.getPlatform()); - if (!platforms.contains(platform)) { - platforms.add(platform); - } - } else { - if (executor.getName().equals("psh")) { - if (!platforms.contains(PLATFORM_TYPE.Windows)) { - platforms.add(PLATFORM_TYPE.Windows); - } - } else if (executor.getName().equals("sh")) { - if (!platforms.contains(PLATFORM_TYPE.Linux)) { - platforms.add(PLATFORM_TYPE.Linux); - } - } else if (executor.getName().equals("cmd")) { - if (!platforms.contains(PLATFORM_TYPE.Windows)) { - platforms.add(PLATFORM_TYPE.Windows); - } - } - } - }); - Contract contract = executableContract( - contractConfig, - ability.getAbility_id(), - Map.of(en, ability.getName(), fr, ability.getName()), - builder.build(), - platforms, - true - ); - contract.addAttackPattern(ability.getTechnique_id()); - return contract; - })).collect(Collectors.toList()); - } - - @Override - public ContractorIcon getIcon() { - InputStream iconStream = getClass().getResourceAsStream("/img/icon-caldera.png"); - return new ContractorIcon(iconStream); - } + if (!executor.getPlatform().equals("unknown")) { + PLATFORM_TYPE platform = toPlatform(executor.getPlatform()); + if (!platforms.contains(platform)) { + platforms.add(platform); + } + } else { + if (executor.getName().equals("psh")) { + if (!platforms.contains(PLATFORM_TYPE.Windows)) { + platforms.add(PLATFORM_TYPE.Windows); + } + } else if (executor.getName().equals("sh")) { + if (!platforms.contains(PLATFORM_TYPE.Linux)) { + platforms.add(PLATFORM_TYPE.Linux); + } + } else if (executor.getName().equals("cmd")) { + if (!platforms.contains(PLATFORM_TYPE.Windows)) { + platforms.add(PLATFORM_TYPE.Windows); + } + } + } + }); + Contract contract = executableContract( + contractConfig, + ability.getAbility_id(), + Map.of(en, ability.getName(), fr, ability.getName()), + builder.build(), + platforms, + true + ); + contract.addAttackPattern(ability.getTechnique_id()); + return contract; + })).collect(Collectors.toList()); + } + + @Override + public ContractorIcon getIcon() { + InputStream iconStream = getClass().getResourceAsStream("/img/icon-caldera.png"); + return new ContractorIcon(iconStream); + } } 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 eff85d067f..506d81a9ef 100644 --- a/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java +++ b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java @@ -45,151 +45,160 @@ @Service public class PayloadService { - @Resource - protected ObjectMapper mapper; + @Resource + protected ObjectMapper mapper; - private final PayloadRepository payloadRepository; - private final InjectorRepository injectorRepository; - private final InjectorContractRepository injectorContractRepository; - private final AttackPatternRepository attackPatternRepository; + private final PayloadRepository payloadRepository; + private final InjectorRepository injectorRepository; + private final InjectorContractRepository injectorContractRepository; + private final AttackPatternRepository attackPatternRepository; - public void updateInjectorContractsForPayload(Payload payload) { - List injectors = this.injectorRepository.findAllByPayloads(true); - injectors.forEach(injector -> updateInjectorContract(injector, payload)); - } + public void updateInjectorContractsForPayload(Payload payload) { + List injectors = this.injectorRepository.findAllByPayloads(true); + injectors.forEach(injector -> updateInjectorContract(injector, payload)); + } - private void updateInjectorContract(Injector injector, Payload payload) { - Optional injectorContract = injectorContractRepository.findInjectorContractByInjectorAndPayload(injector, payload); - if (injectorContract.isPresent()) { - InjectorContract existingInjectorContract = injectorContract.get(); - Contract contract = buildContract(existingInjectorContract.getId(), injector, payload); - Map labels = Map.of("en", payload.getName(), "fr", payload.getName()); - existingInjectorContract.setLabels(labels); - existingInjectorContract.setNeedsExecutor(true); - existingInjectorContract.setManual(false); - existingInjectorContract.setInjector(injector); - existingInjectorContract.setPayload(payload); - existingInjectorContract.setPlatforms(payload.getPlatforms()); - existingInjectorContract.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(payload.getAttackPatterns().stream().map(AttackPattern::getId).toList()))); - existingInjectorContract.setAtomicTesting(true); - try { - existingInjectorContract.setContent(mapper.writeValueAsString(contract)); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - injectorContractRepository.save(existingInjectorContract); - } else { - String contractId = String.valueOf(UUID.randomUUID()); - Map labels = Map.of("en", payload.getName(), "fr", payload.getName()); - Contract contract = buildContract(contractId, injector, payload); - InjectorContract newInjectorContract = new InjectorContract(); - newInjectorContract.setId(contractId); - newInjectorContract.setLabels(labels); - newInjectorContract.setNeedsExecutor(true); - newInjectorContract.setManual(false); - newInjectorContract.setInjector(injector); - newInjectorContract.setPayload(payload); - newInjectorContract.setPlatforms(payload.getPlatforms()); - newInjectorContract.setAttackPatterns(fromIterable(attackPatternRepository.findAllById(payload.getAttackPatterns().stream().map(AttackPattern::getId).toList()))); - newInjectorContract.setAtomicTesting(true); - try { - newInjectorContract.setContent(mapper.writeValueAsString(contract)); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - injectorContractRepository.save(newInjectorContract); - } + private void updateInjectorContract(Injector injector, Payload payload) { + Optional injectorContract = injectorContractRepository.findInjectorContractByInjectorAndPayload( + injector, payload); + if (injectorContract.isPresent()) { + InjectorContract existingInjectorContract = injectorContract.get(); + Contract contract = buildContract(existingInjectorContract.getId(), injector, payload); + Map labels = Map.of("en", payload.getName(), "fr", payload.getName()); + existingInjectorContract.setLabels(labels); + existingInjectorContract.setNeedsExecutor(true); + existingInjectorContract.setManual(false); + existingInjectorContract.setInjector(injector); + existingInjectorContract.setPayload(payload); + existingInjectorContract.setPlatforms(payload.getPlatforms()); + existingInjectorContract.setAttackPatterns(fromIterable(attackPatternRepository.findAllById( + payload.getAttackPatterns().stream().map(AttackPattern::getId).toList()))); + existingInjectorContract.setAtomicTesting(true); + try { + existingInjectorContract.setContent(mapper.writeValueAsString(contract)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + injectorContractRepository.save(existingInjectorContract); + } else { + String contractId = String.valueOf(UUID.randomUUID()); + Map labels = Map.of("en", payload.getName(), "fr", payload.getName()); + Contract contract = buildContract(contractId, injector, payload); + InjectorContract newInjectorContract = new InjectorContract(); + newInjectorContract.setId(contractId); + newInjectorContract.setLabels(labels); + newInjectorContract.setNeedsExecutor(true); + newInjectorContract.setManual(false); + newInjectorContract.setInjector(injector); + newInjectorContract.setPayload(payload); + newInjectorContract.setPlatforms(payload.getPlatforms()); + newInjectorContract.setAttackPatterns(fromIterable(attackPatternRepository.findAllById( + payload.getAttackPatterns().stream().map(AttackPattern::getId).toList()))); + newInjectorContract.setAtomicTesting(true); + try { + newInjectorContract.setContent(mapper.writeValueAsString(contract)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + injectorContractRepository.save(newInjectorContract); } + } - private Contract buildContract(@NotNull final String contractId, @NotNull final Injector injector, @NotNull final Payload payload) { - Map labels = Map.of(en, injector.getName(), fr, injector.getName()); - ContractConfig contractConfig = new ContractConfig(injector.getType(), labels, "#000000", "#000000", "/img/icon-" + injector.getType() + ".png", true); - ContractAsset assetField = assetField("assets", "Assets", Multiple); - ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", Multiple); - ContractExpectations expectationsField = expectations(); - ContractDef builder = contractBuilder(); - builder.mandatoryGroup(assetField, assetGroupField); - builder.optional(expectationsField); - if( payload.getArguments() != null ) { - payload.getArguments().forEach(payloadArgument -> { - builder.mandatory(textField(payloadArgument.getKey(), payloadArgument.getKey(), payloadArgument.getDefaultValue())); - }); - } - return executableContract( - contractConfig, - contractId, - Map.of(en, payload.getName(), fr, payload.getName()), - builder.build(), - Arrays.asList(payload.getPlatforms()), - true - ); + private Contract buildContract(@NotNull final String contractId, @NotNull final Injector injector, + @NotNull final Payload payload) { + Map labels = Map.of(en, injector.getName(), fr, injector.getName()); + ContractConfig contractConfig = new ContractConfig(injector.getType(), labels, "#000000", "#000000", + "/img/icon-" + injector.getType() + ".png", true); + ContractAsset assetField = assetField("assets", "Assets", Multiple); + ContractAssetGroup assetGroupField = assetGroupField("assetgroups", "Asset groups", Multiple); + ContractExpectations expectationsField = expectations(); + ContractDef builder = contractBuilder(); + builder.mandatoryGroup(assetField, assetGroupField); + builder.optional(expectationsField); + if (payload.getArguments() != null) { + payload.getArguments().forEach(payloadArgument -> { + builder.mandatory( + textField(payloadArgument.getKey(), payloadArgument.getKey(), payloadArgument.getDefaultValue())); + }); } + return executableContract( + contractConfig, + contractId, + Map.of(en, payload.getName(), fr, payload.getName()), + builder.build(), + Arrays.asList(payload.getPlatforms()), + true + ); + } - private ContractExpectations expectations() { - // Prevention - Expectation preventionExpectation = new Expectation(); - preventionExpectation.setType(PREVENTION); - preventionExpectation.setName("Expect inject to be prevented"); - preventionExpectation.setScore(100.0); - // Detection - Expectation detectionExpectation = new Expectation(); - detectionExpectation.setType(DETECTION); - detectionExpectation.setName("Expect inject to be detected"); - detectionExpectation.setScore(100.0); - return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); - } + private ContractExpectations expectations() { + // Prevention + Expectation preventionExpectation = new Expectation(); + preventionExpectation.setType(PREVENTION); + preventionExpectation.setName("Expect inject to be prevented"); + preventionExpectation.setScore(100.0); + preventionExpectation.setExpirationTime(21600L); + // Detection + Expectation detectionExpectation = new Expectation(); + detectionExpectation.setType(DETECTION); + detectionExpectation.setName("Expect inject to be detected"); + detectionExpectation.setScore(100.0); + detectionExpectation.setExpirationTime(21600L); + return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); + } - public Payload duplicate(@NotBlank final String payloadId) { - Payload origin = this.payloadRepository.findById(payloadId).orElseThrow(); - Payload duplicate; - switch (origin.getType()) { - case "Command": - Command originCommand = (Command) Hibernate.unproxy(origin); - Command duplicateCommand = new Command(); - duplicateCommonProperties(originCommand, duplicateCommand); - duplicate = payloadRepository.save(duplicateCommand); - break; - case "Executable": - Executable originExecutable = (Executable) Hibernate.unproxy(origin); - Executable duplicateExecutable = new Executable(); - duplicateCommonProperties(originExecutable, duplicateExecutable); - duplicateExecutable.setExecutableFile(originExecutable.getExecutableFile()); - duplicate = payloadRepository.save(duplicateExecutable); - break; - case "FileDrop": - FileDrop originFileDrop = (FileDrop) Hibernate.unproxy(origin); - FileDrop duplicateFileDrop = new FileDrop(); - duplicateCommonProperties(originFileDrop, duplicateFileDrop); - duplicate = payloadRepository.save(duplicateFileDrop); - break; - case "DnsResolution": - DnsResolution originDnsResolution = (DnsResolution) Hibernate.unproxy(origin); - DnsResolution duplicateDnsResolution = new DnsResolution(); - duplicateCommonProperties(originDnsResolution, duplicateDnsResolution); - duplicate = payloadRepository.save(duplicateDnsResolution); - break; - case "NetworkTraffic": - NetworkTraffic originNetworkTraffic = (NetworkTraffic) Hibernate.unproxy(origin); - NetworkTraffic duplicateNetworkTraffic = new NetworkTraffic(); - duplicateCommonProperties(originNetworkTraffic, duplicateNetworkTraffic); - duplicate = payloadRepository.save(duplicateNetworkTraffic); - break; - default: - throw new UnsupportedOperationException("Payload type " + origin.getType() + " is not supported"); - } - this.updateInjectorContractsForPayload(duplicate); - return duplicate; + public Payload duplicate(@NotBlank final String payloadId) { + Payload origin = this.payloadRepository.findById(payloadId).orElseThrow(); + Payload duplicate; + switch (origin.getType()) { + case "Command": + Command originCommand = (Command) Hibernate.unproxy(origin); + Command duplicateCommand = new Command(); + duplicateCommonProperties(originCommand, duplicateCommand); + duplicate = payloadRepository.save(duplicateCommand); + break; + case "Executable": + Executable originExecutable = (Executable) Hibernate.unproxy(origin); + Executable duplicateExecutable = new Executable(); + duplicateCommonProperties(originExecutable, duplicateExecutable); + duplicateExecutable.setExecutableFile(originExecutable.getExecutableFile()); + duplicate = payloadRepository.save(duplicateExecutable); + break; + case "FileDrop": + FileDrop originFileDrop = (FileDrop) Hibernate.unproxy(origin); + FileDrop duplicateFileDrop = new FileDrop(); + duplicateCommonProperties(originFileDrop, duplicateFileDrop); + duplicate = payloadRepository.save(duplicateFileDrop); + break; + case "DnsResolution": + DnsResolution originDnsResolution = (DnsResolution) Hibernate.unproxy(origin); + DnsResolution duplicateDnsResolution = new DnsResolution(); + duplicateCommonProperties(originDnsResolution, duplicateDnsResolution); + duplicate = payloadRepository.save(duplicateDnsResolution); + break; + case "NetworkTraffic": + NetworkTraffic originNetworkTraffic = (NetworkTraffic) Hibernate.unproxy(origin); + NetworkTraffic duplicateNetworkTraffic = new NetworkTraffic(); + duplicateCommonProperties(originNetworkTraffic, duplicateNetworkTraffic); + duplicate = payloadRepository.save(duplicateNetworkTraffic); + break; + default: + throw new UnsupportedOperationException("Payload type " + origin.getType() + " is not supported"); } + this.updateInjectorContractsForPayload(duplicate); + return duplicate; + } - private void duplicateCommonProperties(@org.jetbrains.annotations.NotNull final T origin, @org.jetbrains.annotations.NotNull T duplicate) { - BeanUtils.copyProperties(origin, duplicate); - duplicate.setId(null); - duplicate.setName(StringUtils.duplicateString(origin.getName())); - duplicate.setAttackPatterns(new ArrayList<>(origin.getAttackPatterns())); - duplicate.setTags(new HashSet<>(origin.getTags())); - duplicate.setExternalId(null); - duplicate.setSource(MANUAL); - duplicate.setStatus(VERIFIED); - duplicate.setCollector(null); - } + private void duplicateCommonProperties(@org.jetbrains.annotations.NotNull final T origin, + @org.jetbrains.annotations.NotNull T duplicate) { + BeanUtils.copyProperties(origin, duplicate); + duplicate.setId(null); + duplicate.setName(StringUtils.duplicateString(origin.getName())); + duplicate.setAttackPatterns(new ArrayList<>(origin.getAttackPatterns())); + duplicate.setTags(new HashSet<>(origin.getTags())); + duplicate.setExternalId(null); + duplicate.setSource(MANUAL); + duplicate.setStatus(VERIFIED); + duplicate.setCollector(null); + } } From 83a856a340fef7e7fd1b8db634e45980f6ccb0dc Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Thu, 26 Sep 2024 17:16:54 +0200 Subject: [PATCH 07/14] Add expiration time on automatic expectations --- .../challenge/ChallengeContract.java | 115 +++++++-------- .../injectors/channel/ChannelContract.java | 135 +++++++++--------- 2 files changed, 129 insertions(+), 121 deletions(-) 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 e871f81218..3c4a4c6c63 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 @@ -31,66 +31,69 @@ @Component public class ChallengeContract extends Contractor { - public static final String CHALLENGE_PUBLISH = "f8e70b27-a69c-4b9f-a2df-e217c36b3981"; + public static final String CHALLENGE_PUBLISH = "f8e70b27-a69c-4b9f-a2df-e217c36b3981"; - public static final String TYPE = "openbas_challenge"; + public static final String TYPE = "openbas_challenge"; - @Override - public boolean isExpose() { - return true; - } + @Override + public boolean isExpose() { + return true; + } - @Override - public String getType() { - return TYPE; - } + @Override + public String getType() { + return TYPE; + } - @Override - public ContractConfig getConfig() { - return new ContractConfig(TYPE, Map.of(en, "Challenge", fr, "Challenge"), "#e91e63", "#e91e63", "/img/challenge.png", isExpose()); - } + @Override + public ContractConfig getConfig() { + return new ContractConfig(TYPE, Map.of(en, "Challenge", fr, "Challenge"), "#e91e63", "#e91e63", + "/img/challenge.png", isExpose()); + } - @Override - public List contracts() { - ContractConfig contractConfig = getConfig(); - // In this "internal" contract we can't express choices. - // Choices are contextual to a specific exercise. - String messageBody = """ - Dear player,

- News challenges have been published.

- <#list challenges as challenge> - - ${challenge.name}
- -

- 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)) - .mandatory(teamField("teams", "Teams", Multiple)) - .optional(attachmentField("attachments", "Attachments", Multiple)) - .build(); - Contract publishChallenge = executableContract(contractConfig, - CHALLENGE_PUBLISH, Map.of(en, "Publish challenges", fr, "Publier des challenges"), publishInstance, List.of(Endpoint.PLATFORM_TYPE.Internal), false); - publishChallenge.setAtomicTesting(false); - return List.of(publishChallenge); - } + @Override + public List contracts() { + ContractConfig contractConfig = getConfig(); + // In this "internal" contract we can't express choices. + // Choices are contextual to a specific exercise. + String messageBody = """ + Dear player,

+ News challenges have been published.

+ <#list challenges as challenge> + - ${challenge.name}
+ +

+ 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); + expectation.setExpirationTime(3600L); + 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)) + .mandatory(teamField("teams", "Teams", Multiple)) + .optional(attachmentField("attachments", "Attachments", Multiple)) + .build(); + Contract publishChallenge = executableContract(contractConfig, + CHALLENGE_PUBLISH, Map.of(en, "Publish challenges", fr, "Publier des challenges"), publishInstance, + List.of(Endpoint.PLATFORM_TYPE.Internal), false); + publishChallenge.setAtomicTesting(false); + return List.of(publishChallenge); + } - public ContractorIcon getIcon() { - InputStream iconStream = getClass().getResourceAsStream("/img/icon-challenge.png"); - return new ContractorIcon(iconStream); - } + public ContractorIcon getIcon() { + InputStream iconStream = getClass().getResourceAsStream("/img/icon-challenge.png"); + return new ContractorIcon(iconStream); + } } 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 8b26672e1d..bd49014775 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 @@ -37,78 +37,83 @@ @Component public class ChannelContract extends Contractor { - public static final String CHANNEL_PUBLISH = "fb5e49a2-6366-4492-b69a-f9b9f39a533e"; + public static final String CHANNEL_PUBLISH = "fb5e49a2-6366-4492-b69a-f9b9f39a533e"; - public static final String TYPE = "openbas_channel"; + public static final String TYPE = "openbas_channel"; - @Override - public boolean isExpose() { - return true; - } + @Override + public boolean isExpose() { + return true; + } - @Override - public String getType() { - return TYPE; - } + @Override + public String getType() { + return TYPE; + } - @Override - public ContractConfig getConfig() { - return new ContractConfig(TYPE, Map.of(en, "Media pressure", fr, "Pression médiatique"), "#ff9800", "#ff9800", "/img/channel.png", isExpose()); - } + @Override + public ContractConfig getConfig() { + return new ContractConfig(TYPE, Map.of(en, "Media pressure", fr, "Pression médiatique"), "#ff9800", "#ff9800", + "/img/channel.png", isExpose()); + } - @Override - public List contracts() { - ContractConfig contractConfig = getConfig(); - // In this "internal" contract we can't express choices. - // Choices are contextual to a specific exercise. - String messageBody = """ - Dear player,

- New media pressure entries have been published.

- <#list articles as article> - - ${article.name}
- -

- Kind regards,
- The animation team - """; - ContractCheckbox emailingField = checkboxField("emailing", "Send email", true); - Expectation expectation = new Expectation(); - expectation.setType(ARTICLE); - expectation.setName("Expect targets to read the article(s)"); - expectation.setScore(0.0); - ContractExpectations expectationsField = expectationsField( - "expectations", "Expectations", List.of(expectation) - ); - List publishInstance = contractBuilder() - // built in - .optional(teamField("teams", "Teams", Multiple)) - .optional(attachmentField("attachments", "Attachments", Multiple)) - .mandatory(articleField("articles", "Articles", Multiple)) - // Contract specific - .optional(expectationsField) - // Emailing zone - .optional(emailingField) - .mandatory(textField("subject", "Subject", "New media pressure entries published for ${user.email}", - List.of(emailingField))) - .mandatory(richTextareaField("body", "Body", messageBody, - List.of(emailingField))) - .optional(checkboxField("encrypted", "Encrypted", false, - List.of(emailingField))) - .build(); - Contract publishArticle = executableContract(contractConfig, - CHANNEL_PUBLISH, Map.of(en, "Publish a media pressure", fr, "Publier de la pression médiatique"), publishInstance, List.of(Endpoint.PLATFORM_TYPE.Internal), false); - // Adding generated variables - publishArticle.addVariable(variable(VARIABLE_ARTICLES, "List of articles published by the injection", VariableType.Object, Multiple, List.of( + @Override + public List contracts() { + ContractConfig contractConfig = getConfig(); + // In this "internal" contract we can't express choices. + // Choices are contextual to a specific exercise. + String messageBody = """ + Dear player,

+ New media pressure entries have been published.

+ <#list articles as article> + - ${article.name}
+ +

+ Kind regards,
+ The animation team + """; + ContractCheckbox emailingField = checkboxField("emailing", "Send email", true); + Expectation expectation = new Expectation(); + expectation.setType(ARTICLE); + expectation.setName("Expect targets to read the article(s)"); + expectation.setScore(0.0); + expectation.setExpirationTime(3600L); + ContractExpectations expectationsField = expectationsField( + "expectations", "Expectations", List.of(expectation) + ); + List publishInstance = contractBuilder() + // built in + .optional(teamField("teams", "Teams", Multiple)) + .optional(attachmentField("attachments", "Attachments", Multiple)) + .mandatory(articleField("articles", "Articles", Multiple)) + // Contract specific + .optional(expectationsField) + // Emailing zone + .optional(emailingField) + .mandatory(textField("subject", "Subject", "New media pressure entries published for ${user.email}", + List.of(emailingField))) + .mandatory(richTextareaField("body", "Body", messageBody, + List.of(emailingField))) + .optional(checkboxField("encrypted", "Encrypted", false, + List.of(emailingField))) + .build(); + Contract publishArticle = executableContract(contractConfig, + CHANNEL_PUBLISH, Map.of(en, "Publish a media pressure", fr, "Publier de la pression médiatique"), + publishInstance, List.of(Endpoint.PLATFORM_TYPE.Internal), false); + // Adding generated variables + publishArticle.addVariable( + variable(VARIABLE_ARTICLES, "List of articles published by the injection", VariableType.Object, Multiple, + List.of( variable(VARIABLE_ARTICLE + ".id", "Id of the article in the platform", VariableType.String, One), variable(VARIABLE_ARTICLE + ".name", "Name of the article", VariableType.String, One), variable(VARIABLE_ARTICLE + ".uri", "Http user link to access the article", VariableType.String, One) - ))); - publishArticle.setAtomicTesting(false); - return List.of(publishArticle); - } + ))); + publishArticle.setAtomicTesting(false); + return List.of(publishArticle); + } - public ContractorIcon getIcon() { - InputStream iconStream = getClass().getResourceAsStream("/img/icon-media-pressure.png"); - return new ContractorIcon(iconStream); - } + public ContractorIcon getIcon() { + InputStream iconStream = getClass().getResourceAsStream("/img/icon-media-pressure.png"); + return new ContractorIcon(iconStream); + } } From 89acc1d8398d1098817cadf7cbe26681f3c18da9 Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Fri, 27 Sep 2024 12:42:03 +0200 Subject: [PATCH 08/14] Resolve comments --- .../injectors/caldera/CalderaContract.java | 10 +++++--- .../challenge/ChallengeContract.java | 6 +++-- .../injectors/channel/ChannelContract.java | 6 +++-- .../fixtures/InjectExpectationFixture.java | 25 +++++++++++-------- .../java/io/openbas/execution/Injector.java | 4 ++- .../openbas/integrations/PayloadService.java | 10 +++++--- .../expectation/PreventionExpectation.java | 2 +- .../model/inject/form/Expectation.java | 5 ++++ .../expectations/ExpectationPopover.tsx | 11 ++++---- .../expectations/InjectAddExpectation.tsx | 6 +---- .../database/model/InjectExpectation.java | 3 +++ 11 files changed, 53 insertions(+), 35 deletions(-) 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 31eeb141aa..7f63f80c95 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 @@ -84,18 +84,20 @@ private ContractSelect obfuscatorField() { } private ContractExpectations expectations() { + Long EXPIRATION_TIME = 21600L; + Double SCORE = 100.0; // Prevention Expectation preventionExpectation = new Expectation(); preventionExpectation.setType(PREVENTION); preventionExpectation.setName("Expect inject to be prevented"); - preventionExpectation.setScore(100.0); - preventionExpectation.setExpirationTime(21600L); + preventionExpectation.setScore(SCORE); + preventionExpectation.setExpirationTime(EXPIRATION_TIME); // Detection Expectation detectionExpectation = new Expectation(); detectionExpectation.setType(DETECTION); detectionExpectation.setName("Expect inject to be detected"); - detectionExpectation.setScore(100.0); - detectionExpectation.setExpirationTime(21600L); + detectionExpectation.setScore(SCORE); + detectionExpectation.setExpirationTime(EXPIRATION_TIME); 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 3c4a4c6c63..b57f301368 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 @@ -53,6 +53,8 @@ public ContractConfig getConfig() { @Override public List contracts() { + Long EXPIRATION_TIME = 3600L; + Double SCORE = 0.0; ContractConfig contractConfig = getConfig(); // In this "internal" contract we can't express choices. // Choices are contextual to a specific exercise. @@ -70,8 +72,8 @@ public List contracts() { Expectation expectation = new Expectation(); expectation.setType(CHALLENGE); expectation.setName("Expect targets to complete the challenge(s)"); - expectation.setScore(0.0); - expectation.setExpirationTime(3600L); + expectation.setScore(SCORE); + expectation.setExpirationTime(EXPIRATION_TIME); ContractExpectations expectationsField = expectationsField( "expectations", "Expectations", List.of(expectation) ); 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 bd49014775..b540728364 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 @@ -59,6 +59,8 @@ public ContractConfig getConfig() { @Override public List contracts() { + Long EXPIRATION_TIME = 3600L; + Double SCORE = 0.0; ContractConfig contractConfig = getConfig(); // In this "internal" contract we can't express choices. // Choices are contextual to a specific exercise. @@ -76,8 +78,8 @@ public List contracts() { Expectation expectation = new Expectation(); expectation.setType(ARTICLE); expectation.setName("Expect targets to read the article(s)"); - expectation.setScore(0.0); - expectation.setExpirationTime(3600L); + expectation.setScore(SCORE); + expectation.setExpirationTime(EXPIRATION_TIME); ContractExpectations expectationsField = expectationsField( "expectations", "Expectations", List.of(expectation) ); diff --git a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java index 5908045eeb..f238543e36 100644 --- a/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java +++ b/openbas-api/src/test/java/io/openbas/utils/fixtures/InjectExpectationFixture.java @@ -7,13 +7,18 @@ public class InjectExpectationFixture { + static Long EXPIRATION_TIME_SIX_HOURS = 21600L; + static Long EXPIRATION_TIME_ONE_HOUR = 3600L; + + static Double EXPECTED_SCORE = 100.0; + public static InjectExpectation createPreventionInjectExpectation(Team team, Inject inject) { InjectExpectation injectExpectation = new InjectExpectation(); injectExpectation.setInject(inject); injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.PREVENTION); injectExpectation.setTeam(team); - injectExpectation.setExpectedScore(100.0); - injectExpectation.setExpirationTime(21600L); + injectExpectation.setExpectedScore(EXPECTED_SCORE); + injectExpectation.setExpirationTime(EXPIRATION_TIME_SIX_HOURS); return injectExpectation; } @@ -22,8 +27,8 @@ public static InjectExpectation createDetectionInjectExpectation(Team team, Inje injectExpectation.setInject(inject); injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.DETECTION); injectExpectation.setTeam(team); - injectExpectation.setExpectedScore(100.0); - injectExpectation.setExpirationTime(21600L); + injectExpectation.setExpectedScore(EXPECTED_SCORE); + injectExpectation.setExpirationTime(EXPIRATION_TIME_SIX_HOURS); return injectExpectation; } @@ -32,8 +37,8 @@ public static InjectExpectation createManualInjectExpectation(Team team, Inject injectExpectation.setInject(inject); injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.MANUAL); injectExpectation.setTeam(team); - injectExpectation.setExpectedScore(100.0); - injectExpectation.setExpirationTime(3600L); + injectExpectation.setExpectedScore(EXPECTED_SCORE); + injectExpectation.setExpirationTime(EXPIRATION_TIME_ONE_HOUR); return injectExpectation; } @@ -42,8 +47,8 @@ public static InjectExpectation createArticleInjectExpectation(Team team, Inject injectExpectation.setInject(inject); injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.ARTICLE); injectExpectation.setTeam(team); - injectExpectation.setExpectedScore(100.0); - injectExpectation.setExpirationTime(3600L); + injectExpectation.setExpectedScore(EXPECTED_SCORE); + injectExpectation.setExpirationTime(EXPIRATION_TIME_ONE_HOUR); return injectExpectation; } @@ -53,8 +58,8 @@ public static InjectExpectation createManualInjectExpectationWithExercise(Team t injectExpectation.setInject(inject); injectExpectation.setType(InjectExpectation.EXPECTATION_TYPE.MANUAL); injectExpectation.setTeam(team); - injectExpectation.setExpectedScore(100.0); - injectExpectation.setExpirationTime(3600L); + injectExpectation.setExpectedScore(EXPECTED_SCORE); + injectExpectation.setExpirationTime(EXPIRATION_TIME_ONE_HOUR); injectExpectation.setExercise(exercise); injectExpectation.setName(expectationName); return injectExpectation; 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 562d25d919..e22448aa2f 100644 --- a/openbas-framework/src/main/java/io/openbas/execution/Injector.java +++ b/openbas-framework/src/main/java/io/openbas/execution/Injector.java @@ -88,11 +88,13 @@ private InjectExpectation expectationConverter( @NotNull InjectExpectation expectationExecution, @NotNull final ExecutableInject executableInject, @NotNull final Expectation expectation) { + Long DEFAULT_EXPIRATION_TIME = 86400L; expectationExecution.setExercise(executableInject.getInjection().getExercise()); expectationExecution.setInject(executableInject.getInjection().getInject()); expectationExecution.setExpectedScore(expectation.getScore()); expectationExecution.setExpectationGroup(expectation.isExpectationGroup()); - expectationExecution.setExpirationTime(expectation.getExpirationTime()); + expectationExecution.setExpirationTime( + Optional.ofNullable(expectation.getExpirationTime()).orElse(DEFAULT_EXPIRATION_TIME)); switch (expectation.type()) { case ARTICLE -> { expectationExecution.setName(expectation.getName()); 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 506d81a9ef..3348b8dabf 100644 --- a/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java +++ b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java @@ -132,18 +132,20 @@ private Contract buildContract(@NotNull final String contractId, @NotNull final } private ContractExpectations expectations() { + Long EXPIRATION_TIME = 21600L; + Double SCORE = 100.0; // Prevention Expectation preventionExpectation = new Expectation(); preventionExpectation.setType(PREVENTION); preventionExpectation.setName("Expect inject to be prevented"); - preventionExpectation.setScore(100.0); - preventionExpectation.setExpirationTime(21600L); + preventionExpectation.setScore(SCORE); + preventionExpectation.setExpirationTime(EXPIRATION_TIME); // Detection Expectation detectionExpectation = new Expectation(); detectionExpectation.setType(DETECTION); detectionExpectation.setName("Expect inject to be detected"); - detectionExpectation.setScore(100.0); - detectionExpectation.setExpirationTime(21600L); + detectionExpectation.setScore(SCORE); + detectionExpectation.setExpirationTime(EXPIRATION_TIME); return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); } 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 13d75df542..814894a4b1 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 @@ -63,7 +63,7 @@ public static PreventionExpectation preventionExpectationForAssetGroup( final String description, @NotNull final AssetGroup assetGroup, final boolean expectationGroup, - final Long expirationTime, + @NotNull final Long expirationTime, final List expectationSignatures ) { PreventionExpectation preventionExpectation = new PreventionExpectation(); 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 39f77bfdf4..bab821d378 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 @@ -22,6 +22,11 @@ public class Expectation { @JsonProperty("expectation_expectation_group") private boolean expectationGroup; + /** + * Gets the expiration time + * + * @return the expiration in seconds + */ @JsonProperty("expectation_expiration_time") private Long expirationTime; diff --git a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx index 8b57b53fa9..4bb7fba5ca 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx @@ -29,11 +29,14 @@ const ExpectationPopover: FunctionComponent = ({ const [openDelete, setOpenDelete] = useState(false); const [openEdit, setOpenEdit] = useState(false); + const EXPIRATION_TIME_SIX_HOURS: number = 21600; + const EXPIRATION_TIME_ONE_HOUR: number = 3600; + const getExpirationTime = (expirationTime: number): number => { if (expirationTime !== null || expirationTime !== undefined) { return expirationTime; } - return (expectation.expectation_type === 'DETECTION' || expectation.expectation_type === 'PREVENTION') ? 21600 : 3600; + return (expectation.expectation_type === 'DETECTION' || expectation.expectation_type === 'PREVENTION') ? EXPIRATION_TIME_SIX_HOURS : EXPIRATION_TIME_ONE_HOUR; }; const initialValues = { @@ -61,11 +64,7 @@ const ExpectationPopover: FunctionComponent = ({ const onSubmitEdit = (data: ExpectationInputForm) => { const values: ExpectationInput = { - expectation_type: data.expectation_type, - expectation_name: data.expectation_name, - expectation_description: data.expectation_description, - expectation_score: data.expectation_score, - expectation_expectation_group: data.expectation_expectation_group, + ...data, expectation_expiration_time: data.expiration_time_days * 3600 * 24 + data.expiration_time_hours * 3600 + data.expiration_time_minutes * 60, diff --git a/openbas-front/src/admin/components/common/injects/expectations/InjectAddExpectation.tsx b/openbas-front/src/admin/components/common/injects/expectations/InjectAddExpectation.tsx index 68c8fe4c79..1e87fb39ea 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/InjectAddExpectation.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/InjectAddExpectation.tsx @@ -43,11 +43,7 @@ const InjectAddExpectation: FunctionComponent = ({ // Form const onSubmit = (data: ExpectationInputForm) => { const values: ExpectationInput = { - expectation_type: data.expectation_type, - expectation_name: data.expectation_name, - expectation_description: data.expectation_description, - expectation_score: data.expectation_score, - expectation_expectation_group: data.expectation_expectation_group, + ...data, expectation_expiration_time: data.expiration_time_days * 3600 * 24 + data.expiration_time_hours * 3600 + data.expiration_time_minutes * 60, diff --git a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java index 04f7c7ae25..1830dfb0d4 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java +++ b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java @@ -117,6 +117,9 @@ public EXPECTATION_STATUS getResponse() { @NotNull private Double expectedScore; + /** + * Expiration time stored in db in seconds + */ @Setter @Column(name = "inject_expiration_time") @JsonProperty("inject_expiration_time") From 87fb336762bd56ca5822cf6203193e7fd7d0a909 Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Fri, 27 Sep 2024 14:12:31 +0200 Subject: [PATCH 09/14] Resolve comments --- .../java/io/openbas/injectors/channel/ChannelContract.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 b540728364..245b08ecbc 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 @@ -60,7 +60,6 @@ public ContractConfig getConfig() { @Override public List contracts() { Long EXPIRATION_TIME = 3600L; - Double SCORE = 0.0; ContractConfig contractConfig = getConfig(); // In this "internal" contract we can't express choices. // Choices are contextual to a specific exercise. @@ -78,7 +77,7 @@ public List contracts() { Expectation expectation = new Expectation(); expectation.setType(ARTICLE); expectation.setName("Expect targets to read the article(s)"); - expectation.setScore(SCORE); + expectation.setScore(0.0); expectation.setExpirationTime(EXPIRATION_TIME); ContractExpectations expectationsField = expectationsField( "expectations", "Expectations", List.of(expectation) From ef893076d34a200edc8bdb11032e883b999c1741 Mon Sep 17 00:00:00 2001 From: Johanah LEKEU Date: Fri, 27 Sep 2024 16:46:32 +0200 Subject: [PATCH 10/14] Resolve comments --- .../java/io/openbas/injectors/channel/ChannelContract.java | 3 ++- ...ava => V3_42__Add_column_expiration_time_expectations.java} | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename openbas-api/src/main/java/io/openbas/migration/{V3_41__Add_column_expiration_time_expectations.java => V3_42__Add_column_expiration_time_expectations.java} (95%) 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 245b08ecbc..b540728364 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 @@ -60,6 +60,7 @@ public ContractConfig getConfig() { @Override public List contracts() { Long EXPIRATION_TIME = 3600L; + Double SCORE = 0.0; ContractConfig contractConfig = getConfig(); // In this "internal" contract we can't express choices. // Choices are contextual to a specific exercise. @@ -77,7 +78,7 @@ public List contracts() { Expectation expectation = new Expectation(); expectation.setType(ARTICLE); expectation.setName("Expect targets to read the article(s)"); - expectation.setScore(0.0); + expectation.setScore(SCORE); expectation.setExpirationTime(EXPIRATION_TIME); ContractExpectations expectationsField = expectationsField( "expectations", "Expectations", List.of(expectation) diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java b/openbas-api/src/main/java/io/openbas/migration/V3_42__Add_column_expiration_time_expectations.java similarity index 95% rename from openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java rename to openbas-api/src/main/java/io/openbas/migration/V3_42__Add_column_expiration_time_expectations.java index 9eb137d157..dfd4e7226b 100644 --- a/openbas-api/src/main/java/io/openbas/migration/V3_41__Add_column_expiration_time_expectations.java +++ b/openbas-api/src/main/java/io/openbas/migration/V3_42__Add_column_expiration_time_expectations.java @@ -8,7 +8,7 @@ import java.sql.Statement; @Component -public class V3_41__Add_column_expiration_time_expectations extends BaseJavaMigration { +public class V3_42__Add_column_expiration_time_expectations extends BaseJavaMigration { @Override public void migrate(Context context) throws Exception { From ea39ea947556db0d04f7b9b72798346747bc3aae Mon Sep 17 00:00:00 2001 From: Romuald Lemesle Date: Sat, 28 Sep 2024 01:42:48 +0200 Subject: [PATCH 11/14] [backend/frontend] Add configuration from config file --- .../ExpectationsExpirationManagerService.java | 6 +- .../utils/ExpectationUtils.java | 9 ++- .../injectors/caldera/CalderaContract.java | 28 +++----- .../caldera/CalderaResultCollector.java | 4 -- .../challenge/ChallengeContract.java | 28 ++++---- .../injectors/channel/ChannelContract.java | 28 ++++---- ...d_column_expiration_time_expectations.java | 18 ++--- .../settings/response/PlatformSettings.java | 22 ++++++ .../service/PlatformSettingsService.java | 12 +++- .../io/openbas/utils/ExpectationUtils.java | 3 + .../src/main/resources/application.properties | 13 ++++ .../java/io/openbas/execution/Injector.java | 7 +- .../executors/tanium/TaniumExecutor.java | 1 - .../ExpectationBuilderService.java | 54 ++++++++++++++ .../ExpectationPropertiesConfig.java | 72 +++++++++++++++++++ .../openbas/integrations/PayloadService.java | 28 +++----- .../java/io/openbas/model/Expectation.java | 3 + .../model/expectation/ChannelExpectation.java | 5 -- .../model/expectation/ManualExpectation.java | 2 - .../model/inject/form/Expectation.java | 4 +- .../expectations/ExpectationFormCreate.tsx | 24 ++++--- .../expectations/ExpectationPopover.tsx | 7 +- .../useExpectationExpirationTime.tsx | 25 +++++++ .../components/scenarios/ScenarioForm.tsx | 2 +- openbas-front/src/utils/api-types.d.ts | 12 +++- .../database/model/InjectExpectation.java | 2 +- 26 files changed, 302 insertions(+), 117 deletions(-) create mode 100644 openbas-framework/src/main/java/io/openbas/expectation/ExpectationBuilderService.java create mode 100644 openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java create mode 100644 openbas-front/src/admin/components/common/injects/expectations/useExpectationExpirationTime.tsx diff --git a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java index 220376c518..8ca361a582 100644 --- a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java +++ b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/service/ExpectationsExpirationManagerService.java @@ -39,8 +39,7 @@ public void computeExpectations() { private void computeExpectations(@NotNull final List expectations) { List expectationAssets = expectations.stream().toList(); expectationAssets.forEach((expectation) -> { - Long userExpirationTime = expectation.getExpirationTime(); - if (isExpired(expectation, Math.toIntExact(userExpirationTime / 60))) { + if (isExpired(expectation)) { String result = computeFailedMessage(expectation.getType()); this.injectExpectationService.computeExpectation( expectation, @@ -58,8 +57,7 @@ private void computeExpectations(@NotNull final List expectat private void computeExpectationsForAssets(@NotNull final List expectations) { List expectationAssets = expectations.stream().filter(e -> e.getAsset() != null).toList(); expectationAssets.forEach((expectation) -> { - Long userExpirationTime = expectation.getExpirationTime(); - if (isExpired(expectation, Math.toIntExact(userExpirationTime / 60))) { + if (isExpired(expectation)) { String result = computeFailedMessage(expectation.getType()); this.injectExpectationService.computeExpectation( expectation, diff --git a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/utils/ExpectationUtils.java b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/utils/ExpectationUtils.java index 36d1a5f40c..701f68f7e4 100644 --- a/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/utils/ExpectationUtils.java +++ b/openbas-api/src/main/java/io/openbas/collectors/expectations_expiration_manager/utils/ExpectationUtils.java @@ -12,8 +12,13 @@ public class ExpectationUtils { - public static boolean isExpired(@NotNull final InjectExpectation expectation, final int expirationTime) { - return expectation.getCreatedAt().isBefore(Instant.now().minus(expirationTime, ChronoUnit.MINUTES)); + private ExpectationUtils() { + + } + + public static boolean isExpired(@NotNull final InjectExpectation expectation) { + Instant expirationTime = Instant.now().minus(expectation.getExpirationTime() / 60, ChronoUnit.MINUTES); + return expectation.getCreatedAt().isBefore(expirationTime); } public static String computeFailedMessage(@NotNull final EXPECTATION_TYPE expectationType) { 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 7f63f80c95..b3f4f9f427 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 @@ -1,6 +1,7 @@ package io.openbas.injectors.caldera; import io.openbas.database.model.Endpoint.PLATFORM_TYPE; +import io.openbas.expectation.ExpectationBuilderService; import io.openbas.helper.SupportedLanguage; import io.openbas.injector_contract.*; import io.openbas.injector_contract.fields.*; @@ -8,7 +9,6 @@ import io.openbas.injectors.caldera.config.CalderaInjectorConfig; import io.openbas.injectors.caldera.model.Obfuscator; import io.openbas.injectors.caldera.service.CalderaInjectorService; -import io.openbas.model.inject.form.Expectation; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -21,8 +21,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.DETECTION; -import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.PREVENTION; import static io.openbas.executors.caldera.service.CalderaExecutorService.toPlatform; import static io.openbas.helper.SupportedLanguage.en; import static io.openbas.helper.SupportedLanguage.fr; @@ -42,6 +40,7 @@ public class CalderaContract extends Contractor { private final CalderaInjectorConfig config; private final CalderaInjectorService injectorCalderaService; + private final ExpectationBuilderService expectationBuilderService; @Override public boolean isExpose() { @@ -84,21 +83,14 @@ private ContractSelect obfuscatorField() { } private ContractExpectations expectations() { - Long EXPIRATION_TIME = 21600L; - Double SCORE = 100.0; - // Prevention - Expectation preventionExpectation = new Expectation(); - preventionExpectation.setType(PREVENTION); - preventionExpectation.setName("Expect inject to be prevented"); - preventionExpectation.setScore(SCORE); - preventionExpectation.setExpirationTime(EXPIRATION_TIME); - // Detection - Expectation detectionExpectation = new Expectation(); - detectionExpectation.setType(DETECTION); - detectionExpectation.setName("Expect inject to be detected"); - detectionExpectation.setScore(SCORE); - detectionExpectation.setExpirationTime(EXPIRATION_TIME); - return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); + return expectationsField( + "expectations", + "Expectations", + List.of( + this.expectationBuilderService.buildPreventionExpectation(), + this.expectationBuilderService.buildDetectionExpectation() + ) + ); } private List abilityContracts(@NotNull final ContractConfig contractConfig) { diff --git a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaResultCollector.java b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaResultCollector.java index 4282c81509..e180a86037 100644 --- a/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaResultCollector.java +++ b/openbas-api/src/main/java/io/openbas/injectors/caldera/CalderaResultCollector.java @@ -1,12 +1,8 @@ package io.openbas.injectors.caldera; -import io.openbas.asset.EndpointService; import io.openbas.database.repository.InjectRepository; import io.openbas.database.repository.InjectStatusRepository; -import io.openbas.inject_expectation.InjectExpectationService; -import io.openbas.injectors.caldera.client.CalderaInjectorClient; import io.openbas.injectors.caldera.config.CalderaInjectorConfig; -import io.openbas.injectors.caldera.service.CalderaGarbageCollectorService; import io.openbas.injectors.caldera.service.CalderaInjectorService; import io.openbas.injectors.caldera.service.CalderaResultCollectorService; import jakarta.annotation.PostConstruct; 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 b57f301368..d1689a564c 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 @@ -1,36 +1,39 @@ package io.openbas.injectors.challenge; +import io.openbas.database.model.Endpoint; +import io.openbas.expectation.ExpectationBuilderService; import io.openbas.injector_contract.Contract; import io.openbas.injector_contract.ContractConfig; import io.openbas.injector_contract.Contractor; 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 lombok.RequiredArgsConstructor; 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.helper.SupportedLanguage.en; +import static io.openbas.helper.SupportedLanguage.fr; 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.ContractChallenge.challengeField; +import static io.openbas.injector_contract.fields.ContractCheckbox.checkboxField; 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; import static io.openbas.injector_contract.fields.ContractTextArea.richTextareaField; -import static io.openbas.helper.SupportedLanguage.en; -import static io.openbas.helper.SupportedLanguage.fr; @Component +@RequiredArgsConstructor public class ChallengeContract extends Contractor { + private final ExpectationBuilderService expectationBuilderService; + public static final String CHALLENGE_PUBLISH = "f8e70b27-a69c-4b9f-a2df-e217c36b3981"; public static final String TYPE = "openbas_challenge"; @@ -53,8 +56,6 @@ public ContractConfig getConfig() { @Override public List contracts() { - Long EXPIRATION_TIME = 3600L; - Double SCORE = 0.0; ContractConfig contractConfig = getConfig(); // In this "internal" contract we can't express choices. // Choices are contextual to a specific exercise. @@ -69,13 +70,10 @@ public List contracts() { 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(SCORE); - expectation.setExpirationTime(EXPIRATION_TIME); ContractExpectations expectationsField = expectationsField( - "expectations", "Expectations", List.of(expectation) + "expectations", + "Expectations", + List.of(this.expectationBuilderService.buildChallengeExpectation()) ); List publishInstance = contractBuilder() .mandatory(challengeField("challenges", "Challenges", Multiple)) 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 b540728364..48ad6ba542 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 @@ -1,5 +1,8 @@ package io.openbas.injectors.channel; +import io.openbas.database.model.Endpoint; +import io.openbas.database.model.Variable.VariableType; +import io.openbas.expectation.ExpectationBuilderService; import io.openbas.injector_contract.Contract; import io.openbas.injector_contract.ContractConfig; import io.openbas.injector_contract.Contractor; @@ -7,15 +10,15 @@ import io.openbas.injector_contract.fields.ContractCheckbox; import io.openbas.injector_contract.fields.ContractElement; import io.openbas.injector_contract.fields.ContractExpectations; -import io.openbas.database.model.Endpoint; -import io.openbas.database.model.Variable.VariableType; -import io.openbas.model.inject.form.Expectation; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import java.io.InputStream; import java.util.List; import java.util.Map; +import static io.openbas.helper.SupportedLanguage.en; +import static io.openbas.helper.SupportedLanguage.fr; import static io.openbas.injector_contract.Contract.executableContract; import static io.openbas.injector_contract.ContractCardinality.Multiple; import static io.openbas.injector_contract.ContractCardinality.One; @@ -23,20 +26,20 @@ import static io.openbas.injector_contract.ContractVariable.variable; import static io.openbas.injector_contract.fields.ContractArticle.articleField; import static io.openbas.injector_contract.fields.ContractAttachment.attachmentField; -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.ContractExpectations.expectationsField; +import static io.openbas.injector_contract.fields.ContractTeam.teamField; import static io.openbas.injector_contract.fields.ContractText.textField; import static io.openbas.injector_contract.fields.ContractTextArea.richTextareaField; -import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.ARTICLE; -import static io.openbas.helper.SupportedLanguage.en; -import static io.openbas.helper.SupportedLanguage.fr; import static io.openbas.injectors.channel.ChannelExecutor.VARIABLE_ARTICLE; import static io.openbas.injectors.channel.ChannelExecutor.VARIABLE_ARTICLES; @Component +@RequiredArgsConstructor public class ChannelContract extends Contractor { + private final ExpectationBuilderService expectationBuilderService; + public static final String CHANNEL_PUBLISH = "fb5e49a2-6366-4492-b69a-f9b9f39a533e"; public static final String TYPE = "openbas_channel"; @@ -59,8 +62,6 @@ public ContractConfig getConfig() { @Override public List contracts() { - Long EXPIRATION_TIME = 3600L; - Double SCORE = 0.0; ContractConfig contractConfig = getConfig(); // In this "internal" contract we can't express choices. // Choices are contextual to a specific exercise. @@ -75,13 +76,10 @@ public List contracts() { The animation team """; ContractCheckbox emailingField = checkboxField("emailing", "Send email", true); - Expectation expectation = new Expectation(); - expectation.setType(ARTICLE); - expectation.setName("Expect targets to read the article(s)"); - expectation.setScore(SCORE); - expectation.setExpirationTime(EXPIRATION_TIME); ContractExpectations expectationsField = expectationsField( - "expectations", "Expectations", List.of(expectation) + "expectations", + "Expectations", + List.of(this.expectationBuilderService.buildArticleExpectation()) ); List publishInstance = contractBuilder() // built in diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_42__Add_column_expiration_time_expectations.java b/openbas-api/src/main/java/io/openbas/migration/V3_42__Add_column_expiration_time_expectations.java index dfd4e7226b..71eb02eac0 100644 --- a/openbas-api/src/main/java/io/openbas/migration/V3_42__Add_column_expiration_time_expectations.java +++ b/openbas-api/src/main/java/io/openbas/migration/V3_42__Add_column_expiration_time_expectations.java @@ -7,28 +7,28 @@ import java.sql.Connection; import java.sql.Statement; +import static io.openbas.expectation.ExpectationPropertiesConfig.DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME; +import static io.openbas.expectation.ExpectationPropertiesConfig.DEFAULT_TECHNICAL_EXPECTATION_EXPIRATION_TIME; + @Component public class V3_42__Add_column_expiration_time_expectations extends BaseJavaMigration { @Override public void migrate(Context context) throws Exception { - Statement select = context.getConnection().createStatement(); Connection connection = context.getConnection(); Statement statement = connection.createStatement(); - long technicalMinutesExpirationTime = 60L; - long manualMinutesExpirationTime = 360L; - select.execute("ALTER TABLE injects_expectations ADD inject_expiration_time bigint;"); - select.execute( - "UPDATE injects_expectations SET inject_expiration_time = " + technicalMinutesExpirationTime + " " + statement.execute("ALTER TABLE injects_expectations ADD inject_expiration_time bigint;"); + statement.execute( + "UPDATE injects_expectations SET inject_expiration_time = " + DEFAULT_TECHNICAL_EXPECTATION_EXPIRATION_TIME + " " + "WHERE inject_expectation_type = 'DETECTION' OR inject_expectation_type = 'PREVENTION';"); - select.execute( - "UPDATE injects_expectations SET inject_expiration_time = " + manualMinutesExpirationTime + " " + statement.execute( + "UPDATE injects_expectations SET inject_expiration_time = " + DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME + " " + "WHERE inject_expectation_type = 'MANUAL' OR inject_expectation_type = 'CHALLENGE' " + "OR inject_expectation_type = 'ARTICLE' OR inject_expectation_type = 'DOCUMENT' OR inject_expectation_type = 'TEXT';"); - select.execute( + statement.execute( "ALTER TABLE injects_expectations ALTER COLUMN inject_expiration_time SET NOT NULL;"); } } diff --git a/openbas-api/src/main/java/io/openbas/rest/settings/response/PlatformSettings.java b/openbas-api/src/main/java/io/openbas/rest/settings/response/PlatformSettings.java index 00fc74e828..5b3599a8a9 100644 --- a/openbas-api/src/main/java/io/openbas/rest/settings/response/PlatformSettings.java +++ b/openbas-api/src/main/java/io/openbas/rest/settings/response/PlatformSettings.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.openbas.rest.settings.form.PolicyInput; import io.openbas.rest.settings.form.ThemeInput; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -133,4 +134,25 @@ public Map> getPlatformBannerByLevel() { return null; } + // EXPECTATION + @NotNull + @JsonProperty("expectation_detection_expiration_time") + private long detectionExpirationTime; + + @NotNull + @JsonProperty("expectation_prevention_expiration_time") + private long preventionExpirationTime; + + @NotNull + @JsonProperty("expectation_challenge_expiration_time") + private long challengeExpirationTime; + + @NotNull + @JsonProperty("expectation_article_expiration_time") + private long articleExpirationTime; + + @NotNull + @JsonProperty("expectation_manual_expiration_time") + private long manualExpirationTime; + } diff --git a/openbas-api/src/main/java/io/openbas/service/PlatformSettingsService.java b/openbas-api/src/main/java/io/openbas/service/PlatformSettingsService.java index df3982e46a..476326a27d 100644 --- a/openbas-api/src/main/java/io/openbas/service/PlatformSettingsService.java +++ b/openbas-api/src/main/java/io/openbas/service/PlatformSettingsService.java @@ -8,6 +8,7 @@ import io.openbas.database.model.Theme; import io.openbas.database.repository.SettingRepository; import io.openbas.executors.caldera.config.CalderaExecutorConfig; +import io.openbas.expectation.ExpectationPropertiesConfig; import io.openbas.helper.RabbitMQHelper; import io.openbas.injectors.opencti.config.OpenCTIConfig; import io.openbas.rest.settings.form.*; @@ -51,6 +52,8 @@ public class PlatformSettingsService { @Resource private OpenBASConfig openBASConfig; @Resource + private ExpectationPropertiesConfig expectationPropertiesConfig; + @Resource private RabbitmqConfig rabbitmqConfig; @Autowired @@ -239,7 +242,7 @@ public PlatformSettings findSettings() { String value = getValueFromMapOfSettings(dbSettings, PLATFORM_BANNER + "." + bannerKey.key()); if(value != null) { if(platformBannerByLevel.get(bannerKey.level().name()) == null) { - platformBannerByLevel.put(bannerKey.level().name(), new ArrayList(Arrays.asList(bannerKey.message()))); + platformBannerByLevel.put(bannerKey.level().name(), new ArrayList<>(Arrays.asList(bannerKey.message()))); } else { platformBannerByLevel.get(bannerKey.level().name()).add(bannerKey.message()); } @@ -247,6 +250,13 @@ public PlatformSettings findSettings() { } platformSettings.setPlatformBannerByLevel(platformBannerByLevel); + // EXPECTATION + platformSettings.setDetectionExpirationTime(expectationPropertiesConfig.getDetectionExpirationTime()); + platformSettings.setPreventionExpirationTime(expectationPropertiesConfig.getPreventionExpirationTime()); + platformSettings.setChallengeExpirationTime(expectationPropertiesConfig.getChallengeExpirationTime()); + platformSettings.setArticleExpirationTime(expectationPropertiesConfig.getArticleExpirationTime()); + platformSettings.setManualExpirationTime(expectationPropertiesConfig.getManualExpirationTime()); + return platformSettings; } diff --git a/openbas-api/src/main/java/io/openbas/utils/ExpectationUtils.java b/openbas-api/src/main/java/io/openbas/utils/ExpectationUtils.java index d50d179840..66b29834b7 100644 --- a/openbas-api/src/main/java/io/openbas/utils/ExpectationUtils.java +++ b/openbas-api/src/main/java/io/openbas/utils/ExpectationUtils.java @@ -13,6 +13,9 @@ public class ExpectationUtils { + private ExpectationUtils() { + + } public static List processByValidationType(boolean isaNewExpectationResult, List childrenExpectations, List parentExpectations, Map> playerByTeam) { List updatedExpectations = new ArrayList<>(); diff --git a/openbas-api/src/main/resources/application.properties b/openbas-api/src/main/resources/application.properties index eb6b72fa07..20e7ac9c6b 100644 --- a/openbas-api/src/main/resources/application.properties +++ b/openbas-api/src/main/resources/application.properties @@ -229,3 +229,16 @@ lade.password= ### Telemetry telemetry.enable=false + +### Expectation + +# FIXME: DOCUMENTATION + +openbas.expectation.technical.expiration-time=21600 +openbas.expectation.detection.expiration-time=100 +openbas.expectation.prevention.expiration-time=200 + +openbas.expectation.human.expiration-time=3600 +openbas.expectation.challenge.expiration-time=300 +openbas.expectation.article.expiration-time=400 +openbas.expectation.manual.expiration-time=500 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 e22448aa2f..735b0ea05e 100644 --- a/openbas-framework/src/main/java/io/openbas/execution/Injector.java +++ b/openbas-framework/src/main/java/io/openbas/execution/Injector.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import static io.openbas.database.model.InjectStatusExecution.traceError; +import static java.util.Optional.ofNullable; public abstract class Injector { @@ -88,13 +89,15 @@ private InjectExpectation expectationConverter( @NotNull InjectExpectation expectationExecution, @NotNull final ExecutableInject executableInject, @NotNull final Expectation expectation) { - Long DEFAULT_EXPIRATION_TIME = 86400L; expectationExecution.setExercise(executableInject.getInjection().getExercise()); expectationExecution.setInject(executableInject.getInjection().getInject()); expectationExecution.setExpectedScore(expectation.getScore()); expectationExecution.setExpectationGroup(expectation.isExpectationGroup()); + // One day by default to avoid side effects on existing inject content + // Can be removed after a few time (deprecation time -> 3 months) expectationExecution.setExpirationTime( - Optional.ofNullable(expectation.getExpirationTime()).orElse(DEFAULT_EXPIRATION_TIME)); + ofNullable(expectation.getExpirationTime()).orElse(86400L) + ); switch (expectation.type()) { case ARTICLE -> { expectationExecution.setName(expectation.getName()); diff --git a/openbas-framework/src/main/java/io/openbas/executors/tanium/TaniumExecutor.java b/openbas-framework/src/main/java/io/openbas/executors/tanium/TaniumExecutor.java index 76753295de..c0298a7ebe 100644 --- a/openbas-framework/src/main/java/io/openbas/executors/tanium/TaniumExecutor.java +++ b/openbas-framework/src/main/java/io/openbas/executors/tanium/TaniumExecutor.java @@ -9,7 +9,6 @@ import io.openbas.integrations.InjectorService; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.stereotype.Service; diff --git a/openbas-framework/src/main/java/io/openbas/expectation/ExpectationBuilderService.java b/openbas-framework/src/main/java/io/openbas/expectation/ExpectationBuilderService.java new file mode 100644 index 0000000000..4e929ba70b --- /dev/null +++ b/openbas-framework/src/main/java/io/openbas/expectation/ExpectationBuilderService.java @@ -0,0 +1,54 @@ +package io.openbas.expectation; + +import io.openbas.model.inject.form.Expectation; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.*; + +@RequiredArgsConstructor +@Service +public class ExpectationBuilderService { + + private final ExpectationPropertiesConfig expectationPropertiesConfig; + + public static Double DEFAULT_TECHNICAL_EXPECTATION_SCORE = 100.0; + public static Double DEFAULT_HUMAN_EXPECTATION_SCORE = 0.0; + + public Expectation buildPreventionExpectation() { + Expectation preventionExpectation = new Expectation(); + preventionExpectation.setType(PREVENTION); + preventionExpectation.setName("Expect inject to be prevented"); + preventionExpectation.setScore(DEFAULT_TECHNICAL_EXPECTATION_SCORE); + preventionExpectation.setExpirationTime(this.expectationPropertiesConfig.getPreventionExpirationTime()); + return preventionExpectation; + } + + public Expectation buildDetectionExpectation() { + Expectation detectionExpectation = new Expectation(); + detectionExpectation.setType(DETECTION); + detectionExpectation.setName("Expect inject to be detected"); + detectionExpectation.setScore(DEFAULT_TECHNICAL_EXPECTATION_SCORE); + detectionExpectation.setExpirationTime(this.expectationPropertiesConfig.getDetectionExpirationTime()); + return detectionExpectation; + } + + public Expectation buildChallengeExpectation() { + Expectation challengeExpectation = new Expectation(); + challengeExpectation.setType(CHALLENGE); + challengeExpectation.setName("Expect targets to complete the challenge(s)"); + challengeExpectation.setScore(DEFAULT_HUMAN_EXPECTATION_SCORE); + challengeExpectation.setExpirationTime(this.expectationPropertiesConfig.getChallengeExpirationTime()); + return challengeExpectation; + } + + public Expectation buildArticleExpectation() { + Expectation articleExpectation = new Expectation(); + articleExpectation.setType(ARTICLE); + articleExpectation.setName("Expect targets to read the article(s)"); + articleExpectation.setScore(DEFAULT_HUMAN_EXPECTATION_SCORE); + articleExpectation.setExpirationTime(this.expectationPropertiesConfig.getArticleExpirationTime()); + return articleExpectation; + } + +} diff --git a/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java b/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java new file mode 100644 index 0000000000..c740c2652c --- /dev/null +++ b/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java @@ -0,0 +1,72 @@ +package io.openbas.expectation; + +import lombok.Setter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import static java.util.Optional.ofNullable; + +@Component +@Setter +public class ExpectationPropertiesConfig { + + public static long DEFAULT_TECHNICAL_EXPECTATION_EXPIRATION_TIME = 21600L; + public static long DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME = 3600L; + + @Value("${openbas.expectation.technical.expiration-time}") + private Long technicalExpirationTime; + @Value("${openbas.expectation.detection.expiration-time}") + private Long detectionExpirationTime; + @Value("${openbas.expectation.prevention.expiration-time}") + private Long preventionExpirationTime; + + @Value("${openbas.expectation.human.expiration-time}") + private Long humanExpirationTime; + @Value("${openbas.expectation.challenge.expiration-time}") + private Long challengeExpirationTime; + @Value("${openbas.expectation.article.expiration-time}") + private Long articleExpirationTime; + @Value("${openbas.expectation.manual.expiration-time}") + private Long manualExpirationTime; + + public long getDetectionExpirationTime() { + return ofNullable(this.detectionExpirationTime) + .orElse( + ofNullable(this.technicalExpirationTime) + .orElse(DEFAULT_TECHNICAL_EXPECTATION_EXPIRATION_TIME) + ); + } + + public long getPreventionExpirationTime() { + return ofNullable(this.preventionExpirationTime) + .orElse( + ofNullable(this.technicalExpirationTime) + .orElse(DEFAULT_TECHNICAL_EXPECTATION_EXPIRATION_TIME) + ); + } + + public long getChallengeExpirationTime() { + return ofNullable(this.challengeExpirationTime) + .orElse( + ofNullable(this.humanExpirationTime) + .orElse(DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME) + ); + } + + public long getArticleExpirationTime() { + return ofNullable(this.articleExpirationTime) + .orElse( + ofNullable(this.humanExpirationTime) + .orElse(DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME) + ); + } + + public long getManualExpirationTime() { + return ofNullable(this.manualExpirationTime) + .orElse( + ofNullable(this.humanExpirationTime) + .orElse(DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME) + ); + } + +} 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 3348b8dabf..9d9a15ca5e 100644 --- a/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java +++ b/openbas-framework/src/main/java/io/openbas/integrations/PayloadService.java @@ -7,6 +7,7 @@ import io.openbas.database.repository.InjectorContractRepository; import io.openbas.database.repository.InjectorRepository; import io.openbas.database.repository.PayloadRepository; +import io.openbas.expectation.ExpectationBuilderService; import io.openbas.helper.SupportedLanguage; import io.openbas.injector_contract.Contract; import io.openbas.injector_contract.ContractConfig; @@ -14,7 +15,6 @@ import io.openbas.injector_contract.fields.ContractAsset; import io.openbas.injector_contract.fields.ContractAssetGroup; import io.openbas.injector_contract.fields.ContractExpectations; -import io.openbas.model.inject.form.Expectation; import io.openbas.utils.StringUtils; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotBlank; @@ -26,8 +26,6 @@ import java.util.*; -import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.DETECTION; -import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE.PREVENTION; import static io.openbas.database.model.Payload.PAYLOAD_SOURCE.MANUAL; import static io.openbas.database.model.Payload.PAYLOAD_STATUS.VERIFIED; import static io.openbas.helper.StreamHelper.fromIterable; @@ -52,6 +50,7 @@ public class PayloadService { private final InjectorRepository injectorRepository; private final InjectorContractRepository injectorContractRepository; private final AttackPatternRepository attackPatternRepository; + private final ExpectationBuilderService expectationBuilderService; public void updateInjectorContractsForPayload(Payload payload) { List injectors = this.injectorRepository.findAllByPayloads(true); @@ -132,21 +131,14 @@ private Contract buildContract(@NotNull final String contractId, @NotNull final } private ContractExpectations expectations() { - Long EXPIRATION_TIME = 21600L; - Double SCORE = 100.0; - // Prevention - Expectation preventionExpectation = new Expectation(); - preventionExpectation.setType(PREVENTION); - preventionExpectation.setName("Expect inject to be prevented"); - preventionExpectation.setScore(SCORE); - preventionExpectation.setExpirationTime(EXPIRATION_TIME); - // Detection - Expectation detectionExpectation = new Expectation(); - detectionExpectation.setType(DETECTION); - detectionExpectation.setName("Expect inject to be detected"); - detectionExpectation.setScore(SCORE); - detectionExpectation.setExpirationTime(EXPIRATION_TIME); - return expectationsField("expectations", "Expectations", List.of(preventionExpectation, detectionExpectation)); + return expectationsField( + "expectations", + "Expectations", + List.of( + this.expectationBuilderService.buildPreventionExpectation(), + this.expectationBuilderService.buildDetectionExpectation() + ) + ); } public Payload duplicate(@NotBlank final String payloadId) { 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 770c3ee470..574f5a210d 100644 --- a/openbas-framework/src/main/java/io/openbas/model/Expectation.java +++ b/openbas-framework/src/main/java/io/openbas/model/Expectation.java @@ -14,5 +14,8 @@ default boolean isExpectationGroup() { String getName(); + /** + * Expiration time in seconds + */ Long getExpirationTime(); } 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 8f7776b7be..0c1ccd5521 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 @@ -18,11 +18,6 @@ public class ChannelExpectation implements Expectation { private String name; private Long expirationTime; - public ChannelExpectation(Double score, Article article) { - setScore(Objects.requireNonNullElse(score, 100.0)); - setArticle(article); - } - public ChannelExpectation(io.openbas.model.inject.form.Expectation expectation, Article article) { setScore(Objects.requireNonNullElse(score, 100.0)); 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 23f75c9130..f08e838492 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 @@ -2,7 +2,6 @@ import io.openbas.database.model.Asset; import io.openbas.database.model.AssetGroup; -import io.openbas.database.model.InjectExpectationSignature; import io.openbas.model.Expectation; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -10,7 +9,6 @@ import lombok.Setter; import javax.annotation.Nullable; -import java.util.List; import java.util.Objects; import static io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE; 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 bab821d378..8cffe09a2b 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 @@ -23,9 +23,7 @@ public class Expectation { private boolean expectationGroup; /** - * Gets the expiration time - * - * @return the expiration in seconds + * Expiration time in seconds */ @JsonProperty("expectation_expiration_time") private Long expirationTime; diff --git a/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormCreate.tsx b/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormCreate.tsx index 3d2cf79032..08d3dc46e5 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormCreate.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/ExpectationFormCreate.tsx @@ -10,6 +10,7 @@ import ExpectationGroupField from './field/ExpectationGroupField'; import { isTechnicalExpectation } from './ExpectationUtils'; import ScaleBar from '../../../../../components/scalebar/ScaleBar'; import { splitDuration } from '../../../../../utils/Time'; +import useExpectationExpirationTime from './useExpectationExpirationTime'; const useStyles = makeStyles((theme: Theme) => ({ marginTop_2: { @@ -52,37 +53,38 @@ const ExpectationFormCreate: FunctionComponent = ({ const { t } = useFormatter(); const classes = useStyles(); - const computeValuesFromType = (type: string) => { + const manualExpectationExpirationTime = useExpectationExpirationTime('MANUAL'); + + const computeValuesFromType = (type: string): ExpectationInputForm => { const predefinedExpectation = predefinedExpectations.filter((pe) => pe.expectation_type === type)[0]; if (predefinedExpectation) { + const expirationTime = splitDuration(predefinedExpectation.expectation_expiration_time || 0); return { expectation_type: predefinedExpectation.expectation_type ?? '', expectation_name: predefinedExpectation.expectation_name ?? '', expectation_description: predefinedExpectation.expectation_description ?? '', expectation_score: predefinedExpectation.expectation_score > 0 ? predefinedExpectation.expectation_score : 100, expectation_expectation_group: predefinedExpectation.expectation_expectation_group ?? false, - expectation_expiration_time: predefinedExpectation.expectation_expiration_time, + expiration_time_days: parseInt(expirationTime.days, 10), + expiration_time_hours: parseInt(expirationTime.hours, 10), + expiration_time_minutes: parseInt(expirationTime.minutes, 10), }; } + const expirationTime = splitDuration(manualExpectationExpirationTime || 0); return { expectation_type: 'MANUAL', expectation_name: '', expectation_description: '', expectation_score: 100, expectation_expectation_group: false, - expectation_expiration_time: 3600, + expiration_time_days: parseInt(expirationTime.days, 10), + expiration_time_hours: parseInt(expirationTime.hours, 10), + expiration_time_minutes: parseInt(expirationTime.minutes, 10), }; }; const predefinedTypes = predefinedExpectations.map((e) => e.expectation_type); - const valuesFromComputedTypes = computeValuesFromType(predefinedTypes[0]); - const expirationTime = splitDuration(valuesFromComputedTypes.expectation_expiration_time || 0); - const initialValues: ExpectationInputForm = { - ...valuesFromComputedTypes, - expiration_time_days: parseInt(expirationTime.days, 10), - expiration_time_hours: parseInt(expirationTime.hours, 10), - expiration_time_minutes: parseInt(expirationTime.minutes, 10), - }; + const initialValues: ExpectationInputForm = computeValuesFromType(predefinedTypes[0]); const { register, diff --git a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx index 4bb7fba5ca..5b6f214e32 100644 --- a/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx +++ b/openbas-front/src/admin/components/common/injects/expectations/ExpectationPopover.tsx @@ -7,6 +7,8 @@ import ExpectationFormUpdate from './ExpectationFormUpdate'; import type { ExpectationInput, ExpectationInputForm } from './Expectation'; import Dialog from '../../../../../components/common/Dialog'; import { PermissionsContext } from '../../Context'; +import useExpectationExpirationTime from './useExpectationExpirationTime'; +import type { InjectExpectation } from '../../../../../utils/api-types'; interface ExpectationPopoverProps { index: number; @@ -29,14 +31,11 @@ const ExpectationPopover: FunctionComponent = ({ const [openDelete, setOpenDelete] = useState(false); const [openEdit, setOpenEdit] = useState(false); - const EXPIRATION_TIME_SIX_HOURS: number = 21600; - const EXPIRATION_TIME_ONE_HOUR: number = 3600; - const getExpirationTime = (expirationTime: number): number => { if (expirationTime !== null || expirationTime !== undefined) { return expirationTime; } - return (expectation.expectation_type === 'DETECTION' || expectation.expectation_type === 'PREVENTION') ? EXPIRATION_TIME_SIX_HOURS : EXPIRATION_TIME_ONE_HOUR; + return useExpectationExpirationTime(expectation.expectation_type as InjectExpectation['inject_expectation_type']); // FIXME: should change type of expectation_type property }; const initialValues = { diff --git a/openbas-front/src/admin/components/common/injects/expectations/useExpectationExpirationTime.tsx b/openbas-front/src/admin/components/common/injects/expectations/useExpectationExpirationTime.tsx new file mode 100644 index 0000000000..4158fe0324 --- /dev/null +++ b/openbas-front/src/admin/components/common/injects/expectations/useExpectationExpirationTime.tsx @@ -0,0 +1,25 @@ +import type { InjectExpectation, PlatformSettings } from '../../../../../utils/api-types'; +import { useHelper } from '../../../../../store'; +import type { LoggedHelper } from '../../../../../actions/helper'; + +const useExpectationExpirationTime = (expectationType: InjectExpectation['inject_expectation_type']): number => { + const { settings }: { settings: PlatformSettings } = useHelper((helper: LoggedHelper) => ({ + settings: helper.getPlatformSettings(), + })); + switch (expectationType) { + case 'DETECTION': + return settings.expectation_detection_expiration_time; + case 'PREVENTION': + return settings.expectation_prevention_expiration_time; + case 'CHALLENGE': + return settings.expectation_challenge_expiration_time; + case 'ARTICLE': + return settings.expectation_article_expiration_time; + case 'MANUAL': + return settings.expectation_manual_expiration_time; + default: + return 0; + } +}; + +export default useExpectationExpirationTime; diff --git a/openbas-front/src/admin/components/scenarios/ScenarioForm.tsx b/openbas-front/src/admin/components/scenarios/ScenarioForm.tsx index 7583cce11e..74fe2b92e0 100644 --- a/openbas-front/src/admin/components/scenarios/ScenarioForm.tsx +++ b/openbas-front/src/admin/components/scenarios/ScenarioForm.tsx @@ -60,7 +60,7 @@ const ScenarioForm: FunctionComponent = ({ scenario_name: z.string().min(1, { message: t('Should not be empty') }), scenario_category: z.string().optional(), scenario_main_focus: z.string().optional(), - scenario_severity: z.string().optional(), + scenario_severity: z.enum(['low', 'medium', 'high', 'critical']).optional(), scenario_subtitle: z.string().optional(), scenario_description: z.string().optional(), scenario_tags: z.string().array().optional(), diff --git a/openbas-front/src/utils/api-types.d.ts b/openbas-front/src/utils/api-types.d.ts index 24fee3ef6d..2d2232b5dd 100644 --- a/openbas-front/src/utils/api-types.d.ts +++ b/openbas-front/src/utils/api-types.d.ts @@ -2436,6 +2436,16 @@ export interface PlatformSettings { executor_caldera_enable?: boolean; executor_caldera_public_url?: string; executor_tanium_enable?: boolean; + /** @format int64 */ + expectation_article_expiration_time: number; + /** @format int64 */ + expectation_challenge_expiration_time: number; + /** @format int64 */ + expectation_detection_expiration_time: number; + /** @format int64 */ + expectation_manual_expiration_time: number; + /** @format int64 */ + expectation_prevention_expiration_time: number; java_version?: string; map_tile_server_dark?: string; map_tile_server_light?: string; @@ -2775,7 +2785,7 @@ export interface ScenarioInput { scenario_external_url?: string; scenario_main_focus?: string; scenario_name: string; - scenario_severity?: string; + scenario_severity?: "low" | "medium" | "high" | "critical"; scenario_subtitle?: string; scenario_tags?: string[]; } diff --git a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java index 1830dfb0d4..af73562cfd 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java +++ b/openbas-model/src/main/java/io/openbas/database/model/InjectExpectation.java @@ -118,7 +118,7 @@ public EXPECTATION_STATUS getResponse() { private Double expectedScore; /** - * Expiration time stored in db in seconds + * Expiration time in seconds */ @Setter @Column(name = "inject_expiration_time") From 8559acbcb6225f342aafd147f49ac32317ae6acb Mon Sep 17 00:00:00 2001 From: Romuald Lemesle Date: Sat, 28 Sep 2024 16:36:32 +0200 Subject: [PATCH 12/14] [backend/frontend] Add default values --- .../expectation/ExpectationPropertiesConfig.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java b/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java index c740c2652c..d783e30625 100644 --- a/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java +++ b/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java @@ -13,20 +13,20 @@ public class ExpectationPropertiesConfig { public static long DEFAULT_TECHNICAL_EXPECTATION_EXPIRATION_TIME = 21600L; public static long DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME = 3600L; - @Value("${openbas.expectation.technical.expiration-time}") + @Value("${openbas.expectation.technical.expiration-time:#{null}}") private Long technicalExpirationTime; - @Value("${openbas.expectation.detection.expiration-time}") + @Value("${openbas.expectation.detection.expiration-time:#{null}}") private Long detectionExpirationTime; - @Value("${openbas.expectation.prevention.expiration-time}") + @Value("${openbas.expectation.prevention.expiration-time:#{null}}") private Long preventionExpirationTime; - @Value("${openbas.expectation.human.expiration-time}") + @Value("${openbas.expectation.human.expiration-time:#{null}}") private Long humanExpirationTime; - @Value("${openbas.expectation.challenge.expiration-time}") + @Value("${openbas.expectation.challenge.expiration-time:#{null}}") private Long challengeExpirationTime; - @Value("${openbas.expectation.article.expiration-time}") + @Value("${openbas.expectation.article.expiration-time:#{null}}") private Long articleExpirationTime; - @Value("${openbas.expectation.manual.expiration-time}") + @Value("${openbas.expectation.manual.expiration-time:#{null}}") private Long manualExpirationTime; public long getDetectionExpirationTime() { From 43c6bd87a36f336ea0a51033b722cb5985035423 Mon Sep 17 00:00:00 2001 From: Romuald Lemesle Date: Mon, 30 Sep 2024 09:03:25 +0200 Subject: [PATCH 13/14] [backend] Fix default config properties --- .../src/main/resources/application.properties | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openbas-api/src/main/resources/application.properties b/openbas-api/src/main/resources/application.properties index 20e7ac9c6b..f108d03d76 100644 --- a/openbas-api/src/main/resources/application.properties +++ b/openbas-api/src/main/resources/application.properties @@ -232,13 +232,11 @@ telemetry.enable=false ### Expectation -# FIXME: DOCUMENTATION - openbas.expectation.technical.expiration-time=21600 -openbas.expectation.detection.expiration-time=100 -openbas.expectation.prevention.expiration-time=200 +#openbas.expectation.detection.expiration-time=21600 +#openbas.expectation.prevention.expiration-time=21600 openbas.expectation.human.expiration-time=3600 -openbas.expectation.challenge.expiration-time=300 -openbas.expectation.article.expiration-time=400 -openbas.expectation.manual.expiration-time=500 +#openbas.expectation.challenge.expiration-time=3600 +#openbas.expectation.article.expiration-time=3600 +#openbas.expectation.manual.expiration-time=3600 From 021d968a831d5b09e0c0b99d54cfe2b605608625 Mon Sep 17 00:00:00 2001 From: Romuald Lemesle Date: Mon, 30 Sep 2024 09:53:49 +0200 Subject: [PATCH 14/14] [backend] Change default config properties --- openbas-api/src/main/resources/application.properties | 2 +- .../src/main/java/io/openbas/execution/Injector.java | 5 ++--- .../io/openbas/expectation/ExpectationPropertiesConfig.java | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/openbas-api/src/main/resources/application.properties b/openbas-api/src/main/resources/application.properties index f108d03d76..26ef2e22a9 100644 --- a/openbas-api/src/main/resources/application.properties +++ b/openbas-api/src/main/resources/application.properties @@ -236,7 +236,7 @@ openbas.expectation.technical.expiration-time=21600 #openbas.expectation.detection.expiration-time=21600 #openbas.expectation.prevention.expiration-time=21600 -openbas.expectation.human.expiration-time=3600 +openbas.expectation.human.expiration-time=86400 #openbas.expectation.challenge.expiration-time=3600 #openbas.expectation.article.expiration-time=3600 #openbas.expectation.manual.expiration-time=3600 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 735b0ea05e..13395ed298 100644 --- a/openbas-framework/src/main/java/io/openbas/execution/Injector.java +++ b/openbas-framework/src/main/java/io/openbas/execution/Injector.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import static io.openbas.database.model.InjectStatusExecution.traceError; +import static io.openbas.expectation.ExpectationPropertiesConfig.DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME; import static java.util.Optional.ofNullable; @@ -93,10 +94,8 @@ private InjectExpectation expectationConverter( expectationExecution.setInject(executableInject.getInjection().getInject()); expectationExecution.setExpectedScore(expectation.getScore()); expectationExecution.setExpectationGroup(expectation.isExpectationGroup()); - // One day by default to avoid side effects on existing inject content - // Can be removed after a few time (deprecation time -> 3 months) expectationExecution.setExpirationTime( - ofNullable(expectation.getExpirationTime()).orElse(86400L) + ofNullable(expectation.getExpirationTime()).orElse(DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME) ); switch (expectation.type()) { case ARTICLE -> { diff --git a/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java b/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java index d783e30625..86af8828e2 100644 --- a/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java +++ b/openbas-framework/src/main/java/io/openbas/expectation/ExpectationPropertiesConfig.java @@ -10,8 +10,8 @@ @Setter public class ExpectationPropertiesConfig { - public static long DEFAULT_TECHNICAL_EXPECTATION_EXPIRATION_TIME = 21600L; - public static long DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME = 3600L; + public static long DEFAULT_TECHNICAL_EXPECTATION_EXPIRATION_TIME = 21600L; // 6 hours + public static long DEFAULT_HUMAN_EXPECTATION_EXPIRATION_TIME = 86400L; // 24 hours @Value("${openbas.expectation.technical.expiration-time:#{null}}") private Long technicalExpirationTime;