From 9d93fd47beb97994e5d683a4cbf6e9bc62bf525b Mon Sep 17 00:00:00 2001 From: Landry Trebon <33682259+lndrtrbn@users.noreply.github.com> Date: Fri, 19 Apr 2024 08:54:39 +0200 Subject: [PATCH] [frontend/backend] Public dashboards (#4903) Co-authored-by: marie flores --- .../opencti-front/lang/back/de.json | 1 + .../opencti-front/lang/back/en.json | 1 + .../opencti-front/lang/back/es.json | 1 + .../opencti-front/lang/back/fr.json | 1 + .../opencti-front/lang/back/ja.json | 1 + .../opencti-front/lang/back/zh.json | 1 + .../opencti-front/lang/front/de.json | 25 +- .../opencti-front/lang/front/en.json | 23 +- .../opencti-front/lang/front/es.json | 23 +- .../opencti-front/lang/front/fr.json | 25 +- .../opencti-front/lang/front/ja.json | 23 +- .../opencti-front/lang/front/zh.json | 25 +- .../script/sort-translation-files.js | 2 +- .../dashboard/WidgetDistributionList.tsx | 5 +- .../dashboard/WidgetListCoreObjects.tsx | 15 +- .../dashboard/WidgetListRelationships.tsx | 5 +- .../opencti-front/src/private/Root.tsx | 6 + .../common/form/MarkingsSelectField.tsx | 97 ++++ .../common/form/ObjectMarkingField.tsx | 13 +- .../data/csvMapper/CsvMapperTestDialog.tsx | 2 +- .../private/components/settings/Policies.tsx | 49 +- .../components/workspaces/WorkspaceHeader.jsx | 2 +- .../workspaces/WorkspaceShareButton.tsx | 35 +- .../workspaces/WorkspaceShareForm.tsx | 25 +- .../workspaces/WorkspaceShareList.tsx | 190 ++++--- .../opencti-front/src/public/PublicRoot.tsx | 4 +- .../src/public/components/PublicDashboard.tsx | 7 + .../public/components/PublicDataSharing.tsx | 2 - .../PublicStixCoreObjectsDistributionList.tsx | 2 +- .../PublicStixCoreObjectsList.tsx | 1 + .../PublicStixCoreObjectsTimeline.tsx | 3 - ...ublicStixRelationshipsDistributionList.tsx | 2 +- .../PublicStixRelationshipsList.tsx | 1 + .../PublicStixRelationshipsTimeline.tsx | 9 - .../dashboard/usePublicDashboardWidgets.tsx | 27 +- .../src/schema/relay.schema.graphql | 4 + .../opencti-graphql/config/default.json | 2 +- .../config/schema/opencti.graphql | 1 + .../src/database/data-initialization.js | 8 +- .../opencti-graphql/src/domain/settings.js | 25 +- .../src/domain/stixCoreRelationship.js | 2 +- .../opencti-graphql/src/generated/graphql.ts | 6 + .../src/manager/cacheManager.ts | 1 + ...4893567321-capability-publish-knowledge.js | 16 - ...estigations-and-dashboards-capabilities.js | 37 ++ ...-add-platform-data-sharing-max-markings.js | 18 + .../internalObject-registrationAttributes.ts | 1 + .../publicDashboard-converter.ts | 1 + .../publicDashboard/publicDashboard-domain.ts | 51 +- .../publicDashboard/publicDashboard-types.ts | 4 + .../publicDashboard/publicDashboard-utils.ts | 70 ++- .../publicDashboard/publicDashboard.graphql | 3 + .../publicDashboard/publicDashboard.ts | 11 +- .../opencti-graphql/src/resolvers/settings.js | 2 + .../domain/publicDashboard-utils-test.ts | 149 ++++++ .../02-resolvers/publicDashboard-test.js | 501 +++++++++++++++++- .../02-resolvers/settings-test.js | 107 +++- .../tests/03-streams/00-Raw/raw-test.js | 10 +- .../opencti-graphql/tests/utils/testQuery.ts | 2 +- 59 files changed, 1470 insertions(+), 216 deletions(-) create mode 100644 opencti-platform/opencti-front/src/private/components/common/form/MarkingsSelectField.tsx delete mode 100644 opencti-platform/opencti-graphql/src/migrations/1704893567321-capability-publish-knowledge.js create mode 100644 opencti-platform/opencti-graphql/src/migrations/1713449762000-investigations-and-dashboards-capabilities.js create mode 100644 opencti-platform/opencti-graphql/src/migrations/1713449762001-add-platform-data-sharing-max-markings.js create mode 100644 opencti-platform/opencti-graphql/tests/01-unit/domain/publicDashboard-utils-test.ts diff --git a/opencti-platform/opencti-front/lang/back/de.json b/opencti-platform/opencti-front/lang/back/de.json index ce9ce9f178b9..c0bb8561ae4e 100644 --- a/opencti-platform/opencti-front/lang/back/de.json +++ b/opencti-platform/opencti-front/lang/back/de.json @@ -454,6 +454,7 @@ "Phase name": "Name der Phase", "PID": "PID", "Platform creation date": "Erstellungsdatum der Plattform", + "Platform data sharing max markings": "Plattform für die gemeinsame Nutzung von Daten Maximalmarkierungen", "Platform email": "Plattform E-Mail", "Platform entity files ref": "Plattform-Entity-Dateien ref", "Platform favicon": "Plattform Favicon", diff --git a/opencti-platform/opencti-front/lang/back/en.json b/opencti-platform/opencti-front/lang/back/en.json index 97bfdae727c6..06e81b9c4281 100644 --- a/opencti-platform/opencti-front/lang/back/en.json +++ b/opencti-platform/opencti-front/lang/back/en.json @@ -454,6 +454,7 @@ "Phase name": "Phase name", "PID": "PID", "Platform creation date": "Platform creation date", + "Platform data sharing max markings": "Platform data sharing max markings", "Platform email": "Platform email", "Platform entity files ref": "Platform entity files ref", "Platform favicon": "Platform favicon", diff --git a/opencti-platform/opencti-front/lang/back/es.json b/opencti-platform/opencti-front/lang/back/es.json index 9dd6f73dde96..e358f1045d5c 100644 --- a/opencti-platform/opencti-front/lang/back/es.json +++ b/opencti-platform/opencti-front/lang/back/es.json @@ -454,6 +454,7 @@ "Phase name": "Nombre de fase", "PID": "PID", "Platform creation date": "Fecha de creación de la plataforma", + "Platform data sharing max markings": "Plataforma de intercambio de datos Marcas máximas", "Platform email": "Plataforma email", "Platform entity files ref": "Plataforma de archivos de entidad ref", "Platform favicon": "Favicon de la plataforma", diff --git a/opencti-platform/opencti-front/lang/back/fr.json b/opencti-platform/opencti-front/lang/back/fr.json index 081c99321086..276b8826d952 100644 --- a/opencti-platform/opencti-front/lang/back/fr.json +++ b/opencti-platform/opencti-front/lang/back/fr.json @@ -454,6 +454,7 @@ "Phase name": "Nom de la phase", "PID": "PID", "Platform creation date": "Date de création de la plate-forme", + "Platform data sharing max markings": "Marquages maximum pour partage des données de la plateforme", "Platform email": "Plateforme email", "Platform entity files ref": "Fichiers d'entité de la plate-forme ref", "Platform favicon": "Favicon de la plate-forme", diff --git a/opencti-platform/opencti-front/lang/back/ja.json b/opencti-platform/opencti-front/lang/back/ja.json index 0e2c146cf643..ff87f3fe7a2a 100644 --- a/opencti-platform/opencti-front/lang/back/ja.json +++ b/opencti-platform/opencti-front/lang/back/ja.json @@ -454,6 +454,7 @@ "Phase name": "終了日", "PID": "PID", "Platform creation date": "プラットフォーム作成日", + "Platform data sharing max markings": "プラットフォームのデータ共有 最大マーキング", "Platform email": "プラットフォームメール", "Platform entity files ref": "プラットフォーム非表示タイプ", "Platform favicon": "プラットフォームのファビコン", diff --git a/opencti-platform/opencti-front/lang/back/zh.json b/opencti-platform/opencti-front/lang/back/zh.json index 190d9462a50d..5a2fd41190e4 100644 --- a/opencti-platform/opencti-front/lang/back/zh.json +++ b/opencti-platform/opencti-front/lang/back/zh.json @@ -454,6 +454,7 @@ "Phase name": "阶段名称", "PID": "PID", "Platform creation date": "平台创建日期", + "Platform data sharing max markings": "平台数据共享最大标记", "Platform email": "平台电子邮件", "Platform entity files ref": "平台实体文件参考", "Platform favicon": "平台图标", diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index fbfbff7fc873..7618194fdc6f 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -185,6 +185,7 @@ "Attributes": "Attribute", "Attribution": "Zuschreibung", "Audit logs": "Audit-Protokolle", + "Audits are not supported in public dashboards": "Audits werden in öffentlichen Dashboards nicht unterstützt", "Audits list": "Liste der Audits", "Authentication": "Authentifizierung", "Authentication strategies": "Authentifizierungsstrategien", @@ -220,6 +221,7 @@ "Belonging to this organization": "Zugehörig zu dieser Organisation", "Biographic Information": "Biographische Informationen", "Biographics": "Biografische Daten", + "Bookmarks are not supported in public dashboards": "Lesezeichen werden in öffentlichen Dashboards nicht unterstützt", "Browse the link": "Den Link durchsuchen", "Bulk search": "Bulk-Suche", "Bundle content": "Bundle-Inhalt", @@ -524,6 +526,7 @@ "Data import": "Datenimport", "Data import and analyst workbenches": "Datenimport und Analysten-Workbenches", "Data sharing": "Gemeinsame Nutzung von Daten", + "Data sharing configuration": "Konfiguration der gemeinsamen Datennutzung", "Data source": "Datenquelle", "Data sources": "Datenquellen", "Data type": "Datentyp", @@ -600,6 +603,7 @@ "Disable two-factor authentication": "Zwei-Faktor-Authentifizierung deaktivieren", "Disable vertical tree mode": "Vertikalen Baummodus deaktivieren", "Disabled": "Deaktiviert", + "Disabled dashboard...": "Deaktiviertes öffentliches Dashboard kann nicht eingesehen werden. Sie können diesen Booleschen Wert jederzeit ändern.", "Disconnected": "Getrennt", "Dismiss": "Verlassen Sie", "Dismissible": "Unzulässig", @@ -787,6 +791,7 @@ "Enable Enterprise Edition": "Enterprise Edition einschalten", "Enable forces": "Kräfte einschalten", "Enable horizontal tree mode": "Horizontalen Baummodus einschalten", + "Enable public dashboard": "Öffentliches Dashboard einschalten", "Enable this feature": "Aktivieren Sie diese Funktion", "Enable tree mode": "Baummodus einschalten", "Enable two-factor authentication": "Aktivieren Sie die Zwei-Faktor-Authentifizierung", @@ -1147,6 +1152,7 @@ "I have read and agree to the": "Ich habe die folgenden Bedingungen gelesen und stimme ihnen zu", "I have read and comply with the above statement": "Ich habe die obige Erklärung gelesen und erkläre mich damit einverstanden", "I would like to use a EE feature ...": "Ich würde gerne eine EE-Funktion ({feature}) verwenden, habe aber keine EE aktiviert.\nIch würde gerne mit Ihnen über die Aktivierung von EE diskutieren.", + "ID of your public dashboard": "ID Ihres öffentlichen Dashboards (wird in der URL verwendet)", "Identities": "Identitäten", "Identity": "Identität", "ideology": "Ideologie/Glaube", @@ -1360,7 +1366,6 @@ "Limits": "Begrenzungen", "Line chart": "Liniendiagramm", "Lines view": "Ansicht Zeilen", - "Link created": "Link erstellt", "Linked entities": "Verknüpfte Entitäten", "Linked entity": "Verknüpfte Entität", "Linked knowledge": "Verknüpftes Wissen", @@ -1450,8 +1455,10 @@ "Max file size (in MB)": "Maximale Dateigröße (in MB)", "Max level markings": "Max Level Markierungen", "Max marking definition level": "Maximale Markierungsdefinitionsebene", + "Max marking definitions override...": "Die von Ihnen festgelegten maximalen Markierungsdefinitionen sind höher als die vom Administrator konfigurierten. Sie werden durch ersetzt:", "Max retention": "Maximale Aufbewahrung", "Maximum": "Maximum", + "Maximum marking definition allowed to be shared": "Maximale Markierungsdefinition, die freigegeben werden darf", "Maximum retention days": "Maximale Aufbewahrungstage", "Med": "Mittel", "MEDIUM": "MITTEL", @@ -1579,6 +1586,8 @@ "Not empty": "Nicht leer", "Not ends with": "Nicht endet mit", "Not equals": "Nicht gleich", + "Not implemented yet": "Noch nicht implementiert", + "Not shareable": "Nicht gemeinsam nutzbar", "Not Specified": "Nicht spezifiziert", "Not starts with": "Nicht beginnt mit", "not_contains": "enthält nicht", @@ -1803,7 +1812,8 @@ "Public collection": "Öffentliche Sammlung", "Public CSV feeds": "Öffentliche CSV-Feeds", "Public dashboard": "Öffentliches Dashboard", - "Public dashboard ID": "Öffentliche Dashboard-ID", + "Public dashboard created the": "Das öffentliche Dashboard hat die", + "Public dashboard URI KEY": "Öffentliches Dashboard URI KEY", "Public dashboards": "Öffentliche Dashboards", "Public feed": "Öffentlicher Feed", "Public stream": "Öffentlicher Stream", @@ -2145,7 +2155,6 @@ "source": "Quelle", "Source name": "Name der Quelle", "source_reliability": "Glaubwürdigkeit des Autors", - "Specify the ID of your public dashboard": "Geben Sie die ID Ihres öffentlichen Dashboards an", "spl": "Splunk SPL", "sponsor": "sponsor", "Stable score": "Stabile Punktestand", @@ -2299,6 +2308,7 @@ "The operators and modes are restricted for these filters.": "Die Operatoren und Modi sind für diese Filter eingeschränkt.", "The opinions has no value defined in your vocabulary. Please add them first to be able to add opinions.": "Die Meinungen haben keinen in Ihrem Wortschatz definierten Wert. Bitte fügen Sie sie erst hinzu, um Meinungen hinzufügen zu können.", "The password has been updated": "Das Passwort wurde aktualisiert", + "The public dashboard is disabled": "Das öffentliche Dashboard ist deaktiviert", "The relations attached to selected entities will be copied to the merged entity.": "Die Beziehungen, die mit den ausgewählten Entitäten verbunden sind, werden in die zusammengeführte Entität kopiert.", "The rule has been disabled, clean-up launched...": "Die Regel wurde deaktiviert, Bereinigung eingeleitet...", "The rule has been enabled, rescan of platform data launched...": "Die Regel wurde aktiviert, erneuter Scan der Plattformdaten gestartet...", @@ -2698,16 +2708,15 @@ "You need a confidence level to edit objects in the platform.": "Du benötigst ein Vertrauensniveau, um Objekte auf der Plattform zu bearbeiten.", "You need to activate a two-factor authentication. Please type the code generated in your application.": "Sie müssen eine Zwei-Faktor-Authentifizierung aktivieren. Bitte geben Sie den in Ihrer Anwendung generierten Code ein.", "You need to activate OpenCTI enterprise edition to use this feature.": "Um diese Funktion nutzen zu können, müssen Sie die OpenCTI Enterprise Edition aktivieren.", - "You need to validate your two-factor authentication. Please type the code generated in your application.": "Sie müssen Ihre Zwei-Faktor-Authentifizierung validieren. Bitte geben Sie den in Ihrer Anwendung generierten Code ein.", + "You need to validate your two-factor authentication. Please type the code generated in your application": "Sie müssen Ihre Zwei-Faktor-Authentifizierung validieren. Bitte geben Sie den in Ihrer Anwendung generierten Code ein.", + "You see only marking definitions that can be shared (defined by the admin)": "Sie sehen nur Markierungsdefinitionen, die freigegeben werden können (vom Administrator definiert)", "You will be automatically logged out at end of the timer.": "Sie werden nach Ablauf der Zeit automatisch abgemeldet.", - "You will find here the result in JSON format": "Hier finden Sie das Ergebnis im JSON-Format", "You will find here the result in JSON format.": "Hier finden Sie das Ergebnis im JSON-Format.", "You're targeting more than 1000 entities with this background task, be sure of what you're doing!": "Sie zielen auf mehr als 1000 Entitäten mit dieser Hintergrundaufgabe, seien Sie sicher, was Sie tun!", "Your account has expired. If you would like to reactivate your account, please contact your administrator.": "Ihr Konto ist abgelaufen. Wenn Sie Ihr Konto reaktivieren möchten, wenden Sie sich bitte an Ihren Administrator.", "Your confidence level is insufficient to edit this object.": "Dein Vertrauensniveau ist unzureichend, um dieses Objekt zu bearbeiten.", - "Your testing file (CSV only, max 1MB)": "Ihre Testdatei (nur CSV, max. 1MB)", "Your url contains filters in a deprecated format, parameters stored in the url have been removed.": "Ihre Url enthält Filter in einem veralteten Format, die in der Url gespeicherten Parameter wurden entfernt.", - "Zoom": "Zoom", + "Zoom": "Vergrößern", "Zoom in": "Vergrößern", "Zoom out": "Verkleinern" -} \ No newline at end of file +} diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index db0da0e05258..d1c291222e20 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -185,6 +185,7 @@ "Attributes": "Attributes", "Attribution": "Attribution", "Audit logs": "Audit logs", + "Audits are not supported in public dashboards": "Audits are not supported in public dashboards", "Audits list": "Audits list", "Authentication": "Authentication", "Authentication strategies": "Authentication strategies", @@ -220,6 +221,7 @@ "Belonging to this organization": "Belonging to this organization", "Biographic Information": "Biographic Information", "Biographics": "Biographics", + "Bookmarks are not supported in public dashboards": "Bookmarks are not supported in public dashboards", "Browse the link": "Browse the link", "Bulk search": "Bulk search", "Bundle content": "Bundle content", @@ -524,6 +526,7 @@ "Data import": "Data import", "Data import and analyst workbenches": "Data import and analyst workbenches", "Data sharing": "Data sharing", + "Data sharing configuration": "Data sharing configuration", "Data source": "Data source", "Data sources": "Data sources", "Data type": "Data type", @@ -600,6 +603,7 @@ "Disable two-factor authentication": "Disable two-factor authentication", "Disable vertical tree mode": "Disable vertical tree mode", "Disabled": "Disabled", + "Disabled dashboard...": "Disabled public dashboard cannot be viewed. You can change this boolean at anytime.", "Disconnected": "Disconnected", "Dismiss": "Dismiss", "Dismissible": "Dismissible", @@ -787,6 +791,7 @@ "Enable Enterprise Edition": "Enable Enterprise Edition", "Enable forces": "Enable forces", "Enable horizontal tree mode": "Enable horizontal tree mode", + "Enable public dashboard": "Enable public dashboard", "Enable this feature": "Enable this feature", "Enable tree mode": "Enable tree mode", "Enable two-factor authentication": "Enable two-factor authentication", @@ -1147,6 +1152,7 @@ "I have read and agree to the": "I have read and agree to the", "I have read and comply with the above statement": "I have read and comply with the above statement", "I would like to use a EE feature ...": "I would like to use a EE feature ({feature}) but I don't have EE activated.\nI would like to discuss with you about activating EE.", + "ID of your public dashboard": "ID of your public dashboard (used in the URL)", "Identities": "Identities", "Identity": "Identity", "ideology": "Ideology/Belief", @@ -1360,7 +1366,6 @@ "Limits": "Limits", "Line chart": "Line chart", "Lines view": "Lines view", - "Link created": "Link created", "Linked entities": "Linked entities", "Linked entity": "Linked entity", "Linked knowledge": "Linked knowledge", @@ -1450,8 +1455,10 @@ "Max file size (in MB)": "Max file size (in MB)", "Max level markings": "Max level markings", "Max marking definition level": "Max marking definition level", + "Max marking definitions override...": "Max marking definitions you defined are higher than the ones configured by admin. They will be replaced by:", "Max retention": "Max retention", "Maximum": "Maximum", + "Maximum marking definition allowed to be shared": "Maximum marking definition allowed to be shared", "Maximum retention days": "Maximum retention days", "Med": "Med", "MEDIUM": "MEDIUM", @@ -1579,6 +1586,8 @@ "Not empty": "Not empty", "Not ends with": "Not ends with", "Not equals": "Not equals", + "Not implemented yet": "Not implemented yet", + "Not shareable": "Not shareable", "Not Specified": "Not Specified", "Not starts with": "Not starts with", "not_contains": "not contains", @@ -1803,7 +1812,8 @@ "Public collection": "Public collection", "Public CSV feeds": "Public CSV feeds", "Public dashboard": "Public dashboard", - "Public dashboard ID": "Public dashboard ID", + "Public dashboard created the": "Public dashboard created the", + "Public dashboard URI KEY": "Public dashboard URI KEY", "Public dashboards": "Public dashboards", "Public feed": "Public feed", "Public stream": "Public stream", @@ -2145,7 +2155,6 @@ "source": "Source", "Source name": "Source name", "source_reliability": "Reliability of author", - "Specify the ID of your public dashboard": "Specify the ID of your public dashboard", "spl": "Splunk SPL", "sponsor": "sponsor", "Stable score": "Stable score", @@ -2299,6 +2308,7 @@ "The operators and modes are restricted for these filters.": "The operators and modes are restricted for these filters.", "The opinions has no value defined in your vocabulary. Please add them first to be able to add opinions.": "The opinions has no value defined in your vocabulary. Please add them first to be able to add opinions.", "The password has been updated": "The password has been updated", + "The public dashboard is disabled": "The public dashboard is disabled", "The relations attached to selected entities will be copied to the merged entity.": "The relations attached to selected entities will be copied to the merged entity.", "The rule has been disabled, clean-up launched...": "The rule has been disabled, clean-up launched...", "The rule has been enabled, rescan of platform data launched...": "The rule has been enabled, rescan of platform data launched...", @@ -2698,16 +2708,15 @@ "You need a confidence level to edit objects in the platform.": "You need a confidence level to edit objects in the platform.", "You need to activate a two-factor authentication. Please type the code generated in your application.": "You need to activate a two-factor authentication. Please type the code generated in your application.", "You need to activate OpenCTI enterprise edition to use this feature.": "You need to activate OpenCTI enterprise edition to use this feature.", - "You need to validate your two-factor authentication. Please type the code generated in your application.": "You need to validate your two-factor authentication. Please type the code generated in your application.", + "You need to validate your two-factor authentication. Please type the code generated in your application": "You need to validate your two-factor authentication. Please type the code generated in your application.", + "You see only marking definitions that can be shared (defined by the admin)": "You see only marking definitions that can be shared (defined by the admin)", "You will be automatically logged out at end of the timer.": "You will be automatically logged out at end of the timer.", - "You will find here the result in JSON format": "You will find here the result in JSON format", "You will find here the result in JSON format.": "You will find here the result in JSON format.", "You're targeting more than 1000 entities with this background task, be sure of what you're doing!": "You're targeting more than 1000 entities with this background task, be sure of what you're doing!", "Your account has expired. If you would like to reactivate your account, please contact your administrator.": "Your account has expired. If you would like to reactivate your account, please contact your administrator.", "Your confidence level is insufficient to edit this object.": "Your confidence level is insufficient to edit this object.", - "Your testing file (CSV only, max 1MB)": "Your testing file (CSV only, max 1MB)", "Your url contains filters in a deprecated format, parameters stored in the url have been removed.": "Your url contains filters in a deprecated format, parameters stored in the url have been removed.", "Zoom": "Zoom", "Zoom in": "Zoom in", "Zoom out": "Zoom out" -} \ No newline at end of file +} diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index d5ae71fd64d4..c5b03266c3cb 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -185,6 +185,7 @@ "Attributes": "Atributos", "Attribution": "Atribución", "Audit logs": "Registros de auditoría", + "Audits are not supported in public dashboards": "Los cuadros de mando públicos no admiten auditorías", "Audits list": "Lista de auditorías", "Authentication": "Autenticación", "Authentication strategies": "Estrategias de autenticación", @@ -220,6 +221,7 @@ "Belonging to this organization": "Perteneciente a esta organización", "Biographic Information": "Información biográfica", "Biographics": "Biografías", + "Bookmarks are not supported in public dashboards": "No se admiten marcadores en los cuadros de mando públicos", "Browse the link": "Visitar el enlace", "Bulk search": "Búsqueda masiva", "Bundle content": "Empaquetar contenido", @@ -524,6 +526,7 @@ "Data import": "Importación de datos", "Data import and analyst workbenches": "Bancos de trabajo de importación de datos y de análisis", "Data sharing": "Compartición de datos", + "Data sharing configuration": "Configuración de compartición de datos", "Data source": "Fuente de datos", "Data sources": "Fuentes de datos", "Data type": "Tipo de datos", @@ -600,6 +603,7 @@ "Disable two-factor authentication": "Desactivar la autenticación en dos pasos", "Disable vertical tree mode": "Desactivar el modo de árbol vertical", "Disabled": "Desactovado", + "Disabled dashboard...": "El tablero público deshabilitado no se puede ver. Puede cambiar este booleano en cualquier momento.", "Disconnected": "Desconectado", "Dismiss": "Despedir", "Dismissible": "Despedir", @@ -787,6 +791,7 @@ "Enable Enterprise Edition": "Habilitar Enterprise Edition", "Enable forces": "Activar las fuerzas", "Enable horizontal tree mode": "Activar el modo de árbol horizontal", + "Enable public dashboard": "Activar el panel de control público", "Enable this feature": "Activar esta función", "Enable tree mode": "Activar el modo árbol", "Enable two-factor authentication": "Activar la autenticación en dos pasos", @@ -1147,6 +1152,7 @@ "I have read and agree to the": "He leído y acepto el", "I have read and comply with the above statement": "He leído y acepto la declaración anterior", "I would like to use a EE feature ...": "Me gustaría utilizar una función EE ({feature}) pero no tengo EE activado.\nMe gustaría hablar con usted sobre la activación de EE.", + "ID of your public dashboard": "ID de su panel de control público (utilizado en la URL)", "Identities": "Identidades", "Identity": "Identidad", "ideology": "Ideología o convicción", @@ -1360,7 +1366,6 @@ "Limits": "Límites", "Line chart": "Gráfico de líneas", "Lines view": "Vista por líneas", - "Link created": "Enlace creado", "Linked entities": "Entidades relacionadas", "Linked entity": "Entidad relacionada", "Linked knowledge": "Conocimiento relacionado", @@ -1450,8 +1455,10 @@ "Max file size (in MB)": "Tamaño máximo del archivo (en MB)", "Max level markings": "Marcas de nivel máximo", "Max marking definition level": "Máximo nivel de categoría de clasificación", + "Max marking definitions override...": "Las definiciones de marcado máximo que ha definido son superiores a las configuradas por el administrador. Serán sustituidas por:", "Max retention": "Retención máxima", "Maximum": "Máximo", + "Maximum marking definition allowed to be shared": "Máxima definición de marcado que se permite compartir", "Maximum retention days": "Días de retención máxima", "Med": "Mediano", "MEDIUM": "MEDIO", @@ -1579,6 +1586,8 @@ "Not empty": "No vacío", "Not ends with": "No termina con", "Not equals": "No igual", + "Not implemented yet": "Aún no implementado", + "Not shareable": "No compartible", "Not Specified": "No especificado", "Not starts with": "No comienza con", "not_contains": "no contiene", @@ -1803,7 +1812,8 @@ "Public collection": "Colección pública", "Public CSV feeds": "Fuentes CSV públicas", "Public dashboard": "Cuadro de mandos público", - "Public dashboard ID": "ID del panel público", + "Public dashboard created the": "Cuadro de mandos público creado el", + "Public dashboard URI KEY": "Cuadro de mandos público URI KEY", "Public dashboards": "Cuadros de mando públicos", "Public feed": "Fuente pública", "Public stream": "Transmisión pública", @@ -2145,7 +2155,6 @@ "Source": "Fuente", "Source name": "Nombre de la fuente", "source_reliability": "Fiabilidad del autor", - "Specify the ID of your public dashboard": "Especifique el ID de su panel de control público", "spl": "Splunk SPL", "sponsor": "Patrocinador", "Stable score": "Puntuación estable", @@ -2299,6 +2308,7 @@ "The operators and modes are restricted for these filters.": "Los operadores y modos están restringidos para estos filtros.", "The opinions has no value defined in your vocabulary. Please add them first to be able to add opinions.": "Las opiniones no tienen ningún valor definido en su vocabulario. Por favor, añádelas primero para poder añadir opiniones.", "The password has been updated": "La contraseña se ha modificado", + "The public dashboard is disabled": "El panel público está desactivado", "The relations attached to selected entities will be copied to the merged entity.": "Las relaciones adjuntadas a las entidades seleccionadas serán copiadas a la entidad fusionada.", "The rule has been disabled, clean-up launched...": "La regla ha sido desactivada, limpieza lanzada...", "The rule has been enabled, rescan of platform data launched...": "La regla ha sido activada, reescaneo de los datos de la plataforma lanzado...", @@ -2698,16 +2708,15 @@ "You need a confidence level to edit objects in the platform.": "Necesitas un nivel de confianza para editar objetos en la plataforma.", "You need to activate a two-factor authentication. Please type the code generated in your application.": "Necesitas activar una autenticación de dos factores. Por favor, escriba el código generado en su aplicación.", "You need to activate OpenCTI enterprise edition to use this feature.": "Es necesario activar la edición empresarial de OpenCTI para utilizar esta función.", - "You need to validate your two-factor authentication. Please type the code generated in your application.": "Debe validar su autenticación de dos factores. Por favor, escriba el código generado en su aplicación.", + "You need to validate your two-factor authentication. Please type the code generated in your application": "Debe validar su autenticación de dos factores. Por favor, escriba el código generado en su aplicación.", + "You see only marking definitions that can be shared (defined by the admin)": "Sólo se ven las definiciones de marcado que se pueden compartir (definidas por el administrador)", "You will be automatically logged out at end of the timer.": "Se cerrará la sesión automáticamente al final del temporizador.", - "You will find here the result in JSON format": "Aquí encontrará el resultado en formato JSON", "You will find here the result in JSON format.": "Aquí encontrará el resultado en formato JSON.", "You're targeting more than 1000 entities with this background task, be sure of what you're doing!": "¡Estás seleccionando más de 1000 entidades en esta tarea en segundo plano así que estáte seguro de que sabes lo que haces!", "Your account has expired. If you would like to reactivate your account, please contact your administrator.": "Su cuenta ha expirado. Si desea reactivar su cuenta, comuníquese con su administrador.", "Your confidence level is insufficient to edit this object.": "Tu nivel de confianza es insuficiente para editar este objeto.", - "Your testing file (CSV only, max 1MB)": "Su archivo de prueba (CSV, 1MB max)", "Your url contains filters in a deprecated format, parameters stored in the url have been removed.": "Su URL contiene filtros en un formato obsoleto, los parámetros almacenados en la URL han sido eliminados.", "Zoom": "Zoom", "Zoom in": "Ampliar", "Zoom out": "Alejar" -} \ No newline at end of file +} diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index fe2d8402d3db..3ff2b7af6514 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -185,6 +185,7 @@ "Attributes": "Attributs", "Attribution": "Attribution", "Audit logs": "Journaux d'audit", + "Audits are not supported in public dashboards": "Les audits ne sont pas pris en charge dans les tableaux de bord publics", "Audits list": "Liste des audits", "Authentication": "Authentification", "Authentication strategies": "Stratégies d'authentification", @@ -220,6 +221,7 @@ "Belonging to this organization": "Appartenant à cette organisa.", "Biographic Information": "Informations biographiques", "Biographics": "Biographie", + "Bookmarks are not supported in public dashboards": "Les signets ne sont pas pris en charge dans les tableaux de bord publics", "Browse the link": "Naviguer vers le lien", "Bulk search": "Recherche de masse", "Bundle content": "Contenu du bundle", @@ -524,6 +526,7 @@ "Data import": "Importation de données", "Data import and analyst workbenches": "Importation de données et postes de travail des analystes", "Data sharing": "Partage de données", + "Data sharing configuration": "Configuration du partage des données", "Data source": "Source de données", "Data sources": "Sources de données", "Data type": "Type de données", @@ -600,6 +603,7 @@ "Disable two-factor authentication": "Désactiver l'authentification double facteur", "Disable vertical tree mode": "Désactiver le mode arbre verticale", "Disabled": "Désactivé", + "Disabled dashboard...": "Le tableau de bord public si désactivé ne peut pas être consulté. Vous pouvez modifier ce booléen à tout moment.", "Disconnected": "Déconnecté", "Dismiss": "Rejeter", "Dismissible": "Rejetable", @@ -787,6 +791,7 @@ "Enable Enterprise Edition": "Enable Enterprise Edition", "Enable forces": "Activer les forces", "Enable horizontal tree mode": "Activer le mode arbre horizontal", + "Enable public dashboard": "Activer le tableau de bord public", "Enable this feature": "Activer cette fonctionnalité", "Enable tree mode": "Activer le mode arbre", "Enable two-factor authentication": "Activer l'authentification double facteur", @@ -1147,6 +1152,7 @@ "I have read and agree to the": "J'ai lu et j'accepte l'accord de licence", "I have read and comply with the above statement": "J'ai lu et j'accepte la déclaration ci-dessus", "I would like to use a EE feature ...": "J'aimerais utiliser une fonctionnalité ({feature}) sous Enterprise Edition qui n'est pas activée.\nJ'aimerais savoir si il est possible d'activer EE.", + "ID of your public dashboard": "ID de votre tableau de bord public (utilisé dans l'URL)", "Identities": "Identités", "Identity": "Identité", "ideology": "Idéologie/Conviction", @@ -1305,7 +1311,7 @@ "Last event processed": "Dernier événement traité", "Last execution": "Dernière éxecution", "Last execution traces": "Dernières traces d'exécution", - "Last expansion": "Dernière extension", + "Last expansion": "Dernière expansion", "Last indexation": "Dernière indexation", "Last ingested reports (creation date in the platform)": "Derniers rapports ajoutés (date de création dans la plateforme)", "Last modified": "Dernière modification", @@ -1360,7 +1366,6 @@ "Limits": "Limites", "Line chart": "Graphique linéaire", "Lines view": "Vue lignes", - "Link created": "Lien créé", "Linked entities": "Entités liées", "Linked entity": "Entité liée", "Linked knowledge": "Connaissance liée", @@ -1450,8 +1455,10 @@ "Max file size (in MB)": "Taille maximale du fichier (en Mo)", "Max level markings": "Marquage de niveau maximum", "Max marking definition level": "Niveau de marquage maximum", + "Max marking definitions override...": "Les définitions de marquage maximales que vous avez définies sont supérieures à celles configurées par l'administrateur. Elles seront remplacées par :", "Max retention": "Rétention maximum", "Maximum": "Maximum", + "Maximum marking definition allowed to be shared": "Définition de marquage maximale autorisée à être partagée", "Maximum retention days": "Jours de rétention maximum", "Med": "Moyen", "MEDIUM": "MOYEN", @@ -1579,6 +1586,8 @@ "Not empty": "Non vide", "Not ends with": "Ne se termine pas par", "Not equals": "Pas égal", + "Not implemented yet": "Pas encore mis en œuvre", + "Not shareable": "Non partageable", "Not Specified": "Non spécifié", "Not starts with": "Ne commence pas par", "not_contains": "ne contient pas", @@ -1803,7 +1812,8 @@ "Public collection": "Collection publique", "Public CSV feeds": "Flux CSV publics", "Public dashboard": "Tableau de bord public", - "Public dashboard ID": "ID du tableau de bord public", + "Public dashboard created the": "Tableau de bord public créé le", + "Public dashboard URI KEY": "URI KEY du tableau de bord public", "Public dashboards": "Tableaux de bord publics", "Public feed": "Flux public", "Public stream": "Stream public", @@ -2145,7 +2155,6 @@ "Source": "Source", "Source name": "Nom de la source", "source_reliability": "Fiabilité de l'auteur", - "Specify the ID of your public dashboard": "Spécifiez l'ID de votre tableau de bord public", "spl": "Splunk SPL", "sponsor": "sponsor", "Stable score": "Score stable", @@ -2299,6 +2308,7 @@ "The operators and modes are restricted for these filters.": "Les opérateurs et les modes sont restreints pour ces filtres.", "The opinions has no value defined in your vocabulary. Please add them first to be able to add opinions.": "Les opinions n'ont pas de valeur définie dans votre vocabulaire. Veuillez d'abord les ajouter pour pouvoir ajouter des opinions.", "The password has been updated": "Le mot de passe a été modifié", + "The public dashboard is disabled": "Le tableau de bord public est désactivé", "The relations attached to selected entities will be copied to the merged entity.": "Les relations attachées aux entités sélectionnées seront copiées sur l'entité fusionnée.", "The rule has been disabled, clean-up launched...": "La règle a été déséactivée, purge lancée...", "The rule has been enabled, rescan of platform data launched...": "La règle a été activée, re-scan des données de la plateforme lancé...", @@ -2698,16 +2708,15 @@ "You need a confidence level to edit objects in the platform.": "Vous avez besoin d'un niveau de confiance pour éditer des objets sur la plateforme.", "You need to activate a two-factor authentication. Please type the code generated in your application.": "Vous devez activer une authentification à deux facteurs. Veuillez saisir le code généré dans votre application.", "You need to activate OpenCTI enterprise edition to use this feature.": "Vous devez activer OpenCTI enterprise edition pour utiliser cette fonctionnalité.", - "You need to validate your two-factor authentication. Please type the code generated in your application.": "Vous devez valider votre authentification à deux facteurs. Veuillez saisir le code généré dans votre application.", + "You need to validate your two-factor authentication. Please type the code generated in your application": "Vous devez valider votre authentification à deux facteurs. Veuillez saisir le code généré dans votre application.", + "You see only marking definitions that can be shared (defined by the admin)": "Vous ne voyez que les définitions de marquage qui peuvent être partagées (définies par l'administrateur)", "You will be automatically logged out at end of the timer.": "Vous serez automatiquement déconnecté à la fin du décompte.", - "You will find here the result in JSON format": "Vous trouverez ici le résultat au format JSON", "You will find here the result in JSON format.": "Vous trouverez ici le résultat au format JSON.", "You're targeting more than 1000 entities with this background task, be sure of what you're doing!": "Vous ciblez plus de 1000 entités avec cette tâche de fond, soyez sûrs de ce que vous faites !", "Your account has expired. If you would like to reactivate your account, please contact your administrator.": "Votre compte à expiré. Si vous souhaitez réactiver votre compte, veuillez contacter votre administrateur.", "Your confidence level is insufficient to edit this object.": "Votre niveau de confiance est insuffisant pour éditer cet objet.", - "Your testing file (CSV only, max 1MB)": "Votre fichier de test (format CSV, max 1MB)", "Your url contains filters in a deprecated format, parameters stored in the url have been removed.": "Votre URL contient des filtres dans un format obsolète, les paramètres stockés dans l'URL ont été supprimés.", "Zoom": "Zoom", "Zoom in": "Zoomer", "Zoom out": "Zoom arrière" -} \ No newline at end of file +} diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index 0c43e66b6da3..cc464628092e 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -185,6 +185,7 @@ "Attributes": "属性", "Attribution": "アトリビューション", "Audit logs": "監査ログ", + "Audits are not supported in public dashboards": "パブリックダッシュボードでは監査はサポートされていません。", "Audits list": "監査リスト", "Authentication": "認証", "Authentication strategies": "認証方法", @@ -220,6 +221,7 @@ "Belonging to this organization": "この組織に属するもの", "Biographic Information": "略歴", "Biographics": "略歴", + "Bookmarks are not supported in public dashboards": "ブックマークはパブリックダッシュボードではサポートされていません。", "Browse the link": "リンクを閲覧する", "Bulk search": "一括検索", "Bundle content": "バンドルの内容", @@ -524,6 +526,7 @@ "Data import": "データインポート", "Data import and analyst workbenches": "データインポートとアナリストワークベンチ", "Data sharing": "データ共有", + "Data sharing configuration": "データ共有設定", "Data source": "データソース", "Data sources": "データソース", "Data type": "データ型", @@ -600,6 +603,7 @@ "Disable two-factor authentication": "二要素認証を無効にする", "Disable vertical tree mode": "垂直ツリーモードを無効にする", "Disabled": "無効", + "Disabled dashboard...": "無効化された公開ダッシュボードは閲覧できません。このブール値はいつでも変更できます。", "Disconnected": "切断中", "Dismiss": "解散", "Dismissible": "無視可能", @@ -787,6 +791,7 @@ "Enable Enterprise Edition": "エンタープライズ版を有効にする", "Enable forces": "強制モードにする", "Enable horizontal tree mode": "水平ツリーモードを有効にする", + "Enable public dashboard": "公開ダッシュボードを有効にする", "Enable this feature": "この機能を有効にする", "Enable tree mode": "ツリー表示にする", "Enable two-factor authentication": "二要素認証を有効にする", @@ -1147,6 +1152,7 @@ "I have read and agree to the": "を読み、同意します。", "I have read and comply with the above statement": "私は上記の声明を読み、遵守します。", "I would like to use a EE feature ...": "EE 機能 ({feature}) を使用したいのですが、EE がアクティブ化されていません。\nEEの有効化について相談したいと思います。", + "ID of your public dashboard": "公開ダッシュボードのID(URLで使用される)", "Identities": "アイデンティティ", "Identity": "アイデンティティ", "ideology": "イデオロギー/信念", @@ -1360,7 +1366,6 @@ "Limits": "限界", "Line chart": "ラインチャート", "Lines view": "ライン表示", - "Link created": "リンク作成", "Linked entities": "リンクされたエンティティ", "Linked entity": "リンクされたエンティティ", "Linked knowledge": "リンクされたナレッジ", @@ -1450,8 +1455,10 @@ "Max file size (in MB)": "最大ファイルサイズ(単位:MB)", "Max level markings": "最大レベルのマーキング", "Max marking definition level": "データマーキングレベルの最大値", + "Max marking definitions override...": "あなたが定義したマーキング定義の最大値は、管理者が設定したものよりも高くなります。それらは", "Max retention": "最大保持数", "Maximum": "最大値", + "Maximum marking definition allowed to be shared": "共有可能なマーキング定義の上限", "Maximum retention days": "最大保持日数", "Med": "中くらい", "MEDIUM": "中", @@ -1579,6 +1586,8 @@ "Not empty": "非空", "Not ends with": "で終わらない", "Not equals": "等しくない", + "Not implemented yet": "未実装", + "Not shareable": "共有不可", "Not Specified": "指定されていない", "Not starts with": "で始まらない", "not_contains": "を含まない", @@ -1803,7 +1812,8 @@ "Public collection": "公開コレクション", "Public CSV feeds": "CSVフィードの公開", "Public dashboard": "公開ダッシュボード", - "Public dashboard ID": "公開ダッシュボードID", + "Public dashboard created the": "公開ダッシュボードは", + "Public dashboard URI KEY": "公開ダッシュボードURI KEY", "Public dashboards": "公開ダッシュボード", "Public feed": "公開フィード", "Public stream": "公開ストリーム", @@ -2145,7 +2155,6 @@ "Source": "ソース", "Source name": "ソース名", "source_reliability": "著者の信頼性", - "Specify the ID of your public dashboard": "公開ダッシュボードのIDを指定する", "spl": "Splunk SPL", "sponsor": "スポンサー", "Stable score": "安定したスコア", @@ -2299,6 +2308,7 @@ "The operators and modes are restricted for these filters.": "これらのフィルタには演算子とモードの制限があります。", "The opinions has no value defined in your vocabulary. Please add them first to be able to add opinions.": "意見には、あなたの語彙で定義された価値はありません。意見を追加できるようにするには、まずそれらを追加してください。", "The password has been updated": "パスワードを変更しました", + "The public dashboard is disabled": "公開ダッシュボードは無効", "The relations attached to selected entities will be copied to the merged entity.": "選択されたエンティティに付属するリレーションは、マージされたエンティティにコピーされます。", "The rule has been disabled, clean-up launched...": "ルールは無効化され、クリーンアップが開始されました...", "The rule has been enabled, rescan of platform data launched...": "ルールが有効化され、プラットフォームデータの再スキャンが開始されました...", @@ -2698,16 +2708,15 @@ "You need a confidence level to edit objects in the platform.": "プラットフォーム上でオブジェクトを編集するには、最大の自信レベルが必要です。", "You need to activate a two-factor authentication. Please type the code generated in your application.": "二要素認証を有効にする必要があります。アプリケーションで生成されたコードを入力してください。", "You need to activate OpenCTI enterprise edition to use this feature.": "この機能を使用するには、OpenCTI エンタープライズ版を有効にする必要があります。", - "You need to validate your two-factor authentication. Please type the code generated in your application.": "二要素認証を検証する必要があります。アプリケーションで生成されたコードを入力してください。", + "You need to validate your two-factor authentication. Please type the code generated in your application": "二要素認証を検証する必要があります。アプリケーションで生成されたコードを入力してください。", + "You see only marking definitions that can be shared (defined by the admin)": "共有可能な(管理者によって定義された)マーキング定義のみが表示されます。", "You will be automatically logged out at end of the timer.": "タイマーが終了すると自動的にログアウトされます", - "You will find here the result in JSON format": "こちらでJSON形式の結果が見つかります", "You will find here the result in JSON format.": "ここにJSON形式の結果が表示されます。", "You're targeting more than 1000 entities with this background task, be sure of what you're doing!": "このバックグラウンドタスクで1000以上のエンティティをターゲットにしています。実行内容を確認してください。", "Your account has expired. If you would like to reactivate your account, please contact your administrator.": "アカウントの有効期限が切れました。アカウントを再度アクティブ化したい場合は、管理者に問い合わせてください。", "Your confidence level is insufficient to edit this object.": "このオブジェクトを編集するためには、最大の自信レベルが不足しています。", - "Your testing file (CSV only, max 1MB)": "テスト用のファイル (CSV, 最大1MB)", "Your url contains filters in a deprecated format, parameters stored in the url have been removed.": "お使いのURLには廃止された形式のフィルタが含まれており、URLに保存されていたパラメータが削除されました。", "Zoom": "ズーム", "Zoom in": "ズームイン", "Zoom out": "ズームアウト" -} \ No newline at end of file +} diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index 79849362bcbb..096fc1501904 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -185,6 +185,7 @@ "Attributes": "属性", "Attribution": "属性", "Audit logs": "审计日志", + "Audits are not supported in public dashboards": "公共仪表盘不支持审计", "Audits list": "审核列表", "Authentication": "验证", "Authentication strategies": "认证策略", @@ -220,6 +221,7 @@ "Belonging to this organization": "归属于该组织", "Biographic Information": "简历信息", "Biographics": "传记", + "Bookmarks are not supported in public dashboards": "公共仪表盘不支持书签", "Browse the link": "浏览链接", "Bulk search": "批量搜索", "Bundle content": "捆绑包内容", @@ -524,6 +526,7 @@ "Data import": "数据导入", "Data import and analyst workbenches": "数据导入和分析工作台", "Data sharing": "数据共享", + "Data sharing configuration": "数据共享配置", "Data source": "数据源", "Data sources": "数据源", "Data type": "数据类型", @@ -600,6 +603,7 @@ "Disable two-factor authentication": "禁用双因素身份验证", "Disable vertical tree mode": "禁用垂直树模式", "Disabled": "禁用", + "Disabled dashboard...": "无法查看已禁用的公共仪表板。您可以随时更改此布尔值。", "Disconnected": "断开连接的", "Dismiss": "忽略", "Dismissible": "可忽略", @@ -787,6 +791,7 @@ "Enable Enterprise Edition": "启用企业版", "Enable forces": "启用强制", "Enable horizontal tree mode": "启用水平树模式", + "Enable public dashboard": "启用公共仪表板", "Enable this feature": "启用此功能", "Enable tree mode": "启用树形模式", "Enable two-factor authentication": "启用双因素身份验证", @@ -1147,6 +1152,7 @@ "I have read and agree to the": "我已阅读并同意", "I have read and comply with the above statement": "我已阅读并遵守上述声明", "I would like to use a EE feature ...": "我想使用 EE 功能 ({feature}),但我没有启动 EE。\n我想和您讨论一下启动EE的问题。", + "ID of your public dashboard": "公共仪表板的 ID(在 URL 中使用)", "Identities": "身份", "Identity": "身份", "ideology": "意识形态", @@ -1360,7 +1366,6 @@ "Limits": "边界", "Line chart": "折线图", "Lines view": "线条视图", - "Link created": "创建的链接", "Linked entities": "链接实体", "Linked entity": "链接实体", "Linked knowledge": "链接知识", @@ -1450,8 +1455,10 @@ "Max file size (in MB)": "最大文件大小(兆字节)", "Max level markings": "最大级别标记", "Max marking definition level": "最大标记定义级别", + "Max marking definitions override...": "您定义的最大标记定义高于管理员配置的定义。它们将被替换为", "Max retention": "最长保留期", "Maximum": "最大值", + "Maximum marking definition allowed to be shared": "允许共享的最大标记定义", "Maximum retention days": "最长保留天数", "Med": "中等", "MEDIUM": "中", @@ -1579,6 +1586,8 @@ "Not empty": "非空", "Not ends with": "不以...结尾", "Not equals": "不等于", + "Not implemented yet": "尚未实施", + "Not shareable": "不可共享", "Not Specified": "未指定", "Not starts with": "不以...开始", "not_contains": "不包含", @@ -1803,7 +1812,8 @@ "Public collection": "公共集合", "Public CSV feeds": "公开 CSV 源", "Public dashboard": "公共仪表板", - "Public dashboard ID": "公共仪表板 ID", + "Public dashboard created the": "公共仪表板创建了", + "Public dashboard URI KEY": "公共仪表板 URI KEY", "Public dashboards": "公共仪表板", "Public feed": "公开信息源", "Public stream": "公共流", @@ -2145,7 +2155,6 @@ "Source": "来源", "Source name": "源名称", "source_reliability": "作者的可靠性", - "Specify the ID of your public dashboard": "指定公共仪表板的 ID", "spl": "Splunk SPL", "sponsor": "赞助商", "Stable score": "成绩稳定", @@ -2299,6 +2308,7 @@ "The operators and modes are restricted for these filters.": "这些过滤器的运算符和模式受到限制。", "The opinions has no value defined in your vocabulary. Please add them first to be able to add opinions.": "意见在您的词汇中没有定义值。请先添加意见,才能添加观点。", "The password has been updated": "密码已更新", + "The public dashboard is disabled": "公共仪表板已禁用", "The relations attached to selected entities will be copied to the merged entity.": "附加到选定实体的关系将复制到归并的实体中。", "The rule has been disabled, clean-up launched...": "该规则已禁用,清理已启动", "The rule has been enabled, rescan of platform data launched...": "该规则已启用,平台数据的重新扫描已启动", @@ -2698,16 +2708,15 @@ "You need a confidence level to edit objects in the platform.": "编辑平台中的对象需要信任级别。", "You need to activate a two-factor authentication. Please type the code generated in your application.": "您需要激活双因素身份验证。请输入在您的应用程序中生成的代码。", "You need to activate OpenCTI enterprise edition to use this feature.": "您需要激活 OpenCTI 企业版才能使用此功能。", - "You need to validate your two-factor authentication. Please type the code generated in your application.": "您需要验证您的双因素身份验证。请输入在您的应用程序中生成的代码。", + "You need to validate your two-factor authentication. Please type the code generated in your application": "您需要验证您的双因素身份验证。请输入在您的应用程序中生成的代码。", + "You see only marking definitions that can be shared (defined by the admin)": "您只能看到可以共享的标记定义(由管理员定义)", "You will be automatically logged out at end of the timer.": "计时器结束时您将自动注销", - "You will find here the result in JSON format": "您将在此处找到JSON格式的结果", "You will find here the result in JSON format.": "您将在此处看到 JSON 格式的结果。", "You're targeting more than 1000 entities with this background task, be sure of what you're doing!": "您正在使用此后台任务针对超过1000个实体,请确保您正在执行的操作!", "Your account has expired. If you would like to reactivate your account, please contact your administrator.": "您的账户已过期。如果您希望重新激活您的账户,请联系您的管理员。", "Your confidence level is insufficient to edit this object.": "您的置信度不足以编辑此对象。", - "Your testing file (CSV only, max 1MB)": "您的测试文件 (CSV, 最大1MB)", "Your url contains filters in a deprecated format, parameters stored in the url have been removed.": "您的 URL 包含已弃用的格式的筛选器,URL 中存储的参数已被删除。", - "Zoom": "缩放", + "Zoom": "放大", "Zoom in": "放大", "Zoom out": "缩小" -} \ No newline at end of file +} diff --git a/opencti-platform/opencti-front/script/sort-translation-files.js b/opencti-platform/opencti-front/script/sort-translation-files.js index cece061c9a39..ce0c6b39499a 100644 --- a/opencti-platform/opencti-front/script/sort-translation-files.js +++ b/opencti-platform/opencti-front/script/sort-translation-files.js @@ -37,7 +37,7 @@ function sortAllJSONFiles(dirPath) { const filePath = path.join(dirPath, file); // Skip if not a JSON file or if it's en.json - if (!file.endsWith('.json') || file === 'en.json') { + if (!file.endsWith('.json')) { return; } diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetDistributionList.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetDistributionList.tsx index 81a6c6bf3e54..04ab33930c83 100644 --- a/opencti-platform/opencti-front/src/components/dashboard/WidgetDistributionList.tsx +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetDistributionList.tsx @@ -16,12 +16,14 @@ interface WidgetDistributionListProps { data: any[] hasSettingAccess?: boolean overflow?: string + publicWidget?: boolean } const WidgetDistributionList = ({ data, hasSettingAccess = false, overflow = 'auto', + publicWidget = false, }: WidgetDistributionListProps) => { const theme = useTheme(); const { n } = useFormatter(); @@ -42,7 +44,7 @@ const WidgetDistributionList = ({ const label = getMainRepresentative(entry.entity) || entry.label; let link: string | null = null; - if (entry.type !== 'User' || hasSettingAccess) { + if (!publicWidget && (entry.type !== 'User' || hasSettingAccess)) { const node: { id: string; entity_type: string; @@ -67,6 +69,7 @@ const WidgetDistributionList = ({ key={entry.id ?? entry.label} dense={true} divider={true} + disableRipple={publicWidget} {...linkProps} sx={{ height: 50, diff --git a/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx b/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx index 45c7bcfeaa51..25e609f55428 100644 --- a/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx +++ b/opencti-platform/opencti-front/src/components/dashboard/WidgetListCoreObjects.tsx @@ -28,11 +28,13 @@ interface WidgetListCoreObjectsProps { // eslint-disable-next-line @typescript-eslint/no-explicit-any data: any[] dateAttribute: string + publicWidget?: boolean } const WidgetListCoreObjects = ({ data, dateAttribute, + publicWidget = false, }: WidgetListCoreObjectsProps) => { const { fsd } = useFormatter(); @@ -52,14 +54,23 @@ const WidgetListCoreObjects = ({ const stixCoreObject = stixCoreObjectEdge.node; const date = stixCoreObject[dateAttribute]; + const link = publicWidget ? null : `${resolveLink(stixCoreObject.entity_type)}/${stixCoreObject.id}`; + let linkProps = {}; + if (link) { + linkProps = { + component: Link, + to: link, + }; + } + return ( { const theme = useTheme(); const { fsd, t_i18n } = useFormatter(); @@ -55,7 +57,7 @@ const WidgetListRelationships = ({ ? stixRelationship.from : stixRelationship.to; let link = null; - if (remoteNode) { + if (!publicWidget && remoteNode) { link = computeLink(remoteNode); } let linkProps = {}; @@ -72,6 +74,7 @@ const WidgetListRelationships = ({ dense={true} className="noDrag" divider={true} + disableRipple={publicWidget} {...linkProps} style={{ height: 50, diff --git a/opencti-platform/opencti-front/src/private/Root.tsx b/opencti-platform/opencti-front/src/private/Root.tsx index be6805e0def4..279cf1ae0c92 100644 --- a/opencti-platform/opencti-front/src/private/Root.tsx +++ b/opencti-platform/opencti-front/src/private/Root.tsx @@ -46,6 +46,12 @@ const rootSettingsFragment = graphql` platform_whitemark platform_session_idle_timeout platform_session_timeout + platform_data_sharing_max_markings { + id + definition + definition_type + x_opencti_order + } platform_feature_flags { id enable diff --git a/opencti-platform/opencti-front/src/private/components/common/form/MarkingsSelectField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/MarkingsSelectField.tsx new file mode 100644 index 000000000000..2516b7018e86 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/common/form/MarkingsSelectField.tsx @@ -0,0 +1,97 @@ +import { Field, FieldProps, Formik } from 'formik'; +import React from 'react'; +import MenuItem from '@mui/material/MenuItem'; +import { useFormatter } from '../../../../components/i18n'; +import SelectField from '../../../../components/SelectField'; + +interface MarkingDefinition { + id: string + definition: string + definition_type: string + x_opencti_order: number +} + +type MarkingsSelectFieldValue = string[]; + +interface MarkingsSelectFieldInternalValue { + [key: string]: string +} + +interface MarkingsSelectFieldProps extends FieldProps { + markingDefinitions: MarkingDefinition[] + onChange?: (val: MarkingsSelectFieldValue) => void +} + +const NOT_SHAREABLE_ID = 'not_shareable'; + +const MarkingsSelectField = ({ + form, + field, + markingDefinitions, + onChange, +}: MarkingsSelectFieldProps) => { + const { t_i18n } = useFormatter(); + const { setFieldValue } = form; + const { value, name } = field; + + const markingTypes = Array.from(new Set( + markingDefinitions.map((m) => m.definition_type), + )); + + const initialValues = markingTypes.reduce((acc, type) => ({ + ...acc, + [type]: value.find((defId) => { + const marking = markingDefinitions.find((def) => def.id === defId); + return marking?.definition_type === type; + }) ?? NOT_SHAREABLE_ID, + }), {}); + + const changeMarking = (type: string, markingId: string) => { + const newValue = value.filter((defId) => { + const marking = markingDefinitions.find((def) => def.id === defId); + return marking?.definition_type !== type; + }); + if (markingId !== NOT_SHAREABLE_ID) { + newValue.push(markingId); + } + setFieldValue(name, newValue); + onChange?.(newValue); + }; + + return ( + + initialValues={initialValues} + onSubmit={() => {}} + > + {() => ( + markingTypes.map((type, i) => ( + 0 ? 20 : 5, width: '100%' }} + component={SelectField} + onChange={changeMarking} + displ + > + + {t_i18n('Not shareable')} + + {markingDefinitions + .filter((def) => def.definition_type === type) + .sort((defA, defB) => defA.x_opencti_order - defB.x_opencti_order) + .map((def) => ( + + {def.definition} + + ))} + + )) + )} + + ); +}; + +export default MarkingsSelectField; diff --git a/opencti-platform/opencti-front/src/private/components/common/form/ObjectMarkingField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/ObjectMarkingField.tsx index 33c9c5ad2928..6b7ced9ba361 100644 --- a/opencti-platform/opencti-front/src/private/components/common/form/ObjectMarkingField.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/form/ObjectMarkingField.tsx @@ -58,6 +58,7 @@ interface ObjectMarkingFieldProps { disabled?: boolean; label?: string; setFieldValue?: (name: string, values: Option[]) => void; + limitToMaxSharing?: boolean } interface OptionValues { @@ -73,6 +74,7 @@ const ObjectMarkingField: FunctionComponent = ({ disabled, label, setFieldValue, + limitToMaxSharing = false, }) => { const classes = useStyles(); const { t_i18n } = useFormatter(); @@ -81,8 +83,15 @@ const ObjectMarkingField: FunctionComponent = ({ >(undefined); const [operation, setOperation] = useState(undefined); - const { me } = useAuth(); - const allowedMarkingDefinitions = me.allowed_marking?.map(convertMarking) ?? []; + const { me, settings } = useAuth(); + let allowedMarkingDefinitions = me.allowed_marking?.map(convertMarking) ?? []; + if (limitToMaxSharing) { + const { platform_data_sharing_max_markings } = settings; + allowedMarkingDefinitions = allowedMarkingDefinitions.filter((def) => { + const maxMarking = platform_data_sharing_max_markings?.find((marking) => marking.definition_type === def.definition_type); + return !!maxMarking && maxMarking.x_opencti_order >= def.x_opencti_order; + }); + } const optionSorted = allowedMarkingDefinitions.sort((a, b) => { if (a.definition_type === b.definition_type) { diff --git a/opencti-platform/opencti-front/src/private/components/data/csvMapper/CsvMapperTestDialog.tsx b/opencti-platform/opencti-front/src/private/components/data/csvMapper/CsvMapperTestDialog.tsx index 2b66c67c5271..6a8fd6ae5172 100644 --- a/opencti-platform/opencti-front/src/private/components/data/csvMapper/CsvMapperTestDialog.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/csvMapper/CsvMapperTestDialog.tsx @@ -149,7 +149,7 @@ const CsvMapperTestDialog: FunctionComponent = ({ } diff --git a/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx b/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx index 88a96da3cecb..67db666a1108 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx @@ -15,6 +15,8 @@ import { VpnKeyOutlined } from '@mui/icons-material'; import ListItemText from '@mui/material/ListItemText'; import EEChip from '@components/common/entreprise_edition/EEChip'; import EETooltip from '@components/common/entreprise_edition/EETooltip'; +import { PoliciesMarkingDefinitions$key } from '@components/settings/__generated__/PoliciesMarkingDefinitions.graphql'; +import MarkingsSelectField from '@components/common/form/MarkingsSelectField'; import AccessesMenu from './AccessesMenu'; import ObjectOrganizationField from '../common/form/ObjectOrganizationField'; import { useFormatter } from '../../../components/i18n'; @@ -62,6 +64,11 @@ const PoliciesFragment = graphql` password_policy_min_words password_policy_min_lowercase password_policy_min_uppercase + platform_data_sharing_max_markings { + id + definition + definition_type + } platform_providers { name strategy @@ -74,11 +81,27 @@ const PoliciesFragment = graphql` } `; +const PoliciesMarkingDefinitionsFragment = graphql` + fragment PoliciesMarkingDefinitions on MarkingDefinitionConnection { + edges { + node { + id + definition + definition_type + x_opencti_order + } + } + } +`; + const policiesQuery = graphql` query PoliciesQuery { settings { ...Policies } + markingDefinitions { + ...PoliciesMarkingDefinitions + } } `; @@ -107,6 +130,7 @@ const policiesValidation = () => Yup.object().shape({ platform_consent_confirm_text: Yup.string().nullable(), platform_banner_level: Yup.string().nullable(), platform_banner_text: Yup.string().nullable(), + platform_data_sharing_max_markings: Yup.array().of(Yup.string()).nullable(), }); interface PoliciesComponentProps { @@ -117,13 +141,19 @@ interface PoliciesComponentProps { const PoliciesComponent: FunctionComponent = ({ queryRef, }) => { - const data = usePreloadedQuery(policiesQuery, queryRef); const isEnterpriseEdition = useEnterpriseEdition(); + + const data = usePreloadedQuery(policiesQuery, queryRef); const settings = useFragment(PoliciesFragment, data.settings); + const markings = useFragment( + PoliciesMarkingDefinitionsFragment, + data.markingDefinitions, + ); + const [commitField] = useMutation(policiesFieldPatch); const classes = useStyles(); const { t_i18n } = useFormatter(); - const handleSubmitField = (name: string, value: string | Option) => { + const handleSubmitField = (name: string, value: string | string[] | Option) => { policiesValidation() .validateAt(name, { [name]: value }) .then(() => { @@ -159,6 +189,7 @@ const PoliciesComponent: FunctionComponent = ({ platform_banner_level: settings.platform_banner_level, platform_banner_text: settings.platform_banner_text, otp_mandatory: settings.otp_mandatory, + platform_data_sharing_max_markings: settings.platform_data_sharing_max_markings?.map((m) => m.id) ?? [], }; const authProviders = settings.platform_providers; return ( @@ -433,6 +464,20 @@ const PoliciesComponent: FunctionComponent = ({ /> + + + + {t_i18n('Maximum marking definition allowed to be shared')} + + + e.node)} + name="platform_data_sharing_max_markings" + onChange={(val: string[]) => handleSubmitField('platform_data_sharing_max_markings', val)} + /> + + )} diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceHeader.jsx b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceHeader.jsx index 838d0328d6fa..86d2ecb413d6 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceHeader.jsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceHeader.jsx @@ -352,7 +352,7 @@ const WorkspaceHeader = ({ )} {isFeatureEnable('PUBLIC_DASHBOARD') && ( - +
diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareButton.tsx b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareButton.tsx index 5ceafd90be57..ff5f9d3efa7d 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareButton.tsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareButton.tsx @@ -32,6 +32,16 @@ const workspaceShareButtonDeleteMutation = graphql` } `; +const workspaceShareButtonEditMutation = graphql` + mutation WorkspaceShareButtonEditMutation($id: ID!, $input: [EditInput!]!) { + publicDashboardFieldPatch(id: $id, input: $input) { + id + uri_key + enabled + } + } +`; + interface WorkspaceShareButtonProps { workspaceId: string } @@ -45,6 +55,7 @@ const WorkspaceShareButton = ({ workspaceId }: WorkspaceShareButtonProps) => { const [drawerOpen, setDrawerOpen] = useState(false); const [commitCreateMutation] = useMutation(workspaceShareButtonCreateMutation); const [commitDeleteMutation] = useMutation(workspaceShareButtonDeleteMutation); + const [commitEditMutation] = useMutation(workspaceShareButtonEditMutation); const [publicDashboardsQueryRef, fetchList] = useQueryLoader(workspaceShareListQuery); const fetchWithFilters = (options?: UseQueryLoaderLoadQueryOptions) => { @@ -66,7 +77,7 @@ const WorkspaceShareButton = ({ workspaceId }: WorkspaceShareButtonProps) => { }; useEffect(() => { - fetchWithFilters(); + fetchWithFilters({ fetchPolicy: 'store-and-network' }); }, []); const onSubmit: FormikConfig['onSubmit'] = (values, { setSubmitting, resetForm }) => { @@ -74,6 +85,8 @@ const WorkspaceShareButton = ({ workspaceId }: WorkspaceShareButtonProps) => { variables: { input: { name: values.name, + enabled: values.enabled, + uri_key: values.uri_key, dashboard_id: workspaceId, allowed_markings_ids: values.max_markings.map((marking) => marking.value), }, @@ -81,7 +94,7 @@ const WorkspaceShareButton = ({ workspaceId }: WorkspaceShareButtonProps) => { onCompleted: () => { setSubmitting(false); resetForm(); - fetchWithFilters({ fetchPolicy: 'network-only' }); + fetchWithFilters({ fetchPolicy: 'store-and-network' }); }, onError: (error) => { setSubmitting(false); @@ -106,12 +119,27 @@ const WorkspaceShareButton = ({ workspaceId }: WorkspaceShareButtonProps) => { deletion.setDeleting(false); deletion.handleCloseDelete(); idToDelete.current = undefined; - fetchWithFilters({ fetchPolicy: 'network-only' }); + fetchWithFilters({ fetchPolicy: 'store-and-network' }); }, }); } }; + const onToggleEnabled = (dashboardId: string, enabled: boolean) => { + commitEditMutation({ + variables: { + id: dashboardId, + input: [{ + key: 'enabled', + value: [enabled], + }], + }, + onCompleted: () => { + fetchWithFilters({ fetchPolicy: 'store-and-network' }); + }, + }); + }; + return ( <> @@ -169,6 +197,7 @@ const WorkspaceShareButton = ({ workspaceId }: WorkspaceShareButtonProps) => { )} diff --git a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareForm.tsx b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareForm.tsx index 61942d047820..7296f5cc33f6 100644 --- a/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareForm.tsx +++ b/opencti-platform/opencti-front/src/private/components/workspaces/WorkspaceShareForm.tsx @@ -6,12 +6,15 @@ import Button from '@mui/material/Button'; import { Option } from '@components/common/form/ReferenceField'; import { FormikConfig } from 'formik/dist/types'; import * as Yup from 'yup'; +import Alert from '@mui/material/Alert'; import { useFormatter } from '../../../components/i18n'; import TextField from '../../../components/TextField'; import { fieldSpacingContainerStyle } from '../../../utils/field'; +import SwitchField from '../../../components/SwitchField'; export interface WorkspaceShareFormData { name: string; + enabled: boolean; uri_key: string; max_markings: Option[]; } @@ -25,6 +28,8 @@ const WorkspaceShareForm = ({ onSubmit }: WorkspaceShareFormProps) => { const formValidation = Yup.object().shape({ name: Yup.string().required(t_i18n('This field is required')), + uri_key: Yup.string(), + enabled: Yup.boolean(), max_markings: Yup.array().min(1, 'This field is required').required(t_i18n('This field is required')), }); @@ -34,6 +39,7 @@ const WorkspaceShareForm = ({ onSubmit }: WorkspaceShareFormProps) => { validationSchema={formValidation} initialValues={{ name: '', + enabled: true, uri_key: '', max_markings: [], }} @@ -47,14 +53,17 @@ const WorkspaceShareForm = ({ onSubmit }: WorkspaceShareFormProps) => { variant="standard" label={t_i18n('Name')} style={{ width: '100%' }} + onChange={(_: string, val: string) => { + setFieldValue('uri_key', val.replace(/[^a-zA-Z0-9 ]+/g, '').replace(/\s+/g, '-')); + }} /> { ), }} /> + { style={fieldSpacingContainerStyle} onChange={() => {}} setFieldValue={setFieldValue} + limitToMaxSharing /> + + {t_i18n('You see only marking definitions that can be shared (defined by the admin)')} +
onDelete: (id: string) => void + onToggleEnabled: (id: string, enabled: boolean) => void } -const WorkspaceShareList = ({ queryRef, onDelete }: WorkspaceShareListProps) => { +const WorkspaceShareList = ({ queryRef, onDelete, onToggleEnabled }: WorkspaceShareListProps) => { + const { settings } = useAuth(); + const { platform_data_sharing_max_markings } = settings; + const theme = useTheme(); const { t_i18n, fld } = useFormatter(); + const { publicDashboards } = usePreloadedQuery(workspaceShareListQuery, queryRef); const dashboards = publicDashboards?.edges .map((edge) => edge.node) @@ -60,80 +73,119 @@ const WorkspaceShareList = ({ queryRef, onDelete }: WorkspaceShareListProps) => return

{t_i18n('No public dashboard created yet')}

; } + const filterMaxMarkings = (dashboard: typeof dashboards[0]) => { + const { allowed_markings } = dashboard; + return (platform_data_sharing_max_markings ?? []).filter((maxMarking) => { + const marking = (allowed_markings ?? []).find((m) => m.definition_type === maxMarking.definition_type); + return marking && marking.x_opencti_order > maxMarking.x_opencti_order; + }); + }; + return (
- {dashboards.map((dashboard) => ( - -
{ + const maxMarkings = filterMaxMarkings(dashboard); + return ( + -
- - {dashboard.name} - - - public/dashboard/{dashboard.uri_key} - -
- - - - copyLinkUrl(dashboard.uri_key)} - > - - - - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - - onDelete(dashboard.id)} - > - - - - -
- -
- - - {t_i18n('Link created')} {fld(dashboard.created_at)} - -
-
- ))} + > +
+ + {dashboard.name} + + + public/dashboard/{dashboard.uri_key} + +
+ +
+ + + + + copyLinkUrl(dashboard.uri_key)} + > + + + + + onToggleEnabled(dashboard.id, !dashboard.enabled)} + > + {dashboard.enabled && } + {!dashboard.enabled && } + + + + onDelete(dashboard.id)} + > + + + + +
+
+ +
+
+ + {maxMarkings.length > 0 && ( + +
{t_i18n('Max marking definitions override...')}
+
+ +
+
+ )} + > + + + )} +
+ + {t_i18n('Public dashboard created the')} {fld(dashboard.created_at)} + +
+ + ); + })} ); }; diff --git a/opencti-platform/opencti-front/src/public/PublicRoot.tsx b/opencti-platform/opencti-front/src/public/PublicRoot.tsx index 8cb980eb1950..5950e04cc91a 100644 --- a/opencti-platform/opencti-front/src/public/PublicRoot.tsx +++ b/opencti-platform/opencti-front/src/public/PublicRoot.tsx @@ -12,6 +12,7 @@ import { LoginRootPublicQuery } from './__generated__/LoginRootPublicQuery.graph import PublicDataSharing from './components/PublicDataSharing'; import PublicDashboard from './components/PublicDashboard'; import PublicSettingsProvider from './PublicSettingsProvider'; +import Message from '../components/Message'; const queryRef = loadQuery( environment, @@ -28,8 +29,9 @@ const PublicRoot = () => { - + + diff --git a/opencti-platform/opencti-front/src/public/components/PublicDashboard.tsx b/opencti-platform/opencti-front/src/public/components/PublicDashboard.tsx index e15631d02f98..05369710b74c 100644 --- a/opencti-platform/opencti-front/src/public/components/PublicDashboard.tsx +++ b/opencti-platform/opencti-front/src/public/components/PublicDashboard.tsx @@ -17,6 +17,7 @@ const publicDashboardQuery = graphql` query PublicDashboardQuery($uri_key: String!) { publicDashboardByUriKey(uri_key: $uri_key) { name + enabled public_manifest } } @@ -43,12 +44,17 @@ const PublicDashboardComponent = ({ if (publicDashboardByUriKey === null) { navigate('/'); } + const enabled = publicDashboardByUriKey?.enabled; + if (!enabled) { + navigate('/'); + } }, [publicDashboardByUriKey]); const { entityWidget, relationshipWidget, rawWidget, + auditWidget, } = usePublicDashboardWidgets(uriKey, config); const onChangeRelativeDate = () => {}; @@ -101,6 +107,7 @@ const PublicDashboardComponent = ({ > {widget.perspective === 'entities' && entityWidget(widget)} {widget.perspective === 'relationships' && relationshipWidget(widget)} + {widget.perspective === 'audits' && auditWidget(widget)} {widget.perspective === null && rawWidget(widget)} diff --git a/opencti-platform/opencti-front/src/public/components/PublicDataSharing.tsx b/opencti-platform/opencti-front/src/public/components/PublicDataSharing.tsx index 08b0ece3f1eb..e8f12369a0e7 100644 --- a/opencti-platform/opencti-front/src/public/components/PublicDataSharing.tsx +++ b/opencti-platform/opencti-front/src/public/components/PublicDataSharing.tsx @@ -6,7 +6,6 @@ import PublicFeedLines from '@components/data/feeds/PublicFeedLines'; import React from 'react'; import { loadQuery, usePreloadedQuery } from 'react-relay'; import type { Theme } from '../../components/Theme'; -import Message from '../../components/Message'; import { environment, fileUri } from '../../relay/environment'; import { LoginRootPublicQuery } from '../__generated__/LoginRootPublicQuery.graphql'; import { rootPublicQuery } from '../LoginRoot'; @@ -49,7 +48,6 @@ const PublicDataSharing = () => { return ( <> -
0 ? loginLogo : fileUri(theme.palette.mode === 'dark' ? logoDark : logoLight)} diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDistributionList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDistributionList.tsx index 4a42cd3da85d..206da34eda98 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDistributionList.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsDistributionList.tsx @@ -91,7 +91,7 @@ const PublicStixCoreObjectsDistributionListComponent = ({ type: n.entity?.entity_type ?? n.label, }; }); - return ; + return ; } return ; }; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsList.tsx index 14ceca1a7dbf..dc32f4efc9b2 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsList.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsList.tsx @@ -220,6 +220,7 @@ const PublicStixCoreObjectsListComponent = ({ ); } diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTimeline.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTimeline.tsx index 30bb2a655299..3a4b5356971c 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTimeline.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_core_objects/PublicStixCoreObjectsTimeline.tsx @@ -7,7 +7,6 @@ import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import WidgetContainer from '../../../../components/dashboard/WidgetContainer'; import WidgetLoader from '../../../../components/dashboard/WidgetLoader'; import { PublicStixCoreObjectsTimelineQuery } from './__generated__/PublicStixCoreObjectsTimelineQuery.graphql'; -import { resolveLink } from '../../../../utils/Entity'; import WidgetTimeline from '../../../../components/dashboard/WidgetTimeline'; const publicStixCoreObjectsTimelineQuery = graphql` @@ -86,10 +85,8 @@ const PublicStixCoreObjectsTimelineComponent = ({ const data = stixCoreObjectsEdges.flatMap((stixCoreObjectEdge) => { const stixCoreObject = stixCoreObjectEdge?.node; if (!stixCoreObject) return []; - const link = `${resolveLink(stixCoreObject.entity_type)}/${stixCoreObject.id}`; return { value: stixCoreObject, - link, }; }); return ; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDistributionList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDistributionList.tsx index d2ddcb00a278..1409f08619cb 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDistributionList.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsDistributionList.tsx @@ -82,7 +82,7 @@ const PublicStixRelationshipsDistributionListComponent = ({ type: o.entity?.entity_type ?? o.label, }; }); - return ; + return ; } return ; }; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsList.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsList.tsx index 9ba55acb1c98..62339470e381 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsList.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsList.tsx @@ -4365,6 +4365,7 @@ const PublicStixRelationshipsListComponent = ({ ); } diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTimeline.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTimeline.tsx index 9250ce32ded6..20628b8455de 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTimeline.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/stix_relationships/PublicStixRelationshipsTimeline.tsx @@ -1,6 +1,5 @@ import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; import React from 'react'; -import { resolveLink } from '../../../../utils/Entity'; import WidgetTimeline from '../../../../components/dashboard/WidgetTimeline'; import WidgetNoData from '../../../../components/dashboard/WidgetNoData'; import type { PublicWidgetContainerProps } from '../PublicWidgetContainerProps'; @@ -980,19 +979,11 @@ const PublicStixRelationshipsTimelineComponent = ({ : stixRelationship.from; if (!remoteNode) return []; - const restricted = stixRelationship.from === null - || stixRelationship.to === null; - const link = restricted - ? undefined - : `${resolveLink(remoteNode.entity_type)}/${ - remoteNode.id - }/knowledge/relations/${stixRelationship.id}`; return { value: { ...remoteNode, created: stixRelationship.created, }, - link, }; }); return ; diff --git a/opencti-platform/opencti-front/src/public/components/dashboard/usePublicDashboardWidgets.tsx b/opencti-platform/opencti-front/src/public/components/dashboard/usePublicDashboardWidgets.tsx index c306181c9e76..d16f01f79006 100644 --- a/opencti-platform/opencti-front/src/public/components/dashboard/usePublicDashboardWidgets.tsx +++ b/opencti-platform/opencti-front/src/public/components/dashboard/usePublicDashboardWidgets.tsx @@ -27,24 +27,21 @@ import PublicStixRelationshipsTreeMap from './stix_relationships/PublicStixRelat import PublicStixRelationshipsMap from './stix_relationships/PublicStixRelationshipsMap'; import PublicStixCoreObjectsHorizontalBars from './stix_core_objects/PublicStixCoreObjectsHorizontalBars'; import PublicStixRelationshipsHorizontalBars from './stix_relationships/PublicStixRelationshipsHorizontalBars'; -import PublicStixDomainObjectBookmarksList from './stix_domain_objects/PublicStixDomainObjectBookmarksList'; import PublicStixRelationshipsMultiHorizontalBars from './stix_relationships/PublicStixRelationshipsMultiHorizontalBars'; import PublicStixRelationshipsPolarArea from './stix_relationships/PublicStixRelationshipsPolarArea'; import PublicStixCoreObjectsPolarArea from './stix_core_objects/PublicStixCoreObjectsPolarArea'; +import { useFormatter } from '../../../components/i18n'; const usePublicDashboardWidgets = (uriKey: string, config?: PublicManifestConfig) => { + const { t_i18n } = useFormatter(); + const startDate = config?.relativeDate ? computerRelativeDate(config.relativeDate) : config?.startDate; const endDate = config?.relativeDate ? formatDate(dayStartDate(null, false)) : config?.endDate; const entityWidget = (widget: PublicManifestWidget) => { switch (widget.type) { case 'bookmark': - return ( - - ); + return t_i18n('Bookmarks are not supported in public dashboards'); case 'number': return ( 1) { // TODO implement multi horizontal bars with breakdowns - return 'Not implemented yet'; + return t_i18n('Not implemented yet'); } return ( ); default: - return 'Not implemented yet'; + return t_i18n('Not implemented yet'); } }; @@ -310,7 +307,14 @@ const usePublicDashboardWidgets = (uriKey: string, config?: PublicManifestConfig /> ); default: - return 'Not implemented yet'; + return t_i18n('Not implemented yet'); + } + }; + + const auditWidget = (widget: PublicManifestWidget) => { + switch (widget.type) { + default: + return t_i18n('Audits are not supported in public dashboards'); } }; @@ -324,13 +328,14 @@ const usePublicDashboardWidgets = (uriKey: string, config?: PublicManifestConfig /> ); default: - return 'Not implemented yet'; + return t_i18n('Not implemented yet'); } }; return { entityWidget, relationshipWidget, + auditWidget, rawWidget, }; }; diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index 76cba024f701..9f6ab648032c 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -1169,6 +1169,7 @@ type Settings implements InternalObject & BasicObject { entity_type: String! parent_types: [String!]! platform_organization: Organization + platform_data_sharing_max_markings: [MarkingDefinition!] platform_title: String platform_favicon: String platform_email: String @@ -11328,6 +11329,7 @@ type PublicDashboard implements InternalObject & BasicObject { updated_at: DateTime editContext: [EditUserContext!] authorized_members: [MemberAccess!] + enabled: Boolean! } type PublicDistribution { @@ -11356,9 +11358,11 @@ type PublicDashboardEdge { input PublicDashboardAddInput { name: String! + uri_key: String! description: String dashboard_id: String! allowed_markings_ids: [String!] + enabled: Boolean! } enum Tone { diff --git a/opencti-platform/opencti-graphql/config/default.json b/opencti-platform/opencti-graphql/config/default.json index c2ba8b601442..6e0f79948d52 100644 --- a/opencti-platform/opencti-graphql/config/default.json +++ b/opencti-platform/opencti-graphql/config/default.json @@ -4,7 +4,7 @@ "base_path": "", "base_url": "http://localhost:4000/", "enabled": true, - "disabled_dev_features": ["PUBLIC_DASHBOARD", "LOGICAL_DELETION"], + "disabled_dev_features": ["LOGICAL_DELETION"], "https_cert": { "ca": [], "key": null, diff --git a/opencti-platform/opencti-graphql/config/schema/opencti.graphql b/opencti-platform/opencti-graphql/config/schema/opencti.graphql index d0ad6a965df8..372a4f8b54e3 100644 --- a/opencti-platform/opencti-graphql/config/schema/opencti.graphql +++ b/opencti-platform/opencti-graphql/config/schema/opencti.graphql @@ -1108,6 +1108,7 @@ type Settings implements InternalObject & BasicObject { parent_types: [String!]! @auth # Settings platform_organization: Organization + platform_data_sharing_max_markings: [MarkingDefinition!] platform_title: String platform_favicon: String platform_email: String @auth diff --git a/opencti-platform/opencti-graphql/src/database/data-initialization.js b/opencti-platform/opencti-graphql/src/database/data-initialization.js index fbad13653f69..fb592f63f4b5 100644 --- a/opencti-platform/opencti-graphql/src/database/data-initialization.js +++ b/opencti-platform/opencti-graphql/src/database/data-initialization.js @@ -69,16 +69,16 @@ export const CAPABILITIES = [ KNOWLEDGE_CAPABILITIES, { name: 'EXPLORE', - description: 'Access exploration', + description: 'Access Dashboards and investigations', attribute_order: 1000, dependencies: [ { name: 'EXUPDATE', - description: 'Create / Update exploration', + description: 'Create / Update Dashboards and investigations', attribute_order: 1100, dependencies: [ - { name: 'EXDELETE', description: 'Delete exploration', attribute_order: 1200 }, - { name: 'PUBLISH', description: 'Publish exploration', attribute_order: 1300 }, + { name: 'EXDELETE', description: 'Delete Dashboards and investigations', attribute_order: 1200 }, + { name: 'PUBLISH', description: 'Manage Public Dashboards', attribute_order: 1300 }, ], }, ], diff --git a/opencti-platform/opencti-graphql/src/domain/settings.js b/opencti-platform/opencti-graphql/src/domain/settings.js index 581dad5182db..81a6f748ec4e 100644 --- a/opencti-platform/opencti-graphql/src/domain/settings.js +++ b/opencti-platform/opencti-graphql/src/domain/settings.js @@ -11,11 +11,13 @@ import { isUserHasCapability, SETTINGS_SET_ACCESSES, SYSTEM_USER } from '../util import { storeLoadById } from '../database/middleware-loader'; import { INTERNAL_SECURITY_PROVIDER, PROVIDERS } from '../config/providers'; import { publishUserAction } from '../listener/UserActionListener'; -import { getEntityFromCache } from '../database/cache'; +import { getEntitiesListFromCache, getEntitiesMapFromCache, getEntityFromCache } from '../database/cache'; import { now } from '../utils/format'; import { generateInternalId } from '../schema/identifier'; import { UnsupportedError } from '../config/errors'; import { isEmptyField, isNotEmptyField } from '../database/utils'; +import { ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject'; +import { computeAvailableMarkings } from './user'; export const getMemoryStatistics = () => { return { ...process.memoryUsage(), ...getHeapStatistics() }; @@ -197,3 +199,24 @@ export const getCriticalAlerts = async (context, user) => { // no alert return []; }; + +/** + * Retrieves max level of markings that can be shared. + * @param context + * @param user + * @param settings + * @returns {Promise} + */ +export const getDataSharingMaxMarkings = async (context, user, settings = undefined) => { + const { platform_data_sharing_max_markings } = settings ?? await getEntityFromCache(context, user, ENTITY_TYPE_SETTINGS); + const dataSharingMaxMarkings = platform_data_sharing_max_markings ?? []; + const allMarkingsMap = await getEntitiesMapFromCache(context, SYSTEM_USER, ENTITY_TYPE_MARKING_DEFINITION); + return dataSharingMaxMarkings.map((m) => allMarkingsMap.get(m) ?? m); +}; + +// Retrieves all available markings than can be shared. +export const getAvailableDataSharingMarkings = async (context, user) => { + const maxMarkings = await getDataSharingMaxMarkings(context, user); + const allMarkings = await getEntitiesListFromCache(context, SYSTEM_USER, ENTITY_TYPE_MARKING_DEFINITION); + return computeAvailableMarkings(maxMarkings, allMarkings); +}; diff --git a/opencti-platform/opencti-graphql/src/domain/stixCoreRelationship.js b/opencti-platform/opencti-graphql/src/domain/stixCoreRelationship.js index bff877fd8ee8..33c9c3956325 100644 --- a/opencti-platform/opencti-graphql/src/domain/stixCoreRelationship.js +++ b/opencti-platform/opencti-graphql/src/domain/stixCoreRelationship.js @@ -32,7 +32,7 @@ export const stixCoreRelationshipsDistribution = async (context, user, args) => if (isEmptyDynamic) { return []; } - return distributionRelations(context, context.user, dynamicArgs); + return distributionRelations(context, user, dynamicArgs); }; export const stixCoreRelationshipsNumber = async (context, user, args) => { const { relationship_type = [ABSTRACT_STIX_CORE_RELATIONSHIP], authorId } = args; diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index b727fb39a305..1d795e73e82f 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -17632,6 +17632,7 @@ export type PublicDashboard = BasicObject & InternalObject & { dashboard_id?: Maybe; description?: Maybe; editContext?: Maybe>; + enabled: Scalars['Boolean']['output']; entity_type: Scalars['String']['output']; id: Scalars['ID']['output']; name: Scalars['String']['output']; @@ -17648,7 +17649,9 @@ export type PublicDashboardAddInput = { allowed_markings_ids?: InputMaybe>; dashboard_id: Scalars['String']['input']; description?: InputMaybe; + enabled: Scalars['Boolean']['input']; name: Scalars['String']['input']; + uri_key: Scalars['String']['input']; }; export type PublicDashboardConnection = { @@ -21570,6 +21573,7 @@ export type Settings = BasicObject & InternalObject & { platform_consent_confirm_text?: Maybe; platform_consent_message?: Maybe; platform_critical_alerts: Array; + platform_data_sharing_max_markings?: Maybe>; platform_demo?: Maybe; platform_email?: Maybe; platform_favicon?: Maybe; @@ -35347,6 +35351,7 @@ export type PublicDashboardResolvers, ParentType, ContextType>; description?: Resolver, ParentType, ContextType>; editContext?: Resolver>, ParentType, ContextType>; + enabled?: Resolver; entity_type?: Resolver; id?: Resolver; name?: Resolver; @@ -36181,6 +36186,7 @@ export type SettingsResolvers, ParentType, ContextType>; platform_consent_message?: Resolver, ParentType, ContextType>; platform_critical_alerts?: Resolver, ParentType, ContextType>; + platform_data_sharing_max_markings?: Resolver>, ParentType, ContextType>; platform_demo?: Resolver, ParentType, ContextType>; platform_email?: Resolver, ParentType, ContextType>; platform_favicon?: Resolver, ParentType, ContextType>; diff --git a/opencti-platform/opencti-graphql/src/manager/cacheManager.ts b/opencti-platform/opencti-graphql/src/manager/cacheManager.ts index da3cd0cb703b..29bdd7c4b5ea 100644 --- a/opencti-platform/opencti-graphql/src/manager/cacheManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/cacheManager.ts @@ -199,6 +199,7 @@ const platformPublicDashboards = (context: AuthContext) => { publicDashboardsForCache.push( { id: dash.id, + enabled: dash.enabled, internal_id: dash.internal_id, uri_key: dash.uri_key, dashboard_id: dash.dashboard_id, diff --git a/opencti-platform/opencti-graphql/src/migrations/1704893567321-capability-publish-knowledge.js b/opencti-platform/opencti-graphql/src/migrations/1704893567321-capability-publish-knowledge.js deleted file mode 100644 index 563bcf6646f2..000000000000 --- a/opencti-platform/opencti-graphql/src/migrations/1704893567321-capability-publish-knowledge.js +++ /dev/null @@ -1,16 +0,0 @@ -import { executionContext, SYSTEM_USER } from '../utils/access'; -import { addCapability } from '../domain/grant'; - -export const up = async (next) => { - const context = executionContext('migration'); - await addCapability(context, SYSTEM_USER, { - name: 'EXPLORE_EXUPDATE_PUBLISH', - description: 'Publish exploration', - attribute_order: 1300 - }); - next(); -}; - -export const down = async (next) => { - next(); -}; diff --git a/opencti-platform/opencti-graphql/src/migrations/1713449762000-investigations-and-dashboards-capabilities.js b/opencti-platform/opencti-graphql/src/migrations/1713449762000-investigations-and-dashboards-capabilities.js new file mode 100644 index 000000000000..40872af65c81 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/migrations/1713449762000-investigations-and-dashboards-capabilities.js @@ -0,0 +1,37 @@ +import { executionContext, SYSTEM_USER } from '../utils/access'; +import { addCapability } from '../domain/grant'; +import { elLoadById, elReplace } from '../database/engine'; + +export const up = async (next) => { + const context = executionContext('migration'); + // ------ Add or rename Manage Public Dashboards capability + const managePublicDashboardsCapability = await elLoadById(context, SYSTEM_USER, 'capability--40892bfe-13c2-5e3e-96a3-531760950451'); + if (managePublicDashboardsCapability) { + const managePublicDashboardsCapabilityPatch = { description: 'Manage Public Dashboards' }; + await elReplace(managePublicDashboardsCapability._index, managePublicDashboardsCapability.internal_id, { doc: managePublicDashboardsCapabilityPatch }); + } else { + await addCapability(context, SYSTEM_USER, { + name: 'EXPLORE_EXUPDATE_PUBLISH', + description: 'Manage Public Dashboards', + attribute_order: 1300 + }); + } + + // ------ Access exploration renaming + const accessCapability = await elLoadById(context, SYSTEM_USER, 'capability--c2f6d8be-29c7-5e7f-ab17-dfa14a349025'); + const accessCapabilityPatch = { description: 'Access Dashboards and investigations' }; + await elReplace(accessCapability._index, accessCapability.internal_id, { doc: accessCapabilityPatch }); + // ------ Create / Update exploration renaming + const updateCapability = await elLoadById(context, SYSTEM_USER, 'capability--722e8727-5e8a-5b5e-8c1e-3b71b8415170'); + const updateCapabilityPatch = { description: 'Create / Update Dashboards and investigations' }; + await elReplace(updateCapability._index, updateCapability.internal_id, { doc: updateCapabilityPatch }); + // ------ Delete exploration renaming + const deleteCapability = await elLoadById(context, SYSTEM_USER, 'capability--287d57a8-5e9a-573f-8f22-ea321f5cbc90'); + const deleteCapabilityPatch = { description: 'Delete Dashboards and investigations' }; + await elReplace(deleteCapability._index, deleteCapability.internal_id, { doc: deleteCapabilityPatch }); + next(); +}; + +export const down = async (next) => { + next(); +}; diff --git a/opencti-platform/opencti-graphql/src/migrations/1713449762001-add-platform-data-sharing-max-markings.js b/opencti-platform/opencti-graphql/src/migrations/1713449762001-add-platform-data-sharing-max-markings.js new file mode 100644 index 000000000000..31dfe747b101 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/migrations/1713449762001-add-platform-data-sharing-max-markings.js @@ -0,0 +1,18 @@ +import { executionContext, SYSTEM_USER } from '../utils/access'; +import { getSettings, settingsEditField } from '../domain/settings'; +import { logApp } from '../config/conf'; + +export const up = async (next) => { + logApp.info('[MIGRATION] Add platform data sharing max markings'); + const context = executionContext('migration'); + // ------ Add platform_data_sharing_max_markings + const settings = await getSettings(context); + const patch = [{ key: 'platform_data_sharing_max_markings', value: [] }]; + await settingsEditField(context, SYSTEM_USER, settings.id, patch); + logApp.info('[MIGRATION] Add platform data sharing max markings done.'); + next(); +}; + +export const down = async (next) => { + next(); +}; diff --git a/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts b/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts index 84fc598e7ae0..0a64a5a20c0d 100644 --- a/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts +++ b/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts @@ -160,6 +160,7 @@ const internalObjectsAttributes: { [k: string]: Array } = { [ENTITY_TYPE_SETTINGS]: [ { name: 'platform_title', label: 'Platform title', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, { name: 'platform_organization', label: 'Platform organization', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, + { name: 'platform_data_sharing_max_markings', label: 'Platform data sharing max markings', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: true, upsert: false, isFilterable: false }, { name: 'platform_favicon', label: 'Platform favicon', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, { name: 'platform_email', label: 'Platform email', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, { name: 'platform_theme', label: 'Theme', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-converter.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-converter.ts index 3398f35c228e..48d955eeda8e 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-converter.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-converter.ts @@ -7,6 +7,7 @@ const convertPublicDashboardToStix = (instance: StoreEntityPublicDashboard): Sti return { ...stixDomainObject, name: instance.name, + enabled: instance.enabled, description: instance.description, dashboard_id: instance.dashboard_id, user_id: instance.user_id, diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-domain.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-domain.ts index b6e36793e7cb..b116bf737d85 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-domain.ts @@ -1,4 +1,3 @@ -import { v4 as uuidv4 } from 'uuid'; import { Promise as BluePromise } from 'bluebird'; import type { AuthContext, AuthUser } from '../../types/user'; import { internalLoadById, listEntitiesPaginated, storeLoadById } from '../../database/middleware-loader'; @@ -7,7 +6,7 @@ import { createEntity, deleteElementById, loadEntity, updateAttribute } from '.. import { type BasicStoreEntityWorkspace } from '../workspace/workspace-types'; import { fromBase64, isNotEmptyField, toBase64 } from '../../database/utils'; import { notify } from '../../database/redis'; -import { BUS_TOPICS } from '../../config/conf'; +import { BUS_TOPICS, logApp } from '../../config/conf'; import { type EditInput, type FilterGroup, @@ -25,8 +24,8 @@ import { type QueryPublicStixRelationshipsMultiTimeSeriesArgs, type QueryPublicStixRelationshipsNumberArgs } from '../../generated/graphql'; -import { FunctionalError, UnsupportedError } from '../../config/errors'; -import { SYSTEM_USER } from '../../utils/access'; +import { ForbiddenAccess, FunctionalError, UnsupportedError } from '../../config/errors'; +import { getUserAccessRight, MEMBER_ACCESS_RIGHT_ADMIN, SYSTEM_USER } from '../../utils/access'; import { publishUserAction } from '../../listener/UserActionListener'; import { initializeAuthorizedMembers } from '../workspace/workspace-domain'; import { ENTITY_TYPE_MARKING_DEFINITION } from '../../schema/stixMetaObject'; @@ -46,6 +45,7 @@ import { bookmarks } from '../../domain/user'; import { daysAgo } from '../../utils/format'; import { isStixCoreObject } from '../../schema/stixCoreObject'; import { ES_MAX_CONCURRENCY } from '../../database/engine'; +import { getAvailableDataSharingMarkings } from '../../domain/settings'; export const findById = ( context: AuthContext, @@ -77,6 +77,7 @@ export const getPublicDashboardByUriKey = ( context: AuthContext, uri_key: string, ) => { + logApp.info('[OPENCTI] Public dashboard - trying to fetch public dashboard with URI KEY', { uri_key }); return loadEntity( context, SYSTEM_USER, @@ -122,7 +123,18 @@ export const addPublicDashboard = async ( throw FunctionalError('Cannot find dashboard'); } if (!dashboard.manifest) { - throw FunctionalError('Cannot published empty dashboard'); + throw FunctionalError('Cannot publish an empty dashboard'); + } + + const access = getUserAccessRight(user, dashboard); + if (access !== MEMBER_ACCESS_RIGHT_ADMIN) { + throw ForbiddenAccess('You are not allowed to do this.'); + } + + const uriKey = input.uri_key.replace(/[^a-zA-Z0-9\s-]+/g, '').replace(/\s+/g, '-'); + const existingDashboard = await getPublicDashboardByUriKey(context, uriKey); + if (existingDashboard) { + throw FunctionalError(`Cannot publish this dashboard, uri key ${uriKey} already used.`); } const parsedManifest = JSON.parse(fromBase64(dashboard.manifest) ?? '{}'); @@ -151,15 +163,29 @@ export const addPublicDashboard = async ( [{ id: user.id, access_right: 'admin' }, { id: 'ALL', access_right: 'view' }], user, ); + + // check platform data sharing max markings + const availableDataSharingMarkingIds = (await getAvailableDataSharingMarkings(context, SYSTEM_USER)).map((m) => m.id); + if (input.allowed_markings_ids?.some((id) => !availableDataSharingMarkingIds.includes(id))) { + throw UnsupportedError('Invalid markings'); + } + + // check user allowed markings + const userMarkingIds = user.allowed_marking.map((m) => m.id); + if (input.allowed_markings_ids?.some((id) => !userMarkingIds.includes(id))) { + throw UnsupportedError('Not allowed markings'); + } + // Create publicDashboard const publicDashboardToCreate = { name: input.name, + enabled: input.enabled, description: input.description, public_manifest: publicManifest, private_manifest: dashboard.manifest, dashboard_id: input.dashboard_id, user_id: user.id, - uri_key: uuidv4(), + uri_key: uriKey, authorized_members: authorizedMembers, allowed_markings_ids: input.allowed_markings_ids, }; @@ -193,11 +219,6 @@ export const publicDashboardEditField = async ( id: string, input: EditInput[], ) => { - const invalidInput = input.some((item: EditInput) => item.key !== 'name' && item.key !== 'uri_key'); - if (invalidInput) { - throw UnsupportedError('Only name and uri_key can be updated'); - } - const { element } = await updateAttribute( context, user, @@ -247,6 +268,8 @@ export const publicDashboardDelete = async (context: AuthContext, user: AuthUser // heatmap & vertical-bar & line & area export const publicStixCoreObjectsMultiTimeSeries = async (context: AuthContext, args: QueryPublicStixCoreObjectsMultiTimeSeriesArgs) => { const { user, parameters, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; + const timeSeriesParameters = dataSelection.map((selection) => { const filters = { filterGroups: [selection.filters], @@ -275,6 +298,8 @@ export const publicStixRelationshipsMultiTimeSeries = async ( args: QueryPublicStixRelationshipsMultiTimeSeriesArgs, ) => { const { user, parameters, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; + const timeSeriesParameters = dataSelection.map((selection) => { const filters = { filterGroups: [selection.filters], @@ -305,6 +330,7 @@ export const publicStixCoreObjectsNumber = async ( args: QueryPublicStixCoreObjectsNumberArgs ): Promise => { const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; const selection = dataSelection[0]; const { filters } = selection; @@ -328,6 +354,7 @@ export const publicStixRelationshipsNumber = async ( args: QueryPublicStixRelationshipsNumberArgs ): Promise => { const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; const selection = dataSelection[0]; const { filters } = selection; @@ -351,6 +378,7 @@ export const publicStixCoreObjectsDistribution = async ( args: QueryPublicStixCoreObjectsDistributionArgs ) => { const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; const mainSelection = dataSelection[0]; const breakdownSelection = dataSelection[1]; @@ -517,6 +545,7 @@ export const publicBookmarks = async ( args: QueryPublicBookmarksArgs ) => { const { user, dataSelection } = await getWidgetArguments(context, args.uriKey, args.widgetId); + context.user = user; const selection = dataSelection[0]; const { filters } = selection; diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-types.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-types.ts index 0cdc3010b94e..4e409327168f 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-types.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-types.ts @@ -9,6 +9,7 @@ export const ENTITY_TYPE_PUBLIC_DASHBOARD = 'PublicDashboard'; // region Database types export interface BasicStoreEntityPublicDashboard extends BasicStoreEntity { name: string; + enabled: boolean; description: string; dashboard_id: string; user_id: string; @@ -22,6 +23,7 @@ export interface BasicStoreEntityPublicDashboard extends BasicStoreEntity { export interface StoreEntityPublicDashboard extends StoreEntity { name: string; + enabled: boolean; description: string; dashboard_id: string; user_id: string; @@ -74,6 +76,7 @@ export interface PublicDashboardCachedWidget { export interface PublicDashboardCached { id: string; + enabled: boolean; internal_id: string; uri_key: string; dashboard_id: string; @@ -94,6 +97,7 @@ export interface PublicDashboardCached { // region Stix type export interface StixPublicDashboard extends StixDomainObject { name: string; + enabled: boolean; description: string; dashboard_id: string; user_id: string; diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-utils.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-utils.ts index 1f4e6c681ae2..b4c4c06a6cfd 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-utils.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard-utils.ts @@ -8,6 +8,58 @@ import { computeAvailableMarkings } from '../../domain/user'; import type { StoreMarkingDefinition } from '../../types/store'; import { ENTITY_TYPE_MARKING_DEFINITION } from '../../schema/stixMetaObject'; import { elLoadById } from '../../database/engine'; +import { getDataSharingMaxMarkings } from '../../domain/settings'; +import { cleanMarkings } from '../../utils/markingDefinition-utils'; + +/** + * Find which markings should be used when searching the data to populate in the widgets. + * + * @param context + * @param publicDashboard The one we want to retrieve data for its widgets. + * @param userAuthorPublicDashboard The user who creates the public dashboard. + */ +export const findWidgetsMaxMarkings = async ( + context: AuthContext, + publicDashboard: PublicDashboardCached, + userAuthorPublicDashboard: AuthUser +) => { + // To find max markings allowed for widgets we keep the intersection of markings from: + // - Max markings for data sharing defined by the admin of the platform, + // - Max markings of the public dashboard defined by the user who created it, + // - Max markings of the user who created it. + // (The last case is necessary if the user has lost markings between the time they create the public + // dashboard and the time someone access the public dashboard). + const dataSharingMaxMarkings = await getDataSharingMaxMarkings(context, SYSTEM_USER); + const dashboardMaxMarkings = publicDashboard.allowed_markings; + // Call of cleanMarkings to keep only the max for each type. + const userMaxMarkings = await cleanMarkings(context, userAuthorPublicDashboard.allowed_marking); + + const widgetsMaxMarkingsMap: Record = {}; + [...dataSharingMaxMarkings, ...dashboardMaxMarkings, ...userMaxMarkings] + // To be acceptable, a type should be present in all of the three arrays. + .filter((marking) => { + return ( + dataSharingMaxMarkings.some((m) => m.definition_type === marking.definition_type) + && dashboardMaxMarkings.some((m) => m.definition_type === marking.definition_type) + && userMaxMarkings.some((m) => m.definition_type === marking.definition_type) + ); + }) + .forEach((marking) => { + const saveMarking = widgetsMaxMarkingsMap[marking.definition_type]; + // Keep the min order for each type of markings. + if (!saveMarking || saveMarking.x_opencti_order > marking.x_opencti_order) { + widgetsMaxMarkingsMap[marking.definition_type] = marking; + } + }); + + // Return the list of all available markings from the max markings determined above. + const allMarkings = await getEntitiesListFromCache( + context, + SYSTEM_USER, + ENTITY_TYPE_MARKING_DEFINITION + ); + return computeAvailableMarkings(Object.values(widgetsMaxMarkingsMap), allMarkings); +}; interface WidgetArguments { user: AuthUser, @@ -26,8 +78,11 @@ export const getWidgetArguments = async ( if (!publicDashboard) { throw UnsupportedError('Dashboard not found'); } + if (!publicDashboard.enabled) { + throw UnsupportedError('Dashboard not enabled'); + } - const { user_id, private_manifest, allowed_markings }: PublicDashboardCached = publicDashboard; + const { user_id, private_manifest }: PublicDashboardCached = publicDashboard; // Get user that creates the public dashboard from cache const platformUsersMap = await getEntitiesMapFromCache(context, SYSTEM_USER, ENTITY_TYPE_USER); @@ -36,16 +91,21 @@ export const getWidgetArguments = async ( throw UnsupportedError('User not found'); } - // To replace User markings by publicDashboard allowed_markings - const allMarkings = await getEntitiesListFromCache(context, SYSTEM_USER, ENTITY_TYPE_MARKING_DEFINITION); + // Determine the marking definitions allowed. + const allowedMaxMarkings = await findWidgetsMaxMarkings(context, publicDashboard, platformUser); + // To replace User capabilities by KNOWLEDGE capability - const accessKnowledgeCapability: UserCapability = await elLoadById(context, SYSTEM_USER, 'capability--cbc68f4b-1d0c-51f6-a1b9-10344503b493') as unknown as UserCapability; + const accessKnowledgeCapability: UserCapability = await elLoadById( + context, + SYSTEM_USER, + 'capability--cbc68f4b-1d0c-51f6-a1b9-10344503b493' + ) as unknown as UserCapability; // Construct a fake user to be able to call private API const user = { ...platformUser, origin: { user_id: platformUser.id, referer: 'public-dashboard' }, - allowed_marking: computeAvailableMarkings(allowed_markings, allMarkings), // TODO what if user is downgraded ?? + allowed_marking: allowedMaxMarkings, capabilities: [accessKnowledgeCapability] }; diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.graphql b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.graphql index ab73497c024c..20b1461d586c 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.graphql +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.graphql @@ -17,6 +17,7 @@ type PublicDashboard implements InternalObject & BasicObject { updated_at: DateTime @auth(for: [KNOWLEDGE, EXPLORE]) editContext: [EditUserContext!] @auth(for: [KNOWLEDGE, EXPLORE]) authorized_members: [MemberAccess!] @auth(for: [KNOWLEDGE, EXPLORE]) + enabled: Boolean! } type PublicDistribution { @@ -114,9 +115,11 @@ type PublicDashboardEdge { # Mutation input PublicDashboardAddInput { name: String! + uri_key: String! description: String dashboard_id: String! allowed_markings_ids: [String!] + enabled: Boolean! } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.ts b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.ts index 41e3ee686f12..4fb381bbb25b 100644 --- a/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.ts +++ b/opencti-platform/opencti-graphql/src/modules/publicDashboard/publicDashboard.ts @@ -20,12 +20,13 @@ export const PUBLIC_DASHBOARD_DEFINITION: ModuleDefinition Number(nconf.get('app:session_idle_timeout')), platform_session_timeout: () => Number(nconf.get('app:session_timeout')), platform_organization: (settings, __, context) => findById(context, context.user, settings.platform_organization), + platform_data_sharing_max_markings: (settings, __, context) => getDataSharingMaxMarkings(context, context.user, settings), platform_critical_alerts: (_, __, context) => getCriticalAlerts(context, context.user), activity_listeners: (settings, __, context) => internalFindByIds(context, context.user, settings.activity_listeners_ids), otp_mandatory: (settings) => settings.otp_mandatory ?? false, diff --git a/opencti-platform/opencti-graphql/tests/01-unit/domain/publicDashboard-utils-test.ts b/opencti-platform/opencti-graphql/tests/01-unit/domain/publicDashboard-utils-test.ts new file mode 100644 index 000000000000..afab647447bc --- /dev/null +++ b/opencti-platform/opencti-graphql/tests/01-unit/domain/publicDashboard-utils-test.ts @@ -0,0 +1,149 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import * as settingsModule from '../../../src/domain/settings'; +import { testContext } from '../../utils/testQuery'; +import { findWidgetsMaxMarkings } from '../../../src/modules/publicDashboard/publicDashboard-utils'; +import type { PublicDashboardCached } from '../../../src/modules/publicDashboard/publicDashboard-types'; +import type { AuthUser } from '../../../src/types/user'; +import * as cacheModule from '../../../src/database/cache'; +import type { BasicStoreIdentifier, StoreMarkingDefinition } from '../../../src/types/store'; + +const TLP_AMBER = { + id: 'tlp_amber', + definition_type: 'TLP', + definition: 'TLP:AMBER', + x_opencti_order: 3 +} as unknown as StoreMarkingDefinition; +const TLP_GREEN = { + id: 'tlp_green', + definition_type: 'TLP', + definition: 'TLP:GREEN', + x_opencti_order: 2 +} as unknown as StoreMarkingDefinition; +const TLP_CLEAR = { + id: 'tlp_clear', + definition_type: 'TLP', + definition: 'TLP:CLEAR', + x_opencti_order: 1 +} as unknown as StoreMarkingDefinition; + +const TEST_AMBER = { + id: 'test_amber', + definition_type: 'TEST', + definition: 'TEST:AMBER', + x_opencti_order: 3 +} as unknown as StoreMarkingDefinition; +const TEST_GREEN = { + id: 'test_green', + definition_type: 'TEST', + definition: 'TEST:GREEN', + x_opencti_order: 2 +} as unknown as StoreMarkingDefinition; +const TEST_CLEAR = { + id: 'test_clear', + definition_type: 'TEST', + definition: 'TEST:CLEAR', + x_opencti_order: 1 +} as unknown as StoreMarkingDefinition; + +const PUBLIC_DASHBOARD = { + uri_key: 'my-super-dashboard', + allowed_markings: [TLP_GREEN, TEST_GREEN], +} as unknown as PublicDashboardCached; + +const AUTHOR_DASHBOARD = { + name: 'Jean', + allowed_marking: [TLP_GREEN, TEST_GREEN] +} as unknown as AuthUser; + +describe('publicDashboard-utils', () => { + describe('findWidgetsMaxMarkings', () => { + beforeAll(() => { + vi.spyOn(settingsModule, 'getDataSharingMaxMarkings') + .mockImplementation(async () => [TLP_GREEN, TEST_GREEN]); + vi.spyOn(cacheModule, 'getEntitiesListFromCache') + .mockImplementation(async () => [ + TLP_AMBER, TLP_GREEN, TLP_CLEAR, + TEST_AMBER, TEST_GREEN, TEST_CLEAR + ] as unknown as BasicStoreIdentifier[]); + }); + afterAll(() => { + vi.resetAllMocks(); + }); + + it('should return the correspond marking if all the same', async () => { + const markings = await findWidgetsMaxMarkings(testContext, PUBLIC_DASHBOARD, AUTHOR_DASHBOARD); + const ids = markings.map((marking) => marking.id); + expect(ids).not.toContain('tlp_amber'); + expect(ids).toContain('tlp_green'); + expect(ids).toContain('tlp_clear'); + expect(ids).not.toContain('test_amber'); + expect(ids).toContain('test_green'); + expect(ids).toContain('test_clear'); + }); + + it('should return data sharing marking if data sharing is the min', async () => { + PUBLIC_DASHBOARD.allowed_markings = [TLP_AMBER, TEST_AMBER]; + AUTHOR_DASHBOARD.allowed_marking = [TLP_AMBER, TEST_AMBER]; + const markings = await findWidgetsMaxMarkings(testContext, PUBLIC_DASHBOARD, AUTHOR_DASHBOARD); + const ids = markings.map((marking) => marking.id); + expect(ids).not.toContain('tlp_amber'); + expect(ids).toContain('tlp_green'); + expect(ids).toContain('tlp_clear'); + expect(ids).not.toContain('test_amber'); + expect(ids).toContain('test_green'); + expect(ids).toContain('test_clear'); + }); + + it('should return dashboard marking if dashboard is the min', async () => { + PUBLIC_DASHBOARD.allowed_markings = [TLP_CLEAR, TEST_CLEAR]; + AUTHOR_DASHBOARD.allowed_marking = [TLP_AMBER, TEST_AMBER]; + const markings = await findWidgetsMaxMarkings(testContext, PUBLIC_DASHBOARD, AUTHOR_DASHBOARD); + const ids = markings.map((marking) => marking.id); + expect(ids).not.toContain('tlp_amber'); + expect(ids).not.toContain('tlp_green'); + expect(ids).toContain('tlp_clear'); + expect(ids).not.toContain('test_amber'); + expect(ids).not.toContain('test_green'); + expect(ids).toContain('test_clear'); + }); + + it('should return user marking if user is the min', async () => { + PUBLIC_DASHBOARD.allowed_markings = [TLP_AMBER, TEST_AMBER]; + AUTHOR_DASHBOARD.allowed_marking = [TLP_GREEN, TEST_CLEAR]; + const markings = await findWidgetsMaxMarkings(testContext, PUBLIC_DASHBOARD, AUTHOR_DASHBOARD); + const ids = markings.map((marking) => marking.id); + expect(ids).not.toContain('tlp_amber'); + expect(ids).toContain('tlp_green'); + expect(ids).toContain('tlp_clear'); + expect(ids).not.toContain('test_amber'); + expect(ids).not.toContain('test_green'); + expect(ids).toContain('test_clear'); + }); + + it('should return no marking for a type if dashboard does not contain any', async () => { + PUBLIC_DASHBOARD.allowed_markings = [TLP_AMBER]; + AUTHOR_DASHBOARD.allowed_marking = [TLP_GREEN, TEST_GREEN]; + const markings = await findWidgetsMaxMarkings(testContext, PUBLIC_DASHBOARD, AUTHOR_DASHBOARD); + const ids = markings.map((marking) => marking.id); + expect(ids).not.toContain('tlp_amber'); + expect(ids).toContain('tlp_green'); + expect(ids).toContain('tlp_clear'); + expect(ids).not.toContain('test_amber'); + expect(ids).not.toContain('test_green'); + expect(ids).not.toContain('test_clear'); + }); + + it('should return no marking for a type if user does not contain any', async () => { + PUBLIC_DASHBOARD.allowed_markings = [TLP_GREEN, TEST_GREEN]; + AUTHOR_DASHBOARD.allowed_marking = []; + const markings = await findWidgetsMaxMarkings(testContext, PUBLIC_DASHBOARD, AUTHOR_DASHBOARD); + const ids = markings.map((marking) => marking.id); + expect(ids).not.toContain('tlp_amber'); + expect(ids).not.toContain('tlp_green'); + expect(ids).not.toContain('tlp_clear'); + expect(ids).not.toContain('test_amber'); + expect(ids).not.toContain('test_green'); + expect(ids).not.toContain('test_clear'); + }); + }); +}); diff --git a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-test.js b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-test.js index f018094fb515..3b312b80ced9 100644 --- a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-test.js +++ b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/publicDashboard-test.js @@ -1,8 +1,11 @@ import { describe, expect, it, beforeAll, afterAll } from 'vitest'; import gql from 'graphql-tag'; -import { editorQuery, participantQuery, queryAsAdmin } from '../../utils/testQuery'; +import { ADMIN_USER, editorQuery, getUserIdByEmail, participantQuery, queryAsAdmin, USER_EDITOR } from '../../utils/testQuery'; import { toBase64 } from '../../../src/database/utils'; import { PRIVATE_DASHBOARD_MANIFEST } from './publicDashboard-data'; +import { resetCacheForEntity } from '../../../src/database/cache'; +import { ENTITY_TYPE_SETTINGS } from '../../../src/schema/internalObject'; +import { ENTITY_TYPE_PUBLIC_DASHBOARD } from '../../../src/modules/publicDashboard/publicDashboard-types'; const LIST_QUERY = gql` query publicDashboards( @@ -37,6 +40,7 @@ const READ_QUERY = gql` id name uri_key + enabled } } `; @@ -47,6 +51,7 @@ const READ_URI_KEY_QUERY = gql` id name uri_key + enabled } } `; @@ -75,6 +80,25 @@ const UPDATE_QUERY = gql` publicDashboardFieldPatch(id: $id, input: $input) { id name + enabled + } + } +`; + +const UPDATE_MEMBERS_QUERY = gql` + mutation workspaceEditAuthorizedMembers( + $id: ID! + $input: [MemberAccessInput!]! + ) { + workspaceEditAuthorizedMembers(id: $id, input: $input) { + id + name + authorizedMembers { + id + name + entity_type + access_right + } } } `; @@ -100,6 +124,42 @@ const DELETE_PRIVATE_DASHBOARD_QUERY = gql` } `; +const MARKINGS_QUERY = gql` + query markings { + markingDefinitions { + edges { + node { + id + definition + } + } + } + } +`; + +const READ_MAX_MARKINGS_QUERY = gql` + query settingsMaxMarkings { + settings { + id + } + } +`; + +const EDIT_MAX_MARKINGS_QUERY = gql` + mutation edtSettingsMaxMarkings($id: ID!, $input: [EditInput]!) { + settingsEdit(id: $id) { + fieldPatch(input: $input) { + platform_data_sharing_max_markings { + id + definition + definition_type + x_opencti_order + } + } + } + } +`; + describe('PublicDashboard resolver', () => { let privateDashboardInternalId; const publicDashboardName = 'publicDashboard'; @@ -130,8 +190,10 @@ describe('PublicDashboard resolver', () => { // Create the publicDashboard const PUBLICDASHBOARD_TO_CREATE = { input: { - name: 'private dashboard', + name: 'public dashboard', + uri_key: 'public-dashboard', dashboard_id: privateDashboardInternalId, + enabled: true, }, }; const emptyPublicDashboard = await queryAsAdmin({ @@ -141,7 +203,7 @@ describe('PublicDashboard resolver', () => { expect(emptyPublicDashboard).not.toBeNull(); expect(emptyPublicDashboard.errors.length).toEqual(1); - expect(emptyPublicDashboard.errors.at(0).message).toEqual('Cannot published empty dashboard'); + expect(emptyPublicDashboard.errors.at(0).message).toEqual('Cannot publish an empty dashboard'); }); describe('Tests with manifest', () => { @@ -160,13 +222,16 @@ describe('PublicDashboard resolver', () => { describe('PublicDashboard resolver standard behavior', () => { let publicDashboardInternalId; let publicDashboardUriKey; + let userEditorId; - it('User without EXPLORE_EXUPDATE_PUBLISH capability should not create private dashboards', async () => { + it('User without EXPLORE_EXUPDATE_PUBLISH capability should not create public dashboards', async () => { // Create the publicDashboard const PUBLICDASHBOARD2_TO_CREATE = { input: { name: publicDashboardName, + uri_key: publicDashboardName, dashboard_id: privateDashboardInternalId, + enabled: true, }, }; const publicDashboard = await participantQuery({ @@ -179,12 +244,122 @@ describe('PublicDashboard resolver', () => { expect(publicDashboard.errors.at(0).name).toEqual('FORBIDDEN_ACCESS'); }); + it('User with EXPLORE_EXUPDATE_PUBLISH capability but private dashboard view access right cannot create public dashboard', async () => { + // Add editor user in private dashboard authorizedMembers with view access right + userEditorId = await getUserIdByEmail(USER_EDITOR.email); + const authorizedMembersUpdate = [ + { + id: ADMIN_USER.id, + access_right: 'admin', + }, + { + id: userEditorId, + access_right: 'view', + }, + ]; + const authorizedMembersUpdateQuery = await queryAsAdmin({ + query: UPDATE_MEMBERS_QUERY, + variables: { id: privateDashboardInternalId, input: authorizedMembersUpdate }, + }); + expect(authorizedMembersUpdateQuery.data.workspaceEditAuthorizedMembers.authorizedMembers.length).toEqual(2); + + // Create the publicDashboard + const PUBLICDASHBOARD3_TO_CREATE = { + input: { + name: publicDashboardName, + uri_key: publicDashboardName, + dashboard_id: privateDashboardInternalId, + enabled: true, + }, + }; + const queryResult = await editorQuery({ + query: CREATE_QUERY, + variables: PUBLICDASHBOARD3_TO_CREATE, + }); + expect(queryResult).not.toBeNull(); + expect(queryResult.errors.length).toEqual(1); + expect(queryResult.errors.at(0).message).toEqual('You are not allowed to do this.'); + }); + + it('User cannot create public dashboard with marking not in User allowed markings', async () => { + // Get marking + const { data } = await queryAsAdmin({ query: MARKINGS_QUERY, variables: {} }); + const markings = data.markingDefinitions.edges.map((e) => e.node); + const tlpRed = markings.find((m) => m.definition === 'TLP:RED'); + + // Set max markings + const settingsResult = await queryAsAdmin({ query: READ_MAX_MARKINGS_QUERY, variables: {} }); + const settingsId = settingsResult.data.settings.id; + await queryAsAdmin({ + query: EDIT_MAX_MARKINGS_QUERY, + variables: { + id: settingsId, + input: { + key: 'platform_data_sharing_max_markings', + value: [tlpRed.id] + } + }, + }); + resetCacheForEntity(ENTITY_TYPE_SETTINGS); + + // Add security user in private dashboard authorizedMembers with admin access right + const authorizedMembersUpdate = [ + { + id: ADMIN_USER.id, + access_right: 'admin', + }, + { + id: userEditorId, + access_right: 'admin', + }, + ]; + + const authorizedMembersUpdateQuery = await queryAsAdmin({ + query: UPDATE_MEMBERS_QUERY, + variables: { id: privateDashboardInternalId, input: authorizedMembersUpdate }, + }); + expect(authorizedMembersUpdateQuery.data.workspaceEditAuthorizedMembers.authorizedMembers.length).toEqual(2); + + // Try to create public dashboard + const PUBLIC_DASHBOARD_TO_CREATE = { + input: { + name: 'public dashboard ', + uri_key: 'public-dashboard-markings-red', + dashboard_id: privateDashboardInternalId, + allowed_markings_ids: [tlpRed.id], + enabled: true, + }, + }; + + const publicDashboardQuery = await editorQuery({ + query: CREATE_QUERY, + variables: PUBLIC_DASHBOARD_TO_CREATE, + }); + expect(publicDashboardQuery).not.toBeNull(); + expect(publicDashboardQuery.errors.length).toEqual(1); + expect(publicDashboardQuery.errors.at(0).message).toEqual('Not allowed markings'); + + // Reset max markings. + await queryAsAdmin({ + query: EDIT_MAX_MARKINGS_QUERY, + variables: { + id: settingsId, + input: { + key: 'platform_data_sharing_max_markings', + value: [] + } + }, + }); + }); + it('should publicDashboard created', async () => { // Create the publicDashboard const PUBLIC_DASHBOARD_TO_CREATE = { input: { name: publicDashboardName, + uri_key: publicDashboardName, dashboard_id: privateDashboardInternalId, + enabled: true, }, }; const publicDashboard = await queryAsAdmin({ @@ -237,7 +412,7 @@ describe('PublicDashboard resolver', () => { }); expect(queryResult).not.toBeNull(); expect(queryResult.errors.length).toEqual(1); - expect(queryResult.errors.at(0).message).toEqual('Only name and uri_key can be updated'); + expect(queryResult.errors.at(0).message).toEqual('Validation error'); }); it('should update publicDashboard', async () => { @@ -265,6 +440,28 @@ describe('PublicDashboard resolver', () => { expect(queryResult.errors.at(0).message).toEqual('You are not allowed to do this.'); }); + it('should disabled/enabled publicDashboard', async () => { + // Disabled public dashboard + const disabledQueryResult = await queryAsAdmin({ + query: UPDATE_QUERY, + variables: { + id: publicDashboardInternalId, + input: { key: 'enabled', value: false }, + }, + }); + expect(disabledQueryResult.data.publicDashboardFieldPatch.enabled).toEqual(false); + + // Enabled public dashboard + const enabledQueryResult = await queryAsAdmin({ + query: UPDATE_QUERY, + variables: { + id: publicDashboardInternalId, + input: { key: 'enabled', value: true }, + }, + }); + expect(enabledQueryResult.data.publicDashboardFieldPatch.enabled).toEqual(true); + }); + describe('Tests widgets API', () => { let vadorId; let magnetoId; @@ -403,6 +600,55 @@ describe('PublicDashboard resolver', () => { // endregion }); + it('should not return data if disabled publicDashboard', async () => { + // Disabled public dashboard + const disabledQueryResult = await queryAsAdmin({ + query: UPDATE_QUERY, + variables: { + id: publicDashboardInternalId, + input: { key: 'enabled', value: false }, + }, + }); + expect(disabledQueryResult.data.publicDashboardFieldPatch.enabled).toEqual(false); + + const API_SCO_NUMBER_QUERY = gql` + query PublicStixCoreObjectsNumber( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixCoreObjectsNumber( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + total + count + } + } + `; + const { data } = await queryAsAdmin({ + query: API_SCO_NUMBER_QUERY, + variables: { + uriKey: publicDashboardUriKey, + widgetId: 'ebb25410-7048-4de7-9288-704e962215f6' + }, + }); + expect(data.publicStixCoreObjectsNumber).toBeNull(); + + // Enabled public dashboard + const enabledQueryResult = await queryAsAdmin({ + query: UPDATE_QUERY, + variables: { + id: publicDashboardInternalId, + input: { key: 'enabled', value: true }, + }, + }); + expect(enabledQueryResult.data.publicDashboardFieldPatch.enabled).toEqual(true); + }); + it('should return the data for API: SCO Number', async () => { const API_SCO_NUMBER_QUERY = gql` query PublicStixCoreObjectsNumber( @@ -422,6 +668,8 @@ describe('PublicDashboard resolver', () => { } } `; + resetCacheForEntity(ENTITY_TYPE_PUBLIC_DASHBOARD); + const { data } = await queryAsAdmin({ query: API_SCO_NUMBER_QUERY, variables: { @@ -712,8 +960,6 @@ describe('PublicDashboard resolver', () => { expect(publicStixRelationships.edges[0].node.relationship_type).toEqual('targets'); expect(publicStixRelationships.pageInfo.globalCount).toEqual(4); }); - - // TODO add tests for other APIS }); it('should delete publicDashboard', async () => { @@ -732,5 +978,246 @@ describe('PublicDashboard resolver', () => { expect(queryResult.data.publicDashboards.edges.length).toEqual(0); }); }); + + describe('Tests widgets API with markings', () => { + const API_SCR_NUMBER_QUERY = gql` + query PublicStixRelationshipsNumber( + $startDate: DateTime + $endDate: DateTime + $uriKey: String! + $widgetId : String! + ) { + publicStixRelationshipsNumber( + startDate: $startDate + endDate: $endDate + uriKey: $uriKey + widgetId : $widgetId + ) { + total + count + } + } + `; + + let publicDashboardInternalId; + + let tlpClear; + let tlpGreen; + let tlpRed; + + let spainId; + let raditzId; + let vegetaId; + + let settingsId; + + afterAll(async () => { + // region Reset max markings. + await queryAsAdmin({ + query: EDIT_MAX_MARKINGS_QUERY, + variables: { + id: settingsId, + input: { + key: 'platform_data_sharing_max_markings', + value: [] + } + }, + }); + // endregion + // region Delete areas. + const DELETE_AREA = gql` + mutation administrativeAreaDelete($id: ID!) { + administrativeAreaDelete(id: $id) + } + `; + await queryAsAdmin({ + query: DELETE_AREA, + variables: { id: spainId }, + }); + // endregion + // region Delete malwares. + const DELETE_MALWARE = gql` + mutation malwareDelete($id: ID!) { + malwareEdit(id: $id) { + delete + } + } + `; + await queryAsAdmin({ + query: DELETE_MALWARE, + variables: { id: raditzId }, + }); + await queryAsAdmin({ + query: DELETE_MALWARE, + variables: { id: vegetaId }, + }); + // endregion + // region Delete the publicDashboard. + await queryAsAdmin({ + query: DELETE_QUERY, + variables: { id: publicDashboardInternalId }, + }); + // endregion + }); + + beforeAll(async () => { + // region Fetch markings. + const { data } = await queryAsAdmin({ query: MARKINGS_QUERY, variables: {} }); + const markings = data.markingDefinitions.edges.map((e) => e.node); + tlpClear = markings.find((m) => m.definition === 'TLP:CLEAR'); + tlpGreen = markings.find((m) => m.definition === 'TLP:GREEN'); + tlpRed = markings.find((m) => m.definition === 'TLP:RED'); + // endregion + // region Set max markings + const settingsResult = await queryAsAdmin({ query: READ_MAX_MARKINGS_QUERY, variables: {} }); + settingsId = settingsResult.data.settings.id; + await queryAsAdmin({ + query: EDIT_MAX_MARKINGS_QUERY, + variables: { + id: settingsId, + input: { + key: 'platform_data_sharing_max_markings', + value: [tlpRed.id] + } + }, + }); + resetCacheForEntity(ENTITY_TYPE_SETTINGS); + // endregion + // region Create the publicDashboard. + const PUBLIC_DASHBOARD_TO_CREATE = { + input: { + name: 'public dashboard markings', + uri_key: 'public-dashboard-markings', + dashboard_id: privateDashboardInternalId, + allowed_markings_ids: [tlpGreen.id], + enabled: true, + }, + }; + const publicDashboard = await editorQuery({ + query: CREATE_QUERY, + variables: PUBLIC_DASHBOARD_TO_CREATE, + }); + resetCacheForEntity(ENTITY_TYPE_PUBLIC_DASHBOARD); + publicDashboardInternalId = publicDashboard.data.publicDashboardAdd.id; + // endregion + // region Create some areas. + const CREATE_AREA = gql` + mutation AdministrativeAreaAdd($input: AdministrativeAreaAddInput!) { + administrativeAreaAdd(input: $input) { id } + } + `; + const spain = await queryAsAdmin({ + query: CREATE_AREA, + variables: { + input: { + name: 'spain', + description: 'widget tests', + objectMarking: [tlpGreen.id] + } + }, + }); + spainId = spain.data.administrativeAreaAdd.id; + // endregion + // region Create some malwares. + const CREATE_MALWARES = gql` + mutation MalwareAdd($input: MalwareAddInput!) { + malwareAdd(input: $input) { id } + } + `; + const raditz = await queryAsAdmin({ + query: CREATE_MALWARES, + variables: { + input: { + name: 'raditz', + malware_types: ['ddos'], + description: 'widget tests', + objectMarking: [tlpGreen.id] + } + }, + }); + raditzId = raditz.data.malwareAdd.id; + const vegeta = await editorQuery({ + query: CREATE_MALWARES, + variables: { + input: { + name: 'vegeta', + malware_types: ['backdoor'], + description: 'widget tests', + objectMarking: [tlpGreen.id] + } + }, + }); + vegetaId = vegeta.data.malwareAdd.id; + // endregion + // region Create targets relationships between areas and malwares. + const ADD_TARGETS_REL = gql` + mutation StixCoreRelationshipAdd($input: StixCoreRelationshipAddInput!) { + stixCoreRelationshipAdd(input: $input) { id } + } + `; + await editorQuery({ + query: ADD_TARGETS_REL, + variables: { + input: { + relationship_type: 'targets', + fromId: raditzId, + toId: spainId, + objectMarking: [tlpGreen.id] + } + }, + }); + await editorQuery({ + query: ADD_TARGETS_REL, + variables: { + input: { + relationship_type: 'targets', + fromId: vegetaId, + toId: spainId, + objectMarking: [tlpGreen.id] + } + }, + }); + // endregion + }); + + it('should return the data for API: SCR Number', async () => { + const aaa = await queryAsAdmin({ + query: API_SCR_NUMBER_QUERY, + variables: { + uriKey: 'public-dashboard-markings', + widgetId: 'ecb25410-7048-4de7-9288-704e962215f6' + }, + }); + const result = aaa.data.publicStixRelationshipsNumber; + expect(result.total).toEqual(2); + expect(result.count).toEqual(0); + }); + + it('should return the data for API: SCR Number with limited max marking', async () => { + // Change the max marking to something more restrictive. + await queryAsAdmin({ + query: EDIT_MAX_MARKINGS_QUERY, + variables: { + id: settingsId, + input: { + key: 'platform_data_sharing_max_markings', + value: [tlpClear.id] + } + }, + }); + resetCacheForEntity(ENTITY_TYPE_SETTINGS); + + const { data } = await queryAsAdmin({ + query: API_SCR_NUMBER_QUERY, + variables: { + uriKey: 'public-dashboard-markings', + widgetId: 'ecb25410-7048-4de7-9288-704e962215f6' + }, + }); + const result = data.publicStixRelationshipsNumber; + expect(result.total).toEqual(0); + expect(result.count).toEqual(0); + }); + }); }); }); diff --git a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/settings-test.js b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/settings-test.js index ca2a9a680199..b75d6503722c 100644 --- a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/settings-test.js +++ b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/settings-test.js @@ -1,4 +1,4 @@ -import { expect, it, describe } from 'vitest'; +import { expect, it, describe, beforeAll } from 'vitest'; import gql from 'graphql-tag'; import { head } from 'ramda'; import { queryAsAdmin } from '../../utils/testQuery'; @@ -216,3 +216,108 @@ describe('Settings resolver messages behavior', () => { expect(platform_messages.length).toEqual(0); }); }); + +const READ_MAX_MARKINGS_QUERY = gql` + query settingsMaxMarkings { + settings { + id + platform_data_sharing_max_markings { + id + definition + definition_type + x_opencti_order + } + } + } +`; + +const EDIT_MAX_MARKINGS_QUERY = gql` + mutation edtSettingsMaxMarkings($id: ID!, $input: [EditInput]!) { + settingsEdit(id: $id) { + fieldPatch(input: $input) { + platform_data_sharing_max_markings { + id + definition + definition_type + x_opencti_order + } + } + } + } +`; + +describe('Settings resolver - max shareable marking definitions', () => { + let settingsId; + let tlpGreen; + let papGreen; + + beforeAll(async () => { + const MARKINGS_QUERY = gql` + query markings { + markingDefinitions { + edges { + node { + id + definition + } + } + } + } + `; + const { data } = await queryAsAdmin({ query: MARKINGS_QUERY, variables: {} }); + const markings = data.markingDefinitions.edges.map((e) => e.node); + tlpGreen = markings.find((m) => m.definition === 'TLP:GREEN'); + papGreen = markings.find((m) => m.definition === 'PAP:GREEN'); + }); + + it('should have nothing shareable by default', async () => { + const { data } = await queryAsAdmin({ query: READ_MAX_MARKINGS_QUERY, variables: {} }); + settingsId = data.settings.id; + const maxMarkings = data.settings.platform_data_sharing_max_markings; + expect(maxMarkings).toEqual([]); + }); + + it('should update max shareable markings', async () => { + const { data } = await queryAsAdmin({ + query: EDIT_MAX_MARKINGS_QUERY, + variables: { + id: settingsId, + input: { + key: 'platform_data_sharing_max_markings', + value: [tlpGreen.id] + } + }, + }); + let maxMarkings = data.settingsEdit.fieldPatch.platform_data_sharing_max_markings; + expect(maxMarkings.length).toEqual(1); + expect(maxMarkings[0].id).toEqual(tlpGreen.id); + + const { data: data2 } = await queryAsAdmin({ + query: EDIT_MAX_MARKINGS_QUERY, + variables: { + id: settingsId, + input: { + key: 'platform_data_sharing_max_markings', + value: [tlpGreen.id, papGreen.id] + } + }, + }); + maxMarkings = data2.settingsEdit.fieldPatch.platform_data_sharing_max_markings; + expect(maxMarkings.length).toEqual(2); + expect(maxMarkings[0].id).toEqual(tlpGreen.id); + expect(maxMarkings[1].id).toEqual(papGreen.id); + + const { data: data3 } = await queryAsAdmin({ + query: EDIT_MAX_MARKINGS_QUERY, + variables: { + id: settingsId, + input: { + key: 'platform_data_sharing_max_markings', + value: [] + } + }, + }); + maxMarkings = data3.settingsEdit.fieldPatch.platform_data_sharing_max_markings; + expect(maxMarkings.length).toEqual(0); + }); +}); diff --git a/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js b/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js index 5d57c196572a..621bfec95550 100644 --- a/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js +++ b/opencti-platform/opencti-graphql/tests/03-streams/00-Raw/raw-test.js @@ -24,8 +24,8 @@ describe('Raw streams tests', () => { expect(createEventsByTypes['course-of-action'].length).toBe(3); expect(createEventsByTypes.label.length).toBe(15); expect(createEventsByTypes.identity.length).toBe(31); - expect(createEventsByTypes.location.length).toBe(14); - expect(createEventsByTypes.relationship.length).toBe(130); + expect(createEventsByTypes.location.length).toBe(15); + expect(createEventsByTypes.relationship.length).toBe(132); expect(createEventsByTypes.sighting.length).toBe(4); expect(createEventsByTypes.indicator.length).toBe(33); expect(createEventsByTypes['attack-pattern'].length).toBe(9); @@ -37,7 +37,7 @@ describe('Raw streams tests', () => { expect(createEventsByTypes['ipv4-addr'].length).toBe(1); expect(createEventsByTypes['data-component'].length).toBe(5); expect(createEventsByTypes['data-source'].length).toBe(1); - expect(createEventsByTypes.malware.length).toBe(42); + expect(createEventsByTypes.malware.length).toBe(44); expect(createEventsByTypes.software.length).toBe(1); expect(createEventsByTypes.file.length).toBe(4); expect(createEventsByTypes.campaign.length).toBe(5); @@ -46,7 +46,7 @@ describe('Raw streams tests', () => { expect(createEventsByTypes.tool.length).toBe(2); expect(createEventsByTypes.vocabulary.length).toBe(335); // 328 created at init + 2 created in tests + 5 vocabulary organizations types expect(createEventsByTypes.vulnerability.length).toBe(7); - expect(createEvents.length).toBe(744); + expect(createEvents.length).toBe(749); for (let createIndex = 0; createIndex < createEvents.length; createIndex += 1) { const { data: insideData, origin, type } = createEvents[createIndex]; expect(origin).toBeDefined(); @@ -95,7 +95,7 @@ describe('Raw streams tests', () => { } // 03 - CHECK DELETE EVENTS const deleteEvents = events.filter((e) => e.type === EVENT_TYPE_DELETE); - expect(deleteEvents.length).toBe(113); + expect(deleteEvents.length).toBe(116); // const deleteEventsByTypes = R.groupBy((e) => e.data.data.type, deleteEvents); for (let delIndex = 0; delIndex < deleteEvents.length; delIndex += 1) { const { data: insideData, origin, type } = deleteEvents[delIndex]; diff --git a/opencti-platform/opencti-graphql/tests/utils/testQuery.ts b/opencti-platform/opencti-graphql/tests/utils/testQuery.ts index 618f4c01a15d..0a150dbcfca9 100644 --- a/opencti-platform/opencti-graphql/tests/utils/testQuery.ts +++ b/opencti-platform/opencti-graphql/tests/utils/testQuery.ts @@ -22,7 +22,7 @@ export const SYNC_LIVE_START_REMOTE_URI = conf.get('app:sync_live_start_remote_u export const SYNC_DIRECT_START_REMOTE_URI = conf.get('app:sync_direct_start_remote_uri'); export const SYNC_RESTORE_START_REMOTE_URI = conf.get('app:sync_restore_start_remote_uri'); export const SYNC_TEST_REMOTE_URI = `http://api-tests:${PORT}`; -export const RAW_EVENTS_SIZE = 1006; +export const RAW_EVENTS_SIZE = 1014; export const SYNC_LIVE_EVENTS_SIZE = 600; export const PYTHON_PATH = './src/python/testing';