diff --git a/opencti-platform/opencti-front/lang/back/de.json b/opencti-platform/opencti-front/lang/back/de.json index 66b67d798297..5922a15472f1 100644 --- a/opencti-platform/opencti-front/lang/back/de.json +++ b/opencti-platform/opencti-front/lang/back/de.json @@ -260,7 +260,11 @@ "Event types": "Ereignistypen", "Event user metadata": "Ereignis-Benutzer-Metadaten", "Excluded IDs": "Ausgeschlossene IDs", + "Exclusion list cache build manager": "Ausschlussliste Cache-Build-Manager", + "Exclusion list cache sync manager": "Ausschlussliste Cache-Sync-Manager", "Exclusion list entity types": "Ausschlussliste Entitätstypen", + "EXCLUSION_LIST_CACHE_BUILD_MANAGER": "Ausschlussliste Cache-Build-Manager", + "EXCLUSION_LIST_CACHE_SYNC_MANAGER": "Ausschlussliste Cache-Sync-Manager", "Expected import number": "Erwartete Importnummer", "Expected number": "Erwartete Anzahl", "Expiration date": "Verfallsdatum", diff --git a/opencti-platform/opencti-front/lang/back/en.json b/opencti-platform/opencti-front/lang/back/en.json index e68c1e756870..518e4086a069 100644 --- a/opencti-platform/opencti-front/lang/back/en.json +++ b/opencti-platform/opencti-front/lang/back/en.json @@ -260,7 +260,11 @@ "Event types": "Event types", "Event user metadata": "Event user metadata", "Excluded IDs": "Excluded IDs", + "Exclusion list cache build manager": "Exclusion list cache build manager", + "Exclusion list cache sync manager": "Exclusion list cache sync manager", "Exclusion list entity types": "Exclusion list entity types", + "EXCLUSION_LIST_CACHE_BUILD_MANAGER": "Exclusion list cache build manager", + "EXCLUSION_LIST_CACHE_SYNC_MANAGER": "Exclusion list cache sync manager", "Expected import number": "Expected import number", "Expected number": "Expected number", "Expiration date": "Expiration date", diff --git a/opencti-platform/opencti-front/lang/back/es.json b/opencti-platform/opencti-front/lang/back/es.json index 40b9e6d29eb5..24efa9209f32 100644 --- a/opencti-platform/opencti-front/lang/back/es.json +++ b/opencti-platform/opencti-front/lang/back/es.json @@ -260,7 +260,11 @@ "Event types": "Tipos de eventos", "Event user metadata": "Metadatos del usuario del incidente", "Excluded IDs": "ID excluidos", + "Exclusion list cache build manager": "Lista de exclusión del gestor de caché", + "Exclusion list cache sync manager": "Lista de exclusión gestor de sincronización de caché", "Exclusion list entity types": "Tipos de entidades de la lista de exclusión", + "EXCLUSION_LIST_CACHE_BUILD_MANAGER": "Lista de exclusión del gestor de caché", + "EXCLUSION_LIST_CACHE_SYNC_MANAGER": "Lista de exclusión gestor de sincronización de caché", "Expected import number": "Número de importación esperado", "Expected number": "Número esperado", "Expiration date": "Fecha de caducidad", diff --git a/opencti-platform/opencti-front/lang/back/fr.json b/opencti-platform/opencti-front/lang/back/fr.json index 4be3f46d4cf8..d80aead399f9 100644 --- a/opencti-platform/opencti-front/lang/back/fr.json +++ b/opencti-platform/opencti-front/lang/back/fr.json @@ -260,7 +260,11 @@ "Event types": "Types d'événements", "Event user metadata": "Métadonnées de l'utilisateur de l'événement", "Excluded IDs": "ID exclus", + "Exclusion list cache build manager": "Manager de construction de cache de liste d'exclusion", + "Exclusion list cache sync manager": "Manager de synchronisation de cache de liste d'exclusion", "Exclusion list entity types": "Types d'entités de la liste d'exclusion", + "EXCLUSION_LIST_CACHE_BUILD_MANAGER": "Manager de construction de cache de liste d'exclusion", + "EXCLUSION_LIST_CACHE_SYNC_MANAGER": "Manager de synchronisation de cache de liste d'exclusion", "Expected import number": "Numéro d'importation attendu", "Expected number": "Nombre attendu", "Expiration date": "Date d'expiration", diff --git a/opencti-platform/opencti-front/lang/back/ja.json b/opencti-platform/opencti-front/lang/back/ja.json index fe144e0e5027..8cce6f351fe5 100644 --- a/opencti-platform/opencti-front/lang/back/ja.json +++ b/opencti-platform/opencti-front/lang/back/ja.json @@ -260,7 +260,11 @@ "Event types": "パターンタイプ", "Event user metadata": "イベントユーザメタデータ", "Excluded IDs": "除外ID", + "Exclusion list cache build manager": "キャッシュビルドマネージャーの除外リスト", + "Exclusion list cache sync manager": "除外リストキャッシュ同期マネージャ", "Exclusion list entity types": "除外リストのエンティティタイプ", + "EXCLUSION_LIST_CACHE_BUILD_MANAGER": "キャッシュビルドマネージャーの除外リスト", + "EXCLUSION_LIST_CACHE_SYNC_MANAGER": "除外リストキャッシュ同期マネージャ", "Expected import number": "インポート予定番号", "Expected number": "予想番号", "Expiration date": "有効期限", diff --git a/opencti-platform/opencti-front/lang/back/ko.json b/opencti-platform/opencti-front/lang/back/ko.json index 80f339dadd35..1f377666a844 100644 --- a/opencti-platform/opencti-front/lang/back/ko.json +++ b/opencti-platform/opencti-front/lang/back/ko.json @@ -260,7 +260,11 @@ "Event types": "이벤트 유형", "Event user metadata": "이벤트 사용자 메타데이터", "Excluded IDs": "제외된 ID", + "Exclusion list cache build manager": "제외 목록 캐시 빌드 관리자", + "Exclusion list cache sync manager": "제외 목록 캐시 동기화 관리자", "Exclusion list entity types": "제외 목록 엔티티 유형", + "EXCLUSION_LIST_CACHE_BUILD_MANAGER": "제외 목록 캐시 빌드 관리자", + "EXCLUSION_LIST_CACHE_SYNC_MANAGER": "제외 목록 캐시 동기화 관리자", "Expected import number": "예상 가져오기 수", "Expected number": "예상 수", "Expiration date": "만료 날짜", diff --git a/opencti-platform/opencti-front/lang/back/zh.json b/opencti-platform/opencti-front/lang/back/zh.json index 77248e77010d..d8c8c99db6a9 100644 --- a/opencti-platform/opencti-front/lang/back/zh.json +++ b/opencti-platform/opencti-front/lang/back/zh.json @@ -260,7 +260,11 @@ "Event types": "事件类型", "Event user metadata": "事件用户元数据", "Excluded IDs": "排除 ID", + "Exclusion list cache build manager": "排除列表缓存生成管理器", + "Exclusion list cache sync manager": "排除列表缓存同步管理器", "Exclusion list entity types": "排除清单实体类型", + "EXCLUSION_LIST_CACHE_BUILD_MANAGER": "排除列表缓存生成管理器", + "EXCLUSION_LIST_CACHE_SYNC_MANAGER": "排除列表缓存同步管理器", "Expected import number": "预期导入编号", "Expected number": "预期编号", "Expiration date": "有效期", diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index ff1e49cba3d8..2415ebe1fcb5 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -40,6 +40,7 @@ "Account Expire Date": "Konto-Ablaufdatum", "Account Status": "Kontostatus", "Account status": "Kontostatus", + "Action": "Aktion", "Action type": "Aktionsart", "Actions": "Aktionen", "Activate": "Aktivieren Sie", @@ -525,6 +526,7 @@ "Create an attribute": "Ein Attribut erstellen", "Create an entity": "Erstellen Sie eine Entität", "Create an event": "Ein Ereignis erstellen", + "Create an exclusion list": "Erstellen Sie eine Ausschlussliste", "Create an external reference": "Erstellen einer externen Referenz", "Create an incident": "Einen Vorfall erstellen", "Create an incident response": "Erstellen einer Vorfallsreaktion", @@ -619,6 +621,7 @@ "Data import and analyst workbenches": "Datenimport und Analysten-Workbenches", "Data sharing": "Gemeinsame Nutzung von Daten", "Data sharing configuration": "Konfiguration der gemeinsamen Datennutzung", + "Data Sharing: Live Streams | Data": "Datenaustausch: Live-Streams | Daten", "Data source": "Datenquelle", "Data sources": "Datenquellen", "Data type": "Datentyp", @@ -767,6 +770,7 @@ "Do you want to delete this draft?": "Möchten Sie diesen Entwurf löschen?", "Do you want to delete this entity?": "Möchten Sie diese Entität löschen?", "Do you want to delete this event?": "Möchten Sie dieses Ereignis löschen?", + "Do you want to delete this exclusion list?": "Möchten Sie diese Ausschlussliste löschen?", "Do you want to delete this external reference?": "Möchten Sie diese externe Referenz löschen?", "Do you want to delete this feed?": "Möchten Sie diesen Feed löschen?", "Do you want to delete this feedback ?": "Möchten Sie diese Bewertung löschen?", @@ -1125,6 +1129,7 @@ "Evidences": "Beweise", "Evolution": "Entwicklung", "Example": "Beispiel", + "Exclusion Lists": "Ausschluss-Listen", "Execution at": "Ausführung bei", "Execution ended at": "Ausführung beendet um", "Existing public dashboards": "Vorhandene öffentliche Dashboards", @@ -1375,6 +1380,7 @@ "Import knowledge": "Wissen importieren", "Import successfully asked": "Import erfolgreich abgefragt", "Import type": "Art des Imports", + "Import: Import | Data": "Import: Import | Daten", "Important notice: your action is required!": "Wichtiger Hinweis: Ihr Handeln ist erforderlich!", "Imports": "Importe", "In all the database": "In der gesamten Datenbank", @@ -1446,6 +1452,7 @@ "Ingested entities": "Verschluckte Entitäten", "Ingestion": "Ingestion", "INGESTION_MANAGER": "Ingestion-Manager", + "Ingestion: Connectors | Data": "Aufnahme: Konnektoren | Daten", "Initial score": "Anfangspunktzahl", "Initiator": "Initiator", "innovator": "innovator", @@ -2411,8 +2418,13 @@ "Secondary motivations": "Sekundäre Motivationen", "Sector": "Sektor", "Sectors": "Sektoren", + "Sectors | Entities": "Branchen | Entitäten", "Sectors and organizations": "Sektoren und Organisationen", "Security": "Sicherheit", + "Security: Groups | Settings": "Sicherheit: Gruppen | Einstellungen", + "Security: Marking Definitions | Settings": "Sicherheit: Markierungsdefinitionen | Einstellungen", + "Security: Roles | Settings": "Sicherheit: Rollen | Einstellungen", + "Security: Sessions | Settings": "Sicherheit: Sitzungen | Einstellungen", "See all entities created by user": "Alle vom Benutzer erstellten Entitäten anzeigen", "See all relationships created by user": "Alle vom Benutzer erstellten Beziehungen anzeigen", "Select": "Wählen Sie", @@ -2920,6 +2932,7 @@ "Update an country": "Ein Land aktualisieren", "Update an entity": "Aktualisieren einer Entität", "Update an event": "Ein Ereignis aktualisieren", + "Update an exclusion list": "Aktualisieren einer Ausschlussliste", "Update an external reference": "Eine externe Referenz aktualisieren", "Update an incident": "Aktualisieren eines Vorfalls", "Update an incident response": "Aktualisieren einer Vorfallsreaktion", diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index ad2538e5c49d..82ce3e355e65 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -40,6 +40,7 @@ "Account Expire Date": "Account Expire Date", "Account Status": "Account Status", "Account status": "Account status", + "Action": "Action", "Action type": "Action type", "Actions": "Actions", "Activate": "Activate", @@ -525,6 +526,7 @@ "Create an attribute": "Create an attribute", "Create an entity": "Create an entity", "Create an event": "Create an event", + "Create an exclusion list": "Create an exclusion list", "Create an external reference": "Create an external reference", "Create an incident": "Create an incident", "Create an incident response": "Create an incident response", @@ -619,6 +621,7 @@ "Data import and analyst workbenches": "Data import and analyst workbenches", "Data sharing": "Data sharing", "Data sharing configuration": "Data sharing configuration", + "Data Sharing: Live Streams | Data": "Data Sharing: Live Streams | Data", "Data source": "Data source", "Data sources": "Data sources", "Data type": "Data type", @@ -767,6 +770,7 @@ "Do you want to delete this draft?": "Do you want to delete this draft?", "Do you want to delete this entity?": "Do you want to delete this entity?", "Do you want to delete this event?": "Do you want to delete this event?", + "Do you want to delete this exclusion list?": "Do you want to delete this exclusion list?", "Do you want to delete this external reference?": "Do you want to delete this external reference?", "Do you want to delete this feed?": "Do you want to delete this feed?", "Do you want to delete this feedback ?": "Do you want to delete this feedback ?", @@ -1125,6 +1129,7 @@ "Evidences": "Evidences", "Evolution": "Evolution", "Example": "Example", + "Exclusion Lists": "Exclusion Lists", "Execution at": "Execution at", "Execution ended at": "Execution ended at", "Existing public dashboards": "Existing public dashboards", @@ -1375,6 +1380,7 @@ "Import knowledge": "Import knowledge", "Import successfully asked": "Import successfully asked", "Import type": "Import type", + "Import: Import | Data": "Import: Import | Data", "Important notice: your action is required!": "Important notice: your action is required!", "Imports": "Imports", "In all the database": "In all the database", @@ -1446,6 +1452,7 @@ "Ingested entities": "Ingested entities", "Ingestion": "Ingestion", "INGESTION_MANAGER": "Ingestion manager", + "Ingestion: Connectors | Data": "Ingestion: Connectors | Data", "Initial score": "Initial score", "Initiator": "Initiator", "innovator": "innovator", @@ -2411,8 +2418,13 @@ "Secondary motivations": "Secondary motivations", "Sector": "Sector", "Sectors": "Sectors", + "Sectors | Entities": "Sectors | Entities", "Sectors and organizations": "Sectors and organizations", "Security": "Security", + "Security: Groups | Settings": "Security: Groups | Settings", + "Security: Marking Definitions | Settings": "Security: Marking Definitions | Settings", + "Security: Roles | Settings": "Security: Roles | Settings", + "Security: Sessions | Settings": "Security: Sessions | Settings", "See all entities created by user": "See all entities created by user", "See all relationships created by user": "See all relationships created by user", "Select": "Select", @@ -2920,6 +2932,7 @@ "Update an country": "Update an country", "Update an entity": "Update an entity", "Update an event": "Update an event", + "Update an exclusion list": "Update an exclusion list", "Update an external reference": "Update an external reference", "Update an incident": "Update an incident", "Update an incident response": "Update an incident response", diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index df11be1ff463..20d1151fcd36 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -40,6 +40,7 @@ "Account Expire Date": "Fecha de caducidad de la cuenta", "Account Status": "Estado de la cuenta", "Account status": "Estado de la cuenta", + "Action": "Acción", "Action type": "Tipo de acción", "Actions": "Acciones", "Activate": "Activar", @@ -525,6 +526,7 @@ "Create an attribute": "Crear un atributo", "Create an entity": "Crear una entidad", "Create an event": "Crear un evento", + "Create an exclusion list": "Crear una lista de exclusión", "Create an external reference": "Crear una referencia externa", "Create an incident": "Crear un incidente", "Create an incident response": "Crear una respuesta a incidentes", @@ -619,6 +621,7 @@ "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 Sharing: Live Streams | Data": "Intercambio de datos: transmisiones en vivo | Datos", "Data source": "Fuente de datos", "Data sources": "Fuentes de datos", "Data type": "Tipo de datos", @@ -767,6 +770,7 @@ "Do you want to delete this draft?": "¿Desea eliminar este borrador?", "Do you want to delete this entity?": "¿Quieres borrar esta entidad?", "Do you want to delete this event?": "¿Desea eliminar este evento?", + "Do you want to delete this exclusion list?": "¿Desea eliminar esta lista de exclusión?", "Do you want to delete this external reference?": "¿Quieres borrar esta referencia externa?", "Do you want to delete this feed?": "¿Quieres borrar esta fuente de información?", "Do you want to delete this feedback ?": "¿Quieres eliminar este comentario?", @@ -1125,6 +1129,7 @@ "Evidences": "Evidencias", "Evolution": "Evolución", "Example": "Ejemplo", + "Exclusion Lists": "Listas de exclusión", "Execution at": "Ejecución en", "Execution ended at": "Ejecución finalizada en", "Existing public dashboards": "Cuadros de mando públicos existentes", @@ -1375,6 +1380,7 @@ "Import knowledge": "Importar conocimiento", "Import successfully asked": "Importación solicitada exitosamente", "Import type": "Tipo de importación", + "Import: Import | Data": "Importar: Importar | Datos", "Important notice: your action is required!": "Aviso importante: ¡su acción es necesaria!", "Imports": "Importaciones", "In all the database": "En toda la base de datos", @@ -1446,6 +1452,7 @@ "Ingested entities": "Entidades ingestadas", "Ingestion": "Ingestión", "INGESTION_MANAGER": "Gestor del ingestion", + "Ingestion: Connectors | Data": "Ingestión: Conectores | Datos", "Initial score": "Puntuación inicial", "Initiator": "Iniciador", "innovator": "Innovadora", @@ -2411,8 +2418,13 @@ "Secondary motivations": "Motivaciones secundarias", "Sector": "Sector", "Sectors": "Sectores", + "Sectors | Entities": "Sectores | Entidades", "Sectors and organizations": "Sectores y organizaciones", "Security": "Seguridad", + "Security: Groups | Settings": "Seguridad: Grupos | Configuración", + "Security: Marking Definitions | Settings": "Seguridad: Definiciones de marcado | Configuración", + "Security: Roles | Settings": "Seguridad: Roles | Configuración", + "Security: Sessions | Settings": "Seguridad: Sesiones | Configuración", "See all entities created by user": "Ver todas las entidades creadas por el usuario", "See all relationships created by user": "Ver todas las relaciones creadas por el usuario", "Select": "Seleccione", @@ -2920,6 +2932,7 @@ "Update an country": "Actualizar un país", "Update an entity": "Actualizar una entidad", "Update an event": "Actualizar un evento", + "Update an exclusion list": "Actualizar una lista de exclusión", "Update an external reference": "Actualizar una referencia externa", "Update an incident": "Actualizar un incidente", "Update an incident response": "Actualizar la respuesta a un incidente", diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index 563750460274..58880562a11b 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -40,6 +40,7 @@ "Account Expire Date": "Date d'expiration du compte", "Account Status": "Statut du compte", "Account status": "Statut du compte", + "Action": "Action", "Action type": "Type d'action", "Actions": "Actions", "Activate": "Activer", @@ -525,6 +526,7 @@ "Create an attribute": "Créer un attribut", "Create an entity": "Créer une entité", "Create an event": "Créer un événement", + "Create an exclusion list": "Créer une liste d'exclusion", "Create an external reference": "Créer une référence externe", "Create an incident": "Créer un incident", "Create an incident response": "Créer une réponse à incident", @@ -619,6 +621,7 @@ "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 Sharing: Live Streams | Data": "Partage de données : flux en direct | Données", "Data source": "Source de données", "Data sources": "Sources de données", "Data type": "Type de données", @@ -767,6 +770,7 @@ "Do you want to delete this draft?": "Voulez-vous supprimer ce brouillon ?", "Do you want to delete this entity?": "Souhaitez-vous supprimer cette entité ?", "Do you want to delete this event?": "Voulez-vous supprimer cet événement ?", + "Do you want to delete this exclusion list?": "Voulez-vous supprimer cette liste d'exclusion ?", "Do you want to delete this external reference?": "Souhaitez-vous supprimer cette référence externe ?", "Do you want to delete this feed?": "Souhaitez-vous supprimer ce flux ?", "Do you want to delete this feedback ?": "Voulez-vous supprimer ce commentaire ?", @@ -1125,6 +1129,7 @@ "Evidences": "", "Evolution": "Evolution", "Example": "Exemple", + "Exclusion Lists": "Listes d'exclusion", "Execution at": "Exécution à", "Execution ended at": "L'exécution s'est terminée à", "Existing public dashboards": "Tableaux de bord publics existants", @@ -1375,6 +1380,7 @@ "Import knowledge": "Importer des connaissances", "Import successfully asked": "L'import a été demandé", "Import type": "Type d'import", + "Import: Import | Data": "Importation : Import | Data", "Important notice: your action is required!": "Avis important : votre action est requise !", "Imports": "Importations", "In all the database": "Dans toute la base", @@ -1446,6 +1452,7 @@ "Ingested entities": "Entités stockées", "Ingestion": "Ingestion", "INGESTION_MANAGER": "Manager d'ingestion", + "Ingestion: Connectors | Data": "Ingestion : Connecteurs | Données", "Initial score": "Score initial", "Initiator": "Initiateur", "innovator": "innovateur", @@ -2411,8 +2418,13 @@ "Secondary motivations": "Motivations secondaires", "Sector": "Secteur", "Sectors": "Secteurs", + "Sectors | Entities": "Secteurs | Entités", "Sectors and organizations": "Secteurs et organisations", "Security": "Sécurité", + "Security: Groups | Settings": "Sécurité : Groupes | Paramètres", + "Security: Marking Definitions | Settings": "Sécurité : définitions de marquage | Paramètres", + "Security: Roles | Settings": "Sécurité : Rôles | Paramètres", + "Security: Sessions | Settings": "Sécurité : Sessions | Paramètres", "See all entities created by user": "Voir toutes les entités crées par l'utilisateur", "See all relationships created by user": "Voir toutes les relations crées par l'utilisateur", "Select": "Sélectionnez", @@ -2920,6 +2932,7 @@ "Update an country": "Mettre à jour un pays", "Update an entity": "Modifier une entité", "Update an event": "Modifier un événement", + "Update an exclusion list": "Mise à jour d'une liste d'exclusion", "Update an external reference": "Modifier une référence externe", "Update an incident": "Modifier un incident", "Update an incident response": "Mettre à jour une réponse à incident", diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index 8508f72714e9..dfdd7408109f 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -40,6 +40,7 @@ "Account Expire Date": "アカウントの有効期限", "Account Status": "アカウントのステータス", "Account status": "アカウントステータス", + "Action": "アクション", "Action type": "アクションタイプ", "Actions": "アクション", "Activate": "アクティブ化", @@ -525,6 +526,7 @@ "Create an attribute": "属性を作成", "Create an entity": "エンティティを作成", "Create an event": "イベントを作成", + "Create an exclusion list": "除外リストの作成", "Create an external reference": "外部参照を作成", "Create an incident": "インシデントを作成", "Create an incident response": "ケース インシデントの作成", @@ -619,6 +621,7 @@ "Data import and analyst workbenches": "データインポートとアナリストワークベンチ", "Data sharing": "データ共有", "Data sharing configuration": "データ共有設定", + "Data Sharing: Live Streams | Data": "データ共有:ライブストリーム|データ", "Data source": "データソース", "Data sources": "データソース", "Data type": "データ型", @@ -767,6 +770,7 @@ "Do you want to delete this draft?": "この下書きを削除しますか?", "Do you want to delete this entity?": "このエンティティを削除しますか?", "Do you want to delete this event?": "このイベントを削除しますか?", + "Do you want to delete this exclusion list?": "この除外リストを削除しますか?", "Do you want to delete this external reference?": "この外部参照を削除しますか?", "Do you want to delete this feed?": "このフィードを削除しますか?", "Do you want to delete this feedback ?": "このフィードバックを削除しますか?", @@ -1125,6 +1129,7 @@ "Evidences": "エビデンス", "Evolution": "発展", "Example": "例", + "Exclusion Lists": "除外リスト", "Execution at": "での実行", "Execution ended at": "で実行終了", "Existing public dashboards": "既存の公開ダッシュボード", @@ -1375,6 +1380,7 @@ "Import knowledge": "知識をインポート", "Import successfully asked": "インポート要求を正常に受け付けました", "Import type": "インポート種別", + "Import: Import | Data": "インポート:インポート|データ", "Important notice: your action is required!": "重要なお知らせ:あなたのアクションが必要です!", "Imports": "インポート", "In all the database": "すべてのデータベースで", @@ -1446,6 +1452,7 @@ "Ingested entities": "取り込まれたエンティティ", "Ingestion": "摂取", "INGESTION_MANAGER": "インジェストマネージャー", + "Ingestion: Connectors | Data": "摂取:コネクタ|データ", "Initial score": "初期スコア", "Initiator": "イニシエータ", "innovator": "革新的", @@ -2411,8 +2418,13 @@ "Secondary motivations": "二次的動機", "Sector": "セクター", "Sectors": "セクター", + "Sectors | Entities": "エンティティ", "Sectors and organizations": "セクターと組織", "Security": "安全", + "Security: Groups | Settings": "セキュリティ:グループ|設定", + "Security: Marking Definitions | Settings": "セキュリティ:マーキング定義|設定", + "Security: Roles | Settings": "セキュリティ:ロール|設定", + "Security: Sessions | Settings": "セキュリティ:セッション|設定", "See all entities created by user": "ユーザーが作成したすべてのエンティティを見る", "See all relationships created by user": "ユーザーが作成したすべての関係を見る", "Select": "選択する", @@ -2920,6 +2932,7 @@ "Update an country": "国を更新する", "Update an entity": "エンティティを更新", "Update an event": "イベントを更新", + "Update an exclusion list": "除外リストの更新", "Update an external reference": "外部参照を更新", "Update an incident": "インシデントを更新", "Update an incident response": "ケース インシデントの更新", diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index 488009a72077..04cc7072ad66 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -40,6 +40,7 @@ "Account Expire Date": "계정 만료일", "Account Status": "계정 상태", "Account status": "계정 상태", + "Action": "액션", "Action type": "액션 유형", "Actions": "액션", "Activate": "활성화", @@ -525,6 +526,7 @@ "Create an attribute": "속성 생성", "Create an entity": "엔터티 생성", "Create an event": "이벤트 생성", + "Create an exclusion list": "제외 목록 만들기", "Create an external reference": "외부 참조 생성", "Create an incident": "사건 생성", "Create an incident response": "사건 대응 생성", @@ -619,6 +621,7 @@ "Data import and analyst workbenches": "데이터 가져오기 및 분석가 작업대", "Data sharing": "데이터 공유", "Data sharing configuration": "데이터 공유 구성", + "Data Sharing: Live Streams | Data": "데이터 공유: 실시간 스트리밍 | 데이터", "Data source": "데이터 소스", "Data sources": "데이터 소스", "Data type": "데이터 유형", @@ -767,6 +770,7 @@ "Do you want to delete this draft?": "이 초안을 삭제하시겠습니까?", "Do you want to delete this entity?": "이 엔터티를 삭제하시겠습니까?", "Do you want to delete this event?": "이 이벤트를 삭제하시겠습니까?", + "Do you want to delete this exclusion list?": "이 제외 목록을 삭제하시겠습니까?", "Do you want to delete this external reference?": "이 외부 참조를 삭제하시겠습니까?", "Do you want to delete this feed?": "이 피드를 삭제하시겠습니까?", "Do you want to delete this feedback ?": "이 피드백을 삭제하시겠습니까?", @@ -1125,6 +1129,7 @@ "Evidences": "증거", "Evolution": "진화", "Example": "예", + "Exclusion Lists": "제외 목록", "Execution at": "실행 시간", "Execution ended at": "실행 종료 시간", "Existing public dashboards": "기존 공개 대시보드", @@ -1375,6 +1380,7 @@ "Import knowledge": "지식 가져오기", "Import successfully asked": "가져오기가 성공적으로 요청됨", "Import type": "가져오기 유형", + "Import: Import | Data": "가져오기: 가져오기 | 데이터", "Important notice: your action is required!": "중요 공지: 귀하의 조치가 필요합니다!", "Imports": "Imports", "In all the database": "모든 데이터베이스에서", @@ -1446,6 +1452,7 @@ "Ingested entities": "수집된 엔터티", "Ingestion": "수집", "INGESTION_MANAGER": "수집 관리자", + "Ingestion: Connectors | Data": "섭취: 커넥터 | 데이터", "Initial score": "초기 점수", "Initiator": "발기인", "innovator": "혁신가", @@ -2411,8 +2418,13 @@ "Secondary motivations": "부차적 동기", "Sector": "부문", "Sectors": "부문", + "Sectors | Entities": "분야 | 법인", "Sectors and organizations": "부문 및 조직", "Security": "보안", + "Security: Groups | Settings": "보안: 그룹 | 설정", + "Security: Marking Definitions | Settings": "보안: 정의 표시 | 설정", + "Security: Roles | Settings": "보안: 역할 | 설정", + "Security: Sessions | Settings": "보안: 세션 | 설정", "See all entities created by user": "사용자가 만든 모든 엔터티 보기", "See all relationships created by user": "사용자가 만든 모든 관계 보기", "Select": "선택", @@ -2920,6 +2932,7 @@ "Update an country": "국가 업데이트", "Update an entity": "엔터티 업데이트", "Update an event": "이벤트 업데이트", + "Update an exclusion list": "제외 목록 업데이트", "Update an external reference": "외부 참조 업데이트", "Update an incident": "사건 업데이트", "Update an incident response": "사건 대응 업데이트", diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index 110d94a0a9a2..9db625eca180 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -40,6 +40,7 @@ "Account Expire Date": "帐户到期日", "Account Status": "帐户状态", "Account status": "账户状态", + "Action": "行动", "Action type": "行动类型", "Actions": "行动", "Activate": "激活", @@ -525,6 +526,7 @@ "Create an attribute": "创建属性", "Create an entity": "创建实体", "Create an event": "创建活动", + "Create an exclusion list": "创建排除名单", "Create an external reference": "创建外部引用", "Create an incident": "创建安全事件", "Create an incident response": "创建案例事件", @@ -619,6 +621,7 @@ "Data import and analyst workbenches": "数据导入和分析工作台", "Data sharing": "数据共享", "Data sharing configuration": "数据共享配置", + "Data Sharing: Live Streams | Data": "数据共享:实时流|数据", "Data source": "数据源", "Data sources": "数据源", "Data type": "数据类型", @@ -767,6 +770,7 @@ "Do you want to delete this draft?": "要删除此草稿吗?", "Do you want to delete this entity?": "是否要删除此实体?", "Do you want to delete this event?": "您想删除此事件吗?", + "Do you want to delete this exclusion list?": "要删除此排除列表吗?", "Do you want to delete this external reference?": "是否要删除此外部引用?", "Do you want to delete this feed?": "您要删除此订阅源吗?", "Do you want to delete this feedback ?": "要删除此反馈吗?", @@ -1125,6 +1129,7 @@ "Evidences": "证据", "Evolution": "演变", "Example": "示例", + "Exclusion Lists": "排除列表", "Execution at": "在", "Execution ended at": "执行结束于", "Existing public dashboards": "现有公共仪表盘", @@ -1375,6 +1380,7 @@ "Import knowledge": "导入知识", "Import successfully asked": "请求导入成功", "Import type": "导入类型", + "Import: Import | Data": "导入:导入|数据", "Important notice: your action is required!": "重要提示:需要您执行操作!", "Imports": "导入", "In all the database": "在所有数据库中", @@ -1446,6 +1452,7 @@ "Ingested entities": "引入的实体", "Ingestion": "接入", "INGESTION_MANAGER": "提取管理器", + "Ingestion: Connectors | Data": "摄取:连接器|数据", "Initial score": "初始分数", "Initiator": "发起者", "innovator": "创新者", @@ -2411,8 +2418,13 @@ "Secondary motivations": "次要动机", "Sector": "部门", "Sectors": "部门", + "Sectors | Entities": "部门|实体", "Sectors and organizations": "部门和组织", "Security": "安全", + "Security: Groups | Settings": "安全:组|设置", + "Security: Marking Definitions | Settings": "安全性:标记定义|设置", + "Security: Roles | Settings": "安全:角色|设置", + "Security: Sessions | Settings": "安全:会话|设置", "See all entities created by user": "查看用户创建的所有实体", "See all relationships created by user": "查看用户创建的所有关系", "Select": "选择", @@ -2920,6 +2932,7 @@ "Update an country": "更新国家", "Update an entity": "更新实体", "Update an event": "更新事件", + "Update an exclusion list": "更新排除列表", "Update an external reference": "更新外部引用", "Update an incident": "更新安全事件", "Update an incident response": "更新案例事件", diff --git a/opencti-platform/opencti-front/src/components/AppThemeProvider.tsx b/opencti-platform/opencti-front/src/components/AppThemeProvider.tsx index 711ec7d2f5be..a7b55a85ebc7 100644 --- a/opencti-platform/opencti-front/src/components/AppThemeProvider.tsx +++ b/opencti-platform/opencti-front/src/components/AppThemeProvider.tsx @@ -5,8 +5,10 @@ import { ThemeOptions } from '@mui/material/styles/createTheme'; import { UserContext, UserContextType } from '../utils/hooks/useAuth'; import themeDark from './ThemeDark'; import themeLight from './ThemeLight'; -import { useDocumentFaviconModifier, useDocumentTitleModifier, useDocumentThemeModifier } from '../utils/hooks/useDocumentModifier'; +import { useDocumentFaviconModifier, useDocumentThemeModifier } from '../utils/hooks/useDocumentModifier'; import { AppThemeProvider_settings$data } from './__generated__/AppThemeProvider_settings.graphql'; +import useConnectedDocumentModifier from '../utils/hooks/useConnectedDocumentModifier'; +import { pascalize } from '../utils/String'; interface AppThemeProviderProps { children: React.ReactNode; @@ -62,8 +64,8 @@ const AppThemeProvider: FunctionComponent = ({ settings, }) => { const { me } = useContext(UserContext); - const platformTitle = settings?.platform_title ?? 'OpenCTI - Cyber Threat Intelligence Platform'; - useDocumentTitleModifier(platformTitle); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(pascalize(window.location.pathname.split('/').at(-1))); useDocumentFaviconModifier(settings?.platform_favicon); // region theming const defaultTheme = settings?.platform_theme ?? null; diff --git a/opencti-platform/opencti-front/src/components/ItemIcon.jsx b/opencti-platform/opencti-front/src/components/ItemIcon.jsx index 67e938504a27..5bcd3a3f1fa4 100644 --- a/opencti-platform/opencti-front/src/components/ItemIcon.jsx +++ b/opencti-platform/opencti-front/src/components/ItemIcon.jsx @@ -67,6 +67,7 @@ import { BackupTableOutlined, PlayCircleOutlined, ArchitectureOutlined, + PlaylistRemoveOutlined, } from '@mui/icons-material'; import { ArchiveOutline, @@ -542,6 +543,8 @@ const iconSelector = (type, variant, fontSize, color, isReversed) => { return ; case 'draft_context': return ; + case 'exclusion-list': + return ; case 'default': return ; default: diff --git a/opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx b/opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx index 2e72a3eb1a36..bc9799909924 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/DataTable.tsx @@ -169,6 +169,7 @@ type OCTIDataTableProps = Pick & { diff --git a/opencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx b/opencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx index 9d54918dfdf7..1487bce20ad7 100644 --- a/opencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx +++ b/opencti-platform/opencti-front/src/components/dataGrid/dataTableUtils.tsx @@ -25,7 +25,7 @@ import ItemSeverity from '../ItemSeverity'; import { APP_BASE_PATH } from '../../relay/environment'; import ItemOperations from '../ItemOperations'; -const MAGICAL_SIZE = 0.113; +export const MAGICAL_SIZE = 0.113; const chipStyle = { fontSize: '12px', @@ -265,6 +265,20 @@ const defaultColumns: DataTableProps['dataColumns'] = { isSortable: false, render: (data) => , }, + definition: { + id: 'definition', + label: 'Definition', + percentWidth: 25, + isSortable: true, + render: ({ definition }, helpers) => defaultRender(definition, helpers), + }, + definition_type: { + id: 'definition_type', + label: 'Type', + percentWidth: 25, + isSortable: true, + render: ({ definition_type }, helpers) => defaultRender(definition_type, helpers), + }, event_types: { id: 'event_types', label: 'Types', @@ -840,6 +854,29 @@ const defaultColumns: DataTableProps['dataColumns'] = { isSortable: true, render: ({ x_mitre_id }) => {emptyFilled(x_mitre_id)}, }, + x_opencti_color: { + id: 'x_opencti_color', + label: 'Color', + percentWidth: 15, + isSortable: true, + render: ({ x_opencti_color }, { column: { size } }) => ( + + <> +
+ {truncate(x_opencti_color, size * MAGICAL_SIZE)} + + + ), + }, x_opencti_order: { id: 'x_opencti_order', label: 'Order', diff --git a/opencti-platform/opencti-front/src/private/components/common/files/FileManager.jsx b/opencti-platform/opencti-front/src/private/components/common/files/FileManager.jsx index 494f6b09b980..d1eb3b339135 100644 --- a/opencti-platform/opencti-front/src/private/components/common/files/FileManager.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/files/FileManager.jsx @@ -20,7 +20,7 @@ import FileImportViewer from './FileImportViewer'; import SelectField from '../../../../components/fields/SelectField'; import { commitMutation, handleErrorInForm, MESSAGING$, QueryRenderer } from '../../../../relay/environment'; import inject18n, { useFormatter } from '../../../../components/i18n'; -import { markingDefinitionsLinesSearchQuery } from '../../settings/marking_definitions/MarkingDefinitionsLines'; +import { markingDefinitionsLinesSearchQuery } from '../../settings/MarkingDefinitionsQuery'; import Loader from '../../../../components/Loader'; import FileExternalReferencesViewer from './FileExternalReferencesViewer'; import WorkbenchFileViewer from './workbench/WorkbenchFileViewer'; diff --git a/opencti-platform/opencti-front/src/private/components/common/files/workbench/WorkbenchFileContent.jsx b/opencti-platform/opencti-front/src/private/components/common/files/workbench/WorkbenchFileContent.jsx index 5c691ea16dfe..386c32057ef0 100644 --- a/opencti-platform/opencti-front/src/private/components/common/files/workbench/WorkbenchFileContent.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/files/workbench/WorkbenchFileContent.jsx @@ -62,7 +62,7 @@ import { fieldSpacingContainerStyle } from '../../../../../utils/field'; import RichTextField from '../../../../../components/fields/RichTextField'; import Drawer from '../../drawer/Drawer'; import Transition from '../../../../../components/Transition'; -import { markingDefinitionsLinesSearchQuery } from '../../../settings/marking_definitions/MarkingDefinitionsLines'; +import { markingDefinitionsLinesSearchQuery } from '../../../settings/MarkingDefinitionsQuery'; import { KNOWLEDGE_KNUPDATE } from '../../../../../utils/hooks/useGranted'; import Security from '../../../../../utils/Security'; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectFileExport.tsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectFileExport.tsx index b0ce204a84b6..e85a0ce26003 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectFileExport.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectFileExport.tsx @@ -17,12 +17,12 @@ import { createSearchParams, useNavigate } from 'react-router-dom'; import { FormikHelpers } from 'formik/dist/types'; import { FileManagerExportMutation } from '@components/common/files/__generated__/FileManagerExportMutation.graphql'; import { StixCoreObjectFileExportQuery } from '@components/common/stix_core_objects/__generated__/StixCoreObjectFileExportQuery.graphql'; -import { MarkingDefinitionsLinesSearchQuery$data } from '@components/settings/marking_definitions/__generated__/MarkingDefinitionsLinesSearchQuery.graphql'; import { scopesConn } from '@components/common/stix_core_objects/StixCoreObjectFilesAndHistory'; import ObjectMarkingField from '@components/common/form/ObjectMarkingField'; import { Option } from '@components/common/form/ReferenceField'; import { InfoOutlined } from '@mui/icons-material'; -import { markingDefinitionsLinesSearchQuery } from '../../settings/marking_definitions/MarkingDefinitionsLines'; +import { MarkingDefinitionsQuerySearchQuery$data } from '@components/settings/__generated__/MarkingDefinitionsQuerySearchQuery.graphql'; +import { markingDefinitionsLinesSearchQuery } from '../../settings/MarkingDefinitionsQuery'; import { fileManagerExportMutation } from '../files/FileManager'; import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import Loader, { LoaderVariant } from '../../../../components/Loader'; @@ -197,7 +197,7 @@ const StixCoreObjectFileExportComponent = ({ render={({ props, }: { - props: MarkingDefinitionsLinesSearchQuery$data; + props: MarkingDefinitionsQuerySearchQuery$data; }) => { if (props && props.markingDefinitions) { return ( diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsExportCreation.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsExportCreation.jsx index 599e6c0fb582..06ef375344f4 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsExportCreation.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectsExportCreation.jsx @@ -16,7 +16,7 @@ import Fab from '@mui/material/Fab'; import ObjectMarkingField from '../form/ObjectMarkingField'; import { useFormatter } from '../../../../components/i18n'; import { commitMutation, MESSAGING$, QueryRenderer } from '../../../../relay/environment'; -import { markingDefinitionsLinesSearchQuery } from '../../settings/marking_definitions/MarkingDefinitionsLines'; +import { markingDefinitionsLinesSearchQuery } from '../../settings/MarkingDefinitionsQuery'; import SelectField from '../../../../components/fields/SelectField'; import Loader from '../../../../components/Loader'; import { ExportContext } from '../../../../utils/ExportContextProvider'; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipsExportCreation.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipsExportCreation.jsx index 188945e2d694..aae53f218b0f 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipsExportCreation.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipsExportCreation.jsx @@ -18,7 +18,7 @@ import Fab from '@mui/material/Fab'; import ObjectMarkingField from '../form/ObjectMarkingField'; import inject18n from '../../../../components/i18n'; import { commitMutation, MESSAGING$, QueryRenderer } from '../../../../relay/environment'; -import { markingDefinitionsLinesSearchQuery } from '../../settings/marking_definitions/MarkingDefinitionsLines'; +import { markingDefinitionsLinesSearchQuery } from '../../settings/MarkingDefinitionsQuery'; import SelectField from '../../../../components/fields/SelectField'; import Loader from '../../../../components/Loader'; import { ExportContext } from '../../../../utils/ExportContextProvider'; diff --git a/opencti-platform/opencti-front/src/private/components/data/Connectors.jsx b/opencti-platform/opencti-front/src/private/components/data/Connectors.jsx index efc67d0a20b8..a666299ff2b7 100644 --- a/opencti-platform/opencti-front/src/private/components/data/Connectors.jsx +++ b/opencti-platform/opencti-front/src/private/components/data/Connectors.jsx @@ -1,58 +1,50 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { compose } from 'ramda'; -import withStyles from '@mui/styles/withStyles'; +import React from 'react'; +import makeStyles from '@mui/styles/makeStyles'; import IngestionMenu from './IngestionMenu'; -import inject18n from '../../../components/i18n'; +import { useFormatter } from '../../../components/i18n'; import { QueryRenderer } from '../../../relay/environment'; import WorkersStatus, { workersStatusQuery } from './connectors/WorkersStatus'; import ConnectorsStatus, { connectorsStatusQuery } from './connectors/ConnectorsStatus'; -import Loader from '../../../components/Loader'; +import Loader, { LoaderVariant } from '../../../components/Loader'; import Breadcrumbs from '../../../components/Breadcrumbs'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; -const styles = () => ({ +const useStyles = makeStyles(() => ({ container: { + margin: 0, padding: '0 200px 50px 0', }, -}); +})); -class Connectors extends Component { - render() { - const { t, classes } = this.props; - return ( -
- - - { - if (props) { - return ; - } - return
 
; - }} - /> - { - if (props) { - return ; - } - return ; - }} - /> -
- ); - } -} - -Connectors.propTypes = { - classes: PropTypes.object, - t: PropTypes.func, - connectorsStatus: PropTypes.array, +const Connectors = () => { + const classes = useStyles(); + const { t_i18n } = useFormatter(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Ingestion: Connectors | Data')); + return ( +
+ + + { + if (props) { + return ; + } + return ; + }} + /> + { + if (props) { + return ; + } + return ; + }} + /> +
+ ); }; -export default compose( - inject18n, - withStyles(styles, { withTheme: true }), -)(Connectors); +export default Connectors; diff --git a/opencti-platform/opencti-front/src/private/components/data/Stream.jsx b/opencti-platform/opencti-front/src/private/components/data/Stream.jsx deleted file mode 100644 index fbf42638c736..000000000000 --- a/opencti-platform/opencti-front/src/private/components/data/Stream.jsx +++ /dev/null @@ -1,152 +0,0 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { compose, propOr } from 'ramda'; -import withTheme from '@mui/styles/withTheme'; -import withStyles from '@mui/styles/withStyles'; -import { QueryRenderer } from '../../../relay/environment'; -import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; -import inject18n from '../../../components/i18n'; -import ListLines from '../../../components/list_lines/ListLines'; -import StreamLines, { StreamLinesQuery } from './stream/StreamLines'; -import StreamCollectionCreation from './stream/StreamCollectionCreation'; -import SharingMenu from './SharingMenu'; -import withRouter from '../../../utils/compat_router/withRouter'; -import Breadcrumbs from '../../../components/Breadcrumbs'; -import { TAXIIAPI_SETCOLLECTIONS } from '../../../utils/hooks/useGranted'; -import Security from '../../../utils/Security'; - -const styles = () => ({ - container: { - margin: 0, - padding: '0 200px 50px 0', - }, -}); - -const LOCAL_STORAGE_KEY = 'stream'; - -class Stream extends Component { - constructor(props) { - super(props); - const params = buildViewParamsFromUrlAndStorage( - props.navigate, - props.location, - LOCAL_STORAGE_KEY, - ); - this.state = { - sortBy: propOr('name', 'sortBy', params), - orderAsc: propOr(true, 'orderAsc', params), - searchTerm: propOr('', 'searchTerm', params), - view: propOr('lines', 'view', params), - }; - } - - saveView() { - saveViewParameters( - this.props.navigate, - this.props.location, - LOCAL_STORAGE_KEY, - this.state, - ); - } - - handleSearch(value) { - this.setState({ searchTerm: value }, () => this.saveView()); - } - - handleSort(field, orderAsc) { - this.setState({ sortBy: field, orderAsc }, () => this.saveView()); - } - - renderLines(paginationOptions) { - const { sortBy, orderAsc, searchTerm } = this.state; - const dataColumns = { - name: { - label: 'Name', - width: '10%', - isSortable: true, - }, - description: { - label: 'Description', - width: '20%', - isSortable: false, - }, - id: { - label: 'Stream ID', - width: '15%', - isSortable: true, - }, - stream_public: { - label: 'Public', - width: '8%', - isSortable: true, - }, - stream_live: { - label: 'Status', - width: '10%', - isSortable: true, - }, - filters: { - label: 'Filters', - width: '35%', - }, - }; - return ( - - ( - - )} - /> - - ); - } - - render() { - const { classes, t } = this.props; - const { view, sortBy, orderAsc, searchTerm } = this.state; - const paginationOptions = { - search: searchTerm, - orderBy: sortBy, - orderMode: orderAsc ? 'asc' : 'desc', - }; - return ( -
- - - {view === 'lines' ? this.renderLines(paginationOptions) : ''} - - - -
- ); - } -} - -Stream.propTypes = { - t: PropTypes.func, - navigate: PropTypes.func, - location: PropTypes.object, -}; - -export default compose( - inject18n, - withTheme, - withRouter, - withStyles(styles), -)(Stream); diff --git a/opencti-platform/opencti-front/src/private/components/data/Stream.tsx b/opencti-platform/opencti-front/src/private/components/data/Stream.tsx new file mode 100644 index 000000000000..ee9ce289ab96 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/data/Stream.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useState } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; +import { QueryRenderer } from '../../../relay/environment'; +import { useFormatter } from '../../../components/i18n'; +import ListLines from '../../../components/list_lines/ListLines'; +import StreamLines, { StreamLinesQuery } from './stream/StreamLines'; +import StreamCollectionCreation from './stream/StreamCollectionCreation'; +import SharingMenu from './SharingMenu'; +import Breadcrumbs from '../../../components/Breadcrumbs'; +import { TAXIIAPI_SETCOLLECTIONS } from '../../../utils/hooks/useGranted'; +import Security from '../../../utils/Security'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; +import { OrderMode, PaginationOptions } from '../../../components/list_lines'; +import { StreamLinesPaginationQuery$data } from './stream/__generated__/StreamLinesPaginationQuery.graphql'; + +const LOCAL_STORAGE_KEY = 'stream'; + +const useStyles = makeStyles(() => ({ + container: { + margin: 0, + padding: '0 200px 50px 0', + }, +})); + +const Stream = () => { + const { t_i18n } = useFormatter(); + const classes = useStyles(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Data Sharing: Live Streams | Data')); + const navigate = useNavigate(); + const location = useLocation(); + const params = buildViewParamsFromUrlAndStorage( + navigate, + location, + LOCAL_STORAGE_KEY, + ); + const [streamState, setStreamState] = useState<{ orderAsc: boolean, searchTerm: string, view: string, sortBy: string }>({ + orderAsc: params.orderAsc !== false, + searchTerm: params.searchTerm ?? '', + view: params.view ?? 'lines', + sortBy: params.sortBy ?? 'name', + }); + + function saveView() { + saveViewParameters( + navigate, + location, + LOCAL_STORAGE_KEY, + streamState, + ); + } + + function handleSearch(value: string) { + setStreamState({ ...streamState, searchTerm: value }); + } + function handleSort(field: string, orderAsc: boolean) { + setStreamState({ ...streamState, sortBy: field, orderAsc }); + } + + useEffect(() => { + saveView(); + }, [streamState]); + + function renderLines(paginationOptions: PaginationOptions) { + const { searchTerm, sortBy, orderAsc, view } = streamState; + const dataColumns = { + name: { + label: 'Name', + width: '10%', + isSortable: true, + }, + description: { + label: 'Description', + width: '20%', + isSortable: false, + }, + id: { + label: 'Stream ID', + width: '15%', + isSortable: true, + }, + stream_public: { + label: 'Public', + width: '8%', + isSortable: true, + }, + stream_live: { + label: 'Status', + width: '10%', + isSortable: true, + }, + filters: { + label: 'Filters', + width: '35%', + }, + }; + return ( + + ( + + )} + /> + + ); + } + const paginationOptions: PaginationOptions = { + search: streamState.searchTerm, + orderBy: streamState.sortBy, + orderMode: streamState.orderAsc ? OrderMode.asc : OrderMode.desc, + }; + return ( +
+ + + {streamState.view === 'lines' ? renderLines(paginationOptions) : ''} + + + +
+ ); +}; + +export default Stream; diff --git a/opencti-platform/opencti-front/src/private/components/data/import/ImportContent.jsx b/opencti-platform/opencti-front/src/private/components/data/import/ImportContent.jsx index 8d6603a0b5c0..1b1bb9d72f2d 100644 --- a/opencti-platform/opencti-front/src/private/components/data/import/ImportContent.jsx +++ b/opencti-platform/opencti-front/src/private/components/data/import/ImportContent.jsx @@ -1,5 +1,4 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; import * as R from 'ramda'; import { createRefetchContainer, graphql } from 'react-relay'; import { interval } from 'rxjs'; @@ -8,11 +7,14 @@ import Typography from '@mui/material/Typography'; import List from '@mui/material/List'; import Paper from '@mui/material/Paper'; import Grid from '@mui/material/Grid'; -import { Add, ArrowDropDown, ArrowDropUp } from '@mui/icons-material'; +import { Add, ArrowDropDown, ArrowDropUp, Extension } from '@mui/icons-material'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; +import Link from '@mui/material/Link'; +import Tooltip from '@mui/material/Tooltip'; import { Field, Form, Formik } from 'formik'; import Dialog from '@mui/material/Dialog'; import DialogTitle from '@mui/material/DialogTitle'; @@ -22,13 +24,14 @@ import DialogActions from '@mui/material/DialogActions'; import Button from '@mui/material/Button'; import * as Yup from 'yup'; import Fab from '@mui/material/Fab'; +import { makeStyles } from '@mui/styles'; import ImportMenu from '../ImportMenu'; import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import SelectField from '../../../../components/fields/SelectField'; import { TEN_SECONDS } from '../../../../utils/Time'; import { fileManagerAskJobImportMutation, scopesConn } from '../../common/files/FileManager'; import FileLine from '../../common/files/FileLine'; -import inject18n from '../../../../components/i18n'; +import inject18n, { useFormatter } from '../../../../components/i18n'; import FileUploader from '../../common/files/FileUploader'; import { commitMutation, MESSAGING$ } from '../../../../relay/environment'; import WorkbenchFileLine from '../../common/files/workbench/WorkbenchFileLine'; @@ -39,17 +42,12 @@ import { fieldSpacingContainerStyle } from '../../../../utils/field'; import withRouter from '../../../../utils/compat_router/withRouter'; import Breadcrumbs from '../../../../components/Breadcrumbs'; import { resolveHasUserChoiceParsedCsvMapper } from '../../../../utils/csvMapperUtils'; +import useConnectedDocumentModifier from '../../../../utils/hooks/useConnectedDocumentModifier'; +import { truncate } from '../../../../utils/String'; const interval$ = interval(TEN_SECONDS); -const styles = (theme) => ({ - container: { - margin: 0, - }, - title: { - float: 'left', - textTransform: 'uppercase', - }, +const useStyles = makeStyles(() => ({ gridContainer: { marginBottom: 20, }, @@ -62,32 +60,15 @@ const styles = (theme) => ({ paddingLeft: 10, height: 50, }, - buttons: { - marginTop: 20, - textAlign: 'right', - }, - button: { - marginLeft: theme.spacing(2), - }, - linesContainer: { - marginTop: 10, - }, itemHead: { paddingLeft: 10, textTransform: 'uppercase', }, - bodyItem: { - height: '100%', - fontSize: 13, - }, - itemIcon: { - color: theme.palette.primary.main, - }, createButton: { position: 'fixed', bottom: 30, }, -}); +})); const inlineStylesHeaders = { iconSort: { @@ -183,71 +164,72 @@ const importValidation = (t, configurations) => { return Yup.object().shape(shape); }; -class ImportContentComponent extends Component { - constructor(props) { - super(props); - this.state = { - fileToImport: null, - fileToValidate: null, - displayCreate: false, - sortBy: 'name', - orderAsc: true, - selectedConnector: null, - hasUserChoiceCsvMapper: false, - }; - } +const ImportContentComponent = (props) => { + const classes = useStyles(); + const { t_i18n } = useFormatter(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Import: Import | Data')); + const [selectedConnector, setSelectedConnector] = useState(null); + const { importContent, connectorsImport, relay, nsdt, importFiles, pendingFiles } = props; + const [sortBy, setSortBy] = useState(); + const [fileToImport, setFileToImport] = useState(null); + const [fileToValidate, setFileToValidate] = useState(null); + const [displayCreate, setDisplayCreate] = useState(false); + const [orderAsc, setOrderAsc] = useState(false); + const [hasUserChoiceCsvMapper, sethasUserChoiceCsvMapper] = useState(false); - componentDidMount() { - this.subscription = interval$.subscribe(() => { - this.props.relay.refetch(); + useEffect(() => { + const subscription = interval$.subscribe(() => { + relay.refetch({ + id: importContent.id, + entityType: importContent.entity_type, + }); }); - } - - componentWillUnmount() { - this.subscription.unsubscribe(); - } + return () => { + subscription.unsubscribe(); + }; + }); - handleSetCsvMapper(_, csvMapper) { + const handleSetCsvMapper = (_, csvMapper) => { const parsedCsvMapper = JSON.parse(csvMapper); const parsedRepresentations = JSON.parse(parsedCsvMapper.representations); const selectedCsvMapper = { ...parsedCsvMapper, representations: [...parsedRepresentations], }; - this.setState({ hasUserChoiceCsvMapper: resolveHasUserChoiceParsedCsvMapper(selectedCsvMapper) }); - } - handleOpenImport(file) { - this.setState({ fileToImport: file }); - } + sethasUserChoiceCsvMapper(resolveHasUserChoiceParsedCsvMapper(selectedCsvMapper)); + }; - handleCloseImport() { - this.setState({ - fileToImport: null, - }); - } + const handleOpenImport = (file) => { + setFileToImport(file); + }; - handleOpenValidate(file) { - this.setState({ fileToValidate: file }); - } + const handleCloseImport = () => { + setFileToImport(null); + }; - handleCloseValidate() { - this.setState({ fileToValidate: null }); - } + const handleOpenValidate = (file) => { + setFileToValidate(file); + }; - handleOpenCreate() { - this.setState({ displayCreate: true }); - } + const handleCloseValidate = () => { + setFileToValidate(null); + }; - handleCloseCreate() { - this.setState({ displayCreate: false }); - } + const handleOpenCreate = () => { + setDisplayCreate(true); + }; + + const handleCloseCreate = () => { + setDisplayCreate(false); + }; - onSubmitImport(values, { setSubmitting, resetForm }) { + const onSubmitImport = (values, { setSubmitting, resetForm }) => { const { connector_id, configuration, objectMarking } = values; let config = configuration; // Dynamically inject the markings chosen by the user into the csv mapper. - const isCsvConnector = this.state.selectedConnector?.name === 'ImportCsv'; + const isCsvConnector = selectedConnector?.name === 'ImportCsv'; if (isCsvConnector && configuration && objectMarking) { const parsedConfig = JSON.parse(configuration); if (typeof parsedConfig === 'object') { @@ -258,47 +240,49 @@ class ImportContentComponent extends Component { commitMutation({ mutation: fileManagerAskJobImportMutation, variables: { - fileName: this.state.fileToImport.id, + fileName: fileToImport.id, connectorId: connector_id, configuration: config, }, onCompleted: () => { setSubmitting(false); resetForm(); - this.handleCloseImport(); - MESSAGING$.notifySuccess(this.props.t('Import successfully asked')); + handleCloseImport(); + MESSAGING$.notifySuccess(t_i18n('Import successfully asked')); }, }); - } + }; - onSubmitValidate(values, { setSubmitting, resetForm }) { + const onSubmitValidate = (values, { setSubmitting, resetForm }) => { commitMutation({ mutation: fileManagerAskJobImportMutation, variables: { - fileName: this.state.fileToValidate.id, + fileName: fileToValidate.id, connectorId: values.connector_id, bypassValidation: true, }, onCompleted: () => { setSubmitting(false); resetForm(); - this.handleCloseValidate(); - MESSAGING$.notifySuccess(this.props.t('Import successfully asked')); + handleCloseValidate(); + MESSAGING$.notifySuccess(t_i18n('Import successfully asked')); }, }); - } + }; - onCreateWorkbenchCompleted() { - this.props.relay.refetch(); - } + const onCreateWorkbenchCompleted = () => { + relay.refetch(); + }; - reverseBy(field) { - this.setState({ sortBy: field, orderAsc: !this.state.orderAsc }); - } + const reverseBy = (field) => { + return ( + setSortBy(field), + setOrderAsc(!orderAsc) + ); + }; - SortHeader(field, label, isSortable) { - const { t } = this.props; - const sortComponent = this.state.orderAsc ? ( + const SortHeader = (field, label, isSortable) => { + const sortComponent = orderAsc ? ( ) : ( @@ -307,232 +291,287 @@ class ImportContentComponent extends Component { return (
- {t(label)} - {this.state.sortBy === field ? sortComponent : ''} + {t_i18n(label)} + {sortBy === field ? sortComponent : ''}
); } return (
- {t(label)} + {t_i18n(label)}
); - } - - render() { - const { - classes, - t, - importFiles, - pendingFiles, - connectorsImport, - relay, - isNewImportScreensEnabled, - } = this.props; - const { edges: importFilesEdges } = importFiles; - const { edges: pendingFilesEdges } = pendingFiles; - const { fileToImport, fileToValidate, displayCreate } = this.state; - const connectors = connectorsImport.filter((n) => !n.only_contextual); // Can be null but not empty - const importConnsPerFormat = scopesConn(connectors); - const handleSelectConnector = (_, value) => { - const selectedConnector = connectors.find((c) => c.id === value); - this.setState({ selectedConnector }); - }; - const invalidCsvMapper = this.state.selectedConnector?.name === 'ImportCsv' - && this.state.selectedConnector?.configurations?.length === 0; - return ( -
- - {isNewImportScreensEnabled && } - - -
- - {t('Uploaded files')} - -
- relay.refetch()} - size="medium" - /> - relay.refetch()} - size="medium" - /> -
-
- - {importFilesEdges.length ? ( - - {importFilesEdges.map((file) => file?.node && ( - !n.only_contextual); // Can be null but not empty + const importConnsPerFormat = scopesConn(connectors); + const handleSelectConnector = (_, value) => { + setSelectedConnector(connectors.find((c) => c.id === value)); + }; + const invalidCsvMapper = selectedConnector?.name === 'ImportCsv' + && selectedConnector?.configurations?.length === 0; + return ( +
+ + + + +
+ + {t_i18n('Uploaded files')} + +
+ relay.refetch()} + size="medium" + /> + relay.refetch()} + size="medium" + /> +
+
+ + {importFilesEdges.length ? ( + + {importFilesEdges.map((file) => file?.node && ( + - ))} - - ) : ( -
+ ))} + + ) : ( +
+ + {t_i18n('No file for the moment')} + +
+ )} + +
+ + + + {t_i18n('Enabled import connectors')} + + + {connectors.length ? ( + + {connectors.map((connector) => { + const connectorScope = connector.connector_scope.join(','); + return ( + + + + + + + + + + {connector.updated_at && ( + + )} + + ); + })} + + ) : ( +
+ + {t_i18n('No enrichment connectors on this platform')} + +
+ )} +
+
+ +
+ + {t_i18n('Analyst workbenches')} + + + + + - {t('No file for the moment')} - -
- )} -
-
- - -
- - {t('Analyst workbenches')} - - - - - -   - - - - {this.SortHeader('name', 'Name', false)} - {this.SortHeader('creator_name', 'Creator', false)} - {this.SortHeader('labels', 'Labels', false)} - {this.SortHeader( - 'lastModified', - 'Modification date', - false, - )} -
+ + + + {SortHeader('name', 'Name', false)} + {SortHeader('creator_name', 'Creator', false)} + {SortHeader('labels', 'Labels', false)} + {SortHeader( + 'lastModified', + 'Modification date', + false, + )} +
} - /> -   - - {pendingFilesEdges.map((file) => ( - +   + + {pendingFilesEdges.map((file) => ( + - ))} - - -
- + handleOpenImport={handleOpenValidate} + /> + ))} +
+
+
-
- - {({ submitForm, handleReset, isSubmitting, setFieldValue, isValid }) => ( -
- handleReset()} - fullWidth={true} - > - {`${t('Launch an import')}`} - - - {connectors.map((connector) => { - const disabled = !fileToImport + +
+ + {({ submitForm, handleReset, isSubmitting, setFieldValue, isValid }) => ( + + handleReset()} + fullWidth={true} + > + {`${t_i18n('Launch an import')}`} + + + {connectors.map((connector) => { + const disabled = !fileToImport || (connector.connector_scope.length > 0 && !R.includes( fileToImport.metaData.mimetype, connector.connector_scope, )); + return ( + + {connector.name} + + ); + })} + + {selectedConnector?.configurations?.length > 0 + ? + {selectedConnector.configurations?.map((config) => { return ( - {connector.name} + {config.name} ); })} - {this.state.selectedConnector?.configurations?.length > 0 - ? - {this.state.selectedConnector.configurations?.map((config) => { - return ( - - {config.name} - - ); - })} - - : + : } - {this.state.selectedConnector?.name === 'ImportCsv' - && this.state.hasUserChoiceCsvMapper + {selectedConnector?.name === 'ImportCsv' + && hasUserChoiceCsvMapper && ( <> ) } - - - - - - - - )} - - - {({ submitForm, handleReset, isSubmitting }) => ( -
- - {t('Validate and send for import')} - - - {connectors.map((connector, i) => { - const disabled = !fileToValidate + + + + + + +
+ )} +
+ + {({ submitForm, handleReset, isSubmitting }) => ( +
+ + {t_i18n('Validate and send for import')} + + + {connectors.map((connector, i) => { + const disabled = !fileToValidate || (connector.connector_scope.length > 0 && !R.includes( fileToValidate.metaData.mimetype, connector.connector_scope, )); - return ( - - {connector.name} - - ); - })} - - - - - - - -
- )} -
- -
- - - + return ( + + {connector.name} + + ); + })} +
+
+ + + + +
+ + )} +
+
- ); - } -} - -ImportContentComponent.propTypes = { - connectorsImport: PropTypes.array, - importFiles: PropTypes.object, - pendingFiles: PropTypes.object, - classes: PropTypes.object, - t: PropTypes.func, - nsdt: PropTypes.func, + + + +
+ ); }; const ImportContent = createRefetchContainer( @@ -658,4 +686,4 @@ const ImportContent = createRefetchContainer( importContentQuery, ); -export default R.compose(inject18n, withStyles(styles), withRouter)(ImportContent); +export default R.compose(inject18n, withStyles(useStyles), withRouter)(ImportContent); diff --git a/opencti-platform/opencti-front/src/private/components/entities/Sectors.jsx b/opencti-platform/opencti-front/src/private/components/entities/Sectors.jsx index e78ac4e40d8c..696b359d4767 100644 --- a/opencti-platform/opencti-front/src/private/components/entities/Sectors.jsx +++ b/opencti-platform/opencti-front/src/private/components/entities/Sectors.jsx @@ -1,81 +1,82 @@ -import React, { Component } from 'react'; +import React, { useState, useEffect } from 'react'; import * as PropTypes from 'prop-types'; -import { compose, propOr } from 'ramda'; +import { useNavigate, useLocation } from 'react-router-dom'; import { QueryRenderer } from '../../../relay/environment'; import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; -import inject18n from '../../../components/i18n'; +import { useFormatter } from '../../../components/i18n'; import SectorsLines, { sectorsLinesQuery } from './sectors/SectorsLines'; import SectorCreation from './sectors/SectorCreation'; import SearchInput from '../../../components/SearchInput'; import Security from '../../../utils/Security'; import { KNOWLEDGE_KNUPDATE } from '../../../utils/hooks/useGranted'; -import withRouter from '../../../utils/compat_router/withRouter'; import Breadcrumbs from '../../../components/Breadcrumbs'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; const LOCAL_STORAGE_KEY = 'sectors'; -class Sectors extends Component { - constructor(props) { - super(props); - const params = buildViewParamsFromUrlAndStorage( - props.navigate, - props.location, - LOCAL_STORAGE_KEY, - ); - this.state = { - searchTerm: propOr('', 'searchTerm', params), - openExports: false, - }; - } +const Sectors = () => { + const { t_i18n } = useFormatter(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Sectors | Entities')); + const navigate = useNavigate(); + const location = useLocation(); + const params = buildViewParamsFromUrlAndStorage( + navigate, + location, + LOCAL_STORAGE_KEY, + ); - saveView() { + const [sectorsState, setSectorsState] = useState({ + searchTerm: params.searchTerm ?? '', + openExports: false, + }); + + const saveView = () => { saveViewParameters( - this.props.navigate, - this.props.location, + navigate, + location, LOCAL_STORAGE_KEY, - this.state, + sectorsState, ); - } + }; - handleSearch(value) { - this.setState({ searchTerm: value }, () => this.saveView()); - } + const handleSearch = (value) => { + setSectorsState({ ...sectorsState, + searchTerm: value, + }); + }; - handleToggleExports() { - this.setState({ openExports: !this.state.openExports }); - } + useEffect(() => { + saveView(); + }, [sectorsState]); - render() { - const { searchTerm } = this.state; - const { t } = this.props; - return ( - <> - -
- -
- - - -
-
-
- ( - - )} + return ( + <> + +
+ - - ); - } -} +
+ + + +
+
+
+ ( + + )} + /> + + ); +}; Sectors.propTypes = { t: PropTypes.func, @@ -83,4 +84,4 @@ Sectors.propTypes = { location: PropTypes.object, }; -export default compose(inject18n, withRouter)(Sectors); +export default Sectors; diff --git a/opencti-platform/opencti-front/src/private/components/observations/stix_cyber_observables/StixCyberObservablesExportCreation.jsx b/opencti-platform/opencti-front/src/private/components/observations/stix_cyber_observables/StixCyberObservablesExportCreation.jsx index 66057e428a9d..d7ee1fc7e3f2 100644 --- a/opencti-platform/opencti-front/src/private/components/observations/stix_cyber_observables/StixCyberObservablesExportCreation.jsx +++ b/opencti-platform/opencti-front/src/private/components/observations/stix_cyber_observables/StixCyberObservablesExportCreation.jsx @@ -18,7 +18,7 @@ import Fab from '@mui/material/Fab'; import ObjectMarkingField from '../../common/form/ObjectMarkingField'; import inject18n from '../../../../components/i18n'; import { commitMutation, MESSAGING$, QueryRenderer } from '../../../../relay/environment'; -import { markingDefinitionsLinesSearchQuery } from '../../settings/marking_definitions/MarkingDefinitionsLines'; +import { markingDefinitionsLinesSearchQuery } from '../../settings/MarkingDefinitionsQuery'; import SelectField from '../../../../components/fields/SelectField'; import Loader from '../../../../components/Loader'; import { ExportContext } from '../../../../utils/ExportContextProvider'; diff --git a/opencti-platform/opencti-front/src/private/components/settings/CustomizationMenu.tsx b/opencti-platform/opencti-front/src/private/components/settings/CustomizationMenu.tsx index 22c4a4453380..87836cb0dd18 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/CustomizationMenu.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/CustomizationMenu.tsx @@ -23,6 +23,10 @@ const CustomizationMenu: FunctionComponent = () => { path: '/dashboard/settings/customization/decay', label: 'Decay rules', }, + { + path: '/dashboard/settings/customization/exclusion_lists', + label: 'Exclusion lists', + }, ]; return ; }; diff --git a/opencti-platform/opencti-front/src/private/components/settings/Groups.jsx b/opencti-platform/opencti-front/src/private/components/settings/Groups.tsx similarity index 50% rename from opencti-platform/opencti-front/src/private/components/settings/Groups.jsx rename to opencti-platform/opencti-front/src/private/components/settings/Groups.tsx index f1b28a00fa28..64d72f8db10a 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/Groups.jsx +++ b/opencti-platform/opencti-front/src/private/components/settings/Groups.tsx @@ -1,17 +1,26 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { compose, propOr } from 'ramda'; -import withStyles from '@mui/styles/withStyles'; +import React, { useState, useEffect } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; import { graphql } from 'react-relay'; -import { QueryRenderer } from '../../../relay/environment'; +import { useLocation, useNavigate } from 'react-router-dom'; import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; -import inject18n from '../../../components/i18n'; +import type { Theme } from '../../../components/Theme'; +import { useFormatter } from '../../../components/i18n'; import ListLines from '../../../components/list_lines/ListLines'; import GroupsLines, { groupsLinesQuery } from './groups/GroupsLines'; import GroupCreation from './groups/GroupCreation'; -import AccessesMenu from './AccessesMenu'; +import { OrderMode, PaginationOptions } from '../../../components/list_lines'; import Breadcrumbs from '../../../components/Breadcrumbs'; -import withRouter from '../../../utils/compat_router/withRouter'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; +import { GroupsLinesPaginationQuery$data } from './groups/__generated__/GroupsLinesPaginationQuery.graphql'; +import AccessesMenu from './AccessesMenu'; +import { QueryRenderer } from '../../../relay/environment'; + +const useStyles = makeStyles(() => ({ + container: { + margin: 0, + padding: '0 200px 50px 0', + }, +})); export const groupsSearchQuery = graphql` query GroupsSearchQuery($search: String) { @@ -40,49 +49,51 @@ export const groupsSearchQuery = graphql` } `; -const styles = () => ({ - container: { - margin: 0, - padding: '0 200px 50px 0', - }, -}); - const LOCAL_STORAGE_KEY = 'groups'; -class Groups extends Component { - constructor(props) { - super(props); - const params = buildViewParamsFromUrlAndStorage( - props.navigate, - props.location, - LOCAL_STORAGE_KEY, - ); - this.state = { - sortBy: propOr('name', 'sortBy', params), - orderAsc: propOr(true, 'orderAsc', params), - searchTerm: propOr('', 'searchTerm', params), - view: propOr('lines', 'view', params), - }; - } - saveView() { +const Groups = () => { + const classes = useStyles(); + const { t_i18n } = useFormatter(); + const navigate = useNavigate(); + const location = useLocation(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Security: Groups | Settings')); + const params = buildViewParamsFromUrlAndStorage( + navigate, + location, + LOCAL_STORAGE_KEY, + ); + + const [groupState, setGroupState] = useState<{ orderAsc: boolean, searchTerm: string, view: string, sortBy: string }>({ + sortBy: params.sortBy ?? 'name', + orderAsc: params.orderAsc !== false, + searchTerm: params.searchTerm ?? '', + view: params.view ?? 'lines', + }); + + function saveView() { saveViewParameters( - this.props.navigate, - this.props.location, + navigate, + location, LOCAL_STORAGE_KEY, - this.state, + groupState, ); } - handleSearch(value) { - this.setState({ searchTerm: value }, () => this.saveView()); + function handleSearch(value: string) { + setGroupState({ ...groupState, searchTerm: value }); } - handleSort(field, orderAsc) { - this.setState({ sortBy: field, orderAsc }, () => this.saveView()); + function handleSort(field: string, orderAsc: boolean) { + setGroupState({ ...groupState, sortBy: field, orderAsc }); } - renderLines(paginationOptions) { - const { sortBy, orderAsc, searchTerm } = this.state; + useEffect(() => { + saveView(); + }, [groupState]); + + function renderLines(paginationOptions: PaginationOptions) { + const { sortBy, orderAsc, searchTerm } = groupState; const dataColumns = { name: { label: 'Name', @@ -125,8 +136,8 @@ class Groups extends Component { sortBy={sortBy} orderAsc={orderAsc} dataColumns={dataColumns} - handleSort={this.handleSort.bind(this)} - handleSearch={this.handleSearch.bind(this)} + handleSort={handleSort} + handleSearch={handleSearch} displayImport={false} secondaryAction={false} keyword={searchTerm} @@ -134,7 +145,7 @@ class Groups extends Component { ( + render={({ props }: { props: GroupsLinesPaginationQuery$data }) => ( - - - {view === 'lines' ? this.renderLines(paginationOptions) : ''} - -
- ); - } -} + const paginationOptions: PaginationOptions = { + search: groupState.searchTerm, + orderBy: groupState.sortBy ? groupState.sortBy : null, + orderMode: groupState.orderAsc ? OrderMode.asc : OrderMode.desc, + }; -Groups.propTypes = { - t: PropTypes.func, - classes: PropTypes.object, - navigate: PropTypes.func, - location: PropTypes.object, + return ( +
+ + + {groupState.view === 'lines' ? renderLines(paginationOptions) : ''} + +
+ ); }; -export default compose(inject18n, withRouter, withStyles(styles))(Groups); +export default Groups; diff --git a/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitions.jsx b/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitions.jsx deleted file mode 100644 index 223d5aac0955..000000000000 --- a/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitions.jsx +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; -import { MarkingDefinitionLineDummy } from './marking_definitions/MarkingDefinitionLine'; -import { useFormatter } from '../../../components/i18n'; -import ListLines from '../../../components/list_lines/ListLines'; -import MarkingDefinitionsLines, { markingDefinitionsLinesQuery } from './marking_definitions/MarkingDefinitionsLines'; -import MarkingDefinitionCreation from './marking_definitions/MarkingDefinitionCreation'; -import AccessesMenu from './AccessesMenu'; -import Breadcrumbs from '../../../components/Breadcrumbs'; -import { usePaginationLocalStorage } from '../../../utils/hooks/useLocalStorage'; -import useQueryLoading from '../../../utils/hooks/useQueryLoading'; - -const LOCAL_STORAGE_KEY = 'MarkingDefinitions'; -const MarkingDefinitions = () => { - const { t_i18n } = useFormatter(); - const { viewStorage, helpers, paginationOptions } = usePaginationLocalStorage( - LOCAL_STORAGE_KEY, - { - searchTerm: '', - sortBy: 'definition', - orderAsc: true, - numberOfElements: { - number: 0, - symbol: '', - }, - }, - ); - - const renderLines = () => { - const { - sortBy, - orderAsc, - searchTerm, - numberOfElements, - } = viewStorage; - const dataColumns = { - definition_type: { - label: 'Type', - width: '25%', - isSortable: true, - }, - definition: { - label: 'Definition', - width: '25%', - isSortable: true, - }, - x_opencti_color: { - label: 'Color', - width: '15%', - isSortable: true, - }, - x_opencti_order: { - label: 'Order', - width: '10%', - isSortable: true, - }, - created: { - label: 'Original creation date', - width: '15%', - isSortable: true, - }, - }; - const queryRef = useQueryLoading(markingDefinitionsLinesQuery, { count: 25, ...paginationOptions }); - return ( - - {queryRef && ( - - {Array(20) - .fill(0) - .map((_, idx) => ( - - ))} - - } - > - - - )} - - ); - }; - - return ( -
- - - {renderLines()} - -
- ); -}; - -export default MarkingDefinitions; diff --git a/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitions.tsx b/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitions.tsx new file mode 100644 index 000000000000..04313ee24e24 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitions.tsx @@ -0,0 +1,196 @@ +import React from 'react'; +import { graphql } from 'react-relay'; +import Tooltip from '@mui/material/Tooltip'; +import { MarkingDefinitionsLine_node$data } from '@components/settings/__generated__/MarkingDefinitionsLine_node.graphql'; +import DangerZoneChip from '@components/common/danger_zone/DangerZoneChip'; +import { MarkingDefinitionsLinesPaginationQuery } from './__generated__/MarkingDefinitionsLinesPaginationQuery.graphql'; +import MarkingDefinitionPopover from './marking_definitions/MarkingDefinitionPopover'; +import AccessesMenu from './AccessesMenu'; +import { MarkingDefinitionsLines_data$data } from './__generated__/MarkingDefinitionsLines_data.graphql'; +import { useFormatter } from '../../../components/i18n'; +import MarkingDefinitionCreation from './marking_definitions/MarkingDefinitionCreation'; +import Breadcrumbs from '../../../components/Breadcrumbs'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; +import { usePaginationLocalStorage } from '../../../utils/hooks/useLocalStorage'; +import useQueryLoading from '../../../utils/hooks/useQueryLoading'; +import { useBuildEntityTypeBasedFilterContext } from '../../../utils/filters/filtersUtils'; +import DataTable from '../../../components/dataGrid/DataTable'; +import { UsePreloadedPaginationFragment } from '../../../utils/hooks/usePreloadedPaginationFragment'; +import { truncate } from '../../../utils/String'; +import useSensitiveModifications from '../../../utils/hooks/useSensitiveModifications'; +import { MAGICAL_SIZE } from '../../../components/dataGrid/dataTableUtils'; +import type { DataTableColumn } from '../../../components/dataGrid/dataTableTypes'; + +const LOCAL_STORAGE_KEY = 'MarkingDefinitions'; + +export const markingDefinitionsLinesQuery = graphql` + query MarkingDefinitionsLinesPaginationQuery( + $search: String + $count: Int! + $cursor: ID + $orderBy: MarkingDefinitionsOrdering + $orderMode: OrderingMode + $filters: FilterGroup + ) { + ...MarkingDefinitionsLines_data + @arguments( + search: $search + count: $count + cursor: $cursor + orderBy: $orderBy + orderMode: $orderMode + filters: $filters + ) + } +`; + +const markingDefinitionLineFragment = graphql` + fragment MarkingDefinitionsLine_node on MarkingDefinition { + id + standard_id + definition_type + definition + x_opencti_order + x_opencti_color + created + modified + } +`; + +const markingDefinitionsLinesFragment = graphql` + fragment MarkingDefinitionsLines_data on Query + @argumentDefinitions( + search: { type: "String" } + count: { type: "Int", defaultValue: 25 } + cursor: { type: "ID" } + orderBy: { + type: "MarkingDefinitionsOrdering" + defaultValue: definition + } + orderMode: { type: "OrderingMode", defaultValue: asc } + filters: { type: "FilterGroup" } + ) + @refetchable(queryName: "MarkingDefinitionsLinesRefetchQuery") { + markingDefinitions( + search: $search + first: $count + after: $cursor + orderBy: $orderBy + orderMode: $orderMode + filters: $filters + ) @connection(key: "Pagination_markingDefinitions") { + edges { + node { + id + entity_type + ...MarkingDefinitionsLine_node + } + } + pageInfo { + endCursor + hasNextPage + globalCount + } + } + } +`; + +const MarkingDefinitions = () => { + const { t_i18n } = useFormatter(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Security: Marking Definitions | Settings')); + + const initialValues = { + searchTerm: '', + sortBy: 'definition', + orderAsc: true, + numberOfElements: { + number: 0, + symbol: '', + }, + }; + const { viewStorage: { filters }, helpers, paginationOptions } = usePaginationLocalStorage( + LOCAL_STORAGE_KEY, + initialValues, + ); + const contextFilters = useBuildEntityTypeBasedFilterContext('Marking-Definition', filters); + const queryPaginationOptions = { ...paginationOptions, filters: contextFilters }; + + const definitionTypeRender: DataTableColumn['render'] = ( + data: MarkingDefinitionsLine_node$data, + { column: { size } }, + ) => { + const { standard_id, definition_type } = data; + const { isSensitive } = useSensitiveModifications('markings', standard_id); + return ( + +
+ {truncate(definition_type, size * MAGICAL_SIZE)} + {isSensitive && } +
+
+ ); + }; + + const dataColumns = { + definition_type: { + percentWidth: 25, + render: definitionTypeRender, + }, + definition: { percentWidth: 25 }, + x_opencti_color: { percentWidth: 15 }, + x_opencti_order: { percentWidth: 15 }, + created: { percentWidth: 20 }, + }; + + const queryRef = useQueryLoading( + markingDefinitionsLinesQuery, + { ...queryPaginationOptions, count: 25 }, + ); + + const preloadedPaginationProps = { + linesQuery: markingDefinitionsLinesQuery, + linesFragment: markingDefinitionsLinesFragment, + queryRef, + nodePath: ['markingDefinitions', 'pageInfo', 'globalCount'], + setNumberOfElements: helpers.handleSetNumberOfElements, + } as UsePreloadedPaginationFragment; + + return ( +
+ + + {queryRef && ( + data.markingDefinitions?.edges?.map((e) => e?.node)} + storageKey={LOCAL_STORAGE_KEY} + initialValues={initialValues} + toolbarFilters={contextFilters} + lineFragment={markingDefinitionLineFragment} + preloadedPaginationProps={preloadedPaginationProps} + actions={(markingDefinition) => ( + + )} + entityTypes={['Marking-Definition']} + searchContextFinal={{ entityTypes: ['Marking-Definition'] }} + disableNavigation + disableToolBar + disableSelectAll + canToggleLine={false} + /> + )} + +
+ ); +}; + +export default MarkingDefinitions; diff --git a/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitionsQuery.tsx b/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitionsQuery.tsx new file mode 100644 index 000000000000..8fe7861af329 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/MarkingDefinitionsQuery.tsx @@ -0,0 +1,21 @@ +import { graphql } from 'react-relay'; + +// eslint-disable-next-line import/prefer-default-export +export const markingDefinitionsLinesSearchQuery = graphql` + query MarkingDefinitionsQuerySearchQuery($search: String) { + markingDefinitions(search: $search) { + edges { + node { + id + standard_id + definition_type + definition + x_opencti_order + x_opencti_color + created + modified + } + } + } + } +`; diff --git a/opencti-platform/opencti-front/src/private/components/settings/Roles.jsx b/opencti-platform/opencti-front/src/private/components/settings/Roles.jsx deleted file mode 100644 index a8e7b56662a9..000000000000 --- a/opencti-platform/opencti-front/src/private/components/settings/Roles.jsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { compose, propOr } from 'ramda'; -import withStyles from '@mui/styles/withStyles'; -import { QueryRenderer } from '../../../relay/environment'; -import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; -import inject18n from '../../../components/i18n'; -import ListLines from '../../../components/list_lines/ListLines'; -import RolesLines, { rolesLinesQuery } from './roles/RolesLines'; -import AccessesMenu from './AccessesMenu'; -import RoleCreation from './roles/RoleCreation'; -import withRouter from '../../../utils/compat_router/withRouter'; -import Breadcrumbs from '../../../components/Breadcrumbs'; - -const styles = () => ({ - container: { - margin: 0, - padding: '0 200px 50px 0', - }, -}); - -const LOCAL_STORAGE_KEY = 'roles'; - -class Roles extends Component { - constructor(props) { - super(props); - const params = buildViewParamsFromUrlAndStorage( - props.navigate, - props.location, - LOCAL_STORAGE_KEY, - ); - this.state = { - sortBy: propOr('name', 'sortBy', params), - orderAsc: propOr(true, 'orderAsc', params), - searchTerm: propOr('', 'searchTerm', params), - view: propOr('lines', 'view', params), - }; - } - - saveView() { - saveViewParameters( - this.props.navigate, - this.props.location, - LOCAL_STORAGE_KEY, - this.state, - ); - } - - handleSort(field, orderAsc) { - this.setState({ sortBy: field, orderAsc }, () => this.saveView()); - } - - handleSearch(value) { - this.setState({ searchTerm: value }, () => this.saveView()); - } - - renderLines(paginationOptions) { - const { sortBy, orderAsc, searchTerm } = this.state; - const dataColumns = { - name: { - label: 'Name', - width: '40%', - isSortable: true, - }, - groups: { - label: 'Groups with this role', - width: '20%', - isSortable: false, - }, - created_at: { - label: 'Platform creation date', - width: '20%', - isSortable: true, - }, - updated_at: { - label: 'Modification date', - width: '20%', - isSortable: true, - }, - }; - return ( - - ( - - )} - /> - - ); - } - - render() { - const { view, sortBy, orderAsc, searchTerm } = this.state; - const { classes, t } = this.props; - const paginationOptions = { - search: searchTerm, - orderBy: sortBy, - orderMode: orderAsc ? 'asc' : 'desc', - }; - return ( -
- - - {view === 'lines' ? this.renderLines(paginationOptions) : ''} - -
- ); - } -} - -Roles.propTypes = { - t: PropTypes.func, - classes: PropTypes.object, - navigate: PropTypes.func, - location: PropTypes.object, -}; - -export default compose(inject18n, withRouter, withStyles(styles))(Roles); diff --git a/opencti-platform/opencti-front/src/private/components/settings/Roles.tsx b/opencti-platform/opencti-front/src/private/components/settings/Roles.tsx new file mode 100644 index 000000000000..8a6b90c43cf3 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/Roles.tsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import makeStyles from '@mui/styles/makeStyles'; +import { useFormatter } from '../../../components/i18n'; +import { buildViewParamsFromUrlAndStorage, saveViewParameters } from '../../../utils/ListParameters'; +import ListLines from '../../../components/list_lines/ListLines'; +import RolesLines, { rolesLinesQuery } from './roles/RolesLines'; +import AccessesMenu from './AccessesMenu'; +import { OrderMode, PaginationOptions } from '../../../components/list_lines'; +import RoleCreation from './roles/RoleCreation'; +import Breadcrumbs from '../../../components/Breadcrumbs'; +import { RolesLinesPaginationQuery$data } from './roles/__generated__/RolesLinesPaginationQuery.graphql'; +import { QueryRenderer } from '../../../relay/environment'; +import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; + +const LOCAL_STORAGE_KEY = 'roles'; + +const useStyles = makeStyles(() => ({ + container: { + margin: 0, + padding: '0 200px 50px 0', + }, +})); + +const Role = () => { + const { t_i18n } = useFormatter(); + const navigate = useNavigate(); + const location = useLocation(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Security: Roles | Settings')); + const params = buildViewParamsFromUrlAndStorage( + navigate, + location, + LOCAL_STORAGE_KEY, + ); + const classes = useStyles(); + const [rolesState, setRolesState] = useState<{ orderAsc: boolean, searchTerm: string, view: string, sortBy: string }>({ + orderAsc: params.orderAsc !== false, + searchTerm: params.searchTerm ?? '', + view: params.view ?? 'lines', + sortBy: params.sortBy ?? 'name', + }); + + function saveView() { + saveViewParameters( + navigate, + location, + LOCAL_STORAGE_KEY, + rolesState, + ); + } + + function handleSearch(value: string) { + setRolesState({ ...rolesState, searchTerm: value }); + } + + function handleSort(field: string, orderAsc: boolean) { + setRolesState({ ...rolesState, sortBy: field, orderAsc }); + } + + useEffect(() => { + saveView(); + }, [rolesState]); + + function renderLines(paginationOptions: PaginationOptions) { + const { sortBy, orderAsc } = rolesState; + const dataColumns = { + name: { + label: 'Name', + width: '40%', + isSortable: true, + }, + groups: { + label: 'Groups with this role', + width: '20%', + isSortable: false, + }, + created_at: { + label: 'Platform creation date', + width: '20%', + isSortable: true, + }, + updated_at: { + label: 'Modification date', + width: '20%', + isSortable: true, + }, + }; + return ( + + ( + + )} + /> + + ); + } + + const paginationOptions: PaginationOptions = { + search: rolesState.searchTerm, + orderBy: rolesState.sortBy ? rolesState.sortBy : null, + orderMode: rolesState.orderAsc ? OrderMode.asc : OrderMode.desc, + }; + return ( +
+ + + {rolesState.view === 'lines' ? renderLines(paginationOptions) : ''} + +
+ ); +}; + +export default Role; diff --git a/opencti-platform/opencti-front/src/private/components/settings/Root.jsx b/opencti-platform/opencti-front/src/private/components/settings/Root.jsx index 8ccf7d395ad4..da27611734bd 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/Root.jsx +++ b/opencti-platform/opencti-front/src/private/components/settings/Root.jsx @@ -46,6 +46,7 @@ const Alerting = lazy(() => import('./activity/alerting/Alerting')); const DecayRules = lazy(() => import('./decay/DecayRules')); const DecayRule = lazy(() => import('./decay/DecayRule')); const SupportPackage = lazy(() => import('./support/SupportPackages')); +const ExclusionLists = lazy(() => import('./exclusion_lists/ExclusionLists')); const Root = () => { const adminOrga = isOnlyOrganizationAdmin(); @@ -328,6 +329,14 @@ const Root = () => { } /> + }> + + + } + /> ({ +const useStyles = makeStyles(() => ({ container: { margin: 0, padding: '0 200px 50px 0', }, parameters: { float: 'left', - marginTop: -10, + marginBottom: 10, }, -}); +})); const LOCAL_STORAGE_KEY = 'sessions'; -class Sessions extends Component { - constructor(props) { - super(props); - const params = buildViewParamsFromUrlAndStorage( - props.navigate, - props.location, - LOCAL_STORAGE_KEY, - ); - this.state = { - searchTerm: propOr('', 'searchTerm', params), - openExports: false, - }; - } - - saveView() { - saveViewParameters( - this.props.navigate, - this.props.location, - LOCAL_STORAGE_KEY, - this.state, - ); - } - - handleSearch(value) { - this.setState({ searchTerm: value }, () => this.saveView()); - } - - render() { - const { searchTerm } = this.state; - const { t, classes } = this.props; - return ( -
- - -
-
- -
+const Sessions = () => { + const classes = useStyles(); + const { t_i18n } = useFormatter(); + const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('Security: Sessions | Settings')); + const { viewStorage, helpers } = usePaginationLocalStorage( + LOCAL_STORAGE_KEY, + {}, + ); + return ( +
+ + +
+
+
-
- { - if (props) { - return ; - } - return
; - }} - />
- ); - } -} - -Sessions.propTypes = { - t: PropTypes.func, - classes: PropTypes.object, - navigate: PropTypes.func, - location: PropTypes.object, +
+ { + if (props) { + return ; + } + return
; + }} + /> +
+ ); }; -export default compose(inject18n, withRouter, withStyles(styles))(Sessions); +export default Sessions; diff --git a/opencti-platform/opencti-front/src/private/components/settings/SessionsList.jsx b/opencti-platform/opencti-front/src/private/components/settings/SessionsList.jsx index b6ab3dacf78d..c4ea4f1de0b5 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/SessionsList.jsx +++ b/opencti-platform/opencti-front/src/private/components/settings/SessionsList.jsx @@ -1,8 +1,5 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import * as R from 'ramda'; +import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; -import withStyles from '@mui/styles/withStyles'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemIcon from '@mui/material/ListItemIcon'; @@ -17,17 +14,17 @@ import Button from '@mui/material/Button'; import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; import IconButton from '@mui/material/IconButton'; import { graphql, createRefetchContainer } from 'react-relay'; -import inject18n from '../../../components/i18n'; +import makeStyles from '@mui/styles/makeStyles'; import { commitMutation } from '../../../relay/environment'; import { FIVE_SECONDS, timestamp } from '../../../utils/Time'; import { userSessionKillMutation } from './users/User'; import ItemIcon from '../../../components/ItemIcon'; import Transition from '../../../components/Transition'; -import withRouter from '../../../utils/compat_router/withRouter'; +import { useFormatter } from '../../../components/i18n'; const interval$ = interval(FIVE_SECONDS); -const styles = (theme) => ({ +const useStyles = makeStyles((theme) => ({ item: {}, itemNested: { paddingLeft: theme.spacing(4), @@ -76,187 +73,161 @@ const styles = (theme) => ({ position: 'absolute', right: -10, }, -}); +})); -class SessionsListComponent extends Component { - constructor(props) { - super(props); - this.state = { - displayUpdate: false, - displayKillSession: false, - killing: false, - sessionToKill: null, - }; - } +const SessionsListComponent = ({ relay, data, keyword }) => { + const classes = useStyles(); + const { t_i18n, nsdt } = useFormatter(); + const [displayKillSession, setDisplayKillSession] = useState(false); + const [killing, setKilling] = useState(false); + const [sessionToKill, setSessionToKill] = useState(null); - componentDidMount() { - this.subscription = interval$.subscribe(() => { - this.props.relay.refetch(); - }); - } + useEffect(() => { + const subscription = interval$.subscribe(() => relay.refetch()); + return () => { + subscription.unsubscribe(); + }; + }, []); - componentWillUnmount() { - this.subscription.unsubscribe(); - } + const handleOpenKillSession = (session) => { + setDisplayKillSession(true); + setSessionToKill(session); + }; - handleOpenKillSession(session) { - this.setState({ displayKillSession: true, sessionToKill: session }); - } + const handleCloseKillSession = () => { + setDisplayKillSession(false); + setSessionToKill(null); + }; - handleCloseKillSession() { - this.setState({ displayKillSession: false, sessionToKill: null }); - } - - submitKillSession() { - this.setState({ killing: true }); + const submitKillSession = () => { + setKilling(true); commitMutation({ mutation: userSessionKillMutation, variables: { - id: this.state.sessionToKill, + id: sessionToKill, }, onCompleted: () => { - this.setState({ killing: false }); - this.handleCloseKillSession(); + setKilling(false); + handleCloseKillSession(); }, onError: () => { - this.setState({ killing: false }); + setKilling(false); }, }); - } + }; - render() { - const { classes, nsdt, t, data, keyword } = this.props; - const sortByNameCaseInsensitive = R.sortBy( - R.compose(R.toLower, R.path(['user', 'name'])), - ); - const filterByKeyword = (n) => keyword === '' - || n.user.name.toLowerCase().indexOf(keyword.toLowerCase()) !== -1; - const sessions = R.pipe( - R.propOr([], 'sessions'), - R.filter(filterByKeyword), - sortByNameCaseInsensitive, - )(data); - return ( - <> - - {sessions.map((session) => { - const { user, sessions: userSessions } = session; - const orderedSessions = R.sort( - (a, b) => timestamp(a.created) - timestamp(b.created), - userSessions, - ); - return ( -
- - - - - -
{user.name}
-
{user.email}
-
- } - /> - -   - - - - {orderedSessions.map((userSession) => ( - - - - - -
- {nsdt(userSession.created)} -
-
- {Math.round(userSession.ttl / 60)}{' '} - {t('minutes left')} /{' '} - {Math.round(userSession.originalMaxAge / 60)} -
-
- } - /> - - - - - - - ))} - -
- ); - })} - - - - - {t('Do you want to kill this session?')} - - - - - - - - - ); - } -} + const sortByNameCaseInsensitive = (a, b) => a.user.name.toLowerCase().localeCompare(b.user.name.toLowerCase()); + const filterByKeyword = (n) => keyword === '' + || n.user.name.toLowerCase().indexOf(keyword.toLowerCase()) !== -1; + const sessions = (data.sessions ?? []).filter(filterByKeyword).toSorted(sortByNameCaseInsensitive); -SessionsListComponent.propTypes = { - t: PropTypes.func, - classes: PropTypes.object, - navigate: PropTypes.func, - location: PropTypes.object, - nsdt: PropTypes.func, - data: PropTypes.object, + return ( + <> + + {sessions.map((session) => { + const { user, sessions: userSessions } = session; + const orderedSessions = userSessions.toSorted( + (a, b) => timestamp(a.created) - timestamp(b.created), + ); + return ( +
+ + + + + +
{user.name}
+
{user.email}
+
+ } + /> + +   + + + + {orderedSessions.map((userSession) => ( + + + + + +
+ {nsdt(userSession.created)} +
+
+ {Math.round(userSession.ttl / 60)}{' '} + {t_i18n('minutes left')} /{' '} + {Math.round(userSession.originalMaxAge / 60)} +
+
+ } + /> + + handleOpenKillSession(userSession.id)} + size="large" + color="primary" + > + + + + + ))} + +
+ ); + })} + + + + + {t_i18n('Do you want to kill this session?')} + + + + + + + + + ); }; export const sessionsListQuery = graphql` @@ -265,7 +236,7 @@ export const sessionsListQuery = graphql` } `; -const SessionsList = createRefetchContainer( +export default createRefetchContainer( SessionsListComponent, { data: graphql` @@ -287,9 +258,3 @@ const SessionsList = createRefetchContainer( }, sessionsListQuery, ); - -export default R.compose( - inject18n, - withRouter, - withStyles(styles), -)(SessionsList); diff --git a/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListCreation.tsx b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListCreation.tsx new file mode 100644 index 000000000000..905244c5b15a --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListCreation.tsx @@ -0,0 +1,210 @@ +import React, { FunctionComponent } from 'react'; +import Drawer, { DrawerVariant } from '@components/common/drawer/Drawer'; +import { RecordSourceSelectorProxy } from 'relay-runtime'; +import { graphql } from 'react-relay'; +import { Field, Form, Formik, FormikConfig } from 'formik'; +import exclusionListValidator from '@components/settings/exclusion_lists/ExclusionListValidator'; +import Button from '@mui/material/Button'; +import { ExclusionListsLinesPaginationQuery$variables } from '@components/settings/exclusion_lists/__generated__/ExclusionListsLinesPaginationQuery.graphql'; +import { Option } from '@components/common/form/ReferenceField'; +import CustomFileUploader from '@components/common/files/CustomFileUploader'; +import { insertNode } from '../../../../utils/store'; +import useApiMutation from '../../../../utils/hooks/useApiMutation'; +import { handleErrorInForm } from '../../../../relay/environment'; +import TextField from '../../../../components/TextField'; +import MarkdownField from '../../../../components/fields/MarkdownField'; +import { useFormatter } from '../../../../components/i18n'; +import AutocompleteField from '../../../../components/AutocompleteField'; +import { fieldSpacingContainerStyle } from '../../../../utils/field'; +import useSchema from '../../../../utils/hooks/useSchema'; + +const exclusionListCreationFileMutation = graphql` + mutation ExclusionListCreationFileAddMutation($input: ExclusionListFileAddInput!) { + exclusionListFileAdd(input: $input) { + ...ExclusionListsLine_node + } + } +`; + +interface ExclusionListCreationFileFormData { + name: string; + description: string; + exclusion_list_entity_types: Option[]; + file: File | undefined; + action: string; +} + +interface ExclusionListCreationFormProps { + updater: (store: RecordSourceSelectorProxy, rootField: string) => void; + onReset?: () => void; + onCompleted?: () => void; +} + +interface ExclusionListCreationProps { + paginationOptions: ExclusionListsLinesPaginationQuery$variables; +} + +const ExclusionListCreationForm: FunctionComponent = ({ + updater, + onReset, + onCompleted, +}) => { + const { t_i18n } = useFormatter(); + const { schema: { scos: entityTypes } } = useSchema(); + const actions: string[] = ['Exclusion']; + const [commitFile] = useApiMutation(exclusionListCreationFileMutation); + const onSubmitFile: FormikConfig['onSubmit'] = ( + values, + { setSubmitting, resetForm, setErrors }, + ) => { + const input = { + name: values.name, + description: values.description, + exclusion_list_entity_types: values.exclusion_list_entity_types.map((type) => type.value), + file: values.file, + }; + commitFile({ + variables: { + input, + }, + updater: (store) => { + if (updater) { + updater(store, 'exclusionListFileAdd'); + } + }, + onCompleted: () => { + setSubmitting(false); + resetForm(); + if (onCompleted) { + onCompleted(); + } + }, + onError: (error: Error) => { + handleErrorInForm(error, setErrors); + setSubmitting(false); + }, + }); + }; + + const initialValuesFile: ExclusionListCreationFileFormData = { + name: '', + description: '', + exclusion_list_entity_types: [], + file: undefined, + action: 'Exclusion', + }; + + const entityTypesOptions: Option[] = entityTypes.map((type) => ({ + value: type.id, + label: type.label, + })); + + return ( + + initialValues={initialValuesFile} + validationSchema={exclusionListValidator(t_i18n)} + onSubmit={onSubmitFile} + onReset={onReset} + > + {({ submitForm, handleReset, isSubmitting, setFieldValue }) => ( +
+ + + , + option: Option, + ) =>
  • {option.label}
  • } + textfieldprops={{ label: t_i18n('Entity types') }} + /> + + , + option: string, + ) =>
  • {option}
  • } + textfieldprops={{ label: t_i18n('Action') }} + disabled + /> + +
    + + +
    + + )} + + ); +}; + +const ExclusionListCreation: FunctionComponent = ({ + paginationOptions, +}) => { + const { t_i18n } = useFormatter(); + const updater = (store: RecordSourceSelectorProxy, rootField: string) => { + insertNode( + store, + 'Pagination_exclusionLists', + paginationOptions, + rootField, + ); + }; + + return ( + + {({ onClose }) => ( + <> + + + )} + + ); +}; + +export default ExclusionListCreation; diff --git a/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListEdition.tsx b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListEdition.tsx new file mode 100644 index 000000000000..d8a4e2db8b99 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListEdition.tsx @@ -0,0 +1,142 @@ +import React, { FunctionComponent } from 'react'; +import { graphql, PreloadedQuery, useFragment, usePreloadedQuery } from 'react-relay'; +import { Field, Form, Formik } from 'formik'; +import * as Yup from 'yup'; +import { ExclusionListEditionQuery } from '@components/settings/exclusion_lists/__generated__/ExclusionListEditionQuery.graphql'; +import { ExclusionListEdition_edition$key } from '@components/settings/exclusion_lists/__generated__/ExclusionListEdition_edition.graphql'; +import { Option } from '@components/common/form/ReferenceField'; +import AutocompleteField from '../../../../components/AutocompleteField'; +import { fieldSpacingContainerStyle } from '../../../../utils/field'; +import useApiMutation from '../../../../utils/hooks/useApiMutation'; +import MarkdownField from '../../../../components/fields/MarkdownField'; +import TextField from '../../../../components/TextField'; +import { useFormatter } from '../../../../components/i18n'; +import useSchema from '../../../../utils/hooks/useSchema'; + +export const exclusionListMutationFieldPatch = graphql` + mutation ExclusionListEditionFieldPatchMutation($id: ID!, $input: [EditInput!]!) { + exclusionListFieldPatch(id: $id, input: $input) { + ...ExclusionListsLine_node + ...ExclusionListEdition_edition + } + } +`; + +const exclusionListEditionFragment = graphql` + fragment ExclusionListEdition_edition on ExclusionList { + id + name + description + exclusion_list_entity_types + } +`; + +export const exclusionListEditionQuery = graphql` + query ExclusionListEditionQuery($id: String!) { + exclusionList(id: $id) { + ...ExclusionListEdition_edition + } + } +`; + +const exclusionListValidation = (t: (n: string) => string) => Yup.object().shape({ + name: Yup.string().required(t('This field is required')), + description: Yup.string().nullable(), +}); + +interface ExclusionListEditionComponentProps { + queryRef: PreloadedQuery; + onClose: () => void; +} + +interface ExclusionListEditionValues { + name: string; + description?: string | null; + exclusion_list_entity_types: Option[]; +} + +const ExclusionListEdition: FunctionComponent = ({ + queryRef, + onClose, +}) => { + const { t_i18n } = useFormatter(); + const { schema: { scos: entityTypes } } = useSchema(); + const { exclusionList } = usePreloadedQuery(exclusionListEditionQuery, queryRef); + const data = useFragment(exclusionListEditionFragment, exclusionList); + const [commitFieldPatch] = useApiMutation(exclusionListMutationFieldPatch); + const onSubmit = (name: string, value: string[]) => { + // TODO : Use useFormEditor ? + commitFieldPatch({ + variables: { + id: data?.id, + input: [{ key: name, value }], + }, + }); + }; + + const initialValues: ExclusionListEditionValues = { + name: data?.name ?? '', + description: data?.description, + exclusion_list_entity_types: (data?.exclusion_list_entity_types ?? []).map((type) => ({ + value: type, + label: type, + })), + }; + + const entityTypesOptions: Option[] = entityTypes.map((type) => ({ + value: type.id, + label: type.label, + })); + + return ( + { + }} + onClose={onClose} + > +
    + + + , + option: Option, + ) =>
  • {option.label}
  • } + textfieldprops={{ label: t_i18n('Entity types') }} + onChange={(name: string, value: { value: string; label: string }[]) => { + onSubmit( + name, + value.map((n) => n.label), + ); + }} + /> + +
    + ); +}; + +export default ExclusionListEdition; diff --git a/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListPopover.tsx b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListPopover.tsx new file mode 100644 index 000000000000..0f064c1e9d5c --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListPopover.tsx @@ -0,0 +1,110 @@ +import React, { useState } from 'react'; +import IconButton from '@mui/material/IconButton'; +import MoreVert from '@mui/icons-material/MoreVert'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import Drawer from '@components/common/drawer/Drawer'; +import { graphql, useQueryLoader } from 'react-relay'; +import { ExclusionListsLine_node$data } from '@components/settings/exclusion_lists/__generated__/ExclusionListsLine_node.graphql'; +import { ExclusionListsLinesPaginationQuery$variables } from '@components/settings/exclusion_lists/__generated__/ExclusionListsLinesPaginationQuery.graphql'; +import ExclusionListEdition, { exclusionListEditionQuery, exclusionListMutationFieldPatch } from '@components/settings/exclusion_lists/ExclusionListEdition'; +import { ExclusionListEditionQuery } from '@components/settings/exclusion_lists/__generated__/ExclusionListEditionQuery.graphql'; +import DeleteDialog from '../../../../components/DeleteDialog'; +import { useFormatter } from '../../../../components/i18n'; +import useApiMutation from '../../../../utils/hooks/useApiMutation'; +import useDeletion from '../../../../utils/hooks/useDeletion'; +import { deleteNode } from '../../../../utils/store'; +import Loader, { LoaderVariant } from '../../../../components/Loader'; + +export const exclusionListPopoverDeletionMutation = graphql` + mutation ExclusionListPopoverDeletionMutation($id: ID!) { + exclusionListDelete(id: $id) + } +`; + +const ExclusionListPopover = ({ data, paginationOptions }: { data: ExclusionListsLine_node$data, paginationOptions?: ExclusionListsLinesPaginationQuery$variables }) => { + const { t_i18n } = useFormatter(); + const [queryRef, loadQuery] = useQueryLoader(exclusionListEditionQuery); + const [anchorEl, setAnchorEl] = useState(null); + const [displayEdit, setDisplayEdit] = useState(false); + const [commit] = useApiMutation(exclusionListPopoverDeletionMutation); + const [commitFieldPatch] = useApiMutation(exclusionListMutationFieldPatch); + + // popover + const handleOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => setAnchorEl(null); + + // delete + const deletion = useDeletion({ handleClose }); + const { setDeleting, handleCloseDelete, handleOpenDelete } = deletion; + const submitDelete = () => { + commit({ + variables: { + id: data.id, + }, + updater: (store) => { + deleteNode(store, 'Pagination_exclusionLists', paginationOptions, data.id); + }, + onCompleted: () => { + setDeleting(false); + handleCloseDelete(); + }, + }); + }; + + // edition + const handleDisplayEdit = () => { + loadQuery({ id: data.id }, { fetchPolicy: 'store-and-network' }); + setDisplayEdit(true); + handleClose(); + }; + + // Enable - Disable + const handleEnable = () => { + commitFieldPatch({ + variables: { + id: data.id, + input: [{ key: 'enabled', value: !data.enabled }], + }, + }); + handleClose(); + }; + return ( + <> + + + + + {data.enabled ? t_i18n('Disable') : t_i18n('Enable')} + {t_i18n('Update')} + {t_i18n('Delete')} + + + setDisplayEdit(false)} + > + {queryRef && ( + }> + setDisplayEdit(false)} /> + + )} + + + ); +}; + +export default ExclusionListPopover; diff --git a/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListValidator.ts b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListValidator.ts new file mode 100644 index 000000000000..ee0a1c476499 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListValidator.ts @@ -0,0 +1,10 @@ +import * as Yup from 'yup'; + +const exclusionListValidator = (t: (value: string) => string) => Yup.object().shape({ + name: Yup.string().trim().min(2).required(t('This field is required')), + description: Yup.string().nullable(), + exclusion_list_entity_types: Yup.array().min(1, t('Minimum one entity type')).required(t('This field is required')), + file: Yup.mixed().required(t('This field is required')), +}); + +export default exclusionListValidator; diff --git a/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionLists.tsx b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionLists.tsx new file mode 100644 index 000000000000..33e0082eb1ab --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionLists.tsx @@ -0,0 +1,153 @@ +import CustomizationMenu from '@components/settings/CustomizationMenu'; +import React from 'react'; +import ExclusionListCreation from '@components/settings/exclusion_lists/ExclusionListCreation'; +import { + ExclusionListsLinesPaginationQuery, + ExclusionListsLinesPaginationQuery$variables, +} from '@components/settings/exclusion_lists/__generated__/ExclusionListsLinesPaginationQuery.graphql'; +import ExclusionListsLines, { exclusionListsLinesQuery } from '@components/settings/exclusion_lists/ExclusionListsLines'; +import { ExclusionListsLineDummy } from '@components/settings/exclusion_lists/ExclusionListsLine'; +import { ExclusionListsLine_node$data } from '@components/settings/exclusion_lists/__generated__/ExclusionListsLine_node.graphql'; +import Chip from '@mui/material/Chip'; +import Breadcrumbs from '../../../../components/Breadcrumbs'; +import { useFormatter } from '../../../../components/i18n'; +import { usePaginationLocalStorage } from '../../../../utils/hooks/useLocalStorage'; +import { emptyFilterGroup, useBuildEntityTypeBasedFilterContext } from '../../../../utils/filters/filtersUtils'; +import ItemBoolean from '../../../../components/ItemBoolean'; +import ListLines from '../../../../components/list_lines/ListLines'; +import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; + +const LOCAL_STORAGE_KEY = 'view-exclusion-lists'; + +const ExclusionLists = () => { + const { fd, t_i18n } = useFormatter(); + const { viewStorage, helpers, paginationOptions } = usePaginationLocalStorage( + LOCAL_STORAGE_KEY, + { + searchTerm: '', + sortBy: 'name', + orderAsc: true, + openExports: false, + filters: emptyFilterGroup, + }, + ); + const { sortBy, orderAsc, searchTerm, filters, numberOfElements } = viewStorage; + const contextFilters = useBuildEntityTypeBasedFilterContext('ExclusionList', filters); + const queryPaginationOptions = { + ...paginationOptions, + filters: contextFilters, + } as unknown as ExclusionListsLinesPaginationQuery$variables; + const queryRef = useQueryLoading( + exclusionListsLinesQuery, + queryPaginationOptions, + ); + + const renderLines = () => { + const dataColumns = { + name: { + label: t_i18n('Name'), + width: '20%', + isSortable: true, + render: (node: ExclusionListsLine_node$data) => node.name, + }, + description: { + label: t_i18n('Description'), + width: '30%', + isSortable: false, + render: (node: ExclusionListsLine_node$data) => node.description, + }, + created_at: { + label: t_i18n('Creation date'), + width: '10%', + isSortable: true, + render: (node: ExclusionListsLine_node$data) => fd(node.created_at), + }, + enabled: { + label: t_i18n('Active'), + width: '10%', + isSortable: true, + render: (node: ExclusionListsLine_node$data) => ( + + ), + }, + exclusion_list_entity_types: { + label: t_i18n('Entity type'), + width: '30%', + isSortable: false, + render: (node: ExclusionListsLine_node$data) => ( + <> + {node.exclusion_list_entity_types.map((type) => ( + + ))} + + ), + }, + }; + + return ( + + {queryRef && ( + + {Array(20) + .fill(0) + .map((_, idx) => ( + + ))} + + } + > + + + )} + + ); + }; + + return ( +
    + + + {renderLines()} + +
    + ); +}; + +export default ExclusionLists; diff --git a/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListsLine.tsx b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListsLine.tsx new file mode 100644 index 000000000000..1c8000347a09 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListsLine.tsx @@ -0,0 +1,127 @@ +import ListItem from '@mui/material/ListItem'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Skeleton from '@mui/material/Skeleton'; +import ListItemText from '@mui/material/ListItemText'; +import React, { FunctionComponent } from 'react'; +import makeStyles from '@mui/styles/makeStyles'; +import { graphql, useFragment } from 'react-relay'; +import { ExclusionListsLine_node$key } from '@components/settings/exclusion_lists/__generated__/ExclusionListsLine_node.graphql'; +import ExclusionListPopover from '@components/settings/exclusion_lists/ExclusionListPopover'; +import { ExclusionListsLinesPaginationQuery$variables } from '@components/settings/exclusion_lists/__generated__/ExclusionListsLinesPaginationQuery.graphql'; +import { Theme } from '@mui/material/styles/createTheme'; +import ItemIcon from '../../../../components/ItemIcon'; +import { DataColumns } from '../../../../components/list_lines'; + +// Deprecated - https://mui.com/system/styles/basics/ +// Do not use it for new code. +const useStyles = makeStyles((theme) => ({ + item: { + paddingLeft: 10, + height: 50, + }, + itemIcon: { + color: theme.palette.primary.main, + }, + itemIconDisabled: { + color: theme.palette.grey?.[700], + }, + bodyItem: { + height: 20, + fontSize: 13, + float: 'left', + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + paddingRight: 10, + }, + goIcon: { + position: 'absolute', + right: -10, + }, +})); + +const exclusionListsLineFragment = graphql` + fragment ExclusionListsLine_node on ExclusionList { + id + name + description + enabled + created_at + exclusion_list_entity_types + } +`; + +interface ExclusionListsLineProps { + node: ExclusionListsLine_node$key; + dataColumns: DataColumns; + paginationOptions: ExclusionListsLinesPaginationQuery$variables; +} + +export const ExclusionListsLine: FunctionComponent = ({ + dataColumns, + node, + paginationOptions, +}) => { + const classes = useStyles(); + const exclusionList = useFragment(exclusionListsLineFragment, node); + return ( + + + + + + {Object.values(dataColumns).map((value) => ( +
    + {value.render?.(exclusionList)} +
    + ))} +
    + } + /> + + + + + ); +}; + +export const ExclusionListsLineDummy = ({ dataColumns }: { dataColumns: DataColumns; }) => { + const classes = useStyles(); + return ( + + + + + + {Object.values(dataColumns).map((value) => ( +
    + +
    + ))} +
    + } + /> + + ); +}; diff --git a/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListsLines.tsx b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListsLines.tsx new file mode 100644 index 000000000000..45daca75ac02 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/settings/exclusion_lists/ExclusionListsLines.tsx @@ -0,0 +1,105 @@ +import { graphql, PreloadedQuery } from 'react-relay'; +import React, { FunctionComponent } from 'react'; +import { + ExclusionListsLinesPaginationQuery, + ExclusionListsLinesPaginationQuery$variables, +} from '@components/settings/exclusion_lists/__generated__/ExclusionListsLinesPaginationQuery.graphql'; +import { ExclusionListsLines_data$key } from '@components/settings/exclusion_lists/__generated__/ExclusionListsLines_data.graphql'; +import { ExclusionListsLine, ExclusionListsLineDummy } from '@components/settings/exclusion_lists/ExclusionListsLine'; +import usePreloadedPaginationFragment from '../../../../utils/hooks/usePreloadedPaginationFragment'; +import ListLinesContent from '../../../../components/list_lines/ListLinesContent'; +import { DataColumns } from '../../../../components/list_lines'; + +const nbOfRowsToLoad = 50; + +export const exclusionListsLinesQuery = graphql` + query ExclusionListsLinesPaginationQuery( + $search: String + $count: Int! + $cursor: ID + $orderBy: ExclusionListOrdering + $orderMode: OrderingMode + $filters: FilterGroup + ) { + ...ExclusionListsLines_data + @arguments( + search: $search + count: $count + cursor: $cursor + orderBy: $orderBy + orderMode: $orderMode + filters: $filters + ) + } +`; + +export const exclusionListsLinesFragment = graphql` + fragment ExclusionListsLines_data on Query + @argumentDefinitions( + search: { type: "String" } + count: { type: "Int", defaultValue: 25 } + cursor: { type: "ID" } + orderBy: { type: "ExclusionListOrdering", defaultValue: name } + orderMode: { type: "OrderingMode", defaultValue: desc } + filters: { type: "FilterGroup" } + ) @refetchable(queryName: "ExclusionListsLinesRefetchQuery") { + exclusionLists( + search: $search + first: $count + after: $cursor + orderBy: $orderBy + orderMode: $orderMode + filters: $filters + ) @connection(key: "Pagination_exclusionLists") { + edges { + node { + ...ExclusionListsLine_node + } + } + pageInfo { + endCursor + hasNextPage + globalCount + } + } + } +`; + +export interface ExclusionListsLinesProps { + paginationOptions: ExclusionListsLinesPaginationQuery$variables; + dataColumns: DataColumns; + queryRef: PreloadedQuery; +} + +const ExclusionListsLines: FunctionComponent = ({ + queryRef, + dataColumns, + paginationOptions, +}) => { + const { data, hasMore, loadMore, isLoadingMore } = usePreloadedPaginationFragment< + ExclusionListsLinesPaginationQuery, + ExclusionListsLines_data$key + >({ + queryRef, + linesQuery: exclusionListsLinesQuery, + linesFragment: exclusionListsLinesFragment, + nodePath: ['exclusionLists', 'pageInfo', 'globalCount'], + }); + return ( + + ); +}; + +export default ExclusionListsLines; diff --git a/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionMarkings.tsx b/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionMarkings.tsx index a34148b9aa9e..8b138cb6b185 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionMarkings.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionMarkings.tsx @@ -11,10 +11,9 @@ import makeStyles from '@mui/styles/makeStyles'; import { Field, Form, Formik } from 'formik'; import Typography from '@mui/material/Typography'; import MarkingsSelectField from '@components/common/form/MarkingsSelectField'; +import { MarkingDefinitionsQuerySearchQuery$data } from '@components/settings/__generated__/MarkingDefinitionsQuerySearchQuery.graphql'; import { QueryRenderer } from '../../../../relay/environment'; import { useFormatter } from '../../../../components/i18n'; -import { markingDefinitionsLinesSearchQuery } from '../marking_definitions/MarkingDefinitionsLines'; -import { MarkingDefinitionsLinesSearchQuery$data } from '../marking_definitions/__generated__/MarkingDefinitionsLinesSearchQuery.graphql'; import { GroupEditionMarkings_group$data } from './__generated__/GroupEditionMarkings_group.graphql'; import AutocompleteField from '../../../../components/AutocompleteField'; import ItemIcon from '../../../../components/ItemIcon'; @@ -23,6 +22,7 @@ import { fieldSpacingContainerStyle } from '../../../../utils/field'; import { convertMarking } from '../../../../utils/edition'; import useApiMutation from '../../../../utils/hooks/useApiMutation'; import { checkIsMarkingAllowed } from '../../../../utils/markings/markingsFiltering'; +import { markingDefinitionsLinesSearchQuery } from '../MarkingDefinitionsQuery'; // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. @@ -211,7 +211,7 @@ const GroupEditionMarkingsComponent = ({ render={({ props, }: { - props: MarkingDefinitionsLinesSearchQuery$data; + props: MarkingDefinitionsQuerySearchQuery$data; }) => { if (props) { const markingDefinitions = ( diff --git a/opencti-platform/opencti-front/src/private/components/settings/groups/GroupsLines.tsx b/opencti-platform/opencti-front/src/private/components/settings/groups/GroupsLines.tsx index 0ffdaeec2cc0..8d7ecbf71b80 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/groups/GroupsLines.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/groups/GroupsLines.tsx @@ -1,11 +1,10 @@ import React from 'react'; import { graphql, createPaginationContainer, RelayPaginationProp } from 'react-relay'; import { pathOr } from 'ramda'; -import { GroupsLinesPaginationQuery$variables } from '@components/settings/groups/__generated__/GroupsLinesPaginationQuery.graphql'; import { GroupsLines_data$data } from '@components/settings/groups/__generated__/GroupsLines_data.graphql'; import ListLinesContent from '../../../../components/list_lines/ListLinesContent'; import { GroupLine, GroupLineDummy } from './GroupLine'; -import { DataColumns } from '../../../../components/list_lines'; +import { DataColumns, PaginationOptions } from '../../../../components/list_lines'; const nbOfRowsToLoad = 50; @@ -13,7 +12,7 @@ interface GroupsLinesProps { initialLoading: boolean dataColumns: DataColumns relay: RelayPaginationProp, - paginationOptions: GroupsLinesPaginationQuery$variables + paginationOptions: PaginationOptions, data: GroupsLines_data$data } diff --git a/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionCreation.jsx b/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionCreation.jsx index d2f942d23a96..a247de2978d4 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionCreation.jsx +++ b/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionCreation.jsx @@ -57,13 +57,13 @@ const styles = (theme) => ({ }); const markingDefinitionMutation = graphql` - mutation MarkingDefinitionCreationMutation( - $input: MarkingDefinitionAddInput! - ) { - markingDefinitionAdd(input: $input) { - ...MarkingDefinitionLine_node + mutation MarkingDefinitionCreationMutation( + $input: MarkingDefinitionAddInput! + ) { + markingDefinitionAdd(input: $input) { + ...MarkingDefinitionsLine_node + } } - } `; const markingDefinitionValidation = (t) => Yup.object().shape({ diff --git a/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionLine.tsx b/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionLine.tsx deleted file mode 100644 index 6fd0b16fd69a..000000000000 --- a/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionLine.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import React from 'react'; -import { graphql, useFragment } from 'react-relay'; -import ListItem from '@mui/material/ListItem'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; -import { MoreVert } from '@mui/icons-material'; -import Skeleton from '@mui/material/Skeleton'; -import DangerZoneChip from '@components/common/danger_zone/DangerZoneChip'; -import makeStyles from '@mui/styles/makeStyles'; -import { MarkingDefinitionLine_node$key } from '@components/settings/marking_definitions/__generated__/MarkingDefinitionLine_node.graphql'; -import { useFormatter } from '../../../../components/i18n'; -import MarkingDefinitionPopover from './MarkingDefinitionPopover'; -import ItemIcon from '../../../../components/ItemIcon'; -import useSensitiveModifications from '../../../../utils/hooks/useSensitiveModifications'; -import { DataColumns } from '../../../../components/list_lines'; -import type { Theme } from '../../../../components/Theme'; - -// Deprecated - https://mui.com/system/styles/basics/ -// Do not use it for new code. -const useStyles = makeStyles < Theme >((theme) => ({ - item: { - paddingLeft: 10, - height: 50, - cursor: 'default', - }, - bodyItem: { - height: 20, - fontSize: 13, - float: 'left', - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - paddingRight: 10, - }, - itemIconDisabled: { - color: theme.palette.grey?.[700], - }, -})); - -interface MarkingDefinitionLineProps { - dataColumns: DataColumns - node: MarkingDefinitionLine_node$key - paginationOptions: unknown -} - -const markingDefinitionFragment = graphql` - fragment MarkingDefinitionLine_node on MarkingDefinition { - id - standard_id - definition_type - definition - x_opencti_order - x_opencti_color - created - modified - } -`; - -export const MarkingDefinitionLine: React.FC = (props) => { - const classes = useStyles(); - - const { fd } = useFormatter(); - const { dataColumns, node, paginationOptions } = props; - const data = useFragment(markingDefinitionFragment, node); - const { isSensitive, isAllowed } = useSensitiveModifications('markings', data.standard_id); - - return ( - - - - - -
    - {data.definition_type}{isSensitive && } -
    -
    - {data.definition} -
    -
    - {data.x_opencti_color} -
    -
    - {data.x_opencti_order} -
    -
    - {fd(data.created)} -
    - - } - /> - - - -
    - ); -}; - -export const MarkingDefinitionLineDummy: React.FC> = ({ dataColumns }) => { - const classes = useStyles(); - - return ( - - - - - -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - } - /> - - - - - ); -}; diff --git a/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionPopover.jsx b/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionPopover.jsx index 48a3d8e28298..b083c218bee6 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionPopover.jsx +++ b/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionPopover.jsx @@ -8,18 +8,14 @@ import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogContentText from '@mui/material/DialogContentText'; -import Slide from '@mui/material/Slide'; import MoreVert from '@mui/icons-material/MoreVert'; import { useFormatter } from '../../../../components/i18n'; import { QueryRenderer } from '../../../../relay/environment'; import MarkingDefinitionEdition from './MarkingDefinitionEdition'; import useApiMutation from '../../../../utils/hooks/useApiMutation'; import { deleteNode } from '../../../../utils/store'; - -const Transition = React.forwardRef((props, ref) => ( - -)); -Transition.displayName = 'TransitionSlide'; +import Transition from '../../../../components/Transition'; +import useSensitiveModifications from '../../../../utils/hooks/useSensitiveModifications'; const markingDefinitionPopoverDeletionMutation = graphql` mutation MarkingDefinitionPopoverDeletionMutation($id: ID!) { @@ -42,13 +38,15 @@ const markingDefinitionEditionQuery = graphql` `; const MarkingDefinitionPopover = ({ - markingDefinitionId, disabled, paginationOptions, + markingDefinition, paginationOptions, }) => { const { t_i18n } = useFormatter(); + const { isSensitive, isAllowed } = useSensitiveModifications('markings', markingDefinition.standard_id); const [anchorEl, setAnchorEl] = useState(null); const [displayUpdate, setDisplayUpdate] = useState(false); const [displayDelete, setDisplayDelete] = useState(false); const [deleting, setDeleting] = useState(false); + const disabled = !isAllowed && isSensitive; const handleOpen = (event) => { setAnchorEl(event.currentTarget); @@ -82,10 +80,10 @@ const MarkingDefinitionPopover = ({ setDeleting(true); commit({ variables: { - id: markingDefinitionId, + id: markingDefinition.id, }, updater: (store) => { - deleteNode(store, 'Pagination_markingDefinitions', paginationOptions, markingDefinitionId); + deleteNode(store, 'Pagination_markingDefinitions', paginationOptions, markingDefinition.id); }, onCompleted: () => { setDeleting(false); @@ -119,7 +117,7 @@ const MarkingDefinitionPopover = ({ { if (props) { return ( diff --git a/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionsLines.jsx b/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionsLines.jsx deleted file mode 100644 index 78e73605fa56..000000000000 --- a/opencti-platform/opencti-front/src/private/components/settings/marking_definitions/MarkingDefinitionsLines.jsx +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; -import { graphql } from 'react-relay'; -import ListLinesContent from '../../../../components/list_lines/ListLinesContent'; -import { MarkingDefinitionLine, MarkingDefinitionLineDummy } from './MarkingDefinitionLine'; -import usePreloadedPaginationFragment from '../../../../utils/hooks/usePreloadedPaginationFragment'; - -const nbOfRowsToLoad = 50; - -export const markingDefinitionsLinesQuery = graphql` - query MarkingDefinitionsLinesPaginationQuery( - $search: String - $count: Int! - $cursor: ID - $orderBy: MarkingDefinitionsOrdering - $orderMode: OrderingMode - ) { - ...MarkingDefinitionsLines_data - @arguments( - search: $search - count: $count - cursor: $cursor - orderBy: $orderBy - orderMode: $orderMode - ) - } -`; - -const markingDefinitionsLinesFragment = graphql` - fragment MarkingDefinitionsLines_data on Query - @argumentDefinitions( - search: { type: "String" } - count: { type: "Int", defaultValue: 25 } - cursor: { type: "ID" } - orderBy: { - type: "MarkingDefinitionsOrdering" - defaultValue: definition - } - orderMode: { type: "OrderingMode", defaultValue: asc } - ) - @refetchable(queryName: "MarkingDefinitionsLinesRefetchQuery") { - markingDefinitions( - search: $search - first: $count - after: $cursor - orderBy: $orderBy - orderMode: $orderMode - ) @connection(key: "Pagination_markingDefinitions") { - edges { - node { - ...MarkingDefinitionLine_node - } - } - pageInfo { - endCursor - hasNextPage - globalCount - } - } - } -`; - -export const markingDefinitionsLinesSearchQuery = graphql` - query MarkingDefinitionsLinesSearchQuery($search: String, $filters: FilterGroup, $first: Int) { - markingDefinitions(search: $search, filters: $filters, first: $first) { - edges { - node { - id - definition_type - definition - x_opencti_color - x_opencti_order - } - } - } - } -`; - -const MarkingDefinitionsLines = ({ - dataColumns, - queryRef, - paginationOptions, - setNumberOfElements, -}) => { - const { data, hasMore, loadMore, isLoadingMore } = usePreloadedPaginationFragment({ - linesQuery: markingDefinitionsLinesQuery, - linesFragment: markingDefinitionsLinesFragment, - queryRef, - nodePath: ['markingDefinitions', 'pageInfo', 'globalCount'], - setNumberOfElements, - }); - return ( - - ); -}; - -export default MarkingDefinitionsLines; diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index 961e3070382c..60b2a97ab2f9 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -8957,6 +8957,7 @@ type Mutation { supportPackageDelete(id: ID!): ID exclusionListContentAdd(input: ExclusionListContentAddInput!): ExclusionList exclusionListFileAdd(input: ExclusionListFileAddInput!): ExclusionList + exclusionListFieldPatch(id: ID!, input: [EditInput!]!): ExclusionList exclusionListDelete(id: ID!): ID draftWorkspaceAdd(input: DraftWorkspaceAddInput!): DraftWorkspace draftWorkspaceValidate(id: ID!): Work @@ -12312,7 +12313,7 @@ type ExclusionList implements InternalObject & BasicObject { parent_types: [String]! created_at: DateTime! enabled: Boolean! - list_entity_types: [ExclusionListEntityTypes!]! + exclusion_list_entity_types: [String!]! file_id: String! } @@ -12329,26 +12330,21 @@ type ExclusionListEdge { enum ExclusionListOrdering { name created_at -} - -enum ExclusionListEntityTypes { - IPV4_ADDR - IPV6_ADDR - DOMAIN_NAME - URL + enabled + _score } input ExclusionListContentAddInput { name: String! description: String - list_entity_types: [ExclusionListEntityTypes!]! + exclusion_list_entity_types: [String!]! content: String! } input ExclusionListFileAddInput { name: String! description: String - list_entity_types: [ExclusionListEntityTypes!]! + exclusion_list_entity_types: [String!]! file: Upload! } diff --git a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx index 393ff9fc0d55..b0e18700b9b8 100644 --- a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx @@ -50,6 +50,7 @@ export const entityTypesFilters = [ 'type', // regardingOf subfilter 'x_opencti_main_observable_type', 'main_entity_type', // for DeleteOperation + 'exclusion_list_entity_types', ]; // context filters for audits (filters on the entity involved in an activity/knowledge event) diff --git a/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx b/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx index 1b5a783fe828..61279607f337 100644 --- a/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx @@ -2,7 +2,6 @@ import * as R from 'ramda'; import { Dispatch, useState } from 'react'; import { graphql } from 'react-relay'; import { SelectChangeEvent } from '@mui/material/Select'; -import { markingDefinitionsLinesSearchQuery } from '@components/settings/marking_definitions/MarkingDefinitionsLines'; import { identitySearchCreatorsSearchQuery, identitySearchIdentitiesSearchQuery } from '@components/common/identities/IdentitySearch'; import { stixDomainObjectsLinesSearchQuery } from '@components/common/stix_domain_objects/StixDomainObjectsLines'; import { labelsSearchQuery } from '@components/settings/LabelsQuery'; @@ -12,7 +11,6 @@ import { IdentitySearchIdentitiesSearchQuery$data } from '@components/common/ide import { IdentitySearchCreatorsSearchQuery$data } from '@components/common/identities/__generated__/IdentitySearchCreatorsSearchQuery.graphql'; import { ObjectAssigneeFieldAssigneesSearchQuery$data } from '@components/common/form/__generated__/ObjectAssigneeFieldAssigneesSearchQuery.graphql'; import { StixDomainObjectsLinesSearchQuery$data } from '@components/common/stix_domain_objects/__generated__/StixDomainObjectsLinesSearchQuery.graphql'; -import { MarkingDefinitionsLinesSearchQuery$data } from '@components/settings/marking_definitions/__generated__/MarkingDefinitionsLinesSearchQuery.graphql'; import { LabelsQuerySearchQuery$data } from '@components/settings/__generated__/LabelsQuerySearchQuery.graphql'; import { VocabularyQuery$data } from '@components/settings/__generated__/VocabularyQuery.graphql'; import { ObjectAssigneeFieldMembersSearchQuery$data } from '@components/common/form/__generated__/ObjectAssigneeFieldMembersSearchQuery.graphql'; @@ -27,6 +25,8 @@ import { NotifierFieldQuery } from '@components/common/form/NotifierField'; import { NotifierFieldSearchQuery$data } from '@components/common/form/__generated__/NotifierFieldSearchQuery.graphql'; import { killChainPhasesSearchQuery } from '@components/settings/KillChainPhases'; import { KillChainPhasesSearchQuery$data } from '@components/settings/__generated__/KillChainPhasesSearchQuery.graphql'; +import { MarkingDefinitionsQuerySearchQuery$data } from '@components/settings/__generated__/MarkingDefinitionsQuerySearchQuery.graphql'; +import { markingDefinitionsLinesSearchQuery } from '../../private/components/settings/MarkingDefinitionsQuery'; import useAuth, { FilterDefinition } from '../hooks/useAuth'; import { useSearchEntitiesStixCoreObjectsSearchQuery$data } from './__generated__/useSearchEntitiesStixCoreObjectsSearchQuery.graphql'; import { useFormatter } from '../../components/i18n'; @@ -365,7 +365,7 @@ const useSearchEntities = ({ .toPromise() .then((data) => { const markedByEntities = ( - (data as MarkingDefinitionsLinesSearchQuery$data)?.markingDefinitions?.edges ?? [] + (data as MarkingDefinitionsQuerySearchQuery$data)?.markingDefinitions?.edges ?? [] ).map((n) => ({ label: n?.node.definition, value: n?.node.id, @@ -892,6 +892,7 @@ const useSearchEntities = ({ break; } case 'x_opencti_main_observable_type': + case 'exclusion_list_entity_types': fetchQuery(filtersSchemaSCOSearchQuery) .toPromise() .then((data) => { diff --git a/opencti-platform/opencti-front/src/utils/hooks/useConnectedDocumentModifier.ts b/opencti-platform/opencti-front/src/utils/hooks/useConnectedDocumentModifier.ts index 9d4251a100dc..45b1adb5b713 100644 --- a/opencti-platform/opencti-front/src/utils/hooks/useConnectedDocumentModifier.ts +++ b/opencti-platform/opencti-front/src/utils/hooks/useConnectedDocumentModifier.ts @@ -1,13 +1,14 @@ -import useAuth from './useAuth'; +import { useContext } from 'react'; +import { UserContext, UserContextType } from './useAuth'; const setDocumentTitle = (title: string) => { document.title = title; }; const useConnectedDocumentModifier = () => { - const { settings } = useAuth(); + const { settings } = useContext(UserContext); const setTitle = (title: string) => { - setDocumentTitle(`${title} | ${settings.platform_title}`); + setDocumentTitle(`${title}${!settings ? '' : ` | ${settings.platform_title}`}`); }; return { setTitle }; }; diff --git a/opencti-platform/opencti-graphql/config/default.json b/opencti-platform/opencti-graphql/config/default.json index c474a4d27050..5976392ea3a9 100644 --- a/opencti-platform/opencti-graphql/config/default.json +++ b/opencti-platform/opencti-graphql/config/default.json @@ -263,6 +263,15 @@ "stream_lock_key": "file_index_manager_stream_lock", "interval": 60000 }, + "exclusion_list_cache_build_manager": { + "enabled": true, + "lock_key": "exclusion_list_cache_build_manager_lock", + "interval": 10000 + }, + "exclusion_list_cache_sync_manager": { + "lock_key": "exclusion_list_cache_sync_manager_lock", + "interval": 10000 + }, "redis": { "mode": "single", "namespace": "", diff --git a/opencti-platform/opencti-graphql/script/script-insert-dataset.js b/opencti-platform/opencti-graphql/script/script-insert-dataset.js index a5ca2002cbbf..23d7d54d62d5 100644 --- a/opencti-platform/opencti-graphql/script/script-insert-dataset.js +++ b/opencti-platform/opencti-graphql/script/script-insert-dataset.js @@ -2,9 +2,10 @@ import fetch from 'node-fetch'; import '../src/modules/index'; import { executionContext } from '../src/utils/access'; import { checkPythonAvailability, execChildPython } from '../src/python/pythonBridge'; -import conf, { logApp } from '../src/config/conf'; +import conf, { isFeatureEnabled, logApp } from '../src/config/conf'; import httpServer from '../src/http/httpServer'; import cacheManager from '../src/manager/cacheManager'; +import { initExclusionListCache } from '../src/database/exclusionListCache'; const ADMIN_USER = { id: '88ec0c6a-13ce-5e39-b486-354fe4a7084f' }; const API_URI = `http://localhost:${conf.get('app:port')}`; @@ -55,6 +56,9 @@ const getStartingHandler = () => { start: async () => { logApp.info('[OPENCTI] The httpServer is autostarted'); await cacheManager.start(); + if (isFeatureEnabled('EXCLUSION_LIST')) { + await initExclusionListCache(); + } await httpServer.start(); }, shutdown: async () => { diff --git a/opencti-platform/opencti-graphql/src/config/conf.js b/opencti-platform/opencti-graphql/src/config/conf.js index 5e1c1bafea89..5a8e4004b238 100644 --- a/opencti-platform/opencti-graphql/src/config/conf.js +++ b/opencti-platform/opencti-graphql/src/config/conf.js @@ -34,6 +34,7 @@ import { UnknownError, UnsupportedError } from './errors'; import { ENTITY_TYPE_PUBLIC_DASHBOARD } from '../modules/publicDashboard/publicDashboard-types'; import { AI_BUS } from '../modules/ai/ai-types'; import { SUPPORT_BUS } from '../modules/support/support-types'; +import { ENTITY_TYPE_EXCLUSION_LIST } from '../modules/exclusionList/exclusionList-types'; // https://golang.org/src/crypto/x509/root_linux.go const LINUX_CERTFILES = [ @@ -592,6 +593,11 @@ export const BUS_TOPICS = { DELETE_TOPIC: `${TOPIC_PREFIX}ENTITY_TYPE_DECAY_RULE_DELETE_TOPIC`, ADDED_TOPIC: `${TOPIC_PREFIX}ENTITY_TYPE_DECAY_RULE_ADDED_TOPIC`, }, + [ENTITY_TYPE_EXCLUSION_LIST]: { + EDIT_TOPIC: `${TOPIC_PREFIX}ENTITY_TYPE_EXCLUSION_LIST_EDIT_TOPIC`, + DELETE_TOPIC: `${TOPIC_PREFIX}ENTITY_TYPE_EXCLUSION_LIST_DELETE_TOPIC`, + ADDED_TOPIC: `${TOPIC_PREFIX}ENTITY_TYPE_EXCLUSION_LIST_ADDED_TOPIC`, + }, [ENTITY_TYPE_MANAGER_CONFIGURATION]: { EDIT_TOPIC: `${TOPIC_PREFIX}ENTITY_TYPE_MANAGER_CONFIGURATION_EDIT_TOPIC`, ADDED_TOPIC: `${TOPIC_PREFIX}ENTITY_TYPE_MANAGER_CONFIGURATION_ADDED_TOPIC`, diff --git a/opencti-platform/opencti-graphql/src/database/exclusionListCache.ts b/opencti-platform/opencti-graphql/src/database/exclusionListCache.ts new file mode 100644 index 000000000000..3152d6dcd665 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/database/exclusionListCache.ts @@ -0,0 +1,90 @@ +import { type BasicStoreEntityExclusionList, ENTITY_TYPE_EXCLUSION_LIST } from '../modules/exclusionList/exclusionList-types'; +import { ENTITY_IPV4_ADDR, ENTITY_IPV6_ADDR } from '../schema/stixCyberObservable'; +import { checkExclusionList, checkIpAddressLists, convertIpAddr } from '../utils/exclusionLists'; +import type { AuthContext } from '../types/user'; +import { listAllEntities } from './middleware-loader'; +import { SYSTEM_USER } from '../utils/access'; +import { getFileContent } from './file-storage'; +import { logApp, NODE_INSTANCE_ID } from '../config/conf'; +import { redisGetExclusionListCache, redisSetExclusionListCache, redisUpdateExclusionListStatus } from './redis'; +import { FunctionalError } from '../config/errors'; + +export interface ExclusionListCacheItem { + id: string + types: string[] + values: string[] +} + +let exclusionListCache: ExclusionListCacheItem[] | null = null; + +export const getIsCacheInitialized = (): boolean => exclusionListCache !== null; + +export const getCache = (entityType: string = ''): ExclusionListCacheItem[] | null => { + return entityType && exclusionListCache ? exclusionListCache.filter((e) => e.types.includes(entityType)) : exclusionListCache; +}; + +const setCache = (newCache: ExclusionListCacheItem[]): void => { + exclusionListCache = [...newCache]; +}; + +const isIPExclusionList = (exclusionList: BasicStoreEntityExclusionList) => { + // TODO is it possible to have a list with a mix of IP and other? + return exclusionList.exclusion_list_entity_types.some((t) => ENTITY_IPV4_ADDR === t || ENTITY_IPV6_ADDR === t); +}; + +const buildExclusionListCacheItem = (exclusionList: BasicStoreEntityExclusionList, exclusionListFileContent: string | undefined) => { + let exclusionListFileValues = exclusionListFileContent?.split('\n'); + if (exclusionListFileValues && isIPExclusionList(exclusionList)) { + exclusionListFileValues = convertIpAddr(exclusionListFileValues); + } + return { id: exclusionList.id, types: exclusionList.exclusion_list_entity_types, values: exclusionListFileValues ?? [] }; +}; + +export const buildCacheFromAllExclusionLists = async (context: AuthContext) => { + const exclusionLists: BasicStoreEntityExclusionList[] = await listAllEntities(context, SYSTEM_USER, [ENTITY_TYPE_EXCLUSION_LIST]); + const enabledExclusionLists = exclusionLists.filter((l) => l.enabled); + const exclusionListsCount = enabledExclusionLists.length; + const builtCache: ExclusionListCacheItem[] = []; + for (let i = 0; i < exclusionListsCount; i += 1) { + const currentExclusionList = enabledExclusionLists[i]; + try { + const currentExclusionFileContent = await getFileContent(currentExclusionList.file_id); + const currentExclusionListCacheItem = buildExclusionListCacheItem(currentExclusionList, currentExclusionFileContent); + builtCache.push(currentExclusionListCacheItem); + } catch (e) { + logApp.error('[OPENCTI-MODULE][EXCLUSION-BUILD-MANAGER] Exclusion list could not be built properly.', { cause: e, exclusionList: currentExclusionList }); + } + } + return builtCache; +}; + +// cache is always initialized as an empty array if there is no redis data: we might want to change it to rebuild cache locally as a failsafe is there is no redis data +export const initExclusionListCache = async () => { + const currentCache = await redisGetExclusionListCache(); + setCache(currentCache); +}; + +export const rebuildExclusionListCache = async (context: AuthContext, cacheDate: string) => { + const newCache = await buildCacheFromAllExclusionLists(context); + setCache(newCache); + await redisSetExclusionListCache(newCache); + const exclusionListStatus = { last_cache_date: cacheDate, [NODE_INSTANCE_ID]: cacheDate }; + await redisUpdateExclusionListStatus(exclusionListStatus); +}; + +export const syncExclusionListCache = async (cacheDate: string) => { + const currentCache = await redisGetExclusionListCache(); + setCache(currentCache); + await redisUpdateExclusionListStatus({ [NODE_INSTANCE_ID]: cacheDate }); +}; + +export const checkObservableValue = async (observableValue: any) => { + const { type, value } = observableValue; + const relatedLists = getCache(type); + if (!relatedLists) { + throw FunctionalError('Failed to load exclusion list cache.', { relatedLists, type }); + } + const isIpType = type === ENTITY_IPV4_ADDR || type === ENTITY_IPV6_ADDR; + const listCheck = await (isIpType ? checkIpAddressLists(value, relatedLists) : checkExclusionList(value, relatedLists)); + return listCheck; +}; diff --git a/opencti-platform/opencti-graphql/src/database/redis.ts b/opencti-platform/opencti-graphql/src/database/redis.ts index 83d7957a0c11..d87f7f8850ab 100644 --- a/opencti-platform/opencti-graphql/src/database/redis.ts +++ b/opencti-platform/opencti-graphql/src/database/redis.ts @@ -8,7 +8,7 @@ import type { ChainableCommander } from 'ioredis/built/utils/RedisCommander'; import type { ClusterOptions } from 'ioredis/built/cluster/ClusterOptions'; import type { SentinelConnectionOptions } from 'ioredis/built/connectors/SentinelConnector'; import conf, { booleanConf, configureCA, DEV_MODE, getStoppingState, loadCert, logApp, REDIS_PREFIX } from '../config/conf'; -import { asyncListTransformation, EVENT_TYPE_CREATE, EVENT_TYPE_DELETE, EVENT_TYPE_MERGE, EVENT_TYPE_UPDATE, isEmptyField, waitInSec } from './utils'; +import { asyncListTransformation, EVENT_TYPE_CREATE, EVENT_TYPE_DELETE, EVENT_TYPE_MERGE, EVENT_TYPE_UPDATE, fromBase64, isEmptyField, toBase64, waitInSec } from './utils'; import { isStixExportableData } from '../schema/stixCoreObject'; import { DatabaseError, LockTimeoutError, UnsupportedError } from '../config/errors'; import { mergeDeepRightAll, now, utcDate } from '../utils/format'; @@ -27,6 +27,7 @@ import { generateCreateMessage, generateDeleteMessage, generateMergeMessage, gen import { INPUT_OBJECTS } from '../schema/general'; import { enrichWithRemoteCredentials } from '../config/credentials'; import { getDraftContext } from '../utils/draftContext'; +import type { ExclusionListCacheItem } from './exclusionListCache'; const USE_SSL = booleanConf('redis:use_ssl', false); const REDIS_CA = conf.get('redis:ca').map((path: string) => loadCert(path)); @@ -884,3 +885,28 @@ export const redisDeleteSupportPackageNodeStatus = (supportPackageId: string) => }; // endregion - support package handling + +// region - exclusion list cache handling + +const EXCLUSION_LIST_STATUS_KEY = 'exclusion_list_status'; +const EXCLUSION_LIST_CACHE_KEY = 'exclusion_list_cache'; +export const redisUpdateExclusionListStatus = async (exclusionListStatus: object) => { + const clientBase = getClientBase(); + await redisTx(clientBase, async (tx) => { + tx.hset(EXCLUSION_LIST_STATUS_KEY, exclusionListStatus); + }); +}; +export const redisGetExclusionListStatus = async () => { + return getClientBase().hgetall(EXCLUSION_LIST_STATUS_KEY); +}; + +export const redisGetExclusionListCache = async () => { + const rawCache = await getClientBase().get(EXCLUSION_LIST_CACHE_KEY); + return JSON.parse(fromBase64(rawCache) ?? '[]'); +}; +export const redisSetExclusionListCache = async (cache: ExclusionListCacheItem[]) => { + const stringifiedCache = toBase64(JSON.stringify(cache)) as string; + await getClientBase().set(EXCLUSION_LIST_CACHE_KEY, stringifiedCache); +}; + +// endregion - exclusion list cache handling diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index a9492c3b7248..8eb3f92bb601 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -7494,9 +7494,9 @@ export type ExclusionList = BasicObject & InternalObject & { description?: Maybe; enabled: Scalars['Boolean']['output']; entity_type: Scalars['String']['output']; + exclusion_list_entity_types: Array; file_id: Scalars['String']['output']; id: Scalars['ID']['output']; - list_entity_types: Array; name: Scalars['String']['output']; parent_types: Array>; standard_id: Scalars['String']['output']; @@ -7511,7 +7511,7 @@ export type ExclusionListConnection = { export type ExclusionListContentAddInput = { content: Scalars['String']['input']; description?: InputMaybe; - list_entity_types: Array; + exclusion_list_entity_types: Array; name: Scalars['String']['input']; }; @@ -7521,22 +7521,17 @@ export type ExclusionListEdge = { node: ExclusionList; }; -export enum ExclusionListEntityTypes { - DomainName = 'DOMAIN_NAME', - Ipv4Addr = 'IPV4_ADDR', - Ipv6Addr = 'IPV6_ADDR', - Url = 'URL' -} - export type ExclusionListFileAddInput = { description?: InputMaybe; + exclusion_list_entity_types: Array; file: Scalars['Upload']['input']; - list_entity_types: Array; name: Scalars['String']['input']; }; export enum ExclusionListOrdering { + Score = '_score', CreatedAt = 'created_at', + Enabled = 'enabled', Name = 'name' } @@ -13529,6 +13524,7 @@ export type Mutation = { eventRelationDelete?: Maybe; exclusionListContentAdd?: Maybe; exclusionListDelete?: Maybe; + exclusionListFieldPatch?: Maybe; exclusionListFileAdd?: Maybe; externalReferenceAdd?: Maybe; externalReferenceEdit?: Maybe; @@ -14352,6 +14348,12 @@ export type MutationExclusionListDeleteArgs = { }; +export type MutationExclusionListFieldPatchArgs = { + id: Scalars['ID']['input']; + input: Array; +}; + + export type MutationExclusionListFileAddArgs = { input: ExclusionListFileAddInput; }; @@ -30961,7 +30963,6 @@ export type ResolversTypes = ResolversObject<{ ExclusionListConnection: ResolverTypeWrapper & { edges?: Maybe> }>; ExclusionListContentAddInput: ExclusionListContentAddInput; ExclusionListEdge: ResolverTypeWrapper & { node: ResolversTypes['ExclusionList'] }>; - ExclusionListEntityTypes: ExclusionListEntityTypes; ExclusionListFileAddInput: ExclusionListFileAddInput; ExclusionListOrdering: ExclusionListOrdering; ExportAskInput: ExportAskInput; @@ -34772,9 +34773,9 @@ export type ExclusionListResolvers, ParentType, ContextType>; enabled?: Resolver; entity_type?: Resolver; + exclusion_list_entity_types?: Resolver, ParentType, ContextType>; file_id?: Resolver; id?: Resolver; - list_entity_types?: Resolver, ParentType, ContextType>; name?: Resolver; parent_types?: Resolver>, ParentType, ContextType>; standard_id?: Resolver; @@ -36924,6 +36925,7 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; exclusionListContentAdd?: Resolver, ParentType, ContextType, RequireFields>; exclusionListDelete?: Resolver, ParentType, ContextType, RequireFields>; + exclusionListFieldPatch?: Resolver, ParentType, ContextType, RequireFields>; exclusionListFileAdd?: Resolver, ParentType, ContextType, RequireFields>; externalReferenceAdd?: Resolver, ParentType, ContextType, RequireFields>; externalReferenceEdit?: Resolver, ParentType, ContextType, RequireFields>; diff --git a/opencti-platform/opencti-graphql/src/initialization.js b/opencti-platform/opencti-graphql/src/initialization.js index 895a6b7d87f1..0e676efd2334 100644 --- a/opencti-platform/opencti-graphql/src/initialization.js +++ b/opencti-platform/opencti-graphql/src/initialization.js @@ -1,7 +1,7 @@ // Admin user initialization import { v4 as uuidv4 } from 'uuid'; import semver from 'semver'; -import { ENABLED_FEATURE_FLAGS, logApp, PLATFORM_VERSION } from './config/conf'; +import { ENABLED_FEATURE_FLAGS, isFeatureEnabled, logApp, PLATFORM_VERSION } from './config/conf'; import { elUpdateIndicesMappings, ES_INIT_RETRO_MAPPING_MIGRATION, initializeSchema, searchEngineInit } from './database/engine'; import { initializeAdminUser } from './config/providers'; import { initializeBucket, storageInit } from './database/file-storage'; @@ -19,6 +19,7 @@ import { initCreateEntitySettings } from './modules/entitySetting/entitySetting- import { initDecayRules } from './modules/decayRule/decayRule-domain'; import { initManagerConfigurations } from './modules/managerConfiguration/managerConfiguration-domain'; import { initializeData } from './database/data-initialization'; +import { initExclusionListCache } from './database/exclusionListCache'; // region Platform constants const PLATFORM_LOCK_ID = 'platform_init_lock'; @@ -124,6 +125,9 @@ const platformInit = async (withMarkings = true) => { await initManagerConfigurations(context, SYSTEM_USER); await initDecayRules(context, SYSTEM_USER); } + if (isFeatureEnabled('EXCLUSION_LIST')) { + await initExclusionListCache(); + } } catch (e) { if (e.name === TYPE_LOCK_ERROR) { const reason = 'Platform cant get the lock for initialization (can be due to other instance currently migrating/initializing)'; diff --git a/opencti-platform/opencti-graphql/src/manager/exclusionListCacheBuildManager.ts b/opencti-platform/opencti-graphql/src/manager/exclusionListCacheBuildManager.ts new file mode 100644 index 000000000000..0e220d2de8c1 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/manager/exclusionListCacheBuildManager.ts @@ -0,0 +1,45 @@ +import { type ManagerDefinition, registerManager } from './managerModule'; +import conf, { booleanConf, isFeatureEnabled, logApp } from '../config/conf'; +import { redisGetExclusionListStatus } from '../database/redis'; +import { executionContext } from '../utils/access'; +import { rebuildExclusionListCache } from '../database/exclusionListCache'; + +const EXCLUSION_LIST_CACHE_BUILD_MANAGER_ENABLED = booleanConf('exclusion_list_cache_build_manager:enabled', true); +const EXCLUSION_LIST_CACHE_BUILD_MANAGER_KEY = conf.get('exclusion_list_cache_build_manager:lock_key') || 'exclusion_list_cache_build_manager_lock'; +const SCHEDULE_TIME = conf.get('exclusion_list_cache_build_manager:interval') || 10000; // 10 seconds + +/** + * Look at most recent cache refresh ask date in redis + * if they differ or if status is not initialized, cache needs to be rebuilt and pushed to local cache and redis cache key + * otherwise, do nothing + */ +export const exclusionListCacheBuildHandler = async () => { + const context = executionContext('exclusion_list_cache_build_manager'); + const { last_refresh_ask_date, last_cache_date } = await redisGetExclusionListStatus(); + if (!last_cache_date || (last_refresh_ask_date && last_refresh_ask_date !== last_cache_date)) { + logApp.info('[OPENCTI-MODULE][EXCLUSION-BUILD-MANAGER] Cache needs to be rebuilt.', { last_refresh_ask_date, last_cache_date }); + await rebuildExclusionListCache(context, last_refresh_ask_date ?? (new Date()).toString()); + logApp.info('[OPENCTI-MODULE][EXCLUSION-BUILD-MANAGER] Cache has been rebuilt.', { last_refresh_ask_date, last_cache_date }); + } +}; + +const EXCLUSION_LIST_CACHE_BUILD_MANAGER_DEFINITION: ManagerDefinition = { + id: 'EXCLUSION_LIST_CACHE_BUILD_MANAGER', + label: 'Exclusion list cache build manager', + executionContext: 'exclusion_list_cache_build_manager', + cronSchedulerHandler: { + handler: exclusionListCacheBuildHandler, + interval: SCHEDULE_TIME, + lockKey: EXCLUSION_LIST_CACHE_BUILD_MANAGER_KEY, + }, + enabledByConfig: EXCLUSION_LIST_CACHE_BUILD_MANAGER_ENABLED, + enabledToStart(): boolean { + return this.enabledByConfig; + }, + enabled(): boolean { + return this.enabledByConfig; + } +}; +if (isFeatureEnabled('EXCLUSION_LIST')) { + registerManager(EXCLUSION_LIST_CACHE_BUILD_MANAGER_DEFINITION); +} diff --git a/opencti-platform/opencti-graphql/src/manager/exclusionListCacheSyncManager.ts b/opencti-platform/opencti-graphql/src/manager/exclusionListCacheSyncManager.ts new file mode 100644 index 000000000000..f37b6b1e45fd --- /dev/null +++ b/opencti-platform/opencti-graphql/src/manager/exclusionListCacheSyncManager.ts @@ -0,0 +1,41 @@ +import { type ManagerDefinition, registerManager } from './managerModule'; +import conf, { isFeatureEnabled, logApp, NODE_INSTANCE_ID } from '../config/conf'; +import { redisGetExclusionListStatus } from '../database/redis'; +import { getIsCacheInitialized, syncExclusionListCache } from '../database/exclusionListCache'; + +const EXCLUSION_LIST_CACHE_SYNC_MANAGER_LOCK_KEY = conf.get('exclusion_list_cache_sync_manager:lock_key') || 'exclusion_list_cache_sync_manager_lock'; +const SCHEDULE_TIME = conf.get('exclusion_list_cache_sync_manager:interval') || 10000; // 10 seconds + +const exclusionListCacheSyncHandler = async () => { + const exclusionListStatus = await redisGetExclusionListStatus(); + const isLocalCacheInitialized = getIsCacheInitialized(); + + if (!exclusionListStatus?.last_cache_date) return; + + if (exclusionListStatus.last_cache_date !== exclusionListStatus[NODE_INSTANCE_ID] || !isLocalCacheInitialized) { + logApp.info('[OPENCTI-MODULE][EXCLUSION-SYNC-MANAGER] local cache needs to be updated'); + await syncExclusionListCache(exclusionListStatus.last_cache_date); + logApp.info('[OPENCTI-MODULE][EXCLUSION-SYNC-MANAGER] local cache has been updated'); + } +}; + +const EXCLUSION_LIST_CACHE_SYNC_MANAGER: ManagerDefinition = { + id: 'EXCLUSION_LIST_CACHE_SYNC_MANAGER', + label: 'Exclusion list cache sync manager', + executionContext: 'exclusion_list_cache_sync_manager', + cronSchedulerHandler: { + handler: exclusionListCacheSyncHandler, + interval: SCHEDULE_TIME, + lockKey: EXCLUSION_LIST_CACHE_SYNC_MANAGER_LOCK_KEY + }, + enabledByConfig: true, + enabledToStart(): boolean { + return this.enabledByConfig; + }, + enabled(): boolean { + return this.enabledByConfig; + } +}; +if (isFeatureEnabled('EXCLUSION_LIST')) { + registerManager(EXCLUSION_LIST_CACHE_SYNC_MANAGER); +} diff --git a/opencti-platform/opencti-graphql/src/manager/index.ts b/opencti-platform/opencti-graphql/src/manager/index.ts index 940588a00649..608a848d5fcc 100644 --- a/opencti-platform/opencti-graphql/src/manager/index.ts +++ b/opencti-platform/opencti-graphql/src/manager/index.ts @@ -2,3 +2,5 @@ import './indicatorDecayManager'; import './garbageCollectionManager'; import './telemetryManager'; import './retentionManager'; +import './exclusionListCacheBuildManager'; +import './exclusionListCacheSyncManager'; diff --git a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-converter.ts b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-converter.ts index 1b2ca5d212bd..4090dab824d4 100644 --- a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-converter.ts +++ b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-converter.ts @@ -8,7 +8,7 @@ const convertExclusionListToStix = (instance: StoreEntityExclusionList): StixExc ...stixObject, name: instance.name, description: instance.description, - list_entity_types: instance.list_entity_types, + exclusion_list_entity_types: instance.exclusion_list_entity_types, file_id: instance.file_id, enabled: instance.enabled, extensions: { diff --git a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-domain.ts b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-domain.ts index f9478d9f5ff1..218195fcf870 100644 --- a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-domain.ts @@ -1,12 +1,16 @@ import { Readable } from 'stream'; -import { isFeatureEnabled } from '../../config/conf'; +import { BUS_TOPICS, isFeatureEnabled } from '../../config/conf'; import { type FileUploadData, uploadToStorage } from '../../database/file-storage-helper'; import { deleteFile } from '../../database/file-storage'; import { createInternalObject, deleteInternalObject } from '../../domain/internalObject'; import { listEntitiesPaginated, storeLoadById } from '../../database/middleware-loader'; import type { AuthContext, AuthUser } from '../../types/user'; import { type BasicStoreEntityExclusionList, ENTITY_TYPE_EXCLUSION_LIST, type StoreEntityExclusionList } from './exclusionList-types'; -import type { ExclusionListContentAddInput, ExclusionListFileAddInput, QueryExclusionListsArgs } from '../../generated/graphql'; +import { type EditInput, type ExclusionListContentAddInput, type ExclusionListFileAddInput, type QueryExclusionListsArgs } from '../../generated/graphql'; +import { notify, redisUpdateExclusionListStatus } from '../../database/redis'; +import { FunctionalError } from '../../config/errors'; +import { updateAttribute } from '../../database/middleware'; +import { publishUserAction } from '../../listener/UserActionListener'; const filePath = 'exclusionLists'; @@ -20,18 +24,26 @@ export const findAll = (context: AuthContext, user: AuthUser, args: QueryExclusi return listEntitiesPaginated(context, user, [ENTITY_TYPE_EXCLUSION_LIST], args); }; +const refreshExclusionListStatus = async () => { + await redisUpdateExclusionListStatus({ last_refresh_ask_date: (new Date()).toString() }); +}; + const storeAndCreateExclusionList = async (context: AuthContext, user: AuthUser, input: ExclusionListContentAddInput | ExclusionListFileAddInput, file: FileUploadData) => { - const { upload } = await uploadToStorage(context, user, filePath, file, {}); + const fullFile = await file; + const exclusionFile = { ...fullFile, filename: `${input.name}.txt` }; + const { upload } = await uploadToStorage(context, user, filePath, exclusionFile, {}); const exclusionListToCreate = { name: input.name, description: input.description, - enabled: false, - exclusion_list_entity_types: input.list_entity_types, + enabled: true, + exclusion_list_entity_types: input.exclusion_list_entity_types, file_id: upload.id }; - return createInternalObject(context, user, exclusionListToCreate, ENTITY_TYPE_EXCLUSION_LIST); + const createdExclusionList = createInternalObject(context, user, exclusionListToCreate, ENTITY_TYPE_EXCLUSION_LIST); + await refreshExclusionListStatus(); + return createdExclusionList; }; export const addExclusionListContent = async (context: AuthContext, user: AuthUser, input: ExclusionListContentAddInput) => { @@ -49,9 +61,30 @@ export const addExclusionListFile = async (context: AuthContext, user: AuthUser, return storeAndCreateExclusionList(context, user, input, input.file); }; +export const fieldPatchExclusionList = async (context: AuthContext, user: AuthUser, id: string, input: EditInput[]) => { + const exclusionList = await findById(context, user, id); + if (!exclusionList) { + throw FunctionalError(`Exclusion list ${id} cannot be found`); + } + + const { element } = await updateAttribute(context, user, id, ENTITY_TYPE_EXCLUSION_LIST, input); + await refreshExclusionListStatus(); + await publishUserAction({ + user, + event_type: 'mutation', + event_scope: 'update', + event_access: 'administration', + message: `updates \`${input.map((i) => i.key).join(', ')}\` for exclusion list \`${element.name}\``, + context_data: { id, entity_type: ENTITY_TYPE_EXCLUSION_LIST, input } + }); + return notify(BUS_TOPICS[ENTITY_TYPE_EXCLUSION_LIST].EDIT_TOPIC, element, user); +}; + export const deleteExclusionList = async (context: AuthContext, user: AuthUser, exclusionListId: string) => { if (!isExclusionListEnabled) throw new Error('Feature not yet available'); const exclusionList = await findById(context, user, exclusionListId); await deleteFile(context, user, exclusionList.file_id); - return deleteInternalObject(context, user, exclusionListId, ENTITY_TYPE_EXCLUSION_LIST); + const deletedExclusionList = deleteInternalObject(context, user, exclusionListId, ENTITY_TYPE_EXCLUSION_LIST); + await refreshExclusionListStatus(); + return deletedExclusionList; }; diff --git a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-resolver.ts b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-resolver.ts index 5e27d68b2b84..5708a1e047ce 100644 --- a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-resolver.ts +++ b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-resolver.ts @@ -1,5 +1,5 @@ import type { Resolvers } from '../../generated/graphql'; -import { findById, findAll, addExclusionListContent, addExclusionListFile, deleteExclusionList } from './exclusionList-domain'; +import { findById, findAll, addExclusionListContent, addExclusionListFile, deleteExclusionList, fieldPatchExclusionList } from './exclusionList-domain'; const exclusionListResolver: Resolvers = { Query: { @@ -13,6 +13,9 @@ const exclusionListResolver: Resolvers = { exclusionListFileAdd: (_, { input }, context) => { return addExclusionListFile(context, context.user, input); }, + exclusionListFieldPatch: (_, { id, input }, context) => { + return fieldPatchExclusionList(context, context.user, id, input); + }, exclusionListDelete: (_, { id }, context) => { return deleteExclusionList(context, context.user, id); }, diff --git a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-types.ts b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-types.ts index 87ddbb65082b..630982559a6f 100644 --- a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-types.ts +++ b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList-types.ts @@ -2,12 +2,12 @@ import type { BasicStoreEntity, StoreEntity } from '../../types/store'; import type { StixObject, StixOpenctiExtensionSDO } from '../../types/stix-common'; import { STIX_EXT_OCTI } from '../../types/stix-extensions'; -export const ENTITY_TYPE_EXCLUSION_LIST = 'Exclusion-List'; +export const ENTITY_TYPE_EXCLUSION_LIST = 'ExclusionList'; export interface BasicStoreEntityExclusionList extends BasicStoreEntity { name: string description: string - list_entity_types: string[] + exclusion_list_entity_types: string[] file_id: string enabled: boolean } @@ -15,7 +15,7 @@ export interface BasicStoreEntityExclusionList extends BasicStoreEntity { export interface StoreEntityExclusionList extends StoreEntity { name: string description: string - list_entity_types: string[] + exclusion_list_entity_types: string[] file_id: string enabled: boolean } @@ -23,7 +23,7 @@ export interface StoreEntityExclusionList extends StoreEntity { export interface StixExclusionList extends StixObject { name: string description: string - list_entity_types: string[] + exclusion_list_entity_types: string[] file_id: string enabled: boolean extensions: { diff --git a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql index 41b79044a5d9..bd47f622740f 100644 --- a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql +++ b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql @@ -7,7 +7,7 @@ type ExclusionList implements InternalObject & BasicObject { parent_types: [String]! created_at: DateTime! enabled: Boolean! - list_entity_types: [ExclusionListEntityTypes!]! + exclusion_list_entity_types: [String!]! file_id: String! } @@ -24,26 +24,21 @@ type ExclusionListEdge { enum ExclusionListOrdering { name created_at -} - -enum ExclusionListEntityTypes { - IPV4_ADDR - IPV6_ADDR - DOMAIN_NAME - URL + enabled + _score } input ExclusionListContentAddInput { name: String! description: String - list_entity_types: [ExclusionListEntityTypes!]! + exclusion_list_entity_types: [String!]! content: String! } input ExclusionListFileAddInput { name: String! description: String - list_entity_types: [ExclusionListEntityTypes!]! + exclusion_list_entity_types: [String!]! file: Upload! } @@ -62,5 +57,6 @@ type Query { type Mutation { exclusionListContentAdd(input: ExclusionListContentAddInput!): ExclusionList @auth(for: [SETTINGS_SETCUSTOMIZATION]) exclusionListFileAdd(input: ExclusionListFileAddInput!): ExclusionList @auth(for: [SETTINGS_SETCUSTOMIZATION]) + exclusionListFieldPatch(id: ID!, input: [EditInput!]!): ExclusionList @auth(for: [SETTINGS_SETCUSTOMIZATION]) exclusionListDelete(id: ID!): ID @auth(for: [SETTINGS_SETCUSTOMIZATION]) } \ No newline at end of file diff --git a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.ts b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.ts index 19ab18ef4070..92bedf6907ea 100644 --- a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.ts +++ b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.ts @@ -1,9 +1,9 @@ -import { v4 as uuidv4 } from 'uuid'; import convertExclusionListToStix from './exclusionList-converter'; import { ENTITY_TYPE_EXCLUSION_LIST, type StixExclusionList, type StoreEntityExclusionList } from './exclusionList-types'; import { ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; import { type ModuleDefinition, registerDefinition } from '../../schema/module'; import { isFeatureEnabled } from '../../config/conf'; +import { NAME_FIELD, normalizeName } from '../../schema/identifier'; const EXCLUSION_LIST_DEFINITION: ModuleDefinition = { type: { @@ -14,7 +14,12 @@ const EXCLUSION_LIST_DEFINITION: ModuleDefinition uuidv4() + [ENTITY_TYPE_EXCLUSION_LIST]: [{ src: NAME_FIELD }] + }, + resolvers: { + name(data: object) { + return normalizeName(data); + } }, }, attributes: [ @@ -59,7 +64,7 @@ const EXCLUSION_LIST_DEFINITION: ModuleDefinition { return storeLoadById(context, user, indicatorId, ENTITY_TYPE_INDICATOR); @@ -228,6 +229,21 @@ export const addIndicator = async (context: AuthContext, user: AuthUser, indicat if (check === false) { throw FunctionalError(`Indicator of type ${indicator.pattern_type} is not correctly formatted.`, { doc_code: 'INCORRECT_INDICATOR_FORMAT' }); } + + // Check that indicator is not excluded from an exclusion list + if (isFeatureEnabled('EXCLUSION_LIST')) { + const observableValues = getObservableValuesFromPattern(formattedPattern); + for (let i = 0; i < observableValues.length; i += 1) { + const exclusionListCheck = await checkObservableValue(observableValues[i]); + if (exclusionListCheck) { + throw FunctionalError(`Indicator of type ${indicator.pattern_type} is contained in exclusion list.`, { + excludedValue: exclusionListCheck.value, + exclusionList: exclusionListCheck.listId + }); + } + } + } + const indicatorBaseScore = indicator.x_opencti_score ?? 50; const isDecayActivated = await isModuleActivated('INDICATOR_DECAY_MANAGER'); // find default decay rule (even if decay is not activated, it is used to compute default validFrom and validUntil) diff --git a/opencti-platform/opencti-graphql/src/schema/internalObject.ts b/opencti-platform/opencti-graphql/src/schema/internalObject.ts index 4e3eaa9bafe3..c783440d2db2 100644 --- a/opencti-platform/opencti-graphql/src/schema/internalObject.ts +++ b/opencti-platform/opencti-graphql/src/schema/internalObject.ts @@ -43,6 +43,7 @@ const DATED_INTERNAL_OBJECTS = [ ENTITY_TYPE_PUBLIC_DASHBOARD, ENTITY_TYPE_DELETE_OPERATION, ENTITY_TYPE_DRAFT_WORKSPACE, + ENTITY_TYPE_EXCLUSION_LIST, ]; const INTERNAL_OBJECTS = [ ENTITY_TYPE_SETTINGS, diff --git a/opencti-platform/opencti-graphql/src/utils/exclusionLists.ts b/opencti-platform/opencti-graphql/src/utils/exclusionLists.ts index 1b20c0069c2d..ffe7f62a6753 100644 --- a/opencti-platform/opencti-graphql/src/utils/exclusionLists.ts +++ b/opencti-platform/opencti-graphql/src/utils/exclusionLists.ts @@ -1,5 +1,5 @@ import { MAX_EVENT_LOOP_PROCESSING_TIME } from '../database/utils'; -import { type ExclusionListProperties } from './exclusionListsTypes'; +import { type ExclusionListCacheItem } from '../database/exclusionListCache'; export const getIsRange = (value: string) => value.indexOf('/') !== -1; @@ -56,17 +56,17 @@ export const convertIpAddr = (list: string[]) => { }); }; -export const checkIpAddressLists = async (ipToTest: string, exclusionList: ExclusionListProperties[]) => { +export const checkIpAddressLists = async (ipToTest: string, exclusionList: ExclusionListCacheItem[]) => { const { isIpv4 } = checkIpAddrType(ipToTest); const binary = isIpv4 ? convertIpv4ToBinary(ipToTest) : convertIpv6ToBinary(ipToTest); let startProcessingTime = new Date().getTime(); for (let i = 0; i < exclusionList.length; i += 1) { - const { list, name } = exclusionList[i]; + const { id, values } = exclusionList[i]; - for (let j = 0; j < list.length; j += 1) { - if (binary.startsWith(list[j])) { - return { value: ipToTest, listName: name }; + for (let j = 0; j < values.length; j += 1) { + if (binary.startsWith(values[j])) { + return { value: ipToTest, listId: id }; } if (new Date().getTime() - startProcessingTime > MAX_EVENT_LOOP_PROCESSING_TIME) { @@ -80,16 +80,16 @@ export const checkIpAddressLists = async (ipToTest: string, exclusionList: Exclu return null; }; -export const checkExclusionList = async (valueToTest: string, exclusionList: ExclusionListProperties[]) => { +export const checkExclusionList = async (valueToTest: string, exclusionList: ExclusionListCacheItem[]) => { let startProcessingTime = new Date().getTime(); for (let i = 0; i < exclusionList.length; i += 1) { - const { list, name } = exclusionList[i]; + const { id, values } = exclusionList[i]; - for (let j = 0; j < list.length; j += 1) { - const isWildCard = list[j].startsWith('.'); - if ((isWildCard && valueToTest.endsWith(list[j])) || valueToTest === list[j]) { - return { value: valueToTest, listName: name }; + for (let j = 0; j < values.length; j += 1) { + const isWildCard = values[j].startsWith('.'); + if ((isWildCard && valueToTest.endsWith(values[j])) || valueToTest === values[j]) { + return { value: valueToTest, listId: id }; } if (new Date().getTime() - startProcessingTime > MAX_EVENT_LOOP_PROCESSING_TIME) { diff --git a/opencti-platform/opencti-graphql/src/utils/exclusionListsTypes.ts b/opencti-platform/opencti-graphql/src/utils/exclusionListsTypes.ts deleted file mode 100644 index 32e10a16ff00..000000000000 --- a/opencti-platform/opencti-graphql/src/utils/exclusionListsTypes.ts +++ /dev/null @@ -1,13 +0,0 @@ -export enum exclusionListEntityType { - IPV4_ADDR = 'IPv4-Addr', - IPV6_ADDR = 'IPv6-Addr', - DOMAIN_NAME = 'Domain-Name', - URL = 'Url', -} - -export type ExclusionListProperties = { - name: string; - type: exclusionListEntityType[]; - list: string[]; - actions: null; -}; diff --git a/opencti-platform/opencti-graphql/tests/01-unit/utils/exclusionLists-test.ts b/opencti-platform/opencti-graphql/tests/01-unit/utils/exclusionLists-test.ts index 33c29cb131b5..c47bc333a39a 100644 --- a/opencti-platform/opencti-graphql/tests/01-unit/utils/exclusionLists-test.ts +++ b/opencti-platform/opencti-graphql/tests/01-unit/utils/exclusionLists-test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { checkExclusionList, checkIpAddressLists, checkIpAddrType, convertIpAddr, convertIpv4ToBinary, convertIpv6ToBinary } from '../../../src/utils/exclusionLists'; -import { exclusionListEntityType } from '../../../src/utils/exclusionListsTypes'; +import { ENTITY_DOMAIN_NAME, ENTITY_IPV4_ADDR, ENTITY_IPV6_ADDR, ENTITY_URL } from '../../../src/schema/stixCyberObservable'; const ipv4ListToTest = [ '99.93.60.129', @@ -77,32 +77,27 @@ const domainNameList = [ 'ns4.gamania.com', ]; const ipv4ExclusionList = [{ - name: 'ipv4ListResult', - type: [exclusionListEntityType.IPV4_ADDR], - list: ipv4ListResult, - actions: null, + id: 'ipv4ListResult', + types: [ENTITY_IPV4_ADDR], + values: ipv4ListResult }, { - name: 'ipListResult', - type: [exclusionListEntityType.IPV4_ADDR, exclusionListEntityType.IPV6_ADDR], - list: ipListResult, - actions: null, + id: 'ipListResult', + types: [ENTITY_IPV4_ADDR, ENTITY_IPV6_ADDR], + values: ipListResult }]; const ipv6ExclusionList = [{ - name: 'ipv6ListResult', - type: [exclusionListEntityType.IPV6_ADDR], - list: ipv6ListResult, - actions: null, + id: 'ipv6ListResult', + types: [ENTITY_IPV6_ADDR], + values: ipv6ListResult }, { - name: 'ipListResult', - type: [exclusionListEntityType.IPV4_ADDR, exclusionListEntityType.IPV6_ADDR], - list: ipListResult, - actions: null, + id: 'ipListResult', + types: [ENTITY_IPV4_ADDR, ENTITY_IPV6_ADDR], + values: ipListResult }]; const domainExclusionList = [{ - name: 'domainExclusionList', - type: [exclusionListEntityType.DOMAIN_NAME, exclusionListEntityType.URL], - list: domainNameList, - actions: null, + id: 'domainExclusionList', + types: [ENTITY_DOMAIN_NAME, ENTITY_URL], + values: domainNameList }]; describe('Exclusion Lists', () => { @@ -195,7 +190,7 @@ describe('Exclusion Lists', () => { const result = await checkIpAddressLists('99.99.99.193', ipv4ExclusionList); expect(result).not.toBe(null); expect(result?.value).toBe('99.99.99.193'); - expect(result?.listName).toBe('ipv4ListResult'); + expect(result?.listId).toBe('ipv4ListResult'); }); }); @@ -204,7 +199,7 @@ describe('Exclusion Lists', () => { const result = await checkIpAddressLists('99.87.23.11', ipv4ExclusionList); expect(result).not.toBe(null); expect(result?.value).toBe('99.87.23.11'); - expect(result?.listName).toBe('ipv4ListResult'); + expect(result?.listId).toBe('ipv4ListResult'); }); }); @@ -222,7 +217,7 @@ describe('Exclusion Lists', () => { const result = await checkIpAddressLists('2a12:e342:200::2:1819', ipv6ExclusionList); expect(result).not.toBe(null); expect(result?.value).toBe('2a12:e342:200::2:1819'); - expect(result?.listName).toBe('ipv6ListResult'); + expect(result?.listId).toBe('ipv6ListResult'); }); }); @@ -231,7 +226,7 @@ describe('Exclusion Lists', () => { const result = await checkIpAddressLists('2001:1424:0:1234::', ipv6ExclusionList); expect(result).not.toBe(null); expect(result?.value).toBe('2001:1424:0:1234::'); - expect(result?.listName).toBe('ipListResult'); + expect(result?.listId).toBe('ipListResult'); }); }); @@ -251,7 +246,7 @@ describe('Exclusion Lists', () => { const result = await checkExclusionList('ns4.epidc.co.kr', domainExclusionList); expect(result).not.toBe(null); expect(result?.value).toBe('ns4.epidc.co.kr'); - expect(result?.listName).toBe('domainExclusionList'); + expect(result?.listId).toBe('domainExclusionList'); }); }); @@ -260,7 +255,7 @@ describe('Exclusion Lists', () => { const result = await checkExclusionList('www.test.ambfinancial.com', domainExclusionList); expect(result).not.toBe(null); expect(result?.value).toBe('www.test.ambfinancial.com'); - expect(result?.listName).toBe('domainExclusionList'); + expect(result?.listId).toBe('domainExclusionList'); }); }); diff --git a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/exclusionListResolver-test.ts b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/exclusionListResolver-test.ts index 01d627e4b73a..c853f4e3ea18 100644 --- a/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/exclusionListResolver-test.ts +++ b/opencti-platform/opencti-graphql/tests/02-integration/02-resolvers/exclusionListResolver-test.ts @@ -6,13 +6,14 @@ import { fileToReadStream } from '../../../src/database/file-storage-helper'; import { elLoadById } from '../../../src/database/engine'; import { ADMIN_USER, testContext } from '../../utils/testQuery'; import { queryAsAdminWithSuccess } from '../../utils/testQueryHelper'; -import { ExclusionListEntityTypes } from '../../../src/generated/graphql'; +import { ENTITY_DOMAIN_NAME, ENTITY_IPV4_ADDR } from '../../../src/schema/stixCyberObservable'; const CREATE_CONTENT_MUTATION = gql` mutation exclusionListContentAdd($input: ExclusionListContentAddInput!) { exclusionListContentAdd(input: $input) { id file_id + exclusion_list_entity_types } } `; @@ -22,6 +23,7 @@ const CREATE_FILE_MUTATION = gql` exclusionListFileAdd(input: $input) { id file_id + exclusion_list_entity_types } } `; @@ -32,6 +34,34 @@ const DELETE_MUTATION = gql` } `; +const LIST_QUERY = gql` + query exclusionLists( + $first: Int + $after: ID + $orderBy: ExclusionListOrdering + $orderMode: OrderingMode + $filters: FilterGroup + $search: String + ) { + exclusionLists( + first: $first + after: $after + orderBy: $orderBy + orderMode: $orderMode + filters: $filters + search: $search + ) { + edges { + node { + id + name + exclusion_list_entity_types + } + } + } + } +`; + type ExclusionListResponse = { id: string | null; file_id: string | null; @@ -50,7 +80,7 @@ describe('Exclusion list resolver', () => { input: { name: 'test_name', description: 'test_description', - list_entity_types: [ExclusionListEntityTypes.DomainName], + exclusion_list_entity_types: ENTITY_DOMAIN_NAME, content: 'test_content.fr' } } @@ -89,7 +119,7 @@ describe('Exclusion list resolver', () => { input: { name: 'test_name_file', description: 'test_description_file', - list_entity_types: [ExclusionListEntityTypes.Ipv4Addr], + exclusion_list_entity_types: ENTITY_IPV4_ADDR, file: upload, } } @@ -99,7 +129,7 @@ describe('Exclusion list resolver', () => { it('should create an exclusion list', async () => { expect(exclusionListFileResponse.id).toBeDefined(); - expect(exclusionListFileResponse.file_id).toBe('exclusionLists/testfileexclusionlist.txt'); + expect(exclusionListFileResponse.file_id).toBe('exclusionLists/test_name_file.txt'); }); it('should create a file', async () => { @@ -108,6 +138,34 @@ describe('Exclusion list resolver', () => { const data = await streamConverter(fileStream); expect(data).toEqual('127.0.0.1\n10.10.0.0\n2.2.2.2'); }); + + it('should list exclusion lists', async () => { + const listResult = await queryAsAdminWithSuccess({ + query: LIST_QUERY, + variables: { first: 5 }, + }); + const exclusionLists = listResult.data?.exclusionLists.edges; + expect(exclusionLists).toBeDefined(); + expect(exclusionLists.length).toEqual(2); + + const filters = { + mode: 'and', + filters: [{ + key: 'exclusion_list_entity_types', + operator: 'eq', + values: [ENTITY_IPV4_ADDR], + mode: 'or', + }], + filterGroups: [], + }; + const listWithFilterResult = await queryAsAdminWithSuccess({ + query: LIST_QUERY, + variables: { first: 5, filters }, + }); + const exclusionListsWithFilter = listWithFilterResult.data?.exclusionLists.edges; + expect(exclusionListsWithFilter).toBeDefined(); + expect(exclusionListsWithFilter.length).toEqual(1); + }); }); }); diff --git a/opencti-platform/opencti-graphql/tests/02-integration/04-manager/exclusionListCacheBuildManager-test.ts b/opencti-platform/opencti-graphql/tests/02-integration/04-manager/exclusionListCacheBuildManager-test.ts new file mode 100644 index 000000000000..f5156e3223f1 --- /dev/null +++ b/opencti-platform/opencti-graphql/tests/02-integration/04-manager/exclusionListCacheBuildManager-test.ts @@ -0,0 +1,88 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import gql from 'graphql-tag'; +import { testContext } from '../../utils/testQuery'; +import { queryAsAdminWithSuccess } from '../../utils/testQueryHelper'; +import { ENTITY_DOMAIN_NAME, ENTITY_IPV4_ADDR } from '../../../src/schema/stixCyberObservable'; +import { buildCacheFromAllExclusionLists } from '../../../src/database/exclusionListCache'; + +describe('Exclusion list cache build manager tests ', () => { + const context = testContext; + let exclusionListIPId = ''; + let exclusionListDomainId = ''; + const exclusionListIpValues = '127.0.0.1\n10.10.0.0/28\n2.2.2.2'; + const exclusionListDomainValues = 'google.com\nfiligran.io\nwww.test.net'; + + const CREATE_CONTENT_MUTATION = gql` + mutation exclusionListContentAdd($input: ExclusionListContentAddInput!) { + exclusionListContentAdd(input: $input) { + id + file_id + } + } + `; + + const DELETE_MUTATION = gql` + mutation exclusionListDelete($id: ID!) { + exclusionListDelete(id: $id) + } + `; + beforeAll(async () => { + const exclusionListIP = await queryAsAdminWithSuccess({ + query: CREATE_CONTENT_MUTATION, + variables: { + input: { + name: 'test_ip_list', + description: 'test_description', + exclusion_list_entity_types: [ENTITY_IPV4_ADDR], + content: exclusionListIpValues + } + } + }); + exclusionListIPId = exclusionListIP.data?.exclusionListContentAdd.id; + const exclusionListDomain = await queryAsAdminWithSuccess({ + query: CREATE_CONTENT_MUTATION, + variables: { + input: { + name: 'test_domain_list', + description: 'test_description', + exclusion_list_entity_types: [ENTITY_DOMAIN_NAME], + content: exclusionListDomainValues + } + } + }); + exclusionListDomainId = exclusionListDomain.data?.exclusionListContentAdd.id; + }); + afterAll(async () => { + await queryAsAdminWithSuccess({ + query: DELETE_MUTATION, + variables: { + id: exclusionListIPId + } + }); + await queryAsAdminWithSuccess({ + query: DELETE_MUTATION, + variables: { + id: exclusionListDomainId + } + }); + }); + it('should build cache from current exclusion lists', async () => { + const builtCache = await buildCacheFromAllExclusionLists(context); + expect(builtCache).toBeDefined(); + expect(builtCache.length).toEqual(2); + const ipCache = builtCache.find((c) => c.id === exclusionListIPId); + expect(ipCache).toBeDefined(); + expect(ipCache?.types).toEqual([ENTITY_IPV4_ADDR]); + expect(ipCache?.values.length).toEqual(3); + expect(ipCache?.values.includes('01111111000000000000000000000001')).toBeTruthy(); + expect(ipCache?.values.includes('0000101000001010000000000000')).toBeTruthy(); + expect(ipCache?.values.includes('00000010000000100000001000000010')).toBeTruthy(); + const domainCache = builtCache.find((c) => c.id === exclusionListDomainId); + expect(domainCache).toBeDefined(); + expect(domainCache?.types).toEqual([ENTITY_DOMAIN_NAME]); + expect(domainCache?.values.length).toEqual(3); + expect(domainCache?.values.includes('google.com')).toBeTruthy(); + expect(domainCache?.values.includes('filigran.io')).toBeTruthy(); + expect(domainCache?.values.includes('www.test.net')).toBeTruthy(); + }); +}); diff --git a/opencti-platform/opencti-graphql/tests/utils/globalSetup.ts b/opencti-platform/opencti-graphql/tests/utils/globalSetup.ts index 188ca4894741..3407378cad1d 100644 --- a/opencti-platform/opencti-graphql/tests/utils/globalSetup.ts +++ b/opencti-platform/opencti-graphql/tests/utils/globalSetup.ts @@ -9,7 +9,7 @@ import { ADMIN_USER, createTestUsers, isPlatformAlive, testContext } from './tes import { elDeleteIndices, elPlatformIndices, initializeSchema, searchEngineInit } from '../../src/database/engine'; import { wait } from '../../src/database/utils'; import { createRedisClient, initializeRedisClients, shutdownRedisClients } from '../../src/database/redis'; -import { logApp, environment } from '../../src/config/conf'; +import { logApp, environment, isFeatureEnabled } from '../../src/config/conf'; import cacheManager from '../../src/manager/cacheManager'; import { initializeAdminUser } from '../../src/config/providers'; import { initDefaultNotifiers } from '../../src/modules/notifier/notifier-domain'; @@ -18,6 +18,7 @@ import { executionContext } from '../../src/utils/access'; import { initializeData } from '../../src/database/data-initialization'; import { shutdownModules, startModules } from '../../src/managers'; import { deleteAllBucketContent } from '../../src/database/file-storage-helper'; +import { initExclusionListCache } from '../../src/database/exclusionListCache'; /** * Vitest setup is configurable with environment variables, as you can see in our package.json scripts * INIT_TEST_PLATFORM=1 > cleanup the test platform, removing elastic indices, and setup it again @@ -49,6 +50,10 @@ const testPlatformStart = async () => { try { // Init the cache manager await cacheManager.start(); + // Init the exclusion list cache + if (isFeatureEnabled('EXCLUSION_LIST')) { + await initExclusionListCache(); + } // Init the platform default if it was cleaned up if (!SKIP_CLEANUP_PLATFORM) { await initializePlatform(); diff --git a/opencti-platform/opencti-graphql/tests/utils/testSetup.js b/opencti-platform/opencti-graphql/tests/utils/testSetup.js index 3b240355b240..4e7386cd6407 100644 --- a/opencti-platform/opencti-graphql/tests/utils/testSetup.js +++ b/opencti-platform/opencti-graphql/tests/utils/testSetup.js @@ -3,8 +3,13 @@ import cacheManager from '../../src/manager/cacheManager'; import { initializeRedisClients } from '../../src/database/redis'; import { searchEngineInit } from '../../src/database/engine'; import { initializeFileStorageClient } from '../../src/database/file-storage'; +import { initExclusionListCache } from '../../src/database/exclusionListCache'; +import { isFeatureEnabled } from '../../src/config/conf'; await initializeRedisClients(); await searchEngineInit(); await initializeFileStorageClient(); cacheManager.init(); +if (isFeatureEnabled('EXCLUSION_LIST')) { + await initExclusionListCache(); +}