From 3be52c8dfe3c1e8cc5bdbc469bd3c5952e9e0bd9 Mon Sep 17 00:00:00 2001 From: Julien Richard Date: Fri, 3 Mar 2023 00:42:11 +0100 Subject: [PATCH] [back/front] Improve mandatory and enforce reference management (#1850) Co-authored-by: Romuald Lemesle --- .../src/components/ItemOpenVocab.tsx | 2 +- .../analysis/groupings/GroupingCreation.js | 8 +- .../groupings/GroupingEditionOverview.js | 8 +- .../components/analysis/notes/AddNotes.js | 17 +-- .../analysis/notes/AddNotesLines.js | 39 +++--- .../private/components/analysis/notes/Note.js | 14 +-- .../analysis/notes/NoteCreation.tsx | 11 +- .../components/analysis/notes/NoteEdition.tsx | 41 +++--- .../analysis/notes/NoteEditionContainer.tsx | 6 +- .../analysis/notes/NoteEditionOverview.tsx | 117 ++++-------------- .../components/analysis/notes/NotePopover.tsx | 5 +- ...ObjectOrStixCoreRelationshipNotesCards.tsx | 56 ++++----- .../analysis/opinions/OpinionCreation.tsx | 4 +- .../opinions/OpinionEditionContainer.js | 8 +- .../opinions/OpinionEditionOverview.js | 109 ++++------------ .../analysis/reports/ReportCreation.js | 6 +- .../analysis/reports/ReportEditionOverview.js | 10 +- .../arsenal/channels/ChannelCreation.js | 10 +- .../channels/ChannelEditionOverview.js | 10 +- .../arsenal/malwares/MalwareCreation.js | 10 +- .../arsenal/malwares/MalwareEditionDetails.js | 4 +- .../malwares/MalwareEditionOverview.js | 10 +- .../components/arsenal/tools/ToolCreation.js | 6 +- .../arsenal/tools/ToolEditionOverview.js | 21 ++-- .../vulnerabilities/VulnerabilityCreation.tsx | 14 ++- .../VulnerabilityEditionOverview.js | 11 +- .../cases/feedbacks/FeedbackCreation.tsx | 8 +- .../feedbacks/FeedbackEditionOverview.tsx | 15 +-- .../cases/incidents/IncidentCreation.tsx | 11 +- .../incidents/IncidentEditionOverview.tsx | 21 ++-- .../components/common/form/CommitMessage.tsx | 31 +++-- .../components/common/form/OpenVocabField.tsx | 2 +- .../entities/events/EventCreation.js | 7 +- .../entities/events/EventEditionOverview.js | 10 +- .../individuals/IndividualCreation.js | 6 +- .../individuals/IndividualEditionOverview.js | 6 +- .../organizations/OrganizationCreation.js | 8 +- .../OrganizationEditionOverview.js | 14 +-- .../entities/sectors/SectorCreation.js | 6 +- .../entities/sectors/SectorEditionOverview.js | 10 +- .../entities/systems/SystemCreation.js | 6 +- .../entities/systems/SystemEditionOverview.js | 10 +- .../events/incidents/IncidentCreation.tsx | 14 +-- .../incidents/IncidentEditionDetails.tsx | 4 +- .../incidents/IncidentEditionOverview.tsx | 12 +- .../observed_data/ObservedDataCreation.js | 10 +- .../ObservedDataEditionOverview.js | 17 +-- .../AdministrativeAreaCreation.tsx | 12 +- .../AdministrativeAreaEditionOverview.tsx | 8 +- .../locations/cities/CityCreation.tsx | 8 +- .../locations/cities/CityEditionOverview.tsx | 6 +- .../locations/countries/CountryCreation.tsx | 8 +- .../countries/CountryEditionOverview.tsx | 6 +- .../locations/positions/PositionCreation.js | 6 +- .../positions/PositionEditionOverview.js | 10 +- .../locations/regions/RegionCreation.tsx | 8 +- .../regions/RegionEditionOverview.tsx | 11 +- .../indicators/IndicatorCreation.js | 25 ++-- .../indicators/IndicatorEditionOverview.js | 11 +- .../infrastructures/InfrastructureCreation.js | 16 ++- .../InfrastructureEditionOverview.js | 12 +- .../settings/sub_types/EntitySetting.tsx | 10 +- .../EntitySettingAttributesConfiguration.tsx | 50 ++------ .../components/settings/sub_types/SubType.tsx | 26 +--- .../settings/sub_types/SubTypesLine.tsx | 90 ++++---------- .../settings/sub_types/SubTypesLines.tsx | 1 + .../attack_patterns/AttackPatternCreation.js | 6 +- .../AttackPatternEditionOverview.js | 6 +- .../CourseOfActionCreation.js | 6 +- .../CourseOfActionEditionOverview.js | 6 +- .../data_components/DataComponentCreation.tsx | 10 +- .../DataComponentEditionOverview.tsx | 8 +- .../data_sources/DataSourceCreation.tsx | 10 +- .../DataSourceEditionOverview.tsx | 14 +-- .../narratives/NarrativeCreation.js | 6 +- .../narratives/NarrativeEditionOverview.js | 6 +- .../threats/campaigns/CampaignCreation.js | 8 +- .../campaigns/CampaignEditionOverview.js | 8 +- .../intrusion_sets/IntrusionSetCreation.js | 6 +- .../IntrusionSetEditionDetails.js | 4 +- .../IntrusionSetEditionOverview.js | 9 +- .../threat_actors/ThreatActorCreation.js | 4 +- .../ThreatActorEditionOverview.js | 36 ++---- .../src/schema/relay.schema.graphql | 17 +-- .../src/utils/hooks/useEntitySettings.ts | 73 +++++------ .../src/utils/hooks/useGranted.ts | 1 + .../config/schema/opencti.graphql | 10 +- .../src/database/middleware.js | 83 ++++++++----- .../opencti-graphql/src/domain/attribute.ts | 54 ++++---- .../opencti-graphql/src/domain/individual.js | 10 +- .../src/domain/observedData.js | 7 +- .../opencti-graphql/src/domain/opinion.js | 19 ++- .../domain/stixObjectOrStixRelationship.js | 15 +-- .../opencti-graphql/src/generated/graphql.ts | 44 +++---- .../entitySetting/entitySetting-resolvers.ts | 3 + .../entitySetting/entitySetting-utils.ts | 21 +++- .../entitySetting/entitySetting.graphql | 11 +- .../stixMetaRelationship-registrationRefs.ts | 8 +- .../opencti-graphql/src/resolvers/note.js | 16 ++- .../opencti-graphql/src/resolvers/subType.js | 4 +- .../src/schema/internalObject.ts | 58 +++------ .../src/schema/schema-validator.ts | 12 +- .../src/schema/stixCyberObservable.ts | 10 +- .../src/schema/stixDomainObject.ts | 7 +- .../src/schema/stixMetaObject.ts | 16 +-- .../src/schema/stixSightingRelationship.ts | 2 +- .../02-resolvers/entitySetting-test.js | 36 +++--- .../02-resolvers/subType-test.js | 18 +-- 108 files changed, 792 insertions(+), 1025 deletions(-) diff --git a/opencti-platform/opencti-front/src/components/ItemOpenVocab.tsx b/opencti-platform/opencti-front/src/components/ItemOpenVocab.tsx index bf36cfca33ef..15599d7a3f6c 100644 --- a/opencti-platform/opencti-front/src/components/ItemOpenVocab.tsx +++ b/opencti-platform/opencti-front/src/components/ItemOpenVocab.tsx @@ -80,7 +80,7 @@ Omit : { marginTop: 7 }; return ( -
{value}
+
{t(value)}
({ drawerPaper: { @@ -98,12 +98,12 @@ const GroupingCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), - confidence: Yup.number(), + name: Yup.string().min(2).required(t('This field is required')), + confidence: Yup.number().nullable(), context: Yup.string().required(t('This field is required')), description: Yup.string().nullable(), }; - const groupingValidator = useYupSchemaBuilder('Grouping', basicShape); + const groupingValidator = useSchemaCreationValidation('Grouping', basicShape); const onSubmit = (values, { setSubmitting, setErrors, resetForm }) => { const adaptedValues = R.evolve( diff --git a/opencti-platform/opencti-front/src/private/components/analysis/groupings/GroupingEditionOverview.js b/opencti-platform/opencti-front/src/private/components/analysis/groupings/GroupingEditionOverview.js index 3bbc2cda38a6..1a351c984cb3 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/groupings/GroupingEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/analysis/groupings/GroupingEditionOverview.js @@ -16,7 +16,7 @@ import { adaptFieldValue } from '../../../../utils/String'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import OpenVocabField from '../../common/form/OpenVocabField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; export const groupingMutationFieldPatch = graphql` @@ -84,14 +84,14 @@ const GroupingEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), - confidence: Yup.number(), + name: Yup.string().min(2).required(t('This field is required')), + confidence: Yup.number().nullable(), context: Yup.string().required(t('This field is required')), description: Yup.string().nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const groupingValidator = useYupSchemaBuilder('Grouping', basicShape); + const groupingValidator = useSchemaEditionValidation('Grouping', basicShape); const queries = { fieldPatch: groupingMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/AddNotes.js b/opencti-platform/opencti-front/src/private/components/analysis/notes/AddNotes.js index c3fff75a0837..f559449c2a52 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/AddNotes.js +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/AddNotes.js @@ -86,10 +86,8 @@ class AddNotes extends Component { classes, stixCoreObjectOrStixCoreRelationshipId, stixCoreObjectOrStixCoreRelationshipNotes, + paginationOptions, } = this.props; - const paginationOptions = { - search: this.state.search, - }; return (
); } @@ -194,8 +189,7 @@ class AddNotes extends Component { display={this.state.open} contextual={true} inputValue={this.state.search} - paginationOptions={paginationOptions} - /> + paginationOptions={{ search: this.state.search }} />
); } @@ -204,6 +198,7 @@ class AddNotes extends Component { AddNotes.propTypes = { stixCoreObjectOrStixCoreRelationshipId: PropTypes.string, stixCoreObjectOrStixCoreRelationshipNotes: PropTypes.array, + paginationOptions: PropTypes.object, classes: PropTypes.object, t: PropTypes.func, }; diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/AddNotesLines.js b/opencti-platform/opencti-front/src/private/components/analysis/notes/AddNotesLines.js index 8c4415f88d14..bd0246e993d5 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/AddNotesLines.js +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/AddNotesLines.js @@ -1,18 +1,18 @@ import React, { Component } from 'react'; import * as PropTypes from 'prop-types'; import * as R from 'ramda'; -import { graphql, createPaginationContainer } from 'react-relay'; +import { createPaginationContainer, graphql } from 'react-relay'; import withStyles from '@mui/styles/withStyles'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import { CheckCircle, WorkOutline } from '@mui/icons-material'; -import { ConnectionHandler } from 'relay-runtime'; import { truncate } from '../../../../utils/String'; import inject18n from '../../../../components/i18n'; import { commitMutation } from '../../../../relay/environment'; import ItemMarkings from '../../../../components/ItemMarkings'; +import { deleteNode, insertNode } from '../../../../utils/store'; const styles = (theme) => ({ avatar: { @@ -54,17 +54,12 @@ export const noteMutationRelationDelete = graphql` } `; -const sharedUpdater = (store, entityId, newEdge) => { - const entity = store.get(entityId); - const conn = ConnectionHandler.getConnection(entity, 'Pagination_notes'); - ConnectionHandler.insertEdgeBefore(conn, newEdge); -}; - class AddNotesLinesContainer extends Component { toggleNote(note) { const { stixCoreObjectOrStixCoreRelationshipId, stixCoreObjectOrStixCoreRelationshipNotes, + paginationOptions, } = this.props; const entityNotesIds = R.map( (n) => n.node.id, @@ -86,12 +81,12 @@ class AddNotesLinesContainer extends Component { relationship_type: 'object', }, updater: (store) => { - const entity = store.get(stixCoreObjectOrStixCoreRelationshipId); - const conn = ConnectionHandler.getConnection( - entity, + deleteNode( + store, 'Pagination_notes', + paginationOptions, + existingNote.node.id, ); - ConnectionHandler.deleteNode(conn, note.id); }, }); } else { @@ -106,15 +101,16 @@ class AddNotesLinesContainer extends Component { input, }, updater: (store) => { - const payload = store - .getRootField('noteEdit') - .getLinkedRecord('relationAdd', { input }); - const relationId = payload.getValue('id'); - const node = payload.getLinkedRecord('from'); - const relation = store.get(relationId); - payload.setLinkedRecord(node, 'node'); - payload.setLinkedRecord(relation, 'relation'); - sharedUpdater(store, stixCoreObjectOrStixCoreRelationshipId, payload); + insertNode( + store, + 'Pagination_notes', + paginationOptions, + 'noteEdit', + null, + 'relationAdd', + input, + 'from', + ); }, }); } @@ -171,6 +167,7 @@ class AddNotesLinesContainer extends Component { AddNotesLinesContainer.propTypes = { stixCoreObjectOrStixCoreRelationshipId: PropTypes.string, stixCoreObjectOrStixCoreRelationshipNotes: PropTypes.array, + paginationOptions: PropTypes.object, data: PropTypes.object, limit: PropTypes.number, classes: PropTypes.object, diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/Note.js b/opencti-platform/opencti-front/src/private/components/analysis/notes/Note.js index c237628e55f5..9fea7b75bfe1 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/Note.js +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/Note.js @@ -10,9 +10,9 @@ import NoteDetails from './NoteDetails'; import NoteEdition from './NoteEdition'; import StixDomainObjectOverview from '../../common/stix_domain_objects/StixDomainObjectOverview'; import StixCoreObjectExternalReferences from '../external_references/StixCoreObjectExternalReferences'; -import Security, { CollaborativeSecurity } from '../../../../utils/Security'; +import { CollaborativeSecurity } from '../../../../utils/Security'; import { - KNOWLEDGE_KNPARTICIPATE, + KNOWLEDGE_KNPARTICIPATE, KNOWLEDGE_KNUPDATE, KNOWLEDGE_KNUPDATE_KNDELETE, } from '../../../../utils/hooks/useGranted'; import StixCoreObjectLatestHistory from '../../common/stix_core_objects/StixCoreObjectLatestHistory'; @@ -74,12 +74,10 @@ class NoteComponent extends Component { /> - + style={{ marginTop: 25 }}> @@ -87,9 +85,9 @@ class NoteComponent extends Component { - + - + ); } diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteCreation.tsx b/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteCreation.tsx index 7698efef3fcd..6136ebf381d1 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteCreation.tsx @@ -34,7 +34,7 @@ import { Option } from '../../common/form/ReferenceField'; import { NotesLinesPaginationQuery$variables } from './__generated__/NotesLinesPaginationQuery.graphql'; import SliderField from '../../../../components/SliderField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -140,12 +140,13 @@ const NoteCreation: FunctionComponent = ({ .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) .required(t('This field is required')), attribute_abstract: Yup.string().nullable(), - content: Yup.string().required(t('This field is required')), - confidence: Yup.number(), - note_types: Yup.array(), + content: Yup.string().min(2).required(t('This field is required')), + confidence: Yup.number().nullable(), + note_types: Yup.array().nullable(), likelihood: Yup.number().min(0).max(100), }; - const noteValidator = useYupSchemaBuilder('Note', basicShape); + // createdBy must be excluded from the validation if user is not an editor, it will be handled directly by the backend + const noteValidator = useSchemaCreationValidation('Note', basicShape, userIsKnowledgeEditor ? [] : ['createdBy']); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEdition.tsx b/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEdition.tsx index 9c7aa9272472..5216dcb9b9bc 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEdition.tsx +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEdition.tsx @@ -8,10 +8,10 @@ import NoteEditionContainer from './NoteEditionContainer'; import { QueryRenderer } from '../../../../relay/environment'; import { noteEditionOverviewFocus } from './NoteEditionOverview'; import Loader, { LoaderVariant } from '../../../../components/Loader'; -import useGranted, { KNOWLEDGE_KNUPDATE } from '../../../../utils/hooks/useGranted'; -import useAuth from '../../../../utils/hooks/useAuth'; +import { KNOWLEDGE_KNUPDATE } from '../../../../utils/hooks/useGranted'; import { Theme } from '../../../../components/Theme'; import { NoteEditionContainerQuery$data } from './__generated__/NoteEditionContainerQuery.graphql'; +import { CollaborativeSecurity } from '../../../../utils/Security'; const useStyles = makeStyles((theme) => ({ editButton: { @@ -47,8 +47,6 @@ const NoteEdition = ({ noteId }: { noteId: string }) => { const classes = useStyles(); const [open, setOpen] = useState(false); - const userIsKnowledgeEditor = useGranted([KNOWLEDGE_KNUPDATE]); - const { me } = useAuth(); const handleOpen = () => setOpen(true); const [commit] = useMutation(noteEditionOverviewFocus); @@ -65,31 +63,24 @@ const NoteEdition = ({ noteId }: { noteId: string }) => { return (
- { if (props && props.note) { - // Check is user has edition rights - if (!userIsKnowledgeEditor && me.individual_id !== props.note.createdBy?.id) { - return <>; - } return ( - <> - - - - - - + + <> + + + + + + + + ); } return ; diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEditionContainer.tsx b/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEditionContainer.tsx index 0542c2ec60b1..e3e953a59c4b 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEditionContainer.tsx +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEditionContainer.tsx @@ -9,7 +9,6 @@ import { SubscriptionAvatars } from '../../../../components/Subscription'; import NoteEditionOverview from './NoteEditionOverview'; import { Theme } from '../../../../components/Theme'; import { NoteEditionContainer_note$data } from './__generated__/NoteEditionContainer_note.graphql'; -import { useIsEnforceReference } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ header: { @@ -70,10 +69,7 @@ const NoteEditionContainer: FunctionComponent = ({ no
- +
); diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEditionOverview.tsx index fd8edaff4ff4..ae6810984abb 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/NoteEditionOverview.tsx @@ -2,7 +2,6 @@ import React, { FunctionComponent } from 'react'; import { createFragmentContainer, graphql } from 'react-relay'; import { Field, Form, Formik } from 'formik'; import * as Yup from 'yup'; -import { FormikConfig } from 'formik/dist/types'; import { useFormatter } from '../../../../components/i18n'; import MarkDownField from '../../../../components/MarkDownField'; import { SubscriptionFocus } from '../../../../components/Subscription'; @@ -20,24 +19,13 @@ import OpenVocabField from '../../common/form/OpenVocabField'; import { Option } from '../../common/form/ReferenceField'; import { NoteEditionOverview_note$data } from './__generated__/NoteEditionOverview_note.graphql'; import SliderField from '../../../../components/SliderField'; -import { adaptFieldValue } from '../../../../utils/String'; -import CommitMessage from '../../common/form/CommitMessage'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; export const noteMutationFieldPatch = graphql` - mutation NoteEditionOverviewFieldPatchMutation( - $id: ID! - $input: [EditInput]! - $commitMessage: String - $references: [String] - ) { + mutation NoteEditionOverviewFieldPatchMutation($id: ID!, $input: [EditInput]!) { noteEdit(id: $id) { - fieldPatch( - input: $input - commitMessage: $commitMessage - references: $references - ) { + fieldPatch(input: $input) { ...NoteEditionOverview_note ...Note_note } @@ -92,41 +80,31 @@ interface NoteEditionOverviewProps { readonly name: string; } | null)[] | null; - enableReferences?: boolean handleClose: () => void } -interface NoteEditionFormValues { - message?: string - references?: Option[] - createdBy: Option | undefined - x_opencti_workflow_id: Option - objectMarking?: Option[] -} - const NoteEditionOverviewComponent: FunctionComponent< NoteEditionOverviewProps -> = ({ note, context, enableReferences = false, handleClose }) => { +> = ({ note, context }) => { const { t } = useFormatter(); const userIsKnowledgeEditor = useGranted([KNOWLEDGE_KNUPDATE]); const basicShape = { + content: Yup.string().min(2).required(t('This field is required')), created: Yup.date() .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) .required(t('This field is required')), attribute_abstract: Yup.string().nullable(), - content: Yup.string().required(t('This field is required')), - confidence: Yup.number(), - note_types: Yup.array(), + confidence: Yup.number().nullable(), + note_types: Yup.array().nullable(), likelihood: Yup.number() .min(0) .max(100) .transform((value) => (Number.isNaN(value) ? null : value)) .nullable(true), - references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const noteValidator = useYupSchemaBuilder('Note', basicShape); + const noteValidator = useSchemaEditionValidation('Note', basicShape); const queries = { fieldPatch: noteMutationFieldPatch, @@ -134,52 +112,24 @@ NoteEditionOverviewProps relationDelete: noteMutationRelationDelete, editionFocus: noteEditionOverviewFocus, }; - const editor = useFormEditor(note, enableReferences, queries, noteValidator); - - const onSubmit: FormikConfig['onSubmit'] = (values, { setSubmitting }) => { - const { message, references, ...otherValues } = values; - const commitMessage = message ?? ''; - const commitReferences = (references ?? []).map(({ value }) => value); - - const inputValues = Object.entries({ - ...otherValues, - createdBy: values.createdBy?.value, - x_opencti_workflow_id: values.x_opencti_workflow_id?.value, - objectMarking: (values.objectMarking ?? []).map(({ value }) => value), - }).map(([key, value]) => ({ key, value: adaptFieldValue(value) })); - - editor.fieldPatch({ - variables: { - id: note.id, - input: inputValues, - commitMessage: commitMessage && commitMessage.length > 0 ? commitMessage : null, - references: commitReferences, - }, - onCompleted: () => { - setSubmitting(false); - handleClose(); - }, - }); - }; + const editor = useFormEditor(note, false, queries, noteValidator); const handleSubmitField = (name: string, value: Option | string | string[]) => { - if (!enableReferences) { - let finalValue = value ?? ''; - if (name === 'x_opencti_workflow_id') { - finalValue = (value as Option).value; - } - noteValidator - .validateAt(name, { [name]: value }) - .then(() => { - editor.fieldPatch({ - variables: { - id: note.id, - input: [{ key: name, value: finalValue }], - }, - }); - }) - .catch(() => false); + let finalValue = value ?? ''; + if (name === 'x_opencti_workflow_id') { + finalValue = (value as Option).value; } + noteValidator + .validateAt(name, { [name]: value }) + .then(() => { + editor.fieldPatch({ + variables: { + id: note.id, + input: [{ key: name, value: finalValue }], + }, + }); + }) + .catch(() => false); }; const initialValues = { @@ -198,15 +148,8 @@ NoteEditionOverviewProps - {({ - submitForm, - isSubmitting, - setFieldValue, - values, - isValid, - dirty, - }) => ( + onSubmit={() => {}}> + {({ setFieldValue }) => (
- {enableReferences && ( - - )} )}
diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/NotePopover.tsx b/opencti-platform/opencti-front/src/private/components/analysis/notes/NotePopover.tsx index 18f1fe2a7927..b41053e0977f 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/NotePopover.tsx +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/NotePopover.tsx @@ -187,10 +187,7 @@ const NotePopover: FunctionComponent = ({ render={({ props }: { props: NoteEditionContainerQuery$data }) => { if (props && props.note) { return ( - + ); } return ; diff --git a/opencti-platform/opencti-front/src/private/components/analysis/notes/StixCoreObjectOrStixCoreRelationshipNotesCards.tsx b/opencti-platform/opencti-front/src/private/components/analysis/notes/StixCoreObjectOrStixCoreRelationshipNotesCards.tsx index 8ce1f132c471..ae4c557ddd04 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/notes/StixCoreObjectOrStixCoreRelationshipNotesCards.tsx +++ b/opencti-platform/opencti-front/src/private/components/analysis/notes/StixCoreObjectOrStixCoreRelationshipNotesCards.tsx @@ -38,6 +38,7 @@ import { } from './__generated__/StixCoreObjectOrStixCoreRelationshipNotesCardsQuery.graphql'; import { StixCoreObjectOrStixCoreRelationshipNotesCards_data$key } from './__generated__/StixCoreObjectOrStixCoreRelationshipNotesCards_data.graphql'; import SliderField from '../../../../components/SliderField'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ paper: { @@ -89,14 +90,6 @@ const stixCoreObjectOrStixCoreRelationshipNotesCardsFragment = graphql` } `; -const noteValidation = (t: (message: string) => string) => Yup.object().shape({ - attribute_abstract: Yup.string().nullable(), - content: Yup.string().required(t('This field is required')), - confidence: Yup.number(), - note_types: Yup.array(), - likelihood: Yup.number().min(0).max(100), -}); - const toFinalValues = (values: NoteAddInput, id: string) => { return { attribute_abstract: values.attribute_abstract, @@ -141,6 +134,15 @@ StixCoreObjectOrStixCoreRelationshipNotesCardsProps > = ({ id, marginTop, queryRef, paginationOptions, defaultMarking, title }) => { const { t } = useFormatter(); const classes = useStyles(); + const basicShape = { + content: Yup.string().min(2).required(t('This field is required')), + attribute_abstract: Yup.string().nullable(), + confidence: Yup.number(), + note_types: Yup.array(), + likelihood: Yup.number().min(0).max(100), + }; + // created & createdBy must be excluded from the validation, it will be handled directly by the backend + const noteValidator = useSchemaCreationValidation('Note', basicShape, ['created', 'createdBy']); const data = usePreloadedFragment< StixCoreObjectOrStixCoreRelationshipNotesCardsQuery, StixCoreObjectOrStixCoreRelationshipNotesCards_data$key @@ -201,18 +203,15 @@ StixCoreObjectOrStixCoreRelationshipNotesCardsProps <> - + size="large"> - +
@@ -229,15 +228,10 @@ StixCoreObjectOrStixCoreRelationshipNotesCardsProps ); })} - 0 ? '30' : '0'}px 0 30px 0` }} + 0 ? '30' : '0'}px 0 30px 0` }} expanded={open} - variant="outlined" - > - } - onClick={handleToggleWrite} - > + variant="outlined"> + } onClick={handleToggleWrite}>      @@ -245,12 +239,10 @@ StixCoreObjectOrStixCoreRelationshipNotesCardsProps - + onReset={handleToggleWrite}> {({ submitForm, handleReset, @@ -312,8 +304,7 @@ StixCoreObjectOrStixCoreRelationshipNotesCardsProps variant="contained" onClick={handleReset} disabled={isSubmitting} - classes={{ root: classes.button }} - > + classes={{ root: classes.button }}> {t('Cancel')}
diff --git a/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionCreation.tsx b/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionCreation.tsx index 01b3b75aeaf6..455aba84f268 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionCreation.tsx @@ -28,7 +28,7 @@ import { Option } from '../../common/form/ReferenceField'; import { OpinionsLinesPaginationQuery$variables } from './__generated__/OpinionsLinesPaginationQuery.graphql'; import { Theme } from '../../../../components/Theme'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -119,7 +119,7 @@ const OpinionCreation: FunctionComponent = ({ explanation: Yup.string().nullable(), confidence: Yup.number(), }; - const opinionValidator = useYupSchemaBuilder('Opinion', basicShape); + const opinionValidator = useSchemaCreationValidation('Opinion', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionEditionContainer.js b/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionEditionContainer.js index b5a35d849be5..464b838985e0 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionEditionContainer.js +++ b/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionEditionContainer.js @@ -7,7 +7,6 @@ import { makeStyles } from '@mui/styles'; import { useFormatter } from '../../../../components/i18n'; import { SubscriptionAvatars } from '../../../../components/Subscription'; import OpinionEditionOverview from './OpinionEditionOverview'; -import { useIsEnforceReference } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ header: { @@ -64,12 +63,7 @@ const OpinionEditionContainer = (props) => {
- +
); diff --git a/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionEditionOverview.js b/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionEditionOverview.js index 79d0512dc14f..48b23e5ccfc2 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/analysis/opinions/OpinionEditionOverview.js @@ -13,24 +13,13 @@ import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../ut import StatusField from '../../common/form/StatusField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import OpenVocabField from '../../common/form/OpenVocabField'; -import CommitMessage from '../../common/form/CommitMessage'; -import { adaptFieldValue } from '../../../../utils/String'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; export const opinionMutationFieldPatch = graphql` - mutation OpinionEditionOverviewFieldPatchMutation( - $id: ID! - $input: [EditInput]! - $commitMessage: String - $references: [String] - ) { + mutation OpinionEditionOverviewFieldPatchMutation($id: ID!$input: [EditInput]!) { opinionEdit(id: $id) { - fieldPatch( - input: $input - commitMessage: $commitMessage - references: $references - ) { + fieldPatch(input: $input) { ...OpinionEditionOverview_opinion ...Opinion_opinion } @@ -78,17 +67,16 @@ const opinionMutationRelationDelete = graphql` `; const OpinionEditionOverviewComponent = (props) => { - const { opinion, enableReferences, context, handleClose } = props; + const { opinion, context } = props; const { t } = useFormatter(); const basicShape = { opinion: Yup.string().required(t('This field is required')), explanation: Yup.string().nullable(), confidence: Yup.number(), - references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const opinionValidator = useYupSchemaBuilder('Opinion', basicShape); + const opinionValidator = useSchemaEditionValidation('Opinion', basicShape); const queries = { fieldPatch: opinionMutationFieldPatch, @@ -96,56 +84,24 @@ const OpinionEditionOverviewComponent = (props) => { relationDelete: opinionMutationRelationDelete, editionFocus: opinionEditionOverviewFocus, }; - const editor = useFormEditor(opinion, enableReferences, queries, opinionValidator); - - const onSubmit = (values, { setSubmitting }) => { - const commitMessage = values.message; - const references = R.pluck('value', values.references || []); - const inputValues = R.pipe( - R.dissoc('message'), - R.dissoc('references'), - R.assoc('x_opencti_workflow_id', values.x_opencti_workflow_id?.value), - R.assoc('createdBy', values.createdBy?.value), - R.assoc('objectMarking', R.pluck('value', values.objectMarking)), - R.toPairs, - R.map((n) => ({ - key: n[0], - value: adaptFieldValue(n[1]), - })), - )(values); - editor.fieldPatch({ - variables: { - id: opinion.id, - input: inputValues, - commitMessage: - commitMessage && commitMessage.length > 0 ? commitMessage : null, - references, - }, - onCompleted: () => { - setSubmitting(false); - handleClose(); - }, - }); - }; + const editor = useFormEditor(opinion, false, queries, opinionValidator); const handleSubmitField = (name, value) => { - if (!enableReferences) { - let finalValue = value; - if (name === 'x_opencti_workflow_id') { - finalValue = value.value; - } - opinionValidator - .validateAt(name, { [name]: value }) - .then(() => { - editor.fieldPatch({ - variables: { - id: opinion.id, - input: { key: name, value: finalValue ?? '' }, - }, - }); - }) - .catch(() => false); + let finalValue = value; + if (name === 'x_opencti_workflow_id') { + finalValue = value.value; } + opinionValidator + .validateAt(name, { [name]: value }) + .then(() => { + editor.fieldPatch({ + variables: { + id: opinion.id, + input: { key: name, value: finalValue ?? '' }, + }, + }); + }) + .catch(() => false); }; const initialValues = R.pipe( @@ -162,20 +118,11 @@ const OpinionEditionOverviewComponent = (props) => { ]), )(opinion); return ( - - {({ - submitForm, - isSubmitting, - setFieldValue, - values, - isValid, - dirty, - }) => ( + onSubmit={() => {}}> + {({ setFieldValue }) => (
{ } onChange={editor.changeMarking} /> - {enableReferences && ( - - )}
)} diff --git a/opencti-platform/opencti-front/src/private/components/analysis/reports/ReportCreation.js b/opencti-platform/opencti-front/src/private/components/analysis/reports/ReportCreation.js index e6223c9be19f..feb96bdb63b9 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/reports/ReportCreation.js +++ b/opencti-platform/opencti-front/src/private/components/analysis/reports/ReportCreation.js @@ -25,7 +25,7 @@ import { fieldSpacingContainerStyle } from '../../../../utils/field'; import OpenVocabField from '../../common/form/OpenVocabField'; import { insertNode } from '../../../../utils/store'; import ObjectAssigneeField from '../../common/form/ObjectAssigneeField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -98,7 +98,7 @@ const ReportCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), published: Yup.date() .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) .required(t('This field is required')), @@ -106,7 +106,7 @@ const ReportCreation = ({ paginationOptions }) => { confidence: Yup.number().nullable(), description: Yup.string().nullable(), }; - const reportValidator = useYupSchemaBuilder('Report', basicShape); + const reportValidator = useSchemaCreationValidation('Report', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/analysis/reports/ReportEditionOverview.js b/opencti-platform/opencti-front/src/private/components/analysis/reports/ReportEditionOverview.js index 07ac0b44051e..5d415a11cd67 100644 --- a/opencti-platform/opencti-front/src/private/components/analysis/reports/ReportEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/analysis/reports/ReportEditionOverview.js @@ -19,7 +19,7 @@ import DateTimePickerField from '../../../../components/DateTimePickerField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import OpenVocabField from '../../common/form/OpenVocabField'; import ObjectAssigneeField from '../../common/form/ObjectAssigneeField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; export const reportMutationFieldPatch = graphql` @@ -87,17 +87,17 @@ const ReportEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), published: Yup.date() .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) .required(t('This field is required')), report_types: Yup.array().nullable(), confidence: Yup.number().nullable(), description: Yup.string().nullable(), - references: Yup.array().nullable(), + references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const reportValidator = useYupSchemaBuilder('Report', basicShape); + const reportValidator = useSchemaEditionValidation('Report', basicShape); const queries = { fieldPatch: reportMutationFieldPatch, @@ -167,8 +167,10 @@ const ReportEditionOverviewComponent = (props) => { R.assoc('x_opencti_workflow_id', convertStatus(t, report)), R.assoc('createdBy', convertCreatedBy(report)), R.assoc('objectMarking', convertMarkings(report)), + R.assoc('references', []), R.pick([ 'name', + 'references', 'published', 'description', 'report_types', diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelCreation.js b/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelCreation.js index 10dfe0e342ec..86b1adfb3551 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelCreation.js +++ b/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelCreation.js @@ -21,7 +21,7 @@ import MarkDownField from '../../../../components/MarkDownField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ConfidenceField from '../../common/form/ConfidenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; import OpenVocabField from '../../common/form/OpenVocabField'; const useStyles = makeStyles((theme) => ({ @@ -104,12 +104,12 @@ const ChannelCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), - channel_types: Yup.array(), + name: Yup.string().min(2).required(t('This field is required')), + channel_types: Yup.array().nullable(), description: Yup.string().nullable(), - confidence: Yup.number(), + confidence: Yup.number().nullable(), }; - const channelValidator = useYupSchemaBuilder('Channel', basicShape); + const channelValidator = useSchemaCreationValidation('Channel', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelEditionOverview.js b/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelEditionOverview.js index 648a1a87e052..9889d59e03bb 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelEditionOverview.js @@ -16,7 +16,7 @@ import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../ut import OpenVocabField from '../../common/form/OpenVocabField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ConfidenceField from '../../common/form/ConfidenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const channelMutationFieldPatch = graphql` @@ -80,14 +80,14 @@ const ChannelEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), - channel_types: Yup.array(), + name: Yup.string().min(2).required(t('This field is required')), + channel_types: Yup.array().nullable(), description: Yup.string().nullable(), - confidence: Yup.number(), + confidence: Yup.number().nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const channelValidator = useYupSchemaBuilder('Channel', basicShape); + const channelValidator = useSchemaEditionValidation('Channel', basicShape); const queries = { fieldPatch: channelMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareCreation.js b/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareCreation.js index 930ae66f073c..75c7bdcd196b 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareCreation.js +++ b/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareCreation.js @@ -23,7 +23,7 @@ import OpenVocabField from '../../common/form/OpenVocabField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import { insertNode } from '../../../../utils/store'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; import SwitchField from '../../../../components/SwitchField'; const useStyles = makeStyles((theme) => ({ @@ -83,13 +83,13 @@ const MalwareCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), is_family: Yup.string().required(t('This field is required')), - malware_types: Yup.array(), - confidence: Yup.number(), + malware_types: Yup.array().nullable(), + confidence: Yup.number().nullable(), description: Yup.string().nullable(), }; - const malwareValidator = useYupSchemaBuilder('Malware', basicShape); + const malwareValidator = useSchemaCreationValidation('Malware', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareEditionDetails.js b/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareEditionDetails.js index 5ae0f83c2710..5da89bb6cbc2 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareEditionDetails.js +++ b/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareEditionDetails.js @@ -43,7 +43,9 @@ const malwareEditionDetailsFocus = graphql` const malwareValidation = (t) => Yup.object().shape({ first_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - last_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), + last_seen: Yup.date().nullable() + .min(Yup.ref('first_seen'), "The last seen date can't be before first seen date") + .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), architecture_execution_envs: Yup.array().nullable(), implementation_languages: Yup.array().nullable(), capabilities: Yup.array().nullable(), diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareEditionOverview.js b/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareEditionOverview.js index b62b4fc4c34b..b8cf595cd43a 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareEditionOverview.js @@ -17,7 +17,7 @@ import CommitMessage from '../../common/form/CommitMessage'; import { convertCreatedBy, convertKillChainPhases, convertMarkings, convertStatus } from '../../../../utils/edition'; import StatusField from '../../common/form/StatusField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; import SwitchField from '../../../../components/SwitchField'; @@ -85,15 +85,15 @@ const MalwareEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), is_family: Yup.boolean().required(t('This field is required')), - malware_types: Yup.array(), - confidence: Yup.number(), + malware_types: Yup.array().nullable(), + confidence: Yup.number().nullable(), description: Yup.string().nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const malwareValidator = useYupSchemaBuilder('Malware', basicShape); + const malwareValidator = useSchemaEditionValidation('Malware', basicShape); const queries = { fieldPatch: malwareMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolCreation.js b/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolCreation.js index 308937a9e357..08b50c423243 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolCreation.js +++ b/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolCreation.js @@ -23,7 +23,7 @@ import { ExternalReferencesField } from '../../common/form/ExternalReferencesFie import OpenVocabField from '../../common/form/OpenVocabField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ConfidenceField from '../../common/form/ConfidenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -92,12 +92,12 @@ const ToolCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), confidence: Yup.number().nullable(), tool_types: Yup.array().nullable(), }; - const toolValidator = useYupSchemaBuilder('Tool', basicShape); + const toolValidator = useSchemaCreationValidation('Tool', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolEditionOverview.js b/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolEditionOverview.js index 780622000c1d..8b438f5a016e 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolEditionOverview.js @@ -13,11 +13,11 @@ import MarkDownField from '../../../../components/MarkDownField'; import CommitMessage from '../../common/form/CommitMessage'; import { adaptFieldValue } from '../../../../utils/String'; import StatusField from '../../common/form/StatusField'; -import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; +import { convertCreatedBy, convertKillChainPhases, convertMarkings, convertStatus } from '../../../../utils/edition'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import OpenVocabField from '../../common/form/OpenVocabField'; import ConfidenceField from '../../common/form/ConfidenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const toolMutationFieldPatch = graphql` @@ -84,14 +84,14 @@ const ToolEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), confidence: Yup.number().nullable(), tool_types: Yup.array().nullable(), - references: Yup.array().nullable(), + references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const toolValidator = useYupSchemaBuilder('Tool', basicShape); + const toolValidator = useSchemaEditionValidation('Tool', basicShape); const queries = { fieldPatch: toolMutationFieldPatch, @@ -153,21 +153,16 @@ const ToolEditionOverviewComponent = (props) => { } }; - const killChainPhases = R.pipe( - R.pathOr([], ['killChainPhases', 'edges']), - R.map((n) => ({ - label: `[${n.node.kill_chain_name}] ${n.node.phase_name}`, - value: n.node.id, - })), - )(tool); const initialValues = R.pipe( R.assoc('createdBy', convertCreatedBy(tool)), - R.assoc('killChainPhases', killChainPhases), + R.assoc('killChainPhases', convertKillChainPhases(tool)), R.assoc('objectMarking', convertMarkings(tool)), R.assoc('x_opencti_workflow_id', convertStatus(t, tool)), R.assoc('tool_types', tool.tool_types ?? []), + R.assoc('references', []), R.pick([ 'name', + 'references', 'description', 'createdBy', 'killChainPhases', diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityCreation.tsx b/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityCreation.tsx index 666ad4a3b3f2..854f19604015 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityCreation.tsx @@ -1,5 +1,5 @@ import React, { FunctionComponent, useState } from 'react'; -import { Formik, Field } from 'formik'; +import { Field, Formik } from 'formik'; import Drawer from '@mui/material/Drawer'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; @@ -19,14 +19,16 @@ import ObjectLabelField from '../../common/form/ObjectLabelField'; import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import MarkDownField from '../../../../components/MarkDownField'; import { Theme } from '../../../../components/Theme'; -import { VulnerabilitiesLinesPaginationQuery$variables } from './__generated__/VulnerabilitiesLinesPaginationQuery.graphql'; +import { + VulnerabilitiesLinesPaginationQuery$variables, +} from './__generated__/VulnerabilitiesLinesPaginationQuery.graphql'; import { Option } from '../../common/form/ReferenceField'; import { insertNode } from '../../../../utils/store'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ConfidenceField from '../../common/form/ConfidenceField'; import SelectField from '../../../../components/SelectField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -82,7 +84,7 @@ const vulnerabilityMutation = graphql` interface VulnerabilityAddInput { name: string description: string - createdBy?: Option + createdBy: Option | undefined objectMarking: Option[] objectLabel: Option[] x_opencti_base_score: number @@ -106,14 +108,14 @@ const VulnerabilityCreation: FunctionComponent = ({ const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), x_opencti_base_score: Yup.number().nullable(), x_opencti_base_severity: Yup.string().nullable(), x_opencti_attack_vector: Yup.string().nullable(), confidence: Yup.number().nullable(), }; - const vulnerabilityValidator = useYupSchemaBuilder('Vulnerability', basicShape); + const vulnerabilityValidator = useSchemaCreationValidation('Vulnerability', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityEditionOverview.js b/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityEditionOverview.js index d5b92a549370..4c53fb2107fc 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityEditionOverview.js @@ -15,7 +15,7 @@ import { adaptFieldValue } from '../../../../utils/String'; import StatusField from '../../common/form/StatusField'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const vulnerabilityMutationFieldPatch = graphql` @@ -85,13 +85,13 @@ const VulnerabilityEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), confidence: Yup.number().nullable(), - references: Yup.array().nullable(), + references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const vulnerabilityValidator = useYupSchemaBuilder('Vulnerability', basicShape); + const vulnerabilityValidator = useSchemaEditionValidation('Vulnerability', basicShape); const queries = { fieldPatch: vulnerabilityMutationFieldPatch, @@ -159,12 +159,13 @@ const VulnerabilityEditionOverviewComponent = (props) => { R.assoc('createdBy', convertCreatedBy(vulnerability)), R.assoc('objectMarking', convertMarkings(vulnerability)), R.assoc('x_opencti_workflow_id', convertStatus(t, vulnerability)), + R.assoc('references', []), R.pick([ 'name', + 'references', 'description', 'confidence', 'createdBy', - 'killChainPhases', 'objectMarking', 'x_opencti_workflow_id', ]), diff --git a/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackCreation.tsx b/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackCreation.tsx index 9c859fbf5588..8f346d42dbc4 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackCreation.tsx @@ -23,7 +23,7 @@ import ConfidenceField from '../../common/form/ConfidenceField'; import { Option } from '../../common/form/ReferenceField'; import ObjectLabelField from '../../common/form/ObjectLabelField'; import useGranted, { KNOWLEDGE_KNUPDATE } from '../../../../utils/hooks/useGranted'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -77,6 +77,7 @@ const feedbackMutation = graphql` `; interface FormikCaseAddInput { + name: string description: string confidence: number rating: number @@ -100,14 +101,14 @@ const FeedbackCreation: FunctionComponent<{ confidence: Yup.number(), rating: Yup.number(), }; - const caseValidator = useYupSchemaBuilder('Case', basicShape); + const caseValidator = useSchemaCreationValidation('Case', basicShape); const onSubmit: FormikConfig['onSubmit'] = ( values, { setSubmitting, resetForm }, ) => { const finalValues: FeedbackCreationMutation$variables['input'] = { - name: `Feedback from ${me.user_email}`, + name: values.name, case_type: 'feedback', description: values.description, confidence: parseInt(String(values.confidence), 10), @@ -155,6 +156,7 @@ const FeedbackCreation: FunctionComponent<{
initialValues={{ + name: `Feedback from ${me.user_email}`, rating: 5, description: '', confidence: 75, diff --git a/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackEditionOverview.tsx index abe2bda07770..4e29c45a92ad 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackEditionOverview.tsx @@ -5,12 +5,7 @@ import * as Yup from 'yup'; import { FormikConfig } from 'formik/dist/types'; import { useFormatter } from '../../../../components/i18n'; import { SubscriptionFocus } from '../../../../components/Subscription'; -import { - convertAssignees, - convertCreatedBy, - convertMarkings, - convertStatus, -} from '../../../../utils/edition'; +import { convertAssignees, convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import StatusField from '../../common/form/StatusField'; import { Option } from '../../common/form/ReferenceField'; import { adaptFieldValue } from '../../../../utils/String'; @@ -26,7 +21,7 @@ import RatingField from '../../../../components/RatingField'; import CommitMessage from '../../common/form/CommitMessage'; import ObjectAssigneeField from '../../common/form/ObjectAssigneeField'; import ConfidenceField from '../../common/form/ConfidenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; const feedbackMutationFieldPatch = graphql` mutation FeedbackEditionOverviewFieldPatchMutation( @@ -157,7 +152,7 @@ interface FeedbackEditionOverviewProps { interface CaseEditionFormValues { message?: string references?: Option[] - createdBy?: Option + createdBy: Option | undefined x_opencti_workflow_id: Option objectMarking?: Option[] } @@ -169,7 +164,7 @@ FeedbackEditionOverviewProps const caseData = useFragment(feedbackEditionOverviewFragment, caseRef); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), priority: Yup.string().nullable(), severity: Yup.string().nullable(), description: Yup.string().nullable(), @@ -177,7 +172,7 @@ FeedbackEditionOverviewProps rating: Yup.number(), confidence: Yup.number(), }; - const caseValidator = useYupSchemaBuilder('Case', basicShape); + const caseValidator = useSchemaEditionValidation('Case', basicShape); const queries = { fieldPatch: feedbackMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/cases/incidents/IncidentCreation.tsx b/opencti-platform/opencti-front/src/private/components/cases/incidents/IncidentCreation.tsx index 80436f489322..c16fe46d9a62 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/incidents/IncidentCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/incidents/IncidentCreation.tsx @@ -26,7 +26,8 @@ import OpenVocabField from '../../common/form/OpenVocabField'; import ConfidenceField from '../../common/form/ConfidenceField'; import ObjectAssigneeField from '../../common/form/ObjectAssigneeField'; import ObjectLabelField from '../../common/form/ObjectLabelField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; +import { Option } from '../../common/form/ReferenceField'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -86,7 +87,7 @@ interface FormikCaseAddInput { priority: string description: string file: File | undefined - createdBy?: { value: string; label?: string } + createdBy: Option | undefined objectMarking: { value: string }[] objectAssignee: { value: string }[] objectLabel: { value: string }[] @@ -103,10 +104,10 @@ const IncidentCreation = ({ const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), }; - const caseValidator = useYupSchemaBuilder('Case', basicShape); + const caseValidator = useSchemaCreationValidation('Case', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); @@ -187,7 +188,7 @@ const IncidentCreation = ({ description: '', severity: '', priority: '', - createdBy: { value: '', label: '' }, + createdBy: undefined, objectMarking: [], objectAssignee: [], objectLabel: [], diff --git a/opencti-platform/opencti-front/src/private/components/cases/incidents/IncidentEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/cases/incidents/IncidentEditionOverview.tsx index 1b15d0436c54..78d336b24dc9 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/incidents/IncidentEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/incidents/IncidentEditionOverview.tsx @@ -5,12 +5,7 @@ import * as Yup from 'yup'; import { FormikConfig } from 'formik/dist/types'; import { useFormatter } from '../../../../components/i18n'; import { SubscriptionFocus } from '../../../../components/Subscription'; -import { - convertAssignees, - convertCreatedBy, - convertMarkings, - convertStatus, -} from '../../../../utils/edition'; +import { convertAssignees, convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import StatusField from '../../common/form/StatusField'; import { Option } from '../../common/form/ReferenceField'; import { adaptFieldValue } from '../../../../utils/String'; @@ -24,7 +19,7 @@ import useFormEditor from '../../../../utils/hooks/useFormEditor'; import MarkDownField from '../../../../components/MarkDownField'; import ObjectAssigneeField from '../../common/form/ObjectAssigneeField'; import ConfidenceField from '../../common/form/ConfidenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import CommitMessage from '../../common/form/CommitMessage'; import { ExternalReferencesValues } from '../../common/form/ExternalReferencesField'; @@ -156,7 +151,7 @@ interface IncidentEditionOverviewProps { interface CaseEditionFormValues { message?: string - createdBy?: Option + createdBy: Option | undefined objectMarking?: Option[] objectAssignee?: Option[] x_opencti_workflow_id: Option @@ -170,15 +165,15 @@ IncidentEditionOverviewProps const caseData = useFragment(incidentEditionOverviewFragment, caseRef); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), severity: Yup.string().nullable(), priority: Yup.string().nullable(), description: Yup.string().nullable(), - x_opencti_workflow_id: Yup.object(), - rating: Yup.number(), - confidence: Yup.number(), + x_opencti_workflow_id: Yup.object().nullable(), + rating: Yup.number().nullable(), + confidence: Yup.number().nullable(), }; - const caseValidator = useYupSchemaBuilder('Case', basicShape); + const caseValidator = useSchemaEditionValidation('Case', basicShape); const queries = { fieldPatch: incidentMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/common/form/CommitMessage.tsx b/opencti-platform/opencti-front/src/private/components/common/form/CommitMessage.tsx index 686176a5547b..d8da48b0be08 100644 --- a/opencti-platform/opencti-front/src/private/components/common/form/CommitMessage.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/form/CommitMessage.tsx @@ -9,6 +9,8 @@ import { useFormatter } from '../../../../components/i18n'; import MarkDownField from '../../../../components/MarkDownField'; import type { ExternalReferencesValues } from './ExternalReferencesField'; import { ExternalReferencesField } from './ExternalReferencesField'; +import { BYPASSREFERENCE } from '../../../../utils/hooks/useGranted'; +import Security from '../../../../utils/Security'; interface CommitMessageProps { id: string @@ -36,16 +38,29 @@ const CommitMessage: FunctionComponent = ({ const handleOpen = () => setControlOpen(true); const handleControlClose = () => setControlOpen(false); + const validateReferences = (references: ExternalReferencesValues | undefined) => !!references && references.length > 0; + return (
{ !handleClose && ( - + <> + + + + + )} = ({ diff --git a/opencti-platform/opencti-front/src/private/components/common/form/OpenVocabField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/OpenVocabField.tsx index 1e8ca871496f..d0ea9319850f 100644 --- a/opencti-platform/opencti-front/src/private/components/common/form/OpenVocabField.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/form/OpenVocabField.tsx @@ -75,7 +75,7 @@ Omit } const renderOption: RenderOption = (optionProps, { value, description }) => ( - {value} + {t(value)} ); if (variant === 'edit') { diff --git a/opencti-platform/opencti-front/src/private/components/entities/events/EventCreation.js b/opencti-platform/opencti-front/src/private/components/entities/events/EventCreation.js index f0888f8e8d0f..c111b631d9b9 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/events/EventCreation.js +++ b/opencti-platform/opencti-front/src/private/components/entities/events/EventCreation.js @@ -23,7 +23,7 @@ import DateTimePickerField from '../../../../components/DateTimePickerField'; import OpenVocabField from '../../common/form/OpenVocabField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ObjectLabelField from '../../common/form/ObjectLabelField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -92,7 +92,7 @@ const EventCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), event_types: Yup.array().nullable(), start_time: Yup.date() @@ -100,9 +100,10 @@ const EventCreation = ({ paginationOptions }) => { .nullable(), stop_time: Yup.date() .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) + .min(Yup.ref('start_time'), "The end date can't be before start date") .nullable(), }; - const eventValidator = useYupSchemaBuilder('Event', basicShape); + const eventValidator = useSchemaCreationValidation('Event', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/entities/events/EventEditionOverview.js b/opencti-platform/opencti-front/src/private/components/entities/events/EventEditionOverview.js index 8eda806a97c5..ae0f73ca9e8c 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/events/EventEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/entities/events/EventEditionOverview.js @@ -17,7 +17,7 @@ import { buildDate, parse } from '../../../../utils/Time'; import DateTimePickerField from '../../../../components/DateTimePickerField'; import OpenVocabField from '../../common/form/OpenVocabField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const eventMutationFieldPatch = graphql` @@ -77,15 +77,17 @@ const EventEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), event_types: Yup.array().nullable(), start_time: Yup.date().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')).nullable(), - stop_time: Yup.date().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')).nullable(), + stop_time: Yup.date() + .min(Yup.ref('start_time'), "The end date can't be before start date") + .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')).nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const eventValidator = useYupSchemaBuilder('Event', basicShape); + const eventValidator = useSchemaEditionValidation('Event', basicShape); const queries = { fieldPatch: eventMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/entities/individuals/IndividualCreation.js b/opencti-platform/opencti-front/src/private/components/entities/individuals/IndividualCreation.js index ce9b33897e76..bbdd09610904 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/individuals/IndividualCreation.js +++ b/opencti-platform/opencti-front/src/private/components/entities/individuals/IndividualCreation.js @@ -19,7 +19,7 @@ import ObjectLabelField from '../../common/form/ObjectLabelField'; import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import MarkDownField from '../../../../components/MarkDownField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -88,10 +88,10 @@ const IndividualCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), }; - const individualValidator = useYupSchemaBuilder('Individual', basicShape); + const individualValidator = useSchemaCreationValidation('Individual', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/entities/individuals/IndividualEditionOverview.js b/opencti-platform/opencti-front/src/private/components/entities/individuals/IndividualEditionOverview.js index 441f12a312a0..9555dbd3557f 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/individuals/IndividualEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/entities/individuals/IndividualEditionOverview.js @@ -13,7 +13,7 @@ import CommitMessage from '../../common/form/CommitMessage'; import { adaptFieldValue } from '../../../../utils/String'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import StatusField from '../../common/form/StatusField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const individualMutationFieldPatch = graphql` @@ -83,13 +83,13 @@ const IndividualEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), contact_information: Yup.string().nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const individualValidator = useYupSchemaBuilder('Individual', basicShape); + const individualValidator = useSchemaEditionValidation('Individual', basicShape); const queries = { fieldPatch: individualMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/entities/organizations/OrganizationCreation.js b/opencti-platform/opencti-front/src/private/components/entities/organizations/OrganizationCreation.js index 3e620ecb7c45..1a0abec8fb93 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/organizations/OrganizationCreation.js +++ b/opencti-platform/opencti-front/src/private/components/entities/organizations/OrganizationCreation.js @@ -21,7 +21,7 @@ import ObjectLabelField from '../../common/form/ObjectLabelField'; import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import MarkDownField from '../../../../components/MarkDownField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -90,12 +90,12 @@ const OrganizationCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), x_opencti_organization_type: Yup.string().nullable(), x_opencti_reliability: Yup.string().nullable(), }; - const organizationValidator = useYupSchemaBuilder('Organization', basicShape); + const organizationValidator = useSchemaCreationValidation('Organization', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); @@ -172,7 +172,7 @@ const OrganizationCreation = ({ paginationOptions }) => { initialValues={{ name: '', description: '', - x_opencti_reliability: '', + x_opencti_reliability: undefined, x_opencti_organization_type: 'other', createdBy: '', objectMarking: [], diff --git a/opencti-platform/opencti-front/src/private/components/entities/organizations/OrganizationEditionOverview.js b/opencti-platform/opencti-front/src/private/components/entities/organizations/OrganizationEditionOverview.js index 9702476e9d72..2aa181dd0707 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/organizations/OrganizationEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/entities/organizations/OrganizationEditionOverview.js @@ -15,7 +15,7 @@ import { adaptFieldValue } from '../../../../utils/String'; import CommitMessage from '../../common/form/CommitMessage'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import StatusField from '../../common/form/StatusField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const organizationMutationFieldPatch = graphql` @@ -85,7 +85,7 @@ const OrganizationEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), contact_information: Yup.string().nullable(), x_opencti_organization_type: Yup.string().nullable(), @@ -93,7 +93,7 @@ const OrganizationEditionOverviewComponent = (props) => { references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const organizationValidator = useYupSchemaBuilder('Organization', basicShape); + const organizationValidator = useSchemaEditionValidation('Organization', basicShape); const queries = { fieldPatch: organizationMutationFieldPatch, @@ -157,9 +157,11 @@ const OrganizationEditionOverviewComponent = (props) => { R.assoc('createdBy', convertCreatedBy(organization)), R.assoc('objectMarking', convertMarkings(organization)), R.assoc('x_opencti_workflow_id', convertStatus(t, organization)), + R.assoc('references', []), R.pick([ 'name', 'description', + 'references', 'contact_information', 'x_opencti_organization_type', 'x_opencti_reliability', @@ -169,12 +171,10 @@ const OrganizationEditionOverviewComponent = (props) => { ]), )(organization); return ( - + onSubmit={onSubmit}> {({ submitForm, isSubmitting, diff --git a/opencti-platform/opencti-front/src/private/components/entities/sectors/SectorCreation.js b/opencti-platform/opencti-front/src/private/components/entities/sectors/SectorCreation.js index 4624ed6d00c9..88b975ebb0ec 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/sectors/SectorCreation.js +++ b/opencti-platform/opencti-front/src/private/components/entities/sectors/SectorCreation.js @@ -20,7 +20,7 @@ import MarkDownField from '../../../../components/MarkDownField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import ObjectLabelField from '../../common/form/ObjectLabelField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -101,10 +101,10 @@ const SectorCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), }; - const sectorValidator = useYupSchemaBuilder('Sector', basicShape); + const sectorValidator = useSchemaCreationValidation('Sector', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/entities/sectors/SectorEditionOverview.js b/opencti-platform/opencti-front/src/private/components/entities/sectors/SectorEditionOverview.js index ae4e4fa93eea..d27cd66bbcf5 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/sectors/SectorEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/entities/sectors/SectorEditionOverview.js @@ -13,7 +13,7 @@ import CommitMessage from '../../common/form/CommitMessage'; import { adaptFieldValue } from '../../../../utils/String'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import StatusField from '../../common/form/StatusField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const sectorMutationFieldPatch = graphql` @@ -80,12 +80,12 @@ const SectorEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), - references: Yup.array().nullable(), + references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const sectorValidator = useYupSchemaBuilder('Sector', basicShape); + const sectorValidator = useSchemaEditionValidation('Sector', basicShape); const queries = { fieldPatch: sectorMutationFieldPatch, @@ -149,8 +149,10 @@ const SectorEditionOverviewComponent = (props) => { R.assoc('createdBy', createdBy), R.assoc('objectMarking', objectMarking), R.assoc('x_opencti_workflow_id', status), + R.assoc('references', []), R.pick([ 'name', + 'references', 'description', 'createdBy', 'objectMarking', diff --git a/opencti-platform/opencti-front/src/private/components/entities/systems/SystemCreation.js b/opencti-platform/opencti-front/src/private/components/entities/systems/SystemCreation.js index bd06b0e60d3b..07845d357950 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/systems/SystemCreation.js +++ b/opencti-platform/opencti-front/src/private/components/entities/systems/SystemCreation.js @@ -19,7 +19,7 @@ import ObjectLabelField from '../../common/form/ObjectLabelField'; import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import MarkDownField from '../../../../components/MarkDownField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -88,10 +88,10 @@ const SystemCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), }; - const systemValidator = useYupSchemaBuilder('System', basicShape); + const systemValidator = useSchemaCreationValidation('System', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/entities/systems/SystemEditionOverview.js b/opencti-platform/opencti-front/src/private/components/entities/systems/SystemEditionOverview.js index 80843a5faf58..1771da1e2595 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/systems/SystemEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/entities/systems/SystemEditionOverview.js @@ -13,7 +13,7 @@ import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../ut import StatusField from '../../common/form/StatusField'; import CommitMessage from '../../common/form/CommitMessage'; import { adaptFieldValue } from '../../../../utils/String'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const systemMutationFieldPatch = graphql` @@ -80,13 +80,13 @@ const SystemEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), contact_information: Yup.string().nullable(), - references: Yup.array().nullable(), + references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const systemValidator = useYupSchemaBuilder('System', basicShape); + const systemValidator = useSchemaEditionValidation('System', basicShape); const queries = { fieldPatch: systemMutationFieldPatch, @@ -148,8 +148,10 @@ const SystemEditionOverviewComponent = (props) => { R.assoc('createdBy', convertCreatedBy(system)), R.assoc('objectMarking', convertMarkings(system)), R.assoc('x_opencti_workflow_id', convertStatus(t, system)), + R.assoc('references', []), R.pick([ 'name', + 'references', 'description', 'contact_information', 'createdBy', diff --git a/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentCreation.tsx b/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentCreation.tsx index 9f6ae5f401a8..34b8a24dc651 100644 --- a/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentCreation.tsx @@ -30,7 +30,7 @@ import { Option } from '../../common/form/ReferenceField'; import { IncidentsCardsAndLinesPaginationQuery$variables, } from './__generated__/IncidentsCardsAndLinesPaginationQuery.graphql'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -104,14 +104,14 @@ const IncidentCreation = ({ paginationOptions }: { paginationOptions: IncidentsC const [commit] = useMutation(IncidentMutation); const basicShape = { - name: Yup.string().required(t('This field is required')), - confidence: Yup.number(), - incident_type: Yup.string(), - severity: Yup.string(), - source: Yup.string(), + name: Yup.string().min(2).required(t('This field is required')), + confidence: Yup.number().nullable(), + incident_type: Yup.string().nullable(), + severity: Yup.string().nullable(), + source: Yup.string().nullable(), description: Yup.string().nullable(), }; - const incidentValidator = useYupSchemaBuilder('Incident', basicShape); + const incidentValidator = useSchemaCreationValidation('Incident', basicShape); const onSubmit: FormikConfig['onSubmit'] = (values, { setSubmitting, setErrors, resetForm }) => { const cleanedValues = isEmptyField(values.severity) ? R.dissoc('severity', values) : values; diff --git a/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentEditionDetails.tsx b/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentEditionDetails.tsx index a6ebb0409d2a..2d9ca2f25832 100644 --- a/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentEditionDetails.tsx +++ b/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentEditionDetails.tsx @@ -60,7 +60,9 @@ const incidentEditionDetailsFragment = graphql` const incidentEditionDetailsValidation = (t: (v: string) => string) => Yup.object().shape({ first_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - last_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), + last_seen: Yup.date().nullable() + .min(Yup.ref('first_seen'), "The last seen date can't be before first seen date") + .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), objective: Yup.string().nullable(), source: Yup.string().nullable(), }); diff --git a/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentEditionOverview.tsx index 752f7e833548..84d2422db674 100644 --- a/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/events/incidents/IncidentEditionOverview.tsx @@ -19,7 +19,7 @@ import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ObjectAssigneeField from '../../common/form/ObjectAssigneeField'; import { Option } from '../../common/form/ReferenceField'; import { IncidentEditionOverview_incident$key } from './__generated__/IncidentEditionOverview_incident.graphql'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const incidentMutationFieldPatch = graphql` @@ -90,6 +90,7 @@ const incidentEditionOverviewFragment = graphql` name confidence description + source incident_type severity createdBy { @@ -145,7 +146,7 @@ interface IncidentEditionOverviewProps { interface IncidentEditionFormValues { message?: string references?: Option[] - createdBy?: Option + createdBy: Option | undefined x_opencti_workflow_id: Option objectMarking?: Option[] objectAssignee?: Option[] @@ -156,13 +157,13 @@ const IncidentEditionOverviewComponent : FunctionComponent ({ drawerPaper: { @@ -82,17 +82,17 @@ const ObservedDataCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - objects: Yup.array().min(1), + objects: Yup.array(), first_observed: Yup.date() .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) .required(t('This field is required')), last_observed: Yup.date() .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) .required(t('This field is required')), - number_observed: Yup.number(), - confidence: Yup.number(), + number_observed: Yup.number().required(t('This field is required')), + confidence: Yup.number().nullable(), }; - const observedDataValidator = useYupSchemaBuilder('Observed-Data', basicShape); + const observedDataValidator = useSchemaCreationValidation('Observed-Data', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/events/observed_data/ObservedDataEditionOverview.js b/opencti-platform/opencti-front/src/private/components/events/observed_data/ObservedDataEditionOverview.js index 5a5b9332a76d..17220f7b9e1a 100644 --- a/opencti-platform/opencti-front/src/private/components/events/observed_data/ObservedDataEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/events/observed_data/ObservedDataEditionOverview.js @@ -16,7 +16,7 @@ import { buildDate, parse } from '../../../../utils/Time'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import DateTimePickerField from '../../../../components/DateTimePickerField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; export const observedDataMutationFieldPatch = graphql` @@ -96,7 +96,7 @@ const ObservedDataEditionOverviewComponent = (props) => { references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const observedDataValidator = useYupSchemaBuilder('Observed-Data', basicShape); + const observedDataValidator = useSchemaEditionValidation('Observed-Data', basicShape, ['objects']); const queries = { fieldPatch: observedDataMutationFieldPatch, @@ -164,7 +164,9 @@ const ObservedDataEditionOverviewComponent = (props) => { R.assoc('first_observed', buildDate(observedData.first_observed)), R.assoc('last_observed', buildDate(observedData.last_observed)), R.assoc('x_opencti_workflow_id', convertStatus(t, observedData)), + R.assoc('references', []), R.pick([ + 'references', 'first_observed', 'last_observed', 'number_observed', @@ -176,12 +178,10 @@ const ObservedDataEditionOverviewComponent = (props) => { )(observedData); return ( - + onSubmit={onSubmit}> {({ submitForm, isSubmitting, @@ -274,13 +274,14 @@ const ObservedDataEditionOverviewComponent = (props) => { } onChange={editor.changeMarking} /> - {enableReferences && isValid && dirty && ( + {enableReferences && ( )} diff --git a/opencti-platform/opencti-front/src/private/components/locations/administrative_areas/AdministrativeAreaCreation.tsx b/opencti-platform/opencti-front/src/private/components/locations/administrative_areas/AdministrativeAreaCreation.tsx index 3bfa2bd3f16a..45850aa01246 100644 --- a/opencti-platform/opencti-front/src/private/components/locations/administrative_areas/AdministrativeAreaCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/locations/administrative_areas/AdministrativeAreaCreation.tsx @@ -18,11 +18,13 @@ import MarkDownField from '../../../../components/MarkDownField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import { Theme } from '../../../../components/Theme'; import { insertNode } from '../../../../utils/store'; -import { AdministrativeAreasLinesPaginationQuery$variables } from './__generated__/AdministrativeAreasLinesPaginationQuery.graphql'; +import { + AdministrativeAreasLinesPaginationQuery$variables, +} from './__generated__/AdministrativeAreasLinesPaginationQuery.graphql'; import { AdministrativeAreaCreationMutation$variables } from './__generated__/AdministrativeAreaCreationMutation.graphql'; import ObjectLabelField from '../../common/form/ObjectLabelField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; import { Option } from '../../common/form/ReferenceField'; const useStyles = makeStyles((theme) => ({ @@ -83,7 +85,7 @@ interface AdministrativeAreaAddInput { description: string latitude: string longitude: string - createdBy?: Option; + createdBy: Option | undefined objectMarking: Option[] objectLabel: Option[] externalReferences: Option[] @@ -99,12 +101,12 @@ const AdministrativeAreaCreation = ({ const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), latitude: Yup.number().typeError(t('This field must be a number')).nullable(), longitude: Yup.number().typeError(t('This field must be a number')).nullable(), }; - const administrativeAreaValidator = useYupSchemaBuilder('Administrative-Area', basicShape); + const administrativeAreaValidator = useSchemaCreationValidation('Administrative-Area', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/locations/administrative_areas/AdministrativeAreaEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/locations/administrative_areas/AdministrativeAreaEditionOverview.tsx index de6020aef2c5..305445ec53ea 100644 --- a/opencti-platform/opencti-front/src/private/components/locations/administrative_areas/AdministrativeAreaEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/locations/administrative_areas/AdministrativeAreaEditionOverview.tsx @@ -17,7 +17,7 @@ import { Option } from '../../common/form/ReferenceField'; import { AdministrativeAreaEditionOverview_administrativeArea$key, } from './__generated__/AdministrativeAreaEditionOverview_administrativeArea.graphql'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const administrativeAreaMutationFieldPatch = graphql` @@ -119,7 +119,7 @@ interface AdministrativeAreaEditionOverviewProps { interface AdministrativeAreaEditionFormValues { message?: string references?: Option[] - createdBy?: Option + createdBy: Option | undefined x_opencti_workflow_id: Option objectMarking?: Option[] } @@ -135,14 +135,14 @@ const AdministrativeAreaEditionOverview: FunctionComponent((theme) => ({ drawerPaper: { @@ -80,7 +80,7 @@ interface CityAddInput { description: string latitude: string longitude: string - createdBy?: Option + createdBy: Option | undefined objectMarking: Option[] objectLabel: Option[] externalReferences: Option[] @@ -93,12 +93,12 @@ const CityCreation = ({ paginationOptions }: { paginationOptions: CitiesLinesPag const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), latitude: Yup.number().typeError(t('This field must be a number')).nullable(), longitude: Yup.number().typeError(t('This field must be a number')).nullable(), }; - const cityValidator = useYupSchemaBuilder('City', basicShape); + const cityValidator = useSchemaCreationValidation('City', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/locations/cities/CityEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/locations/cities/CityEditionOverview.tsx index c4c2c8f05644..d2aa0f8ed59f 100644 --- a/opencti-platform/opencti-front/src/private/components/locations/cities/CityEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/locations/cities/CityEditionOverview.tsx @@ -15,7 +15,7 @@ import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../ut import { useFormatter } from '../../../../components/i18n'; import { Option } from '../../common/form/ReferenceField'; import { CityEditionOverview_city$key } from './__generated__/CityEditionOverview_city.graphql'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const cityMutationFieldPatch = graphql` @@ -138,14 +138,14 @@ const CityEditionOverview: FunctionComponent = ({ city const city = useFragment(cityEditionOverviewFragment, cityRef); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable().max(5000, t('The value is too long')), latitude: Yup.number().typeError(t('This field must be a number')).nullable(), longitude: Yup.number().typeError(t('This field must be a number')).nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const cityValidator = useYupSchemaBuilder('City', basicShape); + const cityValidator = useSchemaEditionValidation('City', basicShape); const queries = { fieldPatch: cityMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/locations/countries/CountryCreation.tsx b/opencti-platform/opencti-front/src/private/components/locations/countries/CountryCreation.tsx index 826835e6c6bb..e0380e69982d 100644 --- a/opencti-platform/opencti-front/src/private/components/locations/countries/CountryCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/locations/countries/CountryCreation.tsx @@ -23,7 +23,7 @@ import { ExternalReferencesField } from '../../common/form/ExternalReferencesFie import ObjectLabelField from '../../common/form/ObjectLabelField'; import { Option } from '../../common/form/ReferenceField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -88,7 +88,7 @@ const countryMutation = graphql` interface CountryAddInput { name: string description: string - createdBy?: Option | undefined + createdBy: Option | undefined objectMarking: Option[] objectLabel: Option[] externalReferences: Option[] @@ -101,10 +101,10 @@ const CountryCreation = ({ paginationOptions }: { paginationOptions: CountriesLi const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), }; - const countryValidator = useYupSchemaBuilder('Country', basicShape); + const countryValidator = useSchemaCreationValidation('Country', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/locations/countries/CountryEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/locations/countries/CountryEditionOverview.tsx index 4f0a96916b37..a8e5ac5acabc 100644 --- a/opencti-platform/opencti-front/src/private/components/locations/countries/CountryEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/locations/countries/CountryEditionOverview.tsx @@ -16,7 +16,7 @@ import { CountryEditionOverview_country$key } from './__generated__/CountryEditi import { Option } from '../../common/form/ReferenceField'; import CommitMessage from '../../common/form/CommitMessage'; import { adaptFieldValue } from '../../../../utils/String'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const countryMutationFieldPatch = graphql` @@ -146,12 +146,12 @@ const CountryEditionOverviewComponent: FunctionComponent ({ drawerPaper: { @@ -79,7 +79,7 @@ const PositionCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), latitude: Yup.number() .typeError(t('This field must be a number')) @@ -90,7 +90,7 @@ const PositionCreation = ({ paginationOptions }) => { street_address: Yup.string().nullable().max(1000, t('The value is too long')), postal_code: Yup.string().nullable().max(1000, t('The value is too long')), }; - const positionValidator = useYupSchemaBuilder('Position', basicShape); + const positionValidator = useSchemaCreationValidation('Position', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/locations/positions/PositionEditionOverview.js b/opencti-platform/opencti-front/src/private/components/locations/positions/PositionEditionOverview.js index 2278fb297fa0..e3963552b10b 100644 --- a/opencti-platform/opencti-front/src/private/components/locations/positions/PositionEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/locations/positions/PositionEditionOverview.js @@ -13,7 +13,7 @@ import CommitMessage from '../../common/form/CommitMessage'; import { adaptFieldValue } from '../../../../utils/String'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import StatusField from '../../common/form/StatusField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const positionMutationFieldPatch = graphql` @@ -83,7 +83,7 @@ const PositionEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable().max(5000, t('The value is too long')), latitude: Yup.number() .typeError(t('This field must be a number')) @@ -93,10 +93,10 @@ const PositionEditionOverviewComponent = (props) => { .nullable(), street_address: Yup.string().nullable().max(1000, t('The value is too long')), postal_code: Yup.string().nullable().max(1000, t('The value is too long')), - references: Yup.array().nullable(), + references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const positionValidator = useYupSchemaBuilder('Position', basicShape); + const positionValidator = useSchemaEditionValidation('Position', basicShape); const queries = { fieldPatch: positionMutationFieldPatch, @@ -160,8 +160,10 @@ const PositionEditionOverviewComponent = (props) => { R.assoc('createdBy', convertCreatedBy(position)), R.assoc('objectMarking', convertMarkings(position)), R.assoc('x_opencti_workflow_id', convertStatus(t, position)), + R.assoc('references', []), R.pick([ 'name', + 'references', 'description', 'latitude', 'longitude', diff --git a/opencti-platform/opencti-front/src/private/components/locations/regions/RegionCreation.tsx b/opencti-platform/opencti-front/src/private/components/locations/regions/RegionCreation.tsx index 6f24da125e56..958c17b7f1b0 100644 --- a/opencti-platform/opencti-front/src/private/components/locations/regions/RegionCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/locations/regions/RegionCreation.tsx @@ -23,7 +23,7 @@ import { ExternalReferencesField } from '../../common/form/ExternalReferencesFie import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ObjectLabelField from '../../common/form/ObjectLabelField'; import { Option } from '../../common/form/ReferenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const styles = makeStyles((theme) => ({ drawerPaper: { @@ -88,7 +88,7 @@ const regionMutation = graphql` interface RegionAddInput { name: string description: string - createdBy?: Option + createdBy: Option | undefined objectMarking: Option[] objectLabel: Option[] externalReferences: Option[] @@ -101,10 +101,10 @@ const RegionCreation = ({ paginationOptions }: { paginationOptions: RegionsLines const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), }; - const regionValidator = useYupSchemaBuilder('Region', basicShape); + const regionValidator = useSchemaCreationValidation('Region', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/locations/regions/RegionEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/locations/regions/RegionEditionOverview.tsx index 81e130990d7c..431d0a06e43e 100644 --- a/opencti-platform/opencti-front/src/private/components/locations/regions/RegionEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/locations/regions/RegionEditionOverview.tsx @@ -16,7 +16,7 @@ import { Option } from '../../common/form/ReferenceField'; import { useFormatter } from '../../../../components/i18n'; import { RegionEditionOverview_region$key } from './__generated__/RegionEditionOverview_region.graphql'; import CommitMessage from '../../common/form/CommitMessage'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const regionMutationFieldPatch = graphql` @@ -129,7 +129,7 @@ interface RegionEdititionOverviewProps { interface RegionEditionFormValues { name: string description: string | null - createdBy?: Option + createdBy: Option | undefined objectMarking: Option[] x_opencti_workflow_id: Option message?: string, @@ -146,13 +146,13 @@ const RegionEditionOverviewComponent: FunctionComponent ({ drawerPaper: { @@ -102,21 +99,23 @@ const IndicatorCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), - indicator_types: Yup.array(), - confidence: Yup.number(), + name: Yup.string().min(2).required(t('This field is required')), + indicator_types: Yup.array().nullable(), + confidence: Yup.number().nullable(), pattern: Yup.string().required(t('This field is required')), pattern_type: Yup.string().required(t('This field is required')), x_opencti_main_observable_type: Yup.string().required(t('This field is required')), valid_from: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - valid_until: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - x_mitre_platforms: Yup.array(), + valid_until: Yup.date().nullable() + .min(Yup.ref('valid_from'), "The valid until date can't be before valid from date") + .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), + x_mitre_platforms: Yup.array().nullable(), x_opencti_score: Yup.number().nullable(), description: Yup.string().nullable(), - x_opencti_detection: Yup.boolean(), - createObservables: Yup.boolean(), + x_opencti_detection: Yup.boolean().nullable(), + createObservables: Yup.boolean().nullable(), }; - const indicatorValidator = useYupSchemaBuilder('Indicator', basicShape); + const indicatorValidator = useSchemaCreationValidation('Indicator', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorEditionOverview.js b/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorEditionOverview.js index 99f71d8d6530..49e58ad2b718 100644 --- a/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/observations/indicators/IndicatorEditionOverview.js @@ -21,7 +21,7 @@ import { buildDate, parse } from '../../../../utils/Time'; import DateTimePickerField from '../../../../components/DateTimePickerField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import { useFormatter } from '../../../../components/i18n'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const indicatorMutationFieldPatch = graphql` @@ -90,12 +90,14 @@ const IndicatorEditionOverviewComponent = ({ indicator, handleClose, context, en const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), indicator_types: Yup.array(), confidence: Yup.number(), pattern: Yup.string().required(t('This field is required')), valid_from: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - valid_until: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), + valid_until: Yup.date().nullable() + .min(Yup.ref('valid_from'), "The valid until date can't be before valid from date") + .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), x_mitre_platforms: Yup.array().nullable(), x_opencti_score: Yup.number().nullable(), description: Yup.string().nullable(), @@ -103,7 +105,7 @@ const IndicatorEditionOverviewComponent = ({ indicator, handleClose, context, en references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const indicatorValidator = useYupSchemaBuilder('Indicator', basicShape); + const indicatorValidator = useSchemaEditionValidation('Indicator', basicShape); const queries = { fieldPatch: indicatorMutationFieldPatch, @@ -194,7 +196,6 @@ const IndicatorEditionOverviewComponent = ({ indicator, handleClose, context, en 'x_mitre_platforms', 'killChainPhases', 'createdBy', - 'killChainPhases', 'objectMarking', 'x_opencti_workflow_id', ]), diff --git a/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureCreation.js b/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureCreation.js index 33cd04ca3015..3b3db7c0c5e8 100644 --- a/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureCreation.js +++ b/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureCreation.js @@ -25,7 +25,7 @@ import ConfidenceField from '../../common/form/ConfidenceField'; import { parse } from '../../../../utils/Time'; import DateTimePickerField from '../../../../components/DateTimePickerField'; import KillChainPhasesField from '../../common/form/KillChainPhasesField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -94,18 +94,16 @@ const InfrastructureCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), infrastructure_types: Yup.array().nullable(), - confidence: Yup.number(), - first_seen: Yup.date() - .nullable() - .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - last_seen: Yup.date() - .nullable() + confidence: Yup.number().nullable(), + first_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), + last_seen: Yup.date().nullable() + .min(Yup.ref('first_seen'), "The last seen date can't be before first seen date") .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), }; - const infrastructureValidator = useYupSchemaBuilder('Infrastructure', basicShape); + const infrastructureValidator = useSchemaCreationValidation('Infrastructure', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureEditionOverview.js b/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureEditionOverview.js index af94615dfccf..9fa07d68ab78 100644 --- a/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureEditionOverview.js @@ -19,7 +19,7 @@ import { adaptFieldValue } from '../../../../utils/String'; import CommitMessage from '../../common/form/CommitMessage'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ConfidenceField from '../../common/form/ConfidenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const infrastructureMutationFieldPatch = graphql` @@ -89,16 +89,18 @@ const InfrastructureEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), infrastructure_types: Yup.array().nullable(), - confidence: Yup.number(), + confidence: Yup.number().nullable(), first_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - last_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), + last_seen: Yup.date().nullable() + .min(Yup.ref('first_seen'), "The last seen date can't be before first seen date") + .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const infrastructureValidator = useYupSchemaBuilder('Infrastructure', basicShape); + const infrastructureValidator = useSchemaEditionValidation('Infrastructure', basicShape); const queries = { fieldPatch: infrastructureMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/EntitySetting.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/EntitySetting.tsx index d59f01549eae..aea204b523b9 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/EntitySetting.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/EntitySetting.tsx @@ -23,7 +23,7 @@ export const entitySettingsFragment = graphql` platform_entity_files_ref platform_hidden_type target_type - attributes_configuration + mandatoryAttributes } } } @@ -36,6 +36,13 @@ export const entitySettingFragment = graphql` platform_entity_files_ref platform_hidden_type enforce_reference + mandatoryAttributes + mandatoryDefinitions { + name + label + mandatory + builtIn + } attributes_configuration availableSettings } @@ -61,7 +68,6 @@ export const entitySettingsPatch = graphql` mutation EntitySettingsPatchMutation($ids: [ID!]!, $input: [EditInput!]!) { entitySettingsFieldPatch(ids: $ids, input: $input) { ...EntitySetting_entitySetting - attributes_configuration } } `; diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/EntitySettingAttributesConfiguration.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/EntitySettingAttributesConfiguration.tsx index d033a5625f1f..f43d4f963602 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/EntitySettingAttributesConfiguration.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/EntitySettingAttributesConfiguration.tsx @@ -11,7 +11,6 @@ import FormControlLabel from '@mui/material/FormControlLabel'; import usePreloadedFragment from '../../../../utils/hooks/usePreloadedFragment'; import { EntitySettingQuery } from './__generated__/EntitySettingQuery.graphql'; import { EntitySetting_entitySetting$key } from './__generated__/EntitySetting_entitySetting.graphql'; -import { SubType_subType$data } from './__generated__/SubType_subType.graphql'; import { entitySettingFragment, entitySettingQuery, entitySettingsPatch } from './EntitySetting'; import { useFormatter } from '../../../../components/i18n'; @@ -34,20 +33,11 @@ export interface AttributeConfiguration { mandatory: boolean } -const EntitySettingAttributesConfiguration = ({ - queryRef, - subType, -}: { - queryRef: PreloadedQuery; - subType: SubType_subType$data, -}) => { +const EntitySettingAttributesConfiguration = ({ queryRef }: { queryRef: PreloadedQuery }) => { const { t } = useFormatter(); const classes = useStyles(); - const entitySetting = usePreloadedFragment< - EntitySettingQuery, - EntitySetting_entitySetting$key - >({ + const entitySetting = usePreloadedFragment({ linesQuery: entitySettingQuery, linesFragment: entitySettingFragment, queryRef, @@ -56,22 +46,10 @@ const EntitySettingAttributesConfiguration = ({ const attributesConfiguration: AttributeConfiguration[] = entitySetting.attributes_configuration ? JSON.parse(entitySetting.attributes_configuration) : []; - const isMandatoryAttributeConfiguration = (name: string): boolean => { - return attributesConfiguration.filter((attr) => attr.mandatory) - .map((attr) => attr?.name).includes(name) ?? false; - }; - const [commit] = useMutation(entitySettingsPatch); const handleSubmitField = (field: string, checked: boolean) => { - let entitySettingsAttributesConfiguration; - - if (checked) { - entitySettingsAttributesConfiguration = [...attributesConfiguration, { name: field, mandatory: true }]; - } else { - entitySettingsAttributesConfiguration = attributesConfiguration.filter((attr) => attr?.name !== field); - } - + const entitySettingsAttributesConfiguration = [...attributesConfiguration, { name: field, mandatory: checked }]; commit({ variables: { ids: [entitySetting.id], @@ -83,20 +61,16 @@ const EntitySettingAttributesConfiguration = ({ const mandatoryAttributesBuiltIn: { name: string, mandatory: boolean, label: string }[] = []; const mandatoryAttributes: { name: string, mandatory: boolean, label: string }[] = []; - subType.mandatoryAttributes.forEach((attr) => { - const el = { - name: attr.name, - mandatory: attr.mandatory || isMandatoryAttributeConfiguration(attr.name), - label: attr.label ?? attr.name, - }; + entitySetting.mandatoryDefinitions.forEach((attr) => { + const el = { name: attr.name, mandatory: attr.mandatory, label: attr.label ?? attr.name }; if (attr.builtIn) { mandatoryAttributesBuiltIn.push(el); } else { mandatoryAttributes.push(el); } }); - - if (entitySetting.availableSettings.includes('attributes_configuration') && subType.mandatoryAttributes.length > 0) { + const hasAttributeConfiguration = entitySetting.availableSettings.includes('attributes_configuration'); + if (hasAttributeConfiguration && (mandatoryAttributesBuiltIn.length > 0 || mandatoryAttributes.length > 0)) { return (
@@ -105,7 +79,7 @@ const EntitySettingAttributesConfiguration = ({ {mandatoryAttributesBuiltIn.length > 0 - && <> + &&
{t('Built-in attributes')} @@ -116,10 +90,10 @@ const EntitySettingAttributesConfiguration = ({ label={t(attr.label.charAt(0).toUpperCase() + attr.label.slice(1))} /> ))} - +
} {mandatoryAttributes.length > 0 - &&
+ && <> {t('Customizable attributes')} @@ -128,7 +102,7 @@ const EntitySettingAttributesConfiguration = ({ handleSubmitField(attr.name, checked)} /> } @@ -136,7 +110,7 @@ const EntitySettingAttributesConfiguration = ({ /> ))} -
+ }
diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubType.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubType.tsx index 5a3444aa6233..c129bd80b529 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubType.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubType.tsx @@ -37,6 +37,7 @@ export const subTypeFragment = graphql` platform_entity_files_ref platform_hidden_type target_type + availableSettings } statuses { edges { @@ -50,12 +51,6 @@ export const subTypeFragment = graphql` } } } - mandatoryAttributes { - name - builtIn - mandatory - label - } } `; @@ -65,24 +60,18 @@ const SubType = ({ data }: { data: SubType_subType$key }) => { const subType = useFragment(subTypeFragment, data); const statuses = (subType.statuses?.edges ?? []).map((edge) => edge.node); - - const queryRef = useQueryLoading(entitySettingQuery, { - targetType: subType.id, - }); + const queryRef = useQueryLoading(entitySettingQuery, { targetType: subType.id }); return ( <> {queryRef && ( - } - > + }>
{t(`entity_${subType.label}`)}
- +
@@ -96,14 +85,11 @@ const SubType = ({ data }: { data: SubType_subType$key }) => {
- +
- + )} diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubTypesLine.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubTypesLine.tsx index 302978a8fcd6..66ab4a0ff044 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubTypesLine.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubTypesLine.tsx @@ -76,28 +76,30 @@ const SubTypeLine: FunctionComponent = ({ }) => { const classes = useStyles(); const { t } = useFormatter(); + const renderOptionIcon = (option: string) => { + if (!node.settings?.availableSettings?.includes(option)) { + return ; + } + if ((node.settings as never)?.[option] === true) { + return ; + } + return ; + }; return ( - + to={`/dashboard/settings/entity_types/${node.id}`}> (event.shiftKey ? onToggleShiftEntity(index, { id: node.id }, event) : onToggleEntity({ id: node.id }, event)) } classes={{ root: classes.itemIcon }} - style={{ minWidth: 40 }} - > - + @@ -107,72 +109,24 @@ const SubTypeLine: FunctionComponent = ({ -
+
{t(`entity_${node.label}`)}
-
+
{node.workflowEnabled ? ( ) : ( )}
-
- {node.settings?.enforce_reference ? ( - - ) : ( - - )} +
+ {renderOptionIcon('enforce_reference')}
-
- {node.settings?.platform_entity_files_ref ? ( - - ) : ( - - )} +
+ {renderOptionIcon('platform_entity_files_ref')}
-
- {node.settings?.platform_hidden_type ? ( - - ) : ( - - )} +
+ {renderOptionIcon('platform_hidden_type')}
} diff --git a/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubTypesLines.tsx b/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubTypesLines.tsx index 278f0a36b894..a1efcae3dd65 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubTypesLines.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sub_types/SubTypesLines.tsx @@ -29,6 +29,7 @@ const subTypesLinesFragment = graphql` platform_entity_files_ref platform_hidden_type target_type + availableSettings } statuses { edges { diff --git a/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternCreation.js b/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternCreation.js index 5a30bca5b18b..a285ecd7a8d6 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternCreation.js +++ b/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternCreation.js @@ -21,7 +21,7 @@ import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import MarkDownField from '../../../../components/MarkDownField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -104,11 +104,11 @@ const AttackPatternCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), x_mitre_id: Yup.string().nullable(), }; - const attackPatternValidator = useYupSchemaBuilder('Attack-Pattern', basicShape); + const attackPatternValidator = useSchemaCreationValidation('Attack-Pattern', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternEditionOverview.js b/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternEditionOverview.js index a1b18e746187..6d80ecbd423e 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/techniques/attack_patterns/AttackPatternEditionOverview.js @@ -14,7 +14,7 @@ import StatusField from '../../common/form/StatusField'; import { convertCreatedBy, convertKillChainPhases, convertMarkings, convertStatus } from '../../../../utils/edition'; import { adaptFieldValue } from '../../../../utils/String'; import CommitMessage from '../../common/form/CommitMessage'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const attackPatternMutationFieldPatch = graphql` @@ -84,13 +84,13 @@ const AttackPatternEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), x_mitre_id: Yup.string().nullable(), description: Yup.string().nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const attackPatternValidator = useYupSchemaBuilder('Attack-Pattern', basicShape); + const attackPatternValidator = useSchemaEditionValidation('Attack-Pattern', basicShape); const queries = { fieldPatch: attackPatternMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionCreation.js b/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionCreation.js index 56e86b51bda0..9876409e2afd 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionCreation.js +++ b/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionCreation.js @@ -24,7 +24,7 @@ import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import MarkDownField from '../../../../components/MarkDownField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -102,10 +102,10 @@ const CourseOfActionCreation = ({ paginationOptions, contextual, display, inputV const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), }; - const courseOfActionValidator = useYupSchemaBuilder('Course-Of-Action', basicShape); + const courseOfActionValidator = useSchemaCreationValidation('Course-Of-Action', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionEditionOverview.js b/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionEditionOverview.js index c85de15a45f8..67eb7a026c64 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/techniques/courses_of_action/CourseOfActionEditionOverview.js @@ -13,7 +13,7 @@ import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../ut import StatusField from '../../common/form/StatusField'; import { adaptFieldValue } from '../../../../utils/String'; import CommitMessage from '../../common/form/CommitMessage'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const courseOfActionMutationFieldPatch = graphql` @@ -83,7 +83,7 @@ const CourseOfActionEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), x_opencti_threat_hunting: Yup.string().nullable(), x_opencti_log_sources: Yup.array().nullable(), @@ -91,7 +91,7 @@ const CourseOfActionEditionOverviewComponent = (props) => { references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const courseOfActionValidator = useYupSchemaBuilder('Course-Of-Action', basicShape); + const courseOfActionValidator = useSchemaEditionValidation('Course-Of-Action', basicShape); const queries = { fieldPatch: courseOfActionMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/techniques/data_components/DataComponentCreation.tsx b/opencti-platform/opencti-front/src/private/components/techniques/data_components/DataComponentCreation.tsx index 7adcfbcdfd2b..7c9873a89015 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/data_components/DataComponentCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/data_components/DataComponentCreation.tsx @@ -1,5 +1,5 @@ import React, { FunctionComponent, useState } from 'react'; -import { Field, Formik, Form } from 'formik'; +import { Field, Form, Formik } from 'formik'; import Drawer from '@mui/material/Drawer'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; @@ -28,7 +28,7 @@ import { DataComponentsLinesPaginationQuery$variables } from './__generated__/Da import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ConfidenceField from '../../common/form/ConfidenceField'; import { Option } from '../../common/form/ReferenceField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -116,11 +116,11 @@ const DataComponentCreation: FunctionComponent<{ const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), - confidence: Yup.number(), + confidence: Yup.number().nullable(), }; - const dataComponentValidator = useYupSchemaBuilder('Data-Component', basicShape); + const dataComponentValidator = useSchemaCreationValidation('Data-Component', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/techniques/data_components/DataComponentEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/techniques/data_components/DataComponentEditionOverview.tsx index 3ebba5e35e42..3c877b1d9bae 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/data_components/DataComponentEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/data_components/DataComponentEditionOverview.tsx @@ -19,7 +19,7 @@ import { fieldSpacingContainerStyle } from '../../../../utils/field'; import ConfidenceField from '../../common/form/ConfidenceField'; import { Option } from '../../common/form/ReferenceField'; import { adaptFieldValue } from '../../../../utils/String'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const dataComponentMutationFieldPatch = graphql` @@ -146,13 +146,13 @@ const DataComponentEditionOverview: FunctionComponent((theme) => ({ drawerPaper: { @@ -122,11 +122,11 @@ const DataSourceCreation: FunctionComponent = ({ const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), - confidence: Yup.number(), + confidence: Yup.number().nullable(), }; - const dataSourceValidator = useYupSchemaBuilder('Data-Source', basicShape); + const dataSourceValidator = useSchemaCreationValidation('Data-Source', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/techniques/data_sources/DataSourceEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/techniques/data_sources/DataSourceEditionOverview.tsx index e0894d7bdede..81d3381aea62 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/data_sources/DataSourceEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/techniques/data_sources/DataSourceEditionOverview.tsx @@ -18,7 +18,7 @@ import { DataSourceEditionOverview_dataSource$key } from './__generated__/DataSo import ConfidenceField from '../../common/form/ConfidenceField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import OpenVocabField from '../../common/form/OpenVocabField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; import { dataComponentEditionOverviewFocus } from '../data_components/DataComponentEditionOverview'; @@ -150,15 +150,15 @@ const DataSourceEditionOverview: FunctionComponent ({ drawerPaper: { @@ -110,10 +110,10 @@ const NarrativeCreation = ({ paginationOptions, contextual, display }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), }; - const narrativeValidator = useYupSchemaBuilder('Narrative', basicShape); + const narrativeValidator = useSchemaCreationValidation('Narrative', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeEditionOverview.js b/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeEditionOverview.js index 583d2a1c71b2..038af5b77b97 100644 --- a/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/techniques/narratives/NarrativeEditionOverview.js @@ -13,7 +13,7 @@ import CommitMessage from '../../common/form/CommitMessage'; import { adaptFieldValue } from '../../../../utils/String'; import StatusField from '../../common/form/StatusField'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const narrativeMutationFieldPatch = graphql` @@ -76,12 +76,12 @@ const NarrativeEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), description: Yup.string().nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const narrativeValidator = useYupSchemaBuilder('Narrative', basicShape); + const narrativeValidator = useSchemaEditionValidation('Narrative', basicShape); const queries = { fieldPatch: narrativeMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignCreation.js b/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignCreation.js index 0f4be649dac6..2ed3d965a318 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignCreation.js +++ b/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignCreation.js @@ -21,7 +21,7 @@ import MarkDownField from '../../../../components/MarkDownField'; import ConfidenceField from '../../common/form/ConfidenceField'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -90,11 +90,11 @@ const CampaignCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), - confidence: Yup.number(), + name: Yup.string().min(2).required(t('This field is required')), + confidence: Yup.number().nullable(), description: Yup.string().nullable(), }; - const campaignValidator = useYupSchemaBuilder('Campaign', basicShape); + const campaignValidator = useSchemaCreationValidation('Campaign', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignEditionOverview.js b/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignEditionOverview.js index 79e41f0c641b..0e1a131d399e 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/threats/campaigns/CampaignEditionOverview.js @@ -15,7 +15,7 @@ import { adaptFieldValue } from '../../../../utils/String'; import StatusField from '../../common/form/StatusField'; import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const campaignMutationFieldPatch = graphql` @@ -85,13 +85,13 @@ const CampaignEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), - confidence: Yup.number(), + name: Yup.string().min(2).required(t('This field is required')), + confidence: Yup.number().nullable(), description: Yup.string().nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const campaignValidator = useYupSchemaBuilder('Campaign', basicShape); + const campaignValidator = useSchemaEditionValidation('Campaign', basicShape); const queries = { fieldPatch: campaignMutationFieldPatch, diff --git a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetCreation.js b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetCreation.js index e8bb31e4cd14..b450592f7709 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetCreation.js +++ b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetCreation.js @@ -21,7 +21,7 @@ import ConfidenceField from '../../common/form/ConfidenceField'; import { insertNode } from '../../../../utils/store'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -80,11 +80,11 @@ const IntrusionSetCreation = ({ paginationOptions }) => { const [open, setOpen] = useState(false); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), confidence: Yup.number(), description: Yup.string().nullable(), }; - const intrusionSetValidator = useYupSchemaBuilder('Intrusion-Set', basicShape); + const intrusionSetValidator = useSchemaCreationValidation('Intrusion-Set', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEditionDetails.js b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEditionDetails.js index 072b3b5c216b..df9f0cfe8604 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEditionDetails.js +++ b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEditionDetails.js @@ -49,7 +49,9 @@ const intrusionSetEditionDetailsFocus = graphql` const intrusionSetValidation = (t) => Yup.object().shape({ first_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), - last_seen: Yup.date().nullable().typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), + last_seen: Yup.date().nullable() + .min(Yup.ref('first_seen'), "The last seen date can't be before first seen date") + .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')), resource_level: Yup.string().nullable(), primary_motivation: Yup.string().nullable(), secondary_motivations: Yup.array().nullable(), diff --git a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEditionOverview.js b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEditionOverview.js index 334fb4585785..40717449c2c2 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/threats/intrusion_sets/IntrusionSetEditionOverview.js @@ -15,7 +15,7 @@ import { adaptFieldValue } from '../../../../utils/String'; import { convertCreatedBy, convertKillChainPhases, convertMarkings, convertStatus } from '../../../../utils/edition'; import StatusField from '../../common/form/StatusField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const intrusionSetMutationFieldPatch = graphql` @@ -85,13 +85,13 @@ const IntrusionSetEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), - confidence: Yup.number(), + name: Yup.string().min(2).required(t('This field is required')), + confidence: Yup.number().nullable(), description: Yup.string().nullable(), references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const intrusionSetValidator = useYupSchemaBuilder('Intrusion-Set', basicShape); + const intrusionSetValidator = useSchemaEditionValidation('Intrusion-Set', basicShape); const queries = { fieldPatch: intrusionSetMutationFieldPatch, @@ -110,6 +110,7 @@ const IntrusionSetEditionOverviewComponent = (props) => { R.assoc('x_opencti_workflow_id', values.x_opencti_workflow_id?.value), R.assoc('createdBy', values.createdBy?.value), R.assoc('objectMarking', R.pluck('value', values.objectMarking)), + R.assoc('killChainPhases', R.pluck('value', values.killChainPhases)), R.toPairs, R.map((n) => ({ key: n[0], diff --git a/opencti-platform/opencti-front/src/private/components/threats/threat_actors/ThreatActorCreation.js b/opencti-platform/opencti-front/src/private/components/threats/threat_actors/ThreatActorCreation.js index 9567292a5bc9..b7e34cde9616 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/threat_actors/ThreatActorCreation.js +++ b/opencti-platform/opencti-front/src/private/components/threats/threat_actors/ThreatActorCreation.js @@ -21,7 +21,7 @@ import ConfidenceField from '../../common/form/ConfidenceField'; import { insertNode } from '../../../../utils/store'; import { ExternalReferencesField } from '../../common/form/ExternalReferencesField'; import OpenVocabField from '../../common/form/OpenVocabField'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaCreationValidation } from '../../../../utils/hooks/useEntitySettings'; const useStyles = makeStyles((theme) => ({ drawerPaper: { @@ -85,7 +85,7 @@ const ThreatActorCreation = ({ paginationOptions }) => { confidence: Yup.number().nullable(), description: Yup.string().nullable(), }; - const threatActorValidator = useYupSchemaBuilder('Threat-Actor', basicShape); + const threatActorValidator = useSchemaCreationValidation('Threat-Actor', basicShape); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); diff --git a/opencti-platform/opencti-front/src/private/components/threats/threat_actors/ThreatActorEditionOverview.js b/opencti-platform/opencti-front/src/private/components/threats/threat_actors/ThreatActorEditionOverview.js index 266d7ebbd625..96812ad824a9 100644 --- a/opencti-platform/opencti-front/src/private/components/threats/threat_actors/ThreatActorEditionOverview.js +++ b/opencti-platform/opencti-front/src/private/components/threats/threat_actors/ThreatActorEditionOverview.js @@ -12,10 +12,10 @@ import ConfidenceField from '../../common/form/ConfidenceField'; import CommitMessage from '../../common/form/CommitMessage'; import { adaptFieldValue } from '../../../../utils/String'; import StatusField from '../../common/form/StatusField'; -import { convertCreatedBy, convertMarkings, convertStatus } from '../../../../utils/edition'; +import { convertCreatedBy, convertKillChainPhases, convertMarkings, convertStatus } from '../../../../utils/edition'; import OpenVocabField from '../../common/form/OpenVocabField'; import { useFormatter } from '../../../../components/i18n'; -import { useYupSchemaBuilder } from '../../../../utils/hooks/useEntitySettings'; +import { useSchemaEditionValidation } from '../../../../utils/hooks/useEntitySettings'; import useFormEditor from '../../../../utils/hooks/useFormEditor'; const threatActorMutationFieldPatch = graphql` @@ -85,14 +85,14 @@ const ThreatActorEditionOverviewComponent = (props) => { const { t } = useFormatter(); const basicShape = { - name: Yup.string().required(t('This field is required')), + name: Yup.string().min(2).required(t('This field is required')), threat_actor_types: Yup.array().nullable(), confidence: Yup.number().nullable(), description: Yup.string().nullable(), - references: Yup.array().nullable(), + references: Yup.array(), x_opencti_workflow_id: Yup.object(), }; - const threatActorValidator = useYupSchemaBuilder('Threat-Actor', basicShape); + const threatActorValidator = useSchemaEditionValidation('Threat-Actor', basicShape); const queries = { fieldPatch: threatActorMutationFieldPatch, @@ -111,6 +111,7 @@ const ThreatActorEditionOverviewComponent = (props) => { R.assoc('x_opencti_workflow_id', values.x_opencti_workflow_id?.value), R.assoc('createdBy', values.createdBy?.value), R.assoc('objectMarking', R.pluck('value', values.objectMarking)), + R.assoc('killChainPhases', R.pluck('value', values.killChainPhases)), R.toPairs, R.map((n) => ({ key: n[0], value: adaptFieldValue(n[1]) })), )(values); @@ -149,27 +150,16 @@ const ThreatActorEditionOverviewComponent = (props) => { } }; - const createdBy = convertCreatedBy(threatActor); - const objectMarking = convertMarkings(threatActor); - const status = convertStatus(t, threatActor); - const killChainPhases = R.pipe( - R.pathOr([], ['killChainPhases', 'edges']), - R.map((n) => ({ - label: `[${n.node.kill_chain_name}] ${n.node.phase_name}`, - value: n.node.id, - })), - )(threatActor); const initialValues = R.pipe( - R.assoc('createdBy', createdBy), - R.assoc('killChainPhases', killChainPhases), - R.assoc('objectMarking', objectMarking), - R.assoc('x_opencti_workflow_id', status), - R.assoc( - 'threat_actor_types', - threatActor.threat_actor_types ? threatActor.threat_actor_types : [], - ), + R.assoc('createdBy', convertCreatedBy(threatActor)), + R.assoc('killChainPhases', convertKillChainPhases(threatActor)), + R.assoc('objectMarking', convertMarkings(threatActor)), + R.assoc('x_opencti_workflow_id', convertStatus(t, threatActor)), + R.assoc('references', []), + R.assoc('threat_actor_types', threatActor.threat_actor_types ? threatActor.threat_actor_types : []), R.pick([ 'name', + 'references', 'threat_actor_types', 'confidence', 'description', diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index 33c9104b05fa..9da436636d27 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -611,14 +611,6 @@ type SubType { statuses: StatusConnection workflowEnabled: Boolean settings: EntitySetting - mandatoryAttributes: [SubTypeAttribute!]! -} - -type SubTypeAttribute { - name: String! - builtIn: Boolean! - mandatory: Boolean! - label: String } enum StatusTemplateOrdering { @@ -9754,6 +9746,13 @@ input CaseAddInput { update: Boolean } +type TypeAttribute { + name: String! + builtIn: Boolean! + mandatory: Boolean! + label: String +} + type EntitySetting implements InternalObject & BasicObject { id: ID! entity_type: String! @@ -9764,6 +9763,8 @@ type EntitySetting implements InternalObject & BasicObject { platform_hidden_type: Boolean enforce_reference: Boolean attributes_configuration: String + mandatoryDefinitions: [TypeAttribute!]! + mandatoryAttributes: [String!]! availableSettings: [String!]! created_at: DateTime! updated_at: DateTime! diff --git a/opencti-platform/opencti-front/src/utils/hooks/useEntitySettings.ts b/opencti-platform/opencti-front/src/utils/hooks/useEntitySettings.ts index 7e6fc652434c..1c5415815ff4 100644 --- a/opencti-platform/opencti-front/src/utils/hooks/useEntitySettings.ts +++ b/opencti-platform/opencti-front/src/utils/hooks/useEntitySettings.ts @@ -6,20 +6,14 @@ import useAuth from './useAuth'; import { entitySettingsFragment } from '../../private/components/settings/sub_types/EntitySetting'; import { EntitySettingConnection_entitySettings$data, EntitySettingConnection_entitySettings$key } from '../../private/components/settings/sub_types/__generated__/EntitySettingConnection_entitySettings.graphql'; import { useFormatter } from '../../components/i18n'; -import { - AttributeConfiguration, -} from '../../private/components/settings/sub_types/EntitySettingAttributesConfiguration'; export type EntitySetting = EntitySettingConnection_entitySettings$data['edges'][0]['node']; const useEntitySettings = (entityType?: string | string[]): EntitySetting[] => { const { entitySettings } = useAuth(); - const entityTypes = Array.isArray(entityType) ? entityType : [entityType]; - return useFragment(entitySettingsFragment, entitySettings) - .edges - .map(({ node }) => node) + .edges.map(({ node }) => node) .filter(({ target_type }) => (entityType ? entityTypes.includes(target_type) : true)); }; @@ -37,46 +31,39 @@ export const useIsEnforceReference = (id: string): boolean => { return useEntitySettings(id).some((node) => node.enforce_reference !== null && node.enforce_reference); }; -const useAttributesConfiguration = (id: string): AttributeConfiguration[] | null => { - const entitySetting = useEntitySettings(id)[0]; - if (!entitySetting || !entitySetting.attributes_configuration) { - return null; - } - return JSON.parse(entitySetting.attributes_configuration); -}; - -export const useYupSchemaBuilder = (id: string, existingShape: TNextShape): BaseSchema => { +export const useYupSchemaBuilder = (id: string, existingShape: TNextShape, isCreation: boolean, exclusions?: string[]): BaseSchema => { const { t } = useFormatter(); - - const attributesConfiguration = useAttributesConfiguration(id); - if (!attributesConfiguration) { - return Yup.object().shape(existingShape); + const entitySettings = useEntitySettings(id).at(0); + if (!entitySettings) { + throw Error(`Invalid type for setting: ${id}`); + } + const mandatoryAttributes = [...entitySettings.mandatoryAttributes]; + // In creation, if enforce_reference is activated, externalReferences is required + if (isCreation && entitySettings.enforce_reference === true) { + mandatoryAttributes.push('externalReferences'); } - const existingKeys = Object.keys(existingShape); - - const newShape = Object.fromEntries( - attributesConfiguration - .filter((attr: AttributeConfiguration) => attr.mandatory) - .map((attr: AttributeConfiguration) => attr.name) - .map((attrName: string) => { - let validator; - if (existingKeys.includes(attrName)) { - validator = (existingShape[attrName] as AnySchema) - .transform((v) => (!v || (Array.isArray(v) && v.length === 0) ? undefined : v)) - .required(t('This field is required')); - } else { - validator = Yup.mixed() - .transform((v) => (!v || (Array.isArray(v) && v.length === 0) ? undefined : v)) - .required(t('This field is required')); - } + const newShape = Object.fromEntries(mandatoryAttributes.filter((attr) => !(exclusions ?? []).includes(attr)) + .map((attrName: string) => { + if (existingKeys.includes(attrName)) { + const validator = (existingShape[attrName] as AnySchema) + .transform((v) => (!v || (Array.isArray(v) && v.length === 0) ? undefined : v)) + .required(t('This field is required')); return [attrName, validator]; - }), - ); - return Yup.object().shape({ - ...existingShape, - ...newShape, - }); + } + const validator = Yup.mixed().transform((v) => (!v || (Array.isArray(v) && v.length === 0) ? undefined : v)) + .required(t('This field is required')); + return [attrName, validator]; + })); + return Yup.object().shape({ ...existingShape, ...newShape }); +}; + +export const useSchemaCreationValidation = (id: string, existingShape: TNextShape, exclusions?: string[]): BaseSchema => { + return useYupSchemaBuilder(id, existingShape, true, exclusions); +}; + +export const useSchemaEditionValidation = (id: string, existingShape: TNextShape, exclusions?: string[]): BaseSchema => { + return useYupSchemaBuilder(id, existingShape, false, exclusions); }; export default useEntitySettings; diff --git a/opencti-platform/opencti-front/src/utils/hooks/useGranted.ts b/opencti-platform/opencti-front/src/utils/hooks/useGranted.ts index 8b9548ba8caa..a890b8b8290d 100644 --- a/opencti-platform/opencti-front/src/utils/hooks/useGranted.ts +++ b/opencti-platform/opencti-front/src/utils/hooks/useGranted.ts @@ -4,6 +4,7 @@ import useAuth from './useAuth'; export const OPENCTI_ADMIN_UUID = '88ec0c6a-13ce-5e39-b486-354fe4a7084f'; export const BYPASS = 'BYPASS'; export const KNOWLEDGE = 'KNOWLEDGE'; +export const BYPASSREFERENCE = 'BYPASSREFERENCE'; export const KNOWLEDGE_KNUPDATE = 'KNOWLEDGE_KNUPDATE'; export const KNOWLEDGE_KNPARTICIPATE = 'KNOWLEDGE_KNPARTICIPATE'; export const KNOWLEDGE_KNUPDATE_KNDELETE = 'KNOWLEDGE_KNUPDATE_KNDELETE'; diff --git a/opencti-platform/opencti-graphql/config/schema/opencti.graphql b/opencti-platform/opencti-graphql/config/schema/opencti.graphql index 1adc1674b411..59357582146d 100644 --- a/opencti-platform/opencti-graphql/config/schema/opencti.graphql +++ b/opencti-platform/opencti-graphql/config/schema/opencti.graphql @@ -594,15 +594,7 @@ type SubType { label: String! statuses: StatusConnection workflowEnabled: Boolean - settings: EntitySetting - mandatoryAttributes: [SubTypeAttribute!]! -} - -type SubTypeAttribute { - name: String! - builtIn: Boolean! - mandatory: Boolean! - label: String + settings: EntitySetting # Simpler before moving workflow } ############## Statuses diff --git a/opencti-platform/opencti-graphql/src/database/middleware.js b/opencti-platform/opencti-graphql/src/database/middleware.js index 89e05cf4bc72..b5a9bc8b7016 100644 --- a/opencti-platform/opencti-graphql/src/database/middleware.js +++ b/opencti-platform/opencti-graphql/src/database/middleware.js @@ -100,7 +100,7 @@ import { ABSTRACT_STIX_OBJECT, ABSTRACT_STIX_RELATIONSHIP, BASE_TYPE_ENTITY, - BASE_TYPE_RELATION, + BASE_TYPE_RELATION, buildRefRelationKey, ID_INTERNAL, ID_STANDARD, IDS_STIX, @@ -231,6 +231,7 @@ import { import { getEntitySettingFromCache } from '../modules/entitySetting/entitySetting-utils'; import { schemaRelationsRefDefinition } from '../schema/schema-relationsRef'; import { validateInputCreation, validateInputUpdate } from '../schema/schema-validator'; +import { getMandatoryAttributesForSetting } from '../domain/attribute'; // region global variables export const MAX_BATCH_SIZE = 300; @@ -1692,10 +1693,13 @@ export const updateAttribute = async (context, user, id, type, inputs, opts = {} } const updated = mergeInstanceWithUpdateInputs(initial, inputs); const keys = R.map((t) => t.key, attributes); - if (entitySetting?.enforce_reference) { - const isNoReferenceKey = noReferenceAttributes.includes(R.head(keys)) && keys.length === 1; - if (!isNoReferenceKey && isEmptyField(opts.references)) { - throw ValidationError('references', { message: 'You must provide at least one external reference to update' }); + if (opts.bypassValidation !== true) { // Allow creation directly from the back-end + const isAllowedToByPass = userHaveCapability(user, BYPASS_REFERENCE); + if (!isAllowedToByPass && entitySetting?.enforce_reference) { + const isNoReferenceKey = noReferenceAttributes.includes(R.head(keys)) && keys.length === 1; + if (!isNoReferenceKey && isEmptyField(opts.references)) { + throw ValidationError('references', { message: 'You must provide at least one external reference to update' }); + } } } let locksIds = getInstanceIds(initial); @@ -2044,6 +2048,20 @@ const upsertRelationRule = async (context, instance, input, opts = {}) => { }; // endregion +const validateEntityAndRelationCreation = async (context, user, input, type, entitySetting, opts = {}) => { + if (opts.bypassValidation !== true) { // Allow creation directly from the back-end + const isAllowedToByPass = userHaveCapability(user, BYPASS_REFERENCE); + if (!isAllowedToByPass && entitySetting?.enforce_reference) { + if (isEmptyField(input.externalReferences)) { + throw ValidationError('externalReferences', { + message: 'You must provide at least one external reference for this type of entity/relationship', + }); + } + } + await validateInputCreation(context, user, type, input, entitySetting); + } +}; + // region mutation relation const buildRelationInput = (input) => { const { relationship_type: relationshipType } = input; @@ -2620,28 +2638,19 @@ const buildRelationData = async (context, user, input, opts = {}) => { relations: relToCreate }; }; + export const createRelationRaw = async (context, user, input, opts = {}) => { let lock; const { fromRule, locks = [] } = opts; const { fromId, toId, relationship_type: relationshipType } = input; - const entitySetting = await getEntitySettingFromCache(context, relationshipType); - // Enforce reference controls - if (isStixCoreRelationship(relationshipType)) { - if (entitySetting?.enforce_reference) { - if (isEmptyField(input.externalReferences)) { - throw ValidationError('externalReferences', { - message: 'You must provide at least one external reference to create a relationship', - }); - } - } - } // Pre-check before inputs resolution if (fromId === toId) { /* istanbul ignore next */ const errorData = { from: input.fromId, relationshipType }; throw UnsupportedError('Relation cant be created with the same source and target', { error: errorData }); } - await validateInputCreation(context, user, relationshipType, input, entitySetting); + const entitySetting = await getEntitySettingFromCache(context, relationshipType); + await validateEntityAndRelationCreation(context, user, input, relationshipType, entitySetting, opts); // We need to check existing dependencies const resolvedInput = await inputResolveRefs(context, user, input, relationshipType); const { from, to } = resolvedInput; @@ -2780,7 +2789,11 @@ export const createRelation = async (context, user, input) => { return data.element; }; export const createInferredRelation = async (context, input, ruleContent, opts = {}) => { - const args = { ...opts, fromRule: ruleContent.field }; + const args = { + ...opts, + fromRule: ruleContent.field, + bypassValidation: true, // We need to bypass validation here has we maybe not setup all require fields + }; // eslint-disable-next-line camelcase const { fromId, toId, relationship_type } = input; // In some cases, we can try to create with the same from and to, ignore @@ -2964,17 +2977,8 @@ const userHaveCapability = (user, capability) => { const createEntityRaw = async (context, user, input, type, opts = {}) => { // Region - Pre-Check const entitySetting = await getEntitySettingFromCache(context, type); - const isAllowedToByPass = userHaveCapability(user, BYPASS_REFERENCE); - if (!isAllowedToByPass && entitySetting?.enforce_reference) { - if (isEmptyField(input.externalReferences)) { - throw ValidationError('externalReferences', { - message: 'You must provide at least one external reference for this type of entity', - }); - } - } - await validateInputCreation(context, user, type, input, entitySetting); + await validateEntityAndRelationCreation(context, user, input, type, entitySetting, opts); // Endregion - const { fromRule } = opts; // We need to check existing dependencies const resolvedInput = await inputResolveRefs(context, user, input, type); @@ -3099,23 +3103,23 @@ const createEntityRaw = async (context, user, input, type, opts = {}) => { } }; -export const createEntity = async (context, user, input, type) => { +export const createEntity = async (context, user, input, type, opts = {}) => { // volumes of objects relationships must be controlled if (input.objects && input.objects.length > MAX_BATCH_SIZE) { const objectSequences = R.splitEvery(MAX_BATCH_SIZE, input.objects); const firstSequence = objectSequences.shift(); const subObjectsEntity = R.assoc(INPUT_OBJECTS, firstSequence, input); - const created = await createEntityRaw(context, user, subObjectsEntity, type); + const created = await createEntityRaw(context, user, subObjectsEntity, type, opts); // For each subsequences of objects // We need to produce a batch upsert of object that will be upserted. for (let index = 0; index < objectSequences.length; index += 1) { const objectSequence = objectSequences[index]; const upsertInput = R.assoc(INPUT_OBJECTS, objectSequence, input); - await createEntityRaw(context, user, upsertInput, type); + await createEntityRaw(context, user, upsertInput, type, opts); } return created.element; } - const data = await createEntityRaw(context, user, input, type); + const data = await createEntityRaw(context, user, input, type, opts); // In case of creation, start an enrichment if (data.isCreation) { await createEntityAutoEnrichment(context, user, data.element.id, type); @@ -3123,7 +3127,11 @@ export const createEntity = async (context, user, input, type) => { return data.element; }; export const createInferredEntity = async (context, input, ruleContent, type) => { - const opts = { fromRule: ruleContent.field, impactStandardId: false }; + const opts = { + fromRule: ruleContent.field, + impactStandardId: false, + bypassValidation: true, // We need to bypass validation here has we maybe not setup all require fields + }; // Inferred entity have a specific standardId generated from dependencies data. const standardId = idGenFromData(type, ruleContent.content.dependencies.sort()); const instance = { standard_id: standardId, ...input, [ruleContent.field]: [ruleContent.content] }; @@ -3279,6 +3287,15 @@ export const deleteRelationsByFromAndTo = async (context, user, fromId, toId, re throw FunctionalError('You need to specify a scope type when deleting a relation with from and to'); } const fromThing = await internalLoadById(context, user, fromId, opts); + // Check mandatory attribute + const entitySetting = await getEntitySettingFromCache(context, fromThing.entity_type); + const attributesMandatory = await getMandatoryAttributesForSetting(context, entitySetting); + if (attributesMandatory.length > 0) { + const attribute = attributesMandatory.find((attr) => attr === schemaRelationsRefDefinition.convertDatabaseNameToInputName(relationshipType)); + if (attribute && fromThing[buildRefRelationKey(relationshipType)].length === 1) { + throw ValidationError(attribute, { message: 'This attribute is mandatory', attribute }); + } + } const toThing = await internalLoadById(context, user, toId, opts); // Looks like the caller doesnt give the correct from, to currently const relationsToDelete = await elFindByFromAndTo(context, user, fromThing.internal_id, toThing.internal_id, relationshipType); diff --git a/opencti-platform/opencti-graphql/src/domain/attribute.ts b/opencti-platform/opencti-graphql/src/domain/attribute.ts index 0dd5830c9b5d..9fa0e063b09f 100644 --- a/opencti-platform/opencti-graphql/src/domain/attribute.ts +++ b/opencti-platform/opencti-graphql/src/domain/attribute.ts @@ -4,9 +4,10 @@ import { schemaAttributesDefinition } from '../schema/schema-attributes'; import { buildPagination } from '../database/utils'; import type { AuthContext, AuthUser } from '../types/user'; import type { QueryRuntimeAttributesArgs } from '../generated/graphql'; -import { getAttributesConfiguration, getEntitySettingFromCache } from '../modules/entitySetting/entitySetting-utils'; +import { getAttributesConfiguration } from '../modules/entitySetting/entitySetting-utils'; import { schemaRelationsRefDefinition } from '../schema/schema-relationsRef'; import type { RelationRefDefinition } from '../schema/relationRef-definition'; +import type { BasicStoreEntityEntitySetting } from '../modules/entitySetting/entitySetting-types'; interface MandatoryAttribute { name: string @@ -15,23 +16,28 @@ interface MandatoryAttribute { label?: string } -export const queryMandatoryAttributes = async (context: AuthContext, subTypeId: string) => { +export const queryMandatoryAttributesDefinition = async (context: AuthContext, entitySetting: BasicStoreEntityEntitySetting) => { + if (!entitySetting) { + return []; + } + // From schema attributes const mandatoryAttributes: MandatoryAttribute[] = []; const customizableAttributes: MandatoryAttribute[] = []; - schemaAttributesDefinition.getAttributes(subTypeId) - .forEach((attr) => { - if (attr.mandatoryType === 'external') { - mandatoryAttributes.push({ name: attr.name, builtIn: true, mandatory: true, label: attr.label }); - } - if (attr.mandatoryType === 'customizable') { - customizableAttributes.push({ name: attr.name, builtIn: false, mandatory: false, label: attr.label }); - } - }); + // From schema attributes + const attributes = schemaAttributesDefinition.getAttributes(entitySetting.target_type); + attributes.forEach((attr) => { + if (attr.mandatoryType === 'external') { + mandatoryAttributes.push({ name: attr.name, builtIn: true, mandatory: true, label: attr.label }); + } + if (attr.mandatoryType === 'customizable') { + customizableAttributes.push({ name: attr.name, builtIn: false, mandatory: false, label: attr.label }); + } + }); // From schema relations ref - const relationsRef: RelationRefDefinition[] = schemaRelationsRefDefinition.getRelationsRef(subTypeId); + const relationsRef: RelationRefDefinition[] = schemaRelationsRefDefinition.getRelationsRef(entitySetting.target_type); relationsRef.forEach((rel) => { if (rel.mandatoryType === 'external') { mandatoryAttributes.push({ name: rel.inputName, builtIn: true, mandatory: true, label: rel.label }); @@ -41,24 +47,24 @@ export const queryMandatoryAttributes = async (context: AuthContext, subTypeId: } }); - const entitySetting = await getEntitySettingFromCache(context, subTypeId); - if (entitySetting) { - const userDefinedAttributes = getAttributesConfiguration(entitySetting); - - userDefinedAttributes?.forEach((userDefinedAttr) => { - const customizableAttr = customizableAttributes.find((a) => a.name === userDefinedAttr.name); - if (customizableAttr) { - customizableAttr.mandatory = userDefinedAttr.mandatory; - } - }); - } + const userDefinedAttributes = getAttributesConfiguration(entitySetting); + userDefinedAttributes?.forEach((userDefinedAttr) => { + const customizableAttr = customizableAttributes.find((a) => a.name === userDefinedAttr.name); + if (customizableAttr) { + customizableAttr.mandatory = userDefinedAttr.mandatory; + } + }); return mandatoryAttributes.concat(customizableAttributes); }; +export const getMandatoryAttributesForSetting = async (context: AuthContext, entitySetting: BasicStoreEntityEntitySetting): Promise => { + const attributes = await queryMandatoryAttributesDefinition(context, entitySetting); + return attributes.filter((a) => a.mandatory === true).map((a) => a.name); +}; + const queryAttributeNames = async (types: string[]) => { const attributes = R.uniq(types.map((type) => schemaAttributesDefinition.getAttributeNames(type)).flat()); - const sortByLabel = R.sortBy(R.toLower); const finalResult = R.pipe( sortByLabel, diff --git a/opencti-platform/opencti-graphql/src/domain/individual.js b/opencti-platform/opencti-graphql/src/domain/individual.js index 2b4fcd528563..53e00adde124 100644 --- a/opencti-platform/opencti-graphql/src/domain/individual.js +++ b/opencti-platform/opencti-graphql/src/domain/individual.js @@ -18,13 +18,9 @@ export const findAll = (context, user, args) => { return listEntities(context, user, [ENTITY_TYPE_IDENTITY_INDIVIDUAL], args); }; -export const addIndividual = async (context, user, individual) => { - const created = await createEntity( - context, - user, - R.assoc('identity_class', ENTITY_TYPE_IDENTITY_INDIVIDUAL.toLowerCase(), individual), - ENTITY_TYPE_IDENTITY_INDIVIDUAL - ); +export const addIndividual = async (context, user, individual, opts = {}) => { + const inputWithClass = R.assoc('identity_class', ENTITY_TYPE_IDENTITY_INDIVIDUAL.toLowerCase(), individual); + const created = await createEntity(context, user, inputWithClass, ENTITY_TYPE_IDENTITY_INDIVIDUAL, opts); return notify(BUS_TOPICS[ABSTRACT_STIX_DOMAIN_OBJECT].ADDED_TOPIC, created, user); }; diff --git a/opencti-platform/opencti-graphql/src/domain/observedData.js b/opencti-platform/opencti-graphql/src/domain/observedData.js index c03dee6fd713..3b3cc435c7b3 100644 --- a/opencti-platform/opencti-graphql/src/domain/observedData.js +++ b/opencti-platform/opencti-graphql/src/domain/observedData.js @@ -12,7 +12,7 @@ import { RELATION_CREATED_BY, RELATION_OBJECT } from '../schema/stixMetaRelation import { ABSTRACT_STIX_CORE_OBJECT, ABSTRACT_STIX_DOMAIN_OBJECT, buildRefRelationKey } from '../schema/general'; import { elCount } from '../database/engine'; import { READ_INDEX_STIX_DOMAIN_OBJECTS } from '../database/utils'; -import { DatabaseError, FunctionalError } from '../config/errors'; +import { DatabaseError } from '../config/errors'; import { isStixId } from '../schema/schemaUtils'; import { objects } from './container'; import { observableValue } from '../utils/format'; @@ -35,7 +35,7 @@ export const resolveName = async (context, user, observedData) => { } return observableValue(firstObject.node); } - return observedData.node.last_observed; + return 'empty'; }; // All entities @@ -106,9 +106,6 @@ export const observedDatasDistributionByEntity = async (context, user, args) => // region mutations export const addObservedData = async (context, user, observedData) => { - if (observedData.objects.length === 0) { - throw FunctionalError('Observed data must contain at least 1 object'); - } if (observedData.first_observed > observedData.last_observed) { throw DatabaseError('You cant create an observed data with last_observed less than first_observed', { input: observedData, diff --git a/opencti-platform/opencti-graphql/src/domain/opinion.js b/opencti-platform/opencti-graphql/src/domain/opinion.js index 19d38c94a37c..4d58ff7c3e29 100644 --- a/opencti-platform/opencti-graphql/src/domain/opinion.js +++ b/opencti-platform/opencti-graphql/src/domain/opinion.js @@ -15,6 +15,7 @@ import { elCount } from '../database/engine'; import { READ_INDEX_STIX_DOMAIN_OBJECTS } from '../database/utils'; import { isStixId } from '../schema/schemaUtils'; import { addIndividual, findAll as findIndividuals } from './individual'; +import { userSessionRefresh } from './user'; export const findById = (context, user, opinionId) => { return storeLoadById(context, user, opinionId, ENTITY_TYPE_CONTAINER_OPINION); @@ -114,17 +115,13 @@ export const opinionsDistributionByEntity = async (context, user, args) => { // region mutations export const addOpinion = async (context, user, opinion) => { - const opinionToCreate = opinion; - // For note, auto assign current user as author - if (!opinion.createdBy) { - const args = { filters: [{ key: 'contact_information', values: [user.user_email] }], connectionFormat: false }; - const individuals = await findIndividuals(context, user, args); - if (individuals.length > 0) { - opinionToCreate.createdBy = R.head(individuals).id; - } else { - const individual = await addIndividual(context, user, { name: user.name, contact_information: user.user_email }); - opinionToCreate.createdBy = individual.id; - } + const opinionToCreate = { ...opinion }; + if (opinionToCreate.createdBy === undefined) { + const individualInput = { name: user.name, contact_information: user.user_email }; + // We need to bypass validation here has we maybe not setup all require fields + const individual = await addIndividual(context, user, individualInput, { bypassValidation: true }); + opinionToCreate.createdBy = individual.id; + await userSessionRefresh(user.internal_id); } const created = await createEntity(context, user, opinionToCreate, ENTITY_TYPE_CONTAINER_OPINION); return notify(BUS_TOPICS[ABSTRACT_STIX_DOMAIN_OBJECT].ADDED_TOPIC, created, user); diff --git a/opencti-platform/opencti-graphql/src/domain/stixObjectOrStixRelationship.js b/opencti-platform/opencti-graphql/src/domain/stixObjectOrStixRelationship.js index ba2e8c071dc1..951a094be42a 100644 --- a/opencti-platform/opencti-graphql/src/domain/stixObjectOrStixRelationship.js +++ b/opencti-platform/opencti-graphql/src/domain/stixObjectOrStixRelationship.js @@ -2,13 +2,11 @@ import { elLoadById } from '../database/engine'; import { READ_PLATFORM_INDICES } from '../database/utils'; import { storeLoadById } from '../database/middleware-loader'; import { ABSTRACT_STIX_META_RELATIONSHIP } from '../schema/general'; -import { FunctionalError, ValidationError } from '../config/errors'; +import { FunctionalError } from '../config/errors'; import { isStixMetaRelationship } from '../schema/stixMetaRelationship'; import { deleteRelationsByFromAndTo } from '../database/middleware'; import { notify } from '../database/redis'; import { BUS_TOPICS } from '../config/conf'; -import { getAttributesConfiguration, getEntitySettingFromCache } from '../modules/entitySetting/entitySetting-utils'; -import { schemaRelationsRefDefinition } from '../schema/schema-relationsRef'; // eslint-disable-next-line import/prefer-default-export export const findById = async (context, user, id) => { @@ -23,17 +21,6 @@ export const stixObjectOrRelationshipDeleteRelation = async (context, user, stix if (!isStixMetaRelationship(relationshipType)) { throw FunctionalError(`Only ${ABSTRACT_STIX_META_RELATIONSHIP} can be deleted through this method.`); } - - // check mandatory attribute - const entitySetting = await getEntitySettingFromCache(context, stixObjectOrRelationship.entity_type); - const attributesConfiguration = getAttributesConfiguration(entitySetting); - if (attributesConfiguration) { - const attribute = attributesConfiguration.find((attr) => attr.name === schemaRelationsRefDefinition.convertDatabaseNameToInputName(relationshipType)); - if (attribute?.mandatory && stixObjectOrRelationship[relationshipType].length === 1) { - throw ValidationError(attribute.name, { message: 'This attribute is mandatory', attribute: attribute.name }); - } - } - await deleteRelationsByFromAndTo(context, user, stixObjectOrRelationshipId, toId, relationshipType, ABSTRACT_STIX_META_RELATIONSHIP); return notify(BUS_TOPICS[type].EDIT_TOPIC, stixObjectOrRelationship, user); }; diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index 48d25eff6937..78b0a88946f2 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -5364,6 +5364,8 @@ export type EntitySetting = BasicObject & InternalObject & { enforce_reference?: Maybe; entity_type: Scalars['String']; id: Scalars['ID']; + mandatoryAttributes: Array; + mandatoryDefinitions: Array; parent_types: Array; platform_entity_files_ref?: Maybe; platform_hidden_type?: Maybe; @@ -20732,20 +20734,11 @@ export type SubType = { __typename?: 'SubType'; id: Scalars['ID']; label: Scalars['String']; - mandatoryAttributes: Array; settings?: Maybe; statuses?: Maybe; workflowEnabled?: Maybe; }; -export type SubTypeAttribute = { - __typename?: 'SubTypeAttribute'; - builtIn: Scalars['Boolean']; - label?: Maybe; - mandatory: Scalars['Boolean']; - name: Scalars['String']; -}; - export type SubTypeConnection = { __typename?: 'SubTypeConnection'; edges: Array; @@ -22237,6 +22230,14 @@ export enum TriggersOrdering { TriggerType = 'trigger_type' } +export type TypeAttribute = { + __typename?: 'TypeAttribute'; + builtIn: Scalars['Boolean']; + label?: Maybe; + mandatory: Scalars['Boolean']; + name: Scalars['String']; +}; + export type Url = BasicObject & StixCoreObject & StixCyberObservable & StixObject & { __typename?: 'Url'; cases?: Maybe; @@ -24945,7 +24946,6 @@ export type ResolversTypes = ResolversObject<{ StreamCollectionOrdering: StreamCollectionOrdering; String: ResolverTypeWrapper; SubType: ResolverTypeWrapper & { settings?: Maybe }>; - SubTypeAttribute: ResolverTypeWrapper; SubTypeConnection: ResolverTypeWrapper & { edges: Array }>; SubTypeEdge: ResolverTypeWrapper & { node: ResolversTypes['SubType'] }>; SubTypeEditMutations: ResolverTypeWrapper & { statusAdd?: Maybe, statusDelete?: Maybe, statusFieldPatch?: Maybe }>; @@ -25015,6 +25015,7 @@ export type ResolversTypes = ResolversObject<{ TriggerType: TriggerType; TriggersFiltering: TriggersFiltering; TriggersOrdering: TriggersOrdering; + TypeAttribute: ResolverTypeWrapper; Upload: ResolverTypeWrapper; Url: ResolverTypeWrapper & { cases?: Maybe, createdBy?: Maybe, exportFiles?: Maybe, externalReferences?: Maybe, groupings?: Maybe, importFiles?: Maybe, indicators?: Maybe, notes?: Maybe, objectOrganization?: Maybe, observedData?: Maybe, opinions?: Maybe, pendingFiles?: Maybe, reports?: Maybe, stixCoreRelationships?: Maybe, stixCyberObservableRelationships?: Maybe }>; UrlAddInput: UrlAddInput; @@ -25537,7 +25538,6 @@ export type ResolversParentTypes = ResolversObject<{ StreamCollectionFiltering: StreamCollectionFiltering; String: Scalars['String']; SubType: Omit & { settings?: Maybe }; - SubTypeAttribute: SubTypeAttribute; SubTypeConnection: Omit & { edges: Array }; SubTypeEdge: Omit & { node: ResolversParentTypes['SubType'] }; SubTypeEditMutations: Omit & { statusAdd?: Maybe, statusDelete?: Maybe, statusFieldPatch?: Maybe }; @@ -25589,6 +25589,7 @@ export type ResolversParentTypes = ResolversObject<{ TriggerEdge: Omit & { node: ResolversParentTypes['Trigger'] }; TriggerLiveAddInput: TriggerLiveAddInput; TriggersFiltering: TriggersFiltering; + TypeAttribute: TypeAttribute; Upload: Scalars['Upload']; Url: Omit & { cases?: Maybe, createdBy?: Maybe, exportFiles?: Maybe, externalReferences?: Maybe, groupings?: Maybe, importFiles?: Maybe, indicators?: Maybe, notes?: Maybe, objectOrganization?: Maybe, observedData?: Maybe, opinions?: Maybe, pendingFiles?: Maybe, reports?: Maybe, stixCoreRelationships?: Maybe, stixCyberObservableRelationships?: Maybe }; UrlAddInput: UrlAddInput; @@ -27137,6 +27138,8 @@ export type EntitySettingResolvers, ParentType, ContextType>; entity_type?: Resolver; id?: Resolver; + mandatoryAttributes?: Resolver, ParentType, ContextType>; + mandatoryDefinitions?: Resolver, ParentType, ContextType>; parent_types?: Resolver, ParentType, ContextType>; platform_entity_files_ref?: Resolver, ParentType, ContextType>; platform_hidden_type?: Resolver, ParentType, ContextType>; @@ -31075,21 +31078,12 @@ export type StreamCollectionEditMutationsResolvers = ResolversObject<{ id?: Resolver; label?: Resolver; - mandatoryAttributes?: Resolver, ParentType, ContextType>; settings?: Resolver, ParentType, ContextType>; statuses?: Resolver, ParentType, ContextType>; workflowEnabled?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type SubTypeAttributeResolvers = ResolversObject<{ - builtIn?: Resolver; - label?: Resolver, ParentType, ContextType>; - mandatory?: Resolver; - name?: Resolver; - __isTypeOf?: IsTypeOfResolverFn; -}>; - export type SubTypeConnectionResolvers = ResolversObject<{ edges?: Resolver, ParentType, ContextType>; pageInfo?: Resolver; @@ -31547,6 +31541,14 @@ export type TriggerEdgeResolvers; }>; +export type TypeAttributeResolvers = ResolversObject<{ + builtIn?: Resolver; + label?: Resolver, ParentType, ContextType>; + mandatory?: Resolver; + name?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export interface UploadScalarConfig extends GraphQLScalarTypeConfig { name: 'Upload'; } @@ -32451,7 +32453,6 @@ export type Resolvers = ResolversObject<{ StreamCollectionEdge?: StreamCollectionEdgeResolvers; StreamCollectionEditMutations?: StreamCollectionEditMutationsResolvers; SubType?: SubTypeResolvers; - SubTypeAttribute?: SubTypeAttributeResolvers; SubTypeConnection?: SubTypeConnectionResolvers; SubTypeEdge?: SubTypeEdgeResolvers; SubTypeEditMutations?: SubTypeEditMutationsResolvers; @@ -32487,6 +32488,7 @@ export type Resolvers = ResolversObject<{ Trigger?: TriggerResolvers; TriggerConnection?: TriggerConnectionResolvers; TriggerEdge?: TriggerEdgeResolvers; + TypeAttribute?: TypeAttributeResolvers; Upload?: GraphQLScalarType; Url?: UrlResolvers; User?: UserResolvers; diff --git a/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting-resolvers.ts b/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting-resolvers.ts index 6594d800decc..6ed10dc103f5 100644 --- a/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting-resolvers.ts +++ b/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting-resolvers.ts @@ -5,6 +5,7 @@ import { pubSubAsyncIterator } from '../../database/redis'; import { BUS_TOPICS } from '../../config/conf'; import { ENTITY_TYPE_ENTITY_SETTING } from './entitySetting-types'; import { getAvailableSettings } from './entitySetting-utils'; +import { getMandatoryAttributesForSetting, queryMandatoryAttributesDefinition } from '../../domain/attribute'; const entitySettingResolvers: Resolvers = { Query: { @@ -13,6 +14,8 @@ const entitySettingResolvers: Resolvers = { entitySettingByType: (_, { targetType }, context) => findByType(context, context.user, targetType), }, EntitySetting: { + mandatoryDefinitions: (entitySetting, _, context) => queryMandatoryAttributesDefinition(context, entitySetting), + mandatoryAttributes: (entitySetting, _, context) => getMandatoryAttributesForSetting(context, entitySetting), availableSettings: (entitySetting, _, __) => getAvailableSettings(entitySetting.target_type), }, Mutation: { diff --git a/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting-utils.ts b/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting-utils.ts index 1f35b455a832..4efa5b2e205e 100644 --- a/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting-utils.ts +++ b/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting-utils.ts @@ -6,7 +6,11 @@ import { ABSTRACT_STIX_DOMAIN_OBJECT } from '../../schema/general'; import { STIX_SIGHTING_RELATIONSHIP } from '../../schema/stixSightingRelationship'; -import { isStixDomainObject } from '../../schema/stixDomainObject'; +import { + ENTITY_TYPE_CONTAINER_NOTE, + ENTITY_TYPE_CONTAINER_OPINION, + isStixDomainObject +} from '../../schema/stixDomainObject'; import { UnsupportedError, ValidationError } from '../../config/errors'; import type { AttributeConfiguration, BasicStoreEntityEntitySetting } from './entitySetting-types'; import { ENTITY_TYPE_ENTITY_SETTING } from './entitySetting-types'; @@ -18,6 +22,7 @@ import { isStixCyberObservable } from '../../schema/stixCyberObservable'; import { schemaAttributesDefinition } from '../../schema/schema-attributes'; import { schemaRelationsRefDefinition } from '../../schema/schema-relationsRef'; import type { RelationRefDefinition } from '../../schema/relationRef-definition'; +import { ENTITY_TYPE_CONTAINER_CASE } from '../case/case-types'; export type typeAvailableSetting = boolean | string; @@ -28,11 +33,15 @@ export const defaultEntitySetting: Record = { attributes_configuration: JSON.stringify([]), }; +// Available settings works by override. export const availableSettings: Record> = { [ABSTRACT_STIX_DOMAIN_OBJECT]: ['attributes_configuration', 'platform_entity_files_ref', 'platform_hidden_type', 'enforce_reference'], - [ABSTRACT_STIX_CYBER_OBSERVABLE]: ['platform_entity_files_ref'], - [ABSTRACT_STIX_CORE_RELATIONSHIP]: ['enforce_reference'], - [STIX_SIGHTING_RELATIONSHIP]: ['platform_entity_files_ref'], + [ABSTRACT_STIX_CORE_RELATIONSHIP]: [], + [STIX_SIGHTING_RELATIONSHIP]: [], + // enforce_reference not available on specific entities + [ENTITY_TYPE_CONTAINER_NOTE]: ['attributes_configuration', 'platform_entity_files_ref', 'platform_hidden_type'], + [ENTITY_TYPE_CONTAINER_OPINION]: ['attributes_configuration', 'platform_entity_files_ref', 'platform_hidden_type'], + [ENTITY_TYPE_CONTAINER_CASE]: ['attributes_configuration', 'platform_entity_files_ref', 'platform_hidden_type'], }; const keyAvailableSetting = R.uniq(Object.values(availableSettings).flat()); @@ -40,7 +49,7 @@ const keyAvailableSetting = R.uniq(Object.values(availableSettings).flat()); export const getAvailableSettings = (targetType: string) => { let settings; if (isStixDomainObject(targetType)) { - settings = [...availableSettings[targetType] ?? [], ...availableSettings[ABSTRACT_STIX_DOMAIN_OBJECT]]; + settings = availableSettings[targetType] ?? availableSettings[ABSTRACT_STIX_DOMAIN_OBJECT]; } else { settings = availableSettings[targetType]; } @@ -141,7 +150,7 @@ export const attributeConfiguration: JSONSchemaType = items: { type: 'object', properties: { - name: { type: 'string' }, + name: { type: 'string', minLength: 1 }, mandatory: { type: 'boolean' } }, required: ['name', 'mandatory'] diff --git a/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting.graphql b/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting.graphql index 39c9a89db348..f4b805d0c472 100644 --- a/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting.graphql +++ b/opencti-platform/opencti-graphql/src/modules/entitySetting/entitySetting.graphql @@ -1,3 +1,10 @@ +type TypeAttribute { + name: String! + builtIn: Boolean! + mandatory: Boolean! + label: String +} + type EntitySetting implements InternalObject & BasicObject { id: ID! entity_type: String! @auth @@ -8,7 +15,9 @@ type EntitySetting implements InternalObject & BasicObject { platform_entity_files_ref: Boolean @auth platform_hidden_type: Boolean @auth enforce_reference: Boolean @auth - attributes_configuration: String @auth + attributes_configuration: String @auth(for: [SETTINGS]) + mandatoryDefinitions: [TypeAttribute!]! @auth(for: [SETTINGS]) + mandatoryAttributes: [String!]! @auth availableSettings: [String!]! @auth created_at: DateTime! @auth updated_at: DateTime! @auth diff --git a/opencti-platform/opencti-graphql/src/modules/relationsRef/stixMetaRelationship-registrationRefs.ts b/opencti-platform/opencti-graphql/src/modules/relationsRef/stixMetaRelationship-registrationRefs.ts index c2e9c02c57d0..d8a7f23464cf 100644 --- a/opencti-platform/opencti-graphql/src/modules/relationsRef/stixMetaRelationship-registrationRefs.ts +++ b/opencti-platform/opencti-graphql/src/modules/relationsRef/stixMetaRelationship-registrationRefs.ts @@ -44,7 +44,9 @@ import { schemaRelationsRefDefinition.registerRelationsRef(ABSTRACT_STIX_DOMAIN_OBJECT, [createdBy, objectMarking, objectLabel, externalReferences]); schemaRelationsRefDefinition.registerRelationsRef(ABSTRACT_STIX_CYBER_OBSERVABLE, [createdBy, objectMarking, objectLabel, externalReferences]); -schemaRelationsRefDefinition.registerRelationsRef(ABSTRACT_STIX_RELATIONSHIP, [createdBy, objectMarking, objectLabel, externalReferences, killChainPhases]); +schemaRelationsRefDefinition.registerRelationsRef(ABSTRACT_STIX_RELATIONSHIP, [createdBy, objectMarking, objectLabel, externalReferences, { ...killChainPhases, mandatoryType: 'no' }]); +schemaRelationsRefDefinition.registerRelationsRef(ABSTRACT_STIX_CORE_RELATIONSHIP, [objectOrganization]); +schemaRelationsRefDefinition.registerRelationsRef(STIX_SIGHTING_RELATIONSHIP, [objectOrganization]); schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_CONTAINER, [objects]); @@ -63,11 +65,9 @@ schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_CONTAINER_CASE, [ { ...createdBy, mandatoryType: 'no' }, { ...objectMarking, mandatoryType: 'no' }, { ...objectAssignee, mandatoryType: 'no' }, objectOrganization]); schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_CONTAINER_NOTE, [objectOrganization]); -schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_CONTAINER_OBSERVED_DATA, [objectOrganization]); +schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_CONTAINER_OBSERVED_DATA, [{ ...objects, mandatoryType: 'external' }, objectOrganization]); schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_CONTAINER_OPINION, [objectOrganization]); schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_COURSE_OF_ACTION, [objectOrganization]); -schemaRelationsRefDefinition.registerRelationsRef(ABSTRACT_STIX_CORE_RELATIONSHIP, [objectOrganization]); -schemaRelationsRefDefinition.registerRelationsRef(STIX_SIGHTING_RELATIONSHIP, [objectOrganization]); schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_LOCATION_ADMINISTRATIVE_AREA, [objectOrganization]); schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_CHANNEL, [objectOrganization]); schemaRelationsRefDefinition.registerRelationsRef(ENTITY_TYPE_DATA_SOURCE, [objectOrganization]); diff --git a/opencti-platform/opencti-graphql/src/resolvers/note.js b/opencti-platform/opencti-graphql/src/resolvers/note.js index 3587c60fb97b..e91fe4f42aa5 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/note.js +++ b/opencti-platform/opencti-graphql/src/resolvers/note.js @@ -106,17 +106,21 @@ const noteResolvers = { return stixDomainObjectDeleteRelation(context, context.user, id, toId, relationshipType); }, }), + // For collaborative creation userNoteAdd: async (_, { input }, context) => { const { user } = context; - let individualId = user.individual_id; - if (individualId === undefined) { - const individual = await addIndividual(context, user, { name: user.name, contact_information: user.user_email }); - individualId = individual.id; + const noteToCreate = { ...input }; + noteToCreate.createdBy = user.individual_id; + if (noteToCreate.createdBy === undefined) { + const individualInput = { name: user.name, contact_information: user.user_email }; + // We need to bypass validation here has we maybe not setup all require fields + const individual = await addIndividual(context, user, individualInput, { bypassValidation: true }); + noteToCreate.createdBy = individual.id; await userSessionRefresh(user.internal_id); } - const inputWithCreator = { ...input, createdBy: individualId }; - return addNote(context, user, inputWithCreator); + return addNote(context, user, noteToCreate); }, + // For knowledge noteAdd: (_, { input }, context) => { return addNote(context, context.user, input); }, diff --git a/opencti-platform/opencti-graphql/src/resolvers/subType.js b/opencti-platform/opencti-graphql/src/resolvers/subType.js index 2149e032e1aa..f10899a5bb92 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/subType.js +++ b/opencti-platform/opencti-graphql/src/resolvers/subType.js @@ -1,7 +1,6 @@ import { findAll, findById as findSubTypeById, findById } from '../domain/subType'; import { createStatus, getTypeStatuses, statusDelete, statusEditField } from '../domain/status'; import { findByType } from '../modules/entitySetting/entitySetting-domain'; -import { queryMandatoryAttributes } from '../domain/attribute'; const subTypeResolvers = { Query: { @@ -14,8 +13,7 @@ const subTypeResolvers = { return statusesEdges.edges.length > 0; }, statuses: (current, _, context) => getTypeStatuses(context, context.user, current.id), - settings: (current, _, context) => findByType(context, context.user, current.id), - mandatoryAttributes: (current, _, context) => queryMandatoryAttributes(context, current.id), + settings: (current, _, context) => findByType(context, context.user, current.id), // Simpler before moving workflow }, Mutation: { subTypeEdit: (_, { id }, context) => ({ diff --git a/opencti-platform/opencti-graphql/src/schema/internalObject.ts b/opencti-platform/opencti-graphql/src/schema/internalObject.ts index 5531864f28e9..e3f3bbe6f0c8 100644 --- a/opencti-platform/opencti-graphql/src/schema/internalObject.ts +++ b/opencti-platform/opencti-graphql/src/schema/internalObject.ts @@ -67,7 +67,6 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - { name: 'platform_title', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'platform_organization', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'platform_favicon', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, @@ -104,7 +103,6 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - { name: 'lastRun', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'platformVersion', type: 'string', mandatoryType: 'no', multiple: false, upsert: false } ], @@ -112,7 +110,6 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - { name: 'title', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'timestamp', type: 'date', mandatoryType: 'no', multiple: false, upsert: false } ], @@ -122,8 +119,7 @@ const internalObjectsAttributes: { [k: string]: Array } = { entityType, createdAt, updatedAt, - - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'description', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'default_assignation', type: 'boolean', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'auto_new_marking', type: 'boolean', mandatoryType: 'no', multiple: false, upsert: false }, @@ -135,10 +131,9 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - - { name: 'user_email', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'user_email', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'password', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'description', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'firstname', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'lastname', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, @@ -161,8 +156,7 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'default_assignation', type: 'boolean', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'description', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, createdAt, @@ -175,12 +169,10 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - { name: 'active', type: 'boolean', mandatoryType: 'no', multiple: false, upsert: true } ], [ENTITY_TYPE_RULE_MANAGER]: [ internalId, - { name: 'lastEventId', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'errors', type: 'string', mandatoryType: 'no', multiple: false, upsert: false } ], @@ -188,7 +180,6 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - { name: 'name', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'attribute_order', type: 'numeric', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'description', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, @@ -202,7 +193,6 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - { name: 'name', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'active', type: 'boolean', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'auto', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, @@ -222,9 +212,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { internalId, standardId, entityType, - { name: 'identifier', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'description', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'manifest', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'owner', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, @@ -240,16 +229,14 @@ const internalObjectsAttributes: { [k: string]: Array } = { [ENTITY_TYPE_TAXII_COLLECTION]: [ internalId, standardId, - - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'description', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'filters', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, ], [ENTITY_TYPE_STREAM_COLLECTION]: [ internalId, standardId, - - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'description', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'filters', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'stream_public', type: 'boolean', mandatoryType: 'no', multiple: false, upsert: false }, @@ -258,21 +245,18 @@ const internalObjectsAttributes: { [k: string]: Array } = { [ENTITY_TYPE_STATUS_TEMPLATE]: [ internalId, standardId, - - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'color', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'color', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, ], [ENTITY_TYPE_STATUS]: [ internalId, standardId, - - { name: 'template_id', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'template_id', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'type', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, - { name: 'order', type: 'numeric', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'order', type: 'numeric', mandatoryType: 'external', multiple: false, upsert: false }, ], [ENTITY_TYPE_TASK]: [ standardId, - { name: 'task_position', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'task_processed_number', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'task_expected_number', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, @@ -281,10 +265,9 @@ const internalObjectsAttributes: { [k: string]: Array } = { ], [ENTITY_TYPE_RETENTION_RULE]: [ standardId, - - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'filters', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'max_retention', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'filters', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'max_retention', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'last_execution_date', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'last_deleted_count', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'remaining_count', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, @@ -292,17 +275,16 @@ const internalObjectsAttributes: { [k: string]: Array } = { [ENTITY_TYPE_SYNC]: [ internalId, standardId, - - { name: 'name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'uri', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'uri', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'ssl_verify', type: 'boolean', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'user_id', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, - { name: 'token', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'stream_id', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'token', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'stream_id', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'running', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'current_state', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, - { name: 'listen_deletion', type: 'boolean', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'no_dependencies', type: 'boolean', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'listen_deletion', type: 'boolean', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'no_dependencies', type: 'boolean', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'user_id', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, ], }; diff --git a/opencti-platform/opencti-graphql/src/schema/schema-validator.ts b/opencti-platform/opencti-graphql/src/schema/schema-validator.ts index 7c86b7ff4376..13363a4bc095 100644 --- a/opencti-platform/opencti-graphql/src/schema/schema-validator.ts +++ b/opencti-platform/opencti-graphql/src/schema/schema-validator.ts @@ -6,6 +6,7 @@ import { isNotEmptyField } from '../database/utils'; import { getEntityValidatorCreation, getEntityValidatorUpdate } from './validator-register'; import type { AuthContext, AuthUser } from '../types/user'; import { getAttributesConfiguration } from '../modules/entitySetting/entitySetting-utils'; +import { externalReferences } from './stixMetaRelationship'; const ajv = new Ajv(); @@ -41,16 +42,19 @@ const validateFormatSchemaAttributes = (instanceType: string, input: Record, entitySetting: BasicStoreEntityEntitySetting, + isCreation: boolean, validation: (inputKeys: string[], mandatoryKey: string) => boolean ) => { const attributesConfiguration = getAttributesConfiguration(entitySetting); if (!attributesConfiguration) { return; } - const mandatoryAttributes = attributesConfiguration.filter((attr) => attr.mandatory); + // In creation if enforce reference is activated, user must provide a least 1 external references + if (isCreation && entitySetting.enforce_reference === true) { + mandatoryAttributes.push({ name: externalReferences.inputName, mandatory: true }); + } const inputKeys = Object.keys(input); - mandatoryAttributes.forEach((attr) => { if (!(validation(inputKeys, attr.name))) { throw ValidationError(attr.name, { message: 'This attribute is mandatory', attribute: attr.name }); @@ -66,7 +70,7 @@ const validateMandatoryAttributesOnCreation = ( const inputValidValue = (inputKeys: string[], mandatoryKey: string) => (inputKeys.includes(mandatoryKey) && (Array.isArray(input[mandatoryKey]) ? (input[mandatoryKey] as []).some((i: string) => isNotEmptyField(i)) : isNotEmptyField(input[mandatoryKey]))); - validateMandatoryAttributes(input, entitySetting, inputValidValue); + validateMandatoryAttributes(input, entitySetting, true, inputValidValue); }; const validateMandatoryAttributesOnUpdate = ( input: Record, @@ -76,7 +80,7 @@ const validateMandatoryAttributesOnUpdate = ( const inputValidValue = (inputKeys: string[], mandatoryKey: string) => (!inputKeys.includes(mandatoryKey) || (Array.isArray(input[mandatoryKey]) ? (input[mandatoryKey] as []).some((i: string) => isNotEmptyField(i)) : isNotEmptyField(input[mandatoryKey]))); - validateMandatoryAttributes(input, entitySetting, inputValidValue); + validateMandatoryAttributes(input, entitySetting, false, inputValidValue); }; export const validateInputCreation = async ( diff --git a/opencti-platform/opencti-graphql/src/schema/stixCyberObservable.ts b/opencti-platform/opencti-graphql/src/schema/stixCyberObservable.ts index e17d55e81189..f35c5ee19b44 100644 --- a/opencti-platform/opencti-graphql/src/schema/stixCyberObservable.ts +++ b/opencti-platform/opencti-graphql/src/schema/stixCyberObservable.ts @@ -131,7 +131,7 @@ const stixCyberObservablesAttributes: { [k: string]: Array { name: 'i_created_at_year', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'x_opencti_description', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'x_opencti_score', type: 'numeric', mandatoryType: 'no', multiple: false, upsert: true }, - { name: 'number', type: 'string', mandatoryType: 'internal', multiple: false, upsert: true }, + { name: 'number', type: 'string', mandatoryType: 'external', multiple: false, upsert: true }, { name: 'name', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'rir', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, ], @@ -148,7 +148,7 @@ const stixCyberObservablesAttributes: { [k: string]: Array { name: 'i_created_at_year', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'x_opencti_description', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'x_opencti_score', type: 'numeric', mandatoryType: 'no', multiple: false, upsert: true }, - { name: 'path', type: 'string', mandatoryType: 'internal', multiple: false, upsert: true }, + { name: 'path', type: 'string', mandatoryType: 'external', multiple: false, upsert: true }, { name: 'path_enc', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'ctime', type: 'date', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'mtime', type: 'date', mandatoryType: 'no', multiple: false, upsert: true }, @@ -182,7 +182,7 @@ const stixCyberObservablesAttributes: { [k: string]: Array { name: 'i_created_at_year', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'x_opencti_description', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'x_opencti_score', type: 'numeric', mandatoryType: 'no', multiple: false, upsert: true }, - { name: 'value', type: 'string', mandatoryType: 'internal', multiple: false, upsert: true }, + { name: 'value', type: 'string', mandatoryType: 'external', multiple: false, upsert: true }, { name: 'display_name', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, ], [ENTITY_EMAIL_MESSAGE]: [ @@ -653,7 +653,7 @@ const stixCyberObservablesAttributes: { [k: string]: Array { name: 'i_created_at_year', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'x_opencti_description', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'x_opencti_score', type: 'numeric', mandatoryType: 'no', multiple: false, upsert: true }, - { name: 'card_number', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'card_number', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'expiration_date', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'cvv', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'holder_name', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, @@ -674,7 +674,7 @@ const stixCyberObservablesAttributes: { [k: string]: Array { name: 'title', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'content', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'media_category', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, - { name: 'url', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'url', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'publication_date', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, ], }; diff --git a/opencti-platform/opencti-graphql/src/schema/stixDomainObject.ts b/opencti-platform/opencti-graphql/src/schema/stixDomainObject.ts index 859c62450424..ab76f7b2c709 100644 --- a/opencti-platform/opencti-graphql/src/schema/stixDomainObject.ts +++ b/opencti-platform/opencti-graphql/src/schema/stixDomainObject.ts @@ -258,7 +258,7 @@ const stixDomainObjectsAttributes: { [k: string]: Array } = revoked, confidence, lang, - created, + { ...created, mandatoryType: 'external', label: 'Publication date' }, modified, { name: 'abstract', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'attribute_abstract', type: 'string', mandatoryType: 'customizable', multiple: false, upsert: true, label: 'Abstract' }, @@ -287,8 +287,7 @@ const stixDomainObjectsAttributes: { [k: string]: Array } = modified, { name: 'first_observed', type: 'date', mandatoryType: 'external', multiple: false, upsert: false, label: 'First observed' }, { name: 'last_observed', type: 'date', mandatoryType: 'external', multiple: false, upsert: false, label: 'Last observed' }, - { name: 'objects', type: 'string', mandatoryType: 'external', multiple: false, upsert: false, label: 'Entities' }, - { name: 'number_observed', type: 'numeric', mandatoryType: 'customizable', multiple: false, upsert: false, label: 'Number observed' }, + { name: 'number_observed', type: 'numeric', mandatoryType: 'external', multiple: false, upsert: false, label: 'Number observed' }, { name: 'x_opencti_graph_data', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'x_opencti_workflow_id', type: 'string', mandatoryType: 'no', multiple: false, upsert: false } ], @@ -710,7 +709,7 @@ const stixDomainObjectsAttributes: { [k: string]: Array } = { name: 'name', type: 'string', mandatoryType: 'external', multiple: false, upsert: true }, { name: 'description', type: 'string', mandatoryType: 'customizable', multiple: false, upsert: true }, { name: 'malware_types', type: 'string', mandatoryType: 'customizable', multiple: true, upsert: true, label: 'Malware types' }, - { name: 'is_family', type: 'boolean', mandatoryType: 'external', multiple: false, upsert: true }, + { name: 'is_family', type: 'boolean', mandatoryType: 'external', multiple: false, upsert: true, label: 'Is family' }, { name: 'first_seen', type: 'date', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'i_first_seen_day', type: 'date', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'i_first_seen_month', type: 'date', mandatoryType: 'no', multiple: false, upsert: false }, diff --git a/opencti-platform/opencti-graphql/src/schema/stixMetaObject.ts b/opencti-platform/opencti-graphql/src/schema/stixMetaObject.ts index e78e00b3f054..1e320fcaf22e 100644 --- a/opencti-platform/opencti-graphql/src/schema/stixMetaObject.ts +++ b/opencti-platform/opencti-graphql/src/schema/stixMetaObject.ts @@ -39,9 +39,9 @@ const stixMetaObjectsAttributes: { [k: string]: Array } = { { name: 'i_created_at_year', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, created, modified, - { name: 'definition_type', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'definition', type: 'string', mandatoryType: 'internal', multiple: false, upsert: true }, - { name: 'x_opencti_order', type: 'numeric', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'definition_type', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'definition', type: 'string', mandatoryType: 'external', multiple: false, upsert: true }, + { name: 'x_opencti_order', type: 'numeric', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'x_opencti_color', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, ], [ENTITY_TYPE_LABEL]: [ @@ -57,7 +57,7 @@ const stixMetaObjectsAttributes: { [k: string]: Array } = { { name: 'i_created_at_year', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, created, modified, - { name: 'value', type: 'string', mandatoryType: 'internal', multiple: false, upsert: true }, + { name: 'value', type: 'string', mandatoryType: 'external', multiple: false, upsert: true }, { name: 'color', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, ], [ENTITY_TYPE_EXTERNAL_REFERENCE]: [ @@ -73,7 +73,7 @@ const stixMetaObjectsAttributes: { [k: string]: Array } = { { name: 'i_created_at_year', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, created, modified, - { name: 'source_name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, + { name: 'source_name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, { name: 'description', type: 'string', mandatoryType: 'no', multiple: false, upsert: true }, { name: 'url', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, { name: 'hash', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, @@ -92,9 +92,9 @@ const stixMetaObjectsAttributes: { [k: string]: Array } = { { name: 'i_created_at_year', type: 'string', mandatoryType: 'no', multiple: false, upsert: false }, created, modified, - { name: 'kill_chain_name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'phase_name', type: 'string', mandatoryType: 'internal', multiple: false, upsert: false }, - { name: 'x_opencti_order', type: 'numeric', mandatoryType: 'internal', multiple: false, upsert: true }, + { name: 'kill_chain_name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'phase_name', type: 'string', mandatoryType: 'external', multiple: false, upsert: false }, + { name: 'x_opencti_order', type: 'numeric', mandatoryType: 'external', multiple: false, upsert: true }, ], }; R.forEachObjIndexed((value, key) => schemaAttributesDefinition.registerAttributes(key as string, value), stixMetaObjectsAttributes); diff --git a/opencti-platform/opencti-graphql/src/schema/stixSightingRelationship.ts b/opencti-platform/opencti-graphql/src/schema/stixSightingRelationship.ts index f3ffa7f4f0a6..355fbbb2318d 100644 --- a/opencti-platform/opencti-graphql/src/schema/stixSightingRelationship.ts +++ b/opencti-platform/opencti-graphql/src/schema/stixSightingRelationship.ts @@ -43,7 +43,7 @@ export const stixSightingRelationshipsAttributes: { [k: string]: Array { - let entitySettingIdCoreRelationship; + let entitySettingIdNote; it('should init entity settings', async () => { const context = executionContext('test'); await initCreateEntitySettings(context); const queryResult = await queryAsAdmin({ query: LIST_QUERY }); expect(queryResult.data.entitySettings.edges.length).toEqual(34); - const entitySettingDataComponent = queryResult.data.entitySettings.edges.filter((entitySetting) => entitySetting.node.target_type === ABSTRACT_STIX_CORE_RELATIONSHIP)[0]; - expect(entitySettingDataComponent.platform_entity_files_ref).toSatisfy((s) => s === null || s === undefined); - entitySettingIdCoreRelationship = entitySettingDataComponent.node.id; + const entitySettingNote = queryResult.data.entitySettings.edges.filter((entitySetting) => entitySetting.node.target_type === ENTITY_TYPE_CONTAINER_NOTE)[0]; + expect(entitySettingNote.platform_entity_files_ref).toBeFalsy(); + entitySettingIdNote = entitySettingNote.node.id; }); it('should retrieve entity setting by id', async () => { - const queryResult = await queryAsAdmin({ query: READ_QUERY_BY_ID, variables: { id: entitySettingIdCoreRelationship } }); - expect(queryResult.data.entitySetting.target_type).toEqual(ABSTRACT_STIX_CORE_RELATIONSHIP); - expect(queryResult.data.entitySetting.platform_entity_files_ref).toSatisfy((s) => s === null || s === undefined); + const queryResult = await queryAsAdmin({ query: READ_QUERY_BY_ID, variables: { id: entitySettingIdNote } }); + expect(queryResult.data.entitySetting.target_type).toEqual(ENTITY_TYPE_CONTAINER_NOTE); + expect(queryResult.data.entitySetting.platform_entity_files_ref).toBeFalsy(); expect(queryResult.data.entitySetting.platform_hidden_type).toBeFalsy(); + expect(queryResult.data.entitySetting.enforce_reference).toSatisfy((s) => s === null || s === undefined); }); it('should retrieve entity setting by target type', async () => { - const queryResult = await queryAsAdmin({ query: READ_QUERY_BY_TARGET_TYPE, variables: { targetType: ABSTRACT_STIX_CORE_RELATIONSHIP } }); - expect(queryResult.data.entitySettingByType.target_type).toEqual(ABSTRACT_STIX_CORE_RELATIONSHIP); - expect(queryResult.data.entitySettingByType.platform_entity_files_ref).toSatisfy((s) => s === null || s === undefined); + const queryResult = await queryAsAdmin({ query: READ_QUERY_BY_TARGET_TYPE, variables: { targetType: ENTITY_TYPE_CONTAINER_NOTE } }); + expect(queryResult.data.entitySettingByType.target_type).toEqual(ENTITY_TYPE_CONTAINER_NOTE); + expect(queryResult.data.entitySettingByType.platform_entity_files_ref).toBeFalsy(); expect(queryResult.data.entitySettingByType.platform_hidden_type).toBeFalsy(); + expect(queryResult.data.entitySettingByType.enforce_reference).toSatisfy((s) => s === null || s === undefined); }); it('should update entity settings by ids - valid', async () => { const queryResult = await queryAsAdmin({ query: UPDATE_QUERY, - variables: { ids: [entitySettingIdCoreRelationship], input: { key: 'enforce_reference', value: ['true'] } }, + variables: { ids: [entitySettingIdNote], input: { key: 'platform_entity_files_ref', value: ['true'] } }, }); - const entityTypeDataComponent = queryResult.data.entitySettingsFieldPatch.filter((entityType) => entityType.target_type === ABSTRACT_STIX_CORE_RELATIONSHIP)[0]; - expect(entityTypeDataComponent.enforce_reference).toBeTruthy(); + const entityTypeDataComponent = queryResult.data.entitySettingsFieldPatch.filter((entityType) => entityType.target_type === ENTITY_TYPE_CONTAINER_NOTE)[0]; + expect(entityTypeDataComponent.platform_entity_files_ref).toBeTruthy(); // Clean await queryAsAdmin({ query: UPDATE_QUERY, - variables: { ids: [entitySettingIdCoreRelationship], input: { key: 'enforce_reference', value: ['false'] } }, + variables: { ids: [entitySettingIdNote], input: { key: 'platform_entity_files_ref', value: ['false'] } }, }); }); it('should update entity settings by ids - invalid option setting', async () => { const queryResult = await queryAsAdmin({ query: UPDATE_QUERY, - variables: { ids: [entitySettingIdCoreRelationship], input: { key: 'platform_entity_files_ref', value: ['true'] } }, + variables: { ids: [entitySettingIdNote], input: { key: 'enforce_reference', value: ['true'] } }, }); expect(queryResult.errors.length > 0).toBeTruthy(); }); @@ -102,7 +104,7 @@ describe('EntitySetting resolver standard behavior', () => { const attributesConfiguration = JSON.stringify([{ name: 'newfield', mandatory: true }]); const queryResult = await queryAsAdmin({ query: UPDATE_QUERY, - variables: { ids: [entitySettingIdCoreRelationship], input: { attributes_configuration: attributesConfiguration } }, + variables: { ids: [entitySettingIdNote], input: { attributes_configuration: attributesConfiguration } }, }); expect(queryResult.errors.length > 0).toBeTruthy(); }); diff --git a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/subType-test.js b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/subType-test.js index ae8f1efb6df7..8f90da4e10fd 100644 --- a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/subType-test.js +++ b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/subType-test.js @@ -19,10 +19,11 @@ const LIST_QUERY = gql` const SUB_TYPE_ATTRIBUTES_QUERY = gql` query subType($id: String!) { subType(id: $id) { - mandatoryAttributes { - name - builtIn - mandatory + settings { + mandatoryDefinitions { + name + } + mandatoryAttributes } } } @@ -41,8 +42,11 @@ describe('SubType resolver standard behavior', () => { }); it('should retrieve mandatory attribute for an entity', async () => { const queryResult = await queryAsAdmin({ query: SUB_TYPE_ATTRIBUTES_QUERY, variables: { id: ENTITY_TYPE_DATA_COMPONENT } }); - const attributes = queryResult.data.subType.mandatoryAttributes; - expect(attributes.map((node) => node.name).includes('name')).toBeTruthy(); - expect(attributes.length).toEqual(4); + const { mandatoryDefinitions } = queryResult.data.subType.settings; + expect(mandatoryDefinitions.map((attr) => attr.name).includes('name')).toBeTruthy(); + expect(mandatoryDefinitions.length).toEqual(4); + const { mandatoryAttributes } = queryResult.data.subType.settings; + expect(mandatoryAttributes.includes('name')).toBeTruthy(); + expect(mandatoryAttributes.length).toEqual(1); }); });